Reorganized the code into new packages.

This commit is contained in:
Jonathan Bernard 2011-10-27 20:14:08 -05:00
parent 652cc8703a
commit d076b739f5
20 changed files with 533 additions and 454 deletions

View File

@ -1,5 +0,0 @@
package com.jdbernard.timeanalyzer;
public interface CategoryFilter {
public boolean matchesEvent(Event event);
}

View File

@ -1,107 +1,118 @@
package com.jdbernard.timeanalyzer
import org.joda.time.Duration
/**
* A category represents a collection of like events.
*/
public abstract class Category implements Comparable<Category> {
public List events
public List categories
public List categorizationPlans
public String description
public Category() {
events = []
categories = []
categorizationPlans = []
description = "Unnamed Category"
}
public Category(String description) {
events = []
categories = []
categorizationPlans = []
this.description = description
}
public abstract boolean matchesEvent(Event e)
public Event addEvent(Event event) {
// see if we have or can create a subcategory that will hold this event
Event addedEvent = addToSubcategory(event)
// no, let's just add it on ourself
if (!addedEvent) {
events << event
addedEvent = event
}
return addedEvent
}
public Event addToSubcategory(Event e) {
// find all matching subcategories
def matchingCategories = categories.findAll { it.matchesEvent(e) }
if (matchingCategories)
return matchingCategories[0].addEvent(e)
// no matching subcategories, can we create a new one based on one
// of our plans?
def matchingPlans = categorizationPlans.findAll
{ it.deservesNewCategory(e, events) }
if (matchingPlans) {
// create the new category
def newCategory = matchingPlans[0].newCategory(e, events)
// add it to our list of cateogries
categories << newCategory
// add the new event to the category
def addedEvent = newCategory.addEvent(e)
// move all the events that match the new category over
def existingEvents = matchingPlans[0].findEventsToRecategorize(e, events)
events -= existingEvents
existingEvents.each { newCategory.addEvent(it) }
// return the new entry
return addedEvent
}
return null
}
public Category filter(List<CategoryFilter> filters) {
// create new filtered category
FilteredCategory fc = new FilteredCategory(description)
fc.filters = filters
// filter all events and add them to the category
fc.events
// TODO
}
public Category filter(CategoryFilter filter) { return filter([filter]) }
public Category filter(Closure c) { return filter(c as CategoryFilter) }
public Duration getDuration() {
return categories.sum(new Duration(0)) { it.duration } +
events.sum(new Duration(0)) { it.duration }
}
public int compareTo(Category other) {
return this.getDuration().compareTo(other.getDuration())
}
public String toString() { return description }
}
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.Duration
/**
* A `Category` represents a collection of like `Events` and sub-categories.
*/
public abstract class Category implements Comparable<Category> {
/** List of events directly under this category. */
public List events
/** List of sub-categories under this category. */
public List categories
/** List of `CategorizationPlan`s to use when adding new `Event`s to the
* category. */
public List categorizationPlans
/** A end-user-friendly text description of the category.*/
public String description
public Category() {
events = []
categories = []
categorizationPlans = []
description = "Unnamed Category" }
public Category(String description) {
events = []
categories = []
categorizationPlans = []
this.description = description }
/**
* Does the given event belong in this category?
* @param e `Event` being considered.
* @return **`true`** if this event belongs in this category, **`false`**
* otherwise.
*/
public abstract boolean matchesEvent(Event e)
/**
* Add an event to this category. This method does not check to see if the
* `Event` matches this category. It assumed that the check has already been
* made and the `Event` matches.*/
public Event addEvent(Event event) {
// see if we have or can create a subcategory that will hold this event
Event addedEvent = addToSubcategory(event)
// no, let's just add it on ourself
if (!addedEvent) {
events << event
addedEvent = event }
return addedEvent }
public Event addToSubcategory(Event e) {
// find all matching subcategories
def matchingCategories = categories.findAll { it.matchesEvent(e) }
if (matchingCategories) {
return matchingCategories[0].addEvent(e) }
// no matching subcategories, can we create a new one based on one
// of our plans?
def matchingPlans = categorizationPlans.findAll {
it.deservesNewCategory(e, events) }
if (matchingPlans) {
// create the new category
def newCategory = matchingPlans[0].newCategory(e, events)
// add it to our list of cateogries
categories << newCategory
// add the new event to the category
def addedEvent = newCategory.addEvent(e)
// move all the events that match the new category over
def existingEvents = matchingPlans[0].findEventsToRecategorize(e, events)
events -= existingEvents
existingEvents.each { newCategory.addEvent(it) }
// return the new entry
return addedEvent }
return null }
public Category filter(List<CategoryFilter> filters) {
// create new filtered category
FilteredCategory fc = new FilteredCategory(description)
fc.filters = filters
// filter all events and add them to the category
fc.events
// TODO
}
public Category filter(CategoryFilter filter) { return filter([filter]) }
public Category filter(Closure c) { return filter(c as CategoryFilter) }
public Duration getDuration() {
return categories.sum(new Duration(0)) { it.duration } +
events.sum(new Duration(0)) { it.duration } }
public int compareTo(Category other) {
return this.getDuration().compareTo(other.getDuration()) }
public String toString() { return description }
}

