texp 0.0.3 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +36 -2
- data/README +92 -0
- data/Rakefile +26 -1
- data/TAGS +369 -0
- data/lib/texp.rb +6 -1
- data/lib/texp/base.rb +139 -10
- data/lib/texp/builder.rb +254 -0
- data/lib/texp/day_interval.rb +19 -15
- data/lib/texp/day_of_month.rb +1 -7
- data/lib/texp/day_of_week.rb +1 -7
- data/lib/texp/dsl.rb +338 -0
- data/lib/texp/errors.rb +18 -0
- data/lib/texp/every_day.rb +1 -5
- data/lib/texp/logic.rb +16 -40
- data/lib/texp/month.rb +1 -6
- data/lib/texp/operators.rb +53 -0
- data/lib/texp/parse.rb +9 -27
- data/lib/texp/time_ext.rb +7 -0
- data/lib/texp/version.rb +3 -0
- data/lib/texp/week.rb +1 -7
- data/lib/texp/window.rb +30 -12
- data/lib/texp/year.rb +1 -6
- data/test/texp/base_test.rb +82 -0
- data/test/texp/day_interval_test.rb +24 -12
- data/test/texp/day_of_month_test.rb +10 -10
- data/test/texp/day_of_week_test.rb +14 -14
- data/test/texp/dsl_test.rb +288 -0
- data/test/texp/every_day_test.rb +1 -1
- data/test/texp/ext_test.rb +3 -3
- data/test/texp/logic_test.rb +18 -18
- data/test/texp/month_test.rb +3 -3
- data/test/texp/operators_test.rb +52 -0
- data/test/texp/parse_test.rb +39 -39
- data/test/texp/time_ext_test.rb +8 -0
- data/test/texp/week_test.rb +19 -19
- data/test/texp/window_test.rb +41 -12
- data/test/texp/year_test.rb +7 -7
- data/test/texp_tests.rb +27 -0
- metadata +14 -6
- data/lib/texp/core.rb +0 -41
- data/lib/texp/hash_builder.rb +0 -44
- data/test/texp/hash_test.rb +0 -26
- data/test/texp/logic_text_test.rb +0 -0
data/lib/texp/every_day.rb
CHANGED
@@ -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
|
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
|
-
|
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 <
|
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
|
28
|
-
@terms.all? { |te| te.
|
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 <
|
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
|
55
|
-
@terms.any? { |te| te.
|
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 <
|
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
|
82
|
-
! @term.
|
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
|
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
|
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
|
-
#
|
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
|
-
|
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
|
data/lib/texp/version.rb
ADDED
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
|
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 <
|
2
|
+
class Window < SingleTermBase
|
3
3
|
register_parse_callback('s')
|
4
4
|
|
5
5
|
def initialize(texp, prewindow_days, postwindow_days)
|
6
|
-
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
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
|