texp 0.0.3 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@ module TExp
3
3
  register_parse_callback('e')
4
4
 
5
5
  # Is +date+ included in the temporal expression.
6
- def include?(date)
6
+ def includes?(date)
7
7
  true
8
8
  end
9
9
 
@@ -17,10 +17,6 @@ module TExp
17
17
  codes << encoding_token
18
18
  end
19
19
 
20
- def to_hash
21
- build_hash
22
- end
23
-
24
20
  class << self
25
21
  def parse_callback(stack)
26
22
  stack.push TExp::EveryDay.new
data/lib/texp/logic.rb CHANGED
@@ -1,31 +1,14 @@
1
1
  module TExp
2
2
 
3
- # Base class for temporal expressions with multiple sub-expressions
4
- # (i.e. terms).
5
- class TermsBase < Base
6
- class << self
7
- # Parsing callback for terms based temporal expressions. The
8
- # top of the stack is assumed to be a list that is *-expanded to
9
- # the temporal expression's constructor.
10
- def parse_callback(stack)
11
- stack.push self.new(*stack.pop)
12
- end
13
- end
14
- end
15
-
3
+ ####################################################################
16
4
  # Logically AND a list of temporal expressions. A date is included
17
5
  # only if it is included in all of the sub-expressions.
18
- class And < TermsBase
6
+ class And < MultiTermBase
19
7
  register_parse_callback('a')
20
8
 
21
- # Create an AND temporal expression.
22
- def initialize(*terms)
23
- @terms = terms
24
- end
25
-
26
9
  # Is +date+ included in the temporal expression.
27
- def include?(date)
28
- @terms.all? { |te| te.include?(date) }
10
+ def includes?(date)
11
+ @terms.all? { |te| te.includes?(date) }
29
12
  end
30
13
 
31
14
  # Human readable version of the temporal expression.
@@ -38,21 +21,17 @@ module TExp
38
21
  encode_list(codes, @terms)
39
22
  codes << encoding_token
40
23
  end
41
- end
24
+ end # class And
42
25
 
26
+ ####################################################################
43
27
  # Logically OR a list of temporal expressions. A date is included
44
28
  # if it is included in any of the sub-expressions.
45
- class Or < TermsBase
29
+ class Or < MultiTermBase
46
30
  register_parse_callback('o')
47
31
 
48
- # Create an OR temporal expression.
49
- def initialize(*terms)
50
- @terms = terms
51
- end
52
-
53
32
  # Is +date+ included in the temporal expression.
54
- def include?(date)
55
- @terms.any? { |te| te.include?(date) }
33
+ def includes?(date)
34
+ @terms.any? { |te| te.includes?(date) }
56
35
  end
57
36
 
58
37
  # Human readable version of the temporal expression.
@@ -65,21 +44,17 @@ module TExp
65
44
  encode_list(codes, @terms)
66
45
  codes << encoding_token
67
46
  end
68
- end
47
+ end # class Or
69
48
 
49
+ ####################################################################
70
50
  # Logically NEGATE a temporal expression. A date is included if it
71
51
  # is not included in the sub-expression.
72
- class Not < Base
52
+ class Not < SingleTermBase
73
53
  register_parse_callback('n')
74
54
 
75
- # Create a NOT temporal expression.
76
- def initialize(term)
77
- @term = term
78
- end
79
-
80
55
  # Is date included in the temporal expression.
81
- def include?(date)
82
- ! @term.include?(date)
56
+ def includes?(date)
57
+ ! @term.includes?(date)
83
58
  end
84
59
 
85
60
  # Human readable version of the temporal expression.
@@ -92,5 +67,6 @@ module TExp
92
67
  @term.encode(codes)
93
68
  codes << encoding_token
94
69
  end
95
- end
70
+ end # class Not
71
+
96
72
  end
data/lib/texp/month.rb CHANGED
@@ -7,7 +7,7 @@ module TExp
7
7
  end
8
8
 
9
9
  # Is +date+ included in the temporal expression.