View File

@ -0,0 +1,7 @@
package com.jdbernard.timeanalyzer.categories;
import com.jdbernard.timeanalyzer.events.Event;
public interface CategoryFilter {
public boolean matchesEvent(Event event);
}

View File

@ -1,17 +1,15 @@
package com.jdbernard.timeanalyzer
import org.joda.time.Duration
public class DescriptionBasedCategory extends Category {
public DescriptionBasedCategory(String description) {
super()
this.description = description.replaceAll(/\p{Punct}/, '')
}
public boolean matchesEvent(Event e) {
return e.description.replaceAll(/\p{Punct}/, '').toLowerCase() ==
description.toLowerCase()
}
}
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
public class DescriptionBasedCategory extends Category {
public DescriptionBasedCategory(String description) {
super()
this.description = description.replaceAll(/\p{Punct}/, '') }
public boolean matchesEvent(Event e) {
return e.description.replaceAll(/\p{Punct}/, '').toLowerCase() ==
description.toLowerCase() }
}

View File

@ -1,15 +1,15 @@
package com.jdbernard.timeanalyzer
public class FilteredCategory extends Category{
List<CategoryFilter> filters = []
public FilteredCategory(String description) {
super(description)
}
public boolean matchesEvent(Event e) {
return filters.every { filter -> filter.matchesEvent(e) }
}
}
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
public class FilteredCategory extends Category {
List<CategoryFilter> filters = []
public FilteredCategory(String description) {
super(description) }
public boolean matchesEvent(Event e) {
return filters.every { filter -> filter.matchesEvent(e) } }
}

View File

@ -1,15 +1,17 @@
package com.jdbernard.timeanalyzer
public class GeneralCategory extends Category {
public GeneralCategory() {
this("General")
}
public GeneralCategory(String description) {
super(description)
}
public boolean matchesEvent(Event e) { true }
}
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
public class GeneralCategory extends Category {
public GeneralCategory() {
this("General")
}
public GeneralCategory(String description) {
super(description)
}
public boolean matchesEvent(Event e) { true }
}

View File

@ -0,0 +1,18 @@
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.Interval
public class TimeIntervalCategory extends Category {
private final Interval interval
public TimeIntervalCategory(String desc, Interval interval) {
super(desc)
this.interval = interval }
public boolean matchesEvent(Event e) {
Interval eventIv = new Interval(e.start, e.duration)
return interval.contains(eventIv) }
}

View File

@ -1,23 +1,25 @@
package com.jdbernard.timeanalyzer
import org.joda.time.Interval
import org.joda.time.ReadableInstant
import org.joda.time.ReadableInterval
public class TimeIntervalCategoryFilter implements CategoryFilter {
ReadableInterval interval
public TimeIntervalCategoryFilter(ReadableInterval interval) {
this.interval = interval
}
public TimeIntervalCategoryFilter(ReadableInstant start,
ReadableInstant end) {
this.interval = new Interval(start, end)
}
public boolean matchesEvent(Event event) {
return interval.contains(event.start)
}
}
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.Interval
import org.joda.time.ReadableInstant
import org.joda.time.ReadableInterval
public class TimeIntervalCategoryFilter implements CategoryFilter {
ReadableInterval interval
public TimeIntervalCategoryFilter(ReadableInterval interval) {
this.interval = interval
}
public TimeIntervalCategoryFilter(ReadableInstant start,
ReadableInstant end) {
this.interval = new Interval(start, end)
}
public boolean matchesEvent(Event event) {
return interval.contains(event.start)
}
}

View File

