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.
- 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
|