texp 0.0.3 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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