10
- def include?(date)
10
+ def includes?(date)
11
11
  @months.include?(date.month)
12
12
  end
13
13
 
@@ -23,10 +23,5 @@ module TExp
23
23
  codes << encoding_token
24
24
  end
25
25
 
26
- def to_hash
27
- build_hash do |b|
28
- b.with @months
29
- end
30
- end
31
26
  end
32
27
  end
@@ -0,0 +1,53 @@
1
+ module TExp
2
+ class Base
3
+
4
+ # Combine two temporal expressions so that the result will match
5
+ # the union of the dates matched by the individual temporal
6
+ # expressions.
7
+ #
8
+ # <b>Examples:</b>
9
+ #
10
+ # dow(:monday) + dow(:tuesday) # Match any date falling on Monday or Tuesday
11
+ #
12
+ def +(texp)
13
+ TExp::Or.new(self, texp)
14
+ end
15
+
16
+ # Combine two temporal expressions so that the result will match
17
+ # the intersection of the dates matched by the individual temporal
18
+ # expressions.
19
+ #
20
+ # <b>Examples:</b>
21
+ #
22
+ # month("Feb") * day(14) # Match the 14th of February (in any year)
23
+ #
24
+ def *(texp)
25
+ TExp::And.new(self, texp)
26
+ end
27
+
28
+ # Combine two temporal expressions so that the result will match
29
+ # the any date matched by left expression except for dates matched
30
+ # by the right expression.
31
+ #
32
+ # <b>Examples:</b>
33
+ #
34
+ # month("Feb") - dow(:mon) # Match any day in February except Mondays
35
+ #
36
+ def -(texp)
37
+ TExp::And.new(self, TExp::Not.new(texp))
38
+ end
39
+
40
+ # Return a new temporal expression that negates the sense of the
41
+ # current expression. In other words, match everything the
42
+ # current expressions does not match and don't match anything that
43
+ # it does.
44
+ #
45
+ # <b>Examples:</b>
46
+ #
47
+ # -dow(:mon) # Match everything but Mondays
48
+ #
49
+ def -@()
50
+ TExp::Not.new(self)
51
+ end
52
+ end
53
+ end
data/lib/texp/parse.rb CHANGED
@@ -1,15 +1,10 @@
1
1
  module TExp
2
2
 
3
- # Thrown if an error is encountered during the parsing of a temporal
4
- # expression.
5
- class ParseError < StandardError
6
- end
7
-
8
3
  # ------------------------------------------------------------------
9
4
  # Class methods.
10
5
  #
11
6
  class << self
12
- PARSE_CALLBACKS = {}
7
+ PARSE_CALLBACKS = {} # :nodoc:
13
8
 
14
9
  # Lexical Definitions
15
10
  TOKEN_PATTERNS = [
@@ -22,17 +17,17 @@ module TExp
22
17
  # Everything else is a single character
23
18
  # (except commas and spaces which are ignored)
24
19
  '[^, ]',
25
- ].join('|')
26
- TOKEN_RE = Regexp.new(TOKEN_PATTERNS)
20
+ ].join('|') # :nodoc:
21
+ TOKEN_RE = Regexp.new(TOKEN_PATTERNS) # :nodoc:
27
22
 
28
23
  # Register a parsing callback. Individual Temporal Expression
29
- # classes will register their won callbacks as needed. A handful
24
+ # classes will register their own callbacks as needed. A handful
30
25
  # of non-class based parser callbacks are registered below.
31
26
  def register_parse_callback(token, callback)
32
27
  PARSE_CALLBACKS[token] = callback
33
28
  end
34
29
 
35
- # Parse a temporal expression string
30
+ # Return the temporal expression encoded by string.
36
31
  def parse(string)
37
32
  @stack = []
38
33
  string.scan(TOKEN_RE) do |tok|
@@ -42,21 +37,6 @@ module TExp
42
37
  @stack.pop
43
38
  end
44
39
 
45
- def from_params(hash)
46
- h = hash['1']
47
- tok = h['type']
48
- cb = PARSE_CALLBACKS[tok]
49
- a1 = h["#{tok}1"]
50
- a2 = h["#{tok}2"]
51
- if a2
52
- cb.new(a1, a2)
53
- elsif a1
54
- cb.new(a1)
55
- else
56
- cb.new
57
- end
58
- end
59
-
60
40
  private
61
41
 
62
42
  # Compile the token into the current definition.
@@ -90,7 +70,8 @@ module TExp
90
70
 
91
71
  # List parsing handlers
92
72
 
93
- MARK = :mark
73
+ # Mark the end of the list.
74
+ MARK = :mark # :nodoc:
94
75
 
95
76
  # Push a mark on the stack to start a list.
96
77
  register_parse_callback('[',
@@ -108,5 +89,6 @@ module TExp
108
89
  fail ParseError, "Expression stack exhausted" if stack.empty?
109
90
  stack.pop
110
91
  stack.push list
111
- end)
92
+ end
93
+ )
112
94
  end # module TExp
@@ -0,0 +1,7 @@
1
+ class Time
2
+ unless Time.now.respond_to?(:to_date)
3
+ def to_date
4
+ Date.new(self.year, self.month, self.day)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module TExp
2
+ VERSION = '0.0.7'
3
+ end
data/lib/texp/week.rb CHANGED
@@ -9,7 +9,7 @@ module TExp
9
9
  end
10
10
 
11
11
  # Is +date+ included in the temporal expression.
12
- def include?(date)
12
+ def includes?(date)
13
13
  @weeks.include?(week_from_front(date)) ||
14
14
  @weeks.include?(week_from_back(date))
15
15
  end
@@ -25,12 +25,6 @@ module TExp
25
25
  "it is the " + ordinal_list(@weeks) + " week of the month"
26
26
  end
27
27
 
28
- def to_hash
29
- build_hash do |b|
30
- b.with @weeks
31
- end
32
- end
33
-
34
28
  private
35
29
 
36
30
  def week_from_front(date)
data/lib/texp/window.rb CHANGED
@@ -1,37 +1,55 @@
1
1
  module TExp
2
- class Window < Base
2
+ class Window < SingleTermBase
3
3
  register_parse_callback('s')
4
4
 
5
5
  def initialize(texp, prewindow_days, postwindow_days)
6
- @texp = texp
6
+ super(texp)
7
7
  @prewindow_days = prewindow_days
8
8
  @postwindow_days = postwindow_days
9
9
  end
10
10
 
11
11
  # Is +date+ included in the temporal expression.
12
- def include?(date)
13
- @texp.include?(date) ||
14
- (1..@prewindow_days).any? { |i| @texp.include?(date + i) } ||
15
- (1..@postwindow_days).any? { |i| @texp.include?(date - i) }
12
+ def includes?(date)
13
+ find_pivot(date) != nil
14
+ end
15
+
16
+ # Return the first day of the window for a given date. Return nil
17
+ # if the date is not in a window.
18
+ def first_day_of_window(date)
19
+ pivot = find_pivot(date)
20
+ pivot ? pivot - @prewindow_days : nil
21
+ end
22
+
23
+ # Return the first day of the window for a given date. Return nil
24
+ # if the date is not in a window.
25
+ def last_day_of_window(date)
26
+ pivot = find_pivot(date)
27
+ pivot ? pivot + @postwindow_days : nil
28
+ end
29
+
30
+ # Find the matching date for the window.
31
+ def find_pivot(date)
32
+ d = date - @postwindow_days
33
+ while d <= date + @prewindow_days
34
+ return d if @term.includes?(d)
35
+ d += 1
36
+ end
37
+ return nil
16
38
  end
17
39
 
18
40
  # Human readable version of the temporal expression.
19
41
  def inspect
20
- @texp.inspect + ", " +
42
+ @term.inspect + ", " +
21
43
  "or up to #{days(@prewindow_days)} prior, " +
22
44
  "or up to #{days(@postwindow_days)} after"
23
45
  end
24
46
 
25
47
  # Encode the temporal expression into +codes+.
26
48
  def encode(codes)
27
- @texp.encode(codes)
49
+ @term.encode(codes)
28
50
  codes << @prewindow_days << "," << @postwindow_days << "s"
29
51
  end
30
52
 
31
- def to_hash
32
- fail "TBD"
33
- end
34
-
35
53
  private
36
54
 
37
55
  def days(n)
data/lib/texp/year.rb CHANGED
@@ -7,7 +7,7 @@ module TExp
7
7
  end
8
8
 
9
9
  # Is +date+ included in the temporal expression.
10
- def include?(date)
10
+ def includes?(date)
11
11
  @years.include?(date.year)
12
12
  end
13
13
 
@@ -22,10 +22,5 @@ module TExp
22
22
  codes << encoding_token
23
23
  end
24
24
 
25
- def to_hash
26
- build_hash do |b|
27
- b.with @years
28
- end
29
- end
30
25
  end
31
26
  end
@@ -0,0 +1,82 @@
1
+ require 'test/unit'
2
+ require 'texp'
3
+
4
+ class BaseEachTest < Test::Unit::TestCase
5
+ def test_each_on_base
6
+ te = basic_texp
7
+ assert_equal [te], te.collect { |t| t }
8
+ end
9
+
10
+ def test_each_on_single_term
11
+ te = single_term_texp
12
+ assert_equal [@basic, @single], te.collect { |t| t }
13
+ end
14
+
15
+ def test_each_on_multi_term
16
+ te = multi_term_texp
17
+ assert_equal [@basic, @multi], te.collect { |t| t }
18
+ end
19
+
20
+ private
21
+
22
+ def basic_texp
23
+ @basic = TExp::DayOfMonth.new(1)
24
+ end
25
+
26
+ def single_term_texp
27
+ @single = TExp::Not.new(basic_texp)
28
+ end
29
+
30
+ def multi_term_texp
31
+ @multi = TExp::And.new(basic_texp)
32
+ end
33
+
34
+ end
35
+
36
+ class BaseAnchorTest < Test::Unit::TestCase
37
+ def test_setting_anchor_date
38
+ start_date = Date.parse("Feb 10, 2008")
39
+ te = TExp::DayInterval.new(start_date, 3)
40
+ assert_cycle(te, start_date, 3)
41
+
42
+ te2 = te.reanchor(start_date+1)
43
+
44
+ assert_cycle(te, start_date, 3)
45
+ assert_cycle(te2, start_date+1, 3)
46
+ end
47
+
48
+ def assert_cycle(te, start_date, n)
49
+ (0...2*n).each do |i|
50
+ if (i % n) == 0
51
+ assert te.includes?(start_date + i)
52
+ else
53
+ assert ! te.includes?(start_date + i)
54
+ end
55
+ end
56
+ end
57
+
58
+ def test_that_complex_expression_propagate_anchor_date
59
+ start_date = Date.parse("Feb 14, 2008")
60
+ two_day_cycle = TExp::DayInterval.new(start_date, 2)
61
+ three_day_cycle = TExp::DayInterval.new(start_date, 3)
62
+ year_2008 = TExp::Year.new(2008)
63
+
64
+ te = TExp::And.new(
65
+ year_2008,
66
+ TExp::Or.new(two_day_cycle, TExp::Not.new(three_day_cycle)))
67
+
68
+ new_date = Date.parse("Feb 15, 2008")
69
+ te2 = te.reanchor(new_date)
70
+
71
+ assert_complex_cycle(te, start_date)
72
+ assert_complex_cycle(te2, new_date)
73
+ end
74
+
75
+ def assert_complex_cycle(te, start_date)
76
+ assert te.includes?(start_date)
77
+ assert te.includes?(start_date+1)
78
+ assert te.includes?(start_date+2)
79
+ assert ! te.includes?(start_date+3)
80
+ assert te.includes?(start_date+4)
81
+ end
82
+ end