@ -1,24 +1,23 @@
package com.jdbernard.timeanalyzer
public class TwoLevelCategory extends Category {
private final def descriptionPattern
public TwoLevelCategory(String heading) {
super(heading)
descriptionPattern = ~/^${heading}:\s*(.*)/
}
public boolean matchesEvent(Event e) {
return e.description ==~ descriptionPattern
}
public Event addEvent(Event e) {
def m = e.description =~ descriptionPattern
e = new Event(e, description: m[0][1])
super.addEvent(e)
}
}
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
public class TwoLevelCategory extends Category {
private final def descriptionPattern
public TwoLevelCategory(String heading) {
super(heading)
descriptionPattern = ~/^${heading}:\s*(.*)/ }
public boolean matchesEvent(Event e) {
return e.description ==~ descriptionPattern }
public Event addEvent(Event e) {
def m = e.description =~ descriptionPattern
e = new Event(e, description: m[0][1])
super.addEvent(e) }
}

View File

@ -1,10 +1,13 @@
package com.jdbernard.timeanalyzer;
import java.util.List;
import java.util.Map;
public interface CategorizationPlan {
boolean deservesNewCategory(Event event, List<Event> existingEvents);
Category newCategory(Event event, List<Event> existingEvents);
List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents);
}
package com.jdbernard.timeanalyzer.categorizationplans;
import com.jdbernard.timeanalyzer.categories.Category;
import com.jdbernard.timeanalyzer.events.Event;
import java.util.List;
import java.util.Map;
public interface CategorizationPlan {
boolean deservesNewCategory(Event event, List<Event> existingEvents);
Category newCategory(Event event, List<Event> existingEvents);
List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents);
}

View File

@ -0,0 +1,35 @@
package com.jdbernard.timeanalyzer.categorizationplans;
import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.DateTime
import org.joda.time.Interval
import org.joda.time.Period
public class DailyCategorizationPlan {
boolean deservesNewCategory(Event event, List<Event> existingEvents) {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
Interval eventIv = new Interval(
event.start, event.duration)
return fullDay.contains(eventIv) }
Category newCategory(Event event, List<Event> existingEvents) {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
return TimeIntervalCategory(
event.start.toString("EEE, MMM dd", fullday)) }
List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
return existingEvents.findAll {
Interval iv = new Interval(it.start, it.duration)
fullday.contains(iv) } }
}

View File

@ -1,23 +1,27 @@
package com.jdbernard.timeanalyzer
public class DescriptionBasedCategorizationPlan implements CategorizationPlan {
public boolean deservesNewCategory(Event event, List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.any {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
}
public Category newCategory(Event event,
List<Event> existingEvents) {
return new DescriptionBasedCategory(event.description)
}
public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.findAll {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
}
}
package com.jdbernard.timeanalyzer.categorizationplans
import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.DescriptionBasedCategory
import com.jdbernard.timeanalyzer.events.Event
public class DescriptionBasedCategorizationPlan implements CategorizationPlan {
public boolean deservesNewCategory(Event event, List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.any {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
}
public Category newCategory(Event event,
List<Event> existingEvents) {
return new DescriptionBasedCategory(event.description)
}
public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.findAll {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
}
}

View File

@ -1,21 +1,25 @@
package com.jdbernard.timeanalyzer
public class TwoLevelCategorizationPlan implements CategorizationPlan {
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
public boolean deservesNewCategory(Event event, List<Event> el) {
return event ==~ TWO_LEVEL_PATTERN
}
public Category newCategory(Event event, List<Event> el) {
def m = event.description =~ TWO_LEVEL_PATTERN
return new TwoLevelCategory(m[0][1])
}
public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
def m = event.description =~ TWO_LEVEL_PATTERN
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ }
}
}
package com.jdbernard.timeanalyzer.categorizationplans
import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.TwoLevelCategory
import com.jdbernard.timeanalyzer.events.Event
public class TwoLevelCategorizationPlan implements CategorizationPlan {
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
public boolean deservesNewCategory(Event event, List<Event> el) {
return event ==~ TWO_LEVEL_PATTERN
}
public Category newCategory(Event event, List<Event> el) {
def m = event.description =~ TWO_LEVEL_PATTERN
return new TwoLevelCategory(m[0][1])
}
public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
def m = event.description =~ TWO_LEVEL_PATTERN
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ }
}
}

View File

@ -1,16 +1,16 @@
package com.jdbernard.timeanalyzer.chart
import com.jdbernard.timeanalyzer.Category
import org.jfree.data.general.DefaultPieDataset
import org.jfree.data.general.PieDataset
import org.jfree.util.SortOrder
public class Util {
public static PieDataset makePieDataset(Category category) {
DefaultPieDataset dpds = new DefaultPieDataset()
category.categories { subcat ->
dpds.setValue(subcat.description, subcat.duration.getMillis()) }
}
}
package com.jdbernard.timeanalyzer.chart
import com.jdbernard.timeanalyzer.categories.Category
import org.jfree.data.general.DefaultPieDataset
import org.jfree.data.general.PieDataset
import org.jfree.util.SortOrder
public class Util {
public static PieDataset makePieDataset(Category category) {
DefaultPieDataset dpds = new DefaultPieDataset()
category.categories { subcat ->
dpds.setValue(subcat.description, subcat.duration.getMillis()) }
}
}

View File

@ -1,41 +1,41 @@
package com.jdbernard.timeanalyzer
import org.joda.time.DateTime
import org.joda.time.Duration
import org.joda.time.format.PeriodFormat
import org.joda.time.format.PeriodFormatter
public class Event implements Cloneable {
public final String description
public final String notes
public final DateTime start
public Duration duration // bit of a hack, allows modification for the
// TimelineEventProcessor
public static PeriodFormatter periodFormatter = PeriodFormat.getDefault()
public Event(String desc, String notes, DateTime start, Duration duration) {
this.description = desc
this.notes = notes
this.start = start
this.duration = duration
}
public Event(Map params) {
this.description = params.description ?: ""
this.notes = params.notes ?: ""
this.start = params.start
this.duration = params.duration
}
public Event(Map params, Event e) {
this.description = params.description ?: e.description
this.notes = params.notes ?: e.notes
this.start = params.start ?: e.start
this.duration = params.duration ?: e.duration
}
public String toString() { return description }
}
package com.jdbernard.timeanalyzer.events
import org.joda.time.ReadableDateTime
import org.joda.time.Duration
import org.joda.time.format.PeriodFormat
import org.joda.time.format.PeriodFormatter
public class Event implements Cloneable {
public final String description
public final String notes
public final ReadableDateTime start
public Duration duration // bit of a hack, allows modification for the
// TimelineEventProcessor
public static PeriodFormatter periodFormatter = PeriodFormat.getDefault()
public Event(String desc, String notes, ReadableDateTime start, Duration duration) {
this.description = desc
this.notes = notes
this.start = start
this.duration = duration
}
public Event(Map params) {
this.description = params.description ?: ""
this.notes = params.notes ?: ""
this.start = params.start
this.duration = params.duration
}
public Event(Map params, Event e) {
this.description = params.description ?: e.description
this.notes = params.notes ?: e.notes
this.start = params.start ?: e.start
this.duration = params.duration ?: e.duration
}
public String toString() { return description }
}

View File

@ -1,45 +1,46 @@
package com.jdbernard.timeanalyzer
import com.jdbernard.timestamper.core.Timeline
import org.joda.time.DateTime
import org.joda.time.Duration
public class TimelineEventProcessor {
/** Events whose description matches one of these regex strings or
* patterns are ignored. */
List exclusions = []
public TimelineEventProcessor() {}
public TimelineEventProcessor(List exclusions) {
this.exclusions = exclusions
}
public List<Event> process(Timeline timeline) {
List<Event> events = []
timeline.each { marker ->
Event e = new Event(
description: marker.mark,
notes: marker.notes,
start: new DateTime(marker.timestamp),
duration: new Duration(0))
// if this is not the first event, then we need to update the
// duration of the previous event
if (events.size > 0) {
Event lastEvent = events[-1]
lastEvent.duration = new Duration(lastEvent.start, e.start)
}
events << e
}
def excluded = events.findAll { event ->
exclusions.any { exclusion -> event.description ==~ exclusion }
}
return events - excluded
}
}
package com.jdbernard.timeanalyzer.processors
import com.jdbernard.timeanalyzer.events.Event
import com.jdbernard.timestamper.core.Timeline
import org.joda.time.DateTime
import org.joda.time.Duration
public class TimelineEventProcessor {
/** Events whose description matches one of these regex strings or
* patterns are ignored. */
List exclusions = []
public TimelineEventProcessor() {}
public TimelineEventProcessor(List exclusions) {
this.exclusions = exclusions
}
public List<Event> process(Timeline timeline) {
List<Event> events = []
timeline.each { marker ->
Event e = new Event(
description: marker.mark,
notes: marker.notes,
start: new DateTime(marker.timestamp),
duration: new Duration(0))
// if this is not the first event, then we need to update the
// duration of the previous event
if (events.size > 0) {
Event lastEvent = events[-1]
lastEvent.duration = new Duration(lastEvent.start, e.start)
}
events << e
}
def excluded = events.findAll { event ->
exclusions.any { exclusion -> event.description ==~ exclusion }
}
return events - excluded
}
}

View File

@ -1,29 +1,29 @@
package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.Category
import com.jdbernard.timeanalyzer.CategorizationPlan
import com.jdbernard.timeanalyzer.Event
public class TicketCategorizationPlan implements CategorizationPlan {
private static def TICKET_PATTERN = ~/.*#(\d+).*/
public boolean deservesNewCategory(Event e, List<Event> el) {
return e.description ==~ TICKET_PATTERN
}
public Category newCategory(Event e, List<Event> el) {
def m = e.description =~ TICKET_PATTERN
return new TicketCategory(m[0][1] as int)
}
public List<Event> findEventsToRecategorize(Event e,
List<Event> existingEvents) {
def m = e.description =~ TICKET_PATTERN
int ticketId = m[0][1] as int
return existingEvents.findAll {
it.description ==~ /.*#${ticketId}.*/ }
}
}
package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categorizationplans.CategorizationPlan
import com.jdbernard.timeanalyzer.events.Event
public class TicketCategorizationPlan implements CategorizationPlan {
private static def TICKET_PATTERN = ~/.*#(\d+).*/
public boolean deservesNewCategory(Event e, List<Event> el) {
return e.description ==~ TICKET_PATTERN
}
public Category newCategory(Event e, List<Event> el) {
def m = e.description =~ TICKET_PATTERN
return new TicketCategory(m[0][1] as int)
}
public List<Event> findEventsToRecategorize(Event e,
List<Event> existingEvents) {
def m = e.description =~ TICKET_PATTERN
int ticketId = m[0][1] as int
return existingEvents.findAll {
it.description ==~ /.*#${ticketId}.*/ }
}
}

View File

@ -1,25 +1,25 @@
package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.Category
import com.jdbernard.timeanalyzer.Event
public class TicketCategory extends Category {
public final int ticketId
public TicketCategory(int ticketId) {
super()
this.ticketId = ticketId
this.description = "Ticket #${ticketId}"
}
public boolean matchesEvent(Event e) {
return (e.description ==~ /.*#${ticketId}.*/)
}
public Event addEvent(Event e) {
TicketEvent te = new TicketEvent(e)
events << te
return te
}
}
package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.events.Event
public class TicketCategory extends Category {
public final int ticketId
public TicketCategory(int ticketId) {
super()
this.ticketId = ticketId
this.description = "Ticket #${ticketId}"
}
public boolean matchesEvent(Event e) {
return (e.description ==~ /.*#${ticketId}.*/)
}
public Event addEvent(Event e) {
TicketEvent te = new TicketEvent(e)
events << te
return te
}
}

View File

@ -1,37 +1,37 @@
package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.Event
public class TicketEvent extends Event {
public final int id
public TicketEvent(String desc, String notes, String start, String duration) {
super(desc, notes, start, duration)
def m = desc =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
public TicketEvent(Map params) {
super(params)
def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
public TicketEvent(Map params, Event e) {
super(params, e)
def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
public TicketEvent(Event e) {
super([:], e)
def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
}
package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.events.Event
public class TicketEvent extends Event {
public final int id
public TicketEvent(String desc, String notes, String start, String duration) {
super(desc, notes, start, duration)
def m = desc =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
public TicketEvent(Map params) {
super(params)
def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
public TicketEvent(Map params, Event e) {
super(params, e)
def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
public TicketEvent(Event e) {
super([:], e)
def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int
}
}

View File

@ -18,7 +18,7 @@ events = tep.process(timeline)
topcat = new FilteredCategory("Top Category")
topcat.filters << new TimeIntervalCategoryFilter(
new DateTime(2011, 1, 2, 0, 0, 0, 0), new DateTime(2011, 1, 9, 0, 0, 0, 0))
new DateTime(2011, 1, 24, 0, 0, 0, 0), new DateTime(2011, 1, 25, 0, 0, 0, 0))
twoLevelCatPlan = new TwoLevelCategorizationPlan()
descriptionBasedCatPlan = new DescriptionBasedCategorizationPlan()