texp 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/texp/month.rb ADDED
@@ -0,0 +1,32 @@
1
+ module TExp
2
+ class Month < Base
3
+ register_parse_callback('m')
4
+
5
+ def initialize(months)
6
+ @months = listize(months)
7
+ end
8
+
9
+ # Is +date+ included in the temporal expression.
10
+ def include?(date)
11
+ @months.include?(date.month)
12
+ end
13
+
14
+ # Human readable version of the temporal expression.
15
+ def inspect
16
+ "the month is " +
17
+ humanize_list(@months) { |m| Date::MONTHNAMES[m] }
18
+ end
19
+
20
+ # Encode the temporal expression into +codes+.
21
+ def encode(codes)
22
+ encode_list(codes, @months)
23
+ codes << encoding_token
24
+ end
25
+
26
+ def to_hash
27
+ build_hash do |b|
28
+ b.with @months
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/texp/parse.rb ADDED
@@ -0,0 +1,112 @@
1
+ module TExp
2
+
3
+ # Thrown if an error is encountered during the parsing of a temporal
4
+ # expression.
5
+ class ParseError < StandardError
6
+ end
7
+
8
+ # ------------------------------------------------------------------
9
+ # Class methods.
10
+ #
11
+ class << self
12
+ PARSE_CALLBACKS = {}
13
+
14
+ # Lexical Definitions
15
+ TOKEN_PATTERNS = [
16
+ # Dates
17
+ '\d\d\d\d-\d\d-\d\d',
18
+ # Numbers
19
+ '[+-]?\d+',
20
+ # Extension Tokens
21
+ '<(?:[a-zA-Z_][a-zA-Z0-9_]*::)?[a-zA-Z_][a-zA-Z0-9_]*>',
22
+ # Everything else is a single character
23
+ # (except commas and spaces which are ignored)
24
+ '[^, ]',
25
+ ].join('|')
26
+ TOKEN_RE = Regexp.new(TOKEN_PATTERNS)
27
+
28
+ # Register a parsing callback. Individual Temporal Expression
29
+ # classes will register their won callbacks as needed. A handful
30
+ # of non-class based parser callbacks are registered below.
31
+ def register_parse_callback(token, callback)
32
+ PARSE_CALLBACKS[token] = callback
33
+ end
34
+
35
+ # Parse a temporal expression string
36
+ def parse(string)
37
+ @stack = []
38
+ string.scan(TOKEN_RE) do |tok|
39
+ compile(tok)
40
+ end
41
+ fail ParseError, "Incomplete expression" if @stack.size > 1
42
+ @stack.pop
43
+ end
44
+
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
+ private
61
+
62
+ # Compile the token into the current definition.
63
+ def compile(tok)
64
+ handler = PARSE_CALLBACKS[tok]
65
+ if handler
66
+ handler.parse_callback(@stack)
67
+ else
68
+ case tok
69
+ when /^\d\d\d\d-\d\d-\d\d/
70
+ @stack.push Date.parse(tok)
71
+ when /^[-+]?\d+$/
72
+ @stack.push tok.to_i
73
+ else
74
+ fail ParseError, "Unrecoginized TExp Token '#{tok}'"
75
+ end
76
+ end
77
+ end
78
+
79
+ end # << TExp
80
+
81
+ # Convenience class for creating parse callbacks.
82
+ class ParseCallback
83
+ def initialize(&block)
84
+ @callback = block
85
+ end
86
+ def parse_callback(stack)
87
+ @callback.call(stack)
88
+ end
89
+ end
90
+
91
+ # List parsing handlers
92
+
93
+ MARK = :mark
94
+
95
+ # Push a mark on the stack to start a list.
96
+ register_parse_callback('[',
97
+ ParseCallback.new do |stack|
98
+ stack.push MARK
99
+ end)
100
+
101
+ # Pop the stack and build a list until you find a mark.
102
+ register_parse_callback(']',
103
+ ParseCallback.new do |stack|
104
+ list = []
105
+ while ! stack.empty? && stack.last != MARK
106
+ list.unshift stack.pop
107
+ end
108
+ fail ParseError, "Expression stack exhausted" if stack.empty?
109
+ stack.pop
110
+ stack.push list
111
+ end)
112
+ end # module TExp
data/lib/texp/week.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'date'
2
+
3
+ module TExp
4
+ class Week < Base
5
+ register_parse_callback('k')
6
+
7
+ def initialize(weeks)
8
+ @weeks = listize(weeks)
9
+ end
10
+
11
+ # Is +date+ included in the temporal expression.
12
+ def include?(date)
13
+ @weeks.include?(week_from_front(date)) ||
14
+ @weeks.include?(week_from_back(date))
15
+ end
16
+
17
+ # Encode the temporal expression into +codes+.
18
+ def encode(codes)
19
+ encode_list(codes, @weeks)
20
+ codes << encoding_token
21
+ end
22
+
23
+ # Human readable version of the temporal expression.
24
+ def inspect
25
+ "it is the " + ordinal_list(@weeks) + " week of the month"
26
+ end
27
+
28
+ def to_hash
29
+ build_hash do |b|
30
+ b.with @weeks
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def week_from_front(date)
37
+ week = ((date.day-1) / 7) + 1
38
+ end
39
+
40
+ def week_from_back(date)
41
+ last_day = last_day_of_month(date)
42
+ -(((last_day - date.day) / 7) + 1)
43
+ end
44
+
45
+ def self.days_in_month(month)
46
+ d = Date.new(2007, month, 1) + 31
47
+ while d.day < 27
48
+ d -= 1
49
+ end
50
+ d.day
51
+ end
52
+
53
+ DAYS_IN_MONTH = [ 0 ] +
54
+ (1..12).collect { |m| days_in_month(m) }
55
+
56
+ def last_day_of_month(date)
57
+ DAYS_IN_MONTH[date.month] +
58
+ (date.month == 2 && Date.leap?(date.year) ? 1 : 0)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,52 @@
1
+ module TExp
2
+ class Window < Base
3
+ register_parse_callback('s')
4
+
5
+ def initialize(texp, prewindow_days, postwindow_days)
6
+ @texp = texp
7
+ @prewindow_days = prewindow_days
8
+ @postwindow_days = postwindow_days
9
+ end
10
+
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) }
16
+ end
17
+
18
+ # Human readable version of the temporal expression.
19
+ def inspect
20
+ @texp.inspect + ", " +
21
+ "or up to #{days(@prewindow_days)} prior, " +
22
+ "or up to #{days(@postwindow_days)} after"
23
+ end
24
+
25
+ # Encode the temporal expression into +codes+.
26
+ def encode(codes)
27
+ @texp.encode(codes)
28
+ codes << @prewindow_days << "," << @postwindow_days << "s"
29
+ end
30
+
31
+ def to_hash
32
+ fail "TBD"
33
+ end
34
+
35
+ private
36
+
37
+ def days(n)
38
+ n == 1 ? "#{n} day" : "#{n} days"
39
+ end
40
+
41
+ class << self
42
+ # Parsing callback for window temporal expressions.
43
+ def parse_callback(stack)
44
+ postwindow = stack.pop
45
+ prewindow = stack.pop
46
+ te = stack.pop
47
+ stack.push TExp::Window.new(te, prewindow, postwindow)
48
+ end
49
+ end # class << self
50
+
51
+ end
52
+ end
data/lib/texp/year.rb ADDED
@@ -0,0 +1,31 @@
1
+ module TExp
2
+ class Year < Base
3
+ register_parse_callback('y')
4
+
5
+ def initialize(years)
6
+ @years = listize(years)
7
+ end
8
+
9
+ # Is +date+ included in the temporal expression.
10
+ def include?(date)
11
+ @years.include?(date.year)
12
+ end
13
+
14
+ # Human readable version of the temporal expression.
15
+ def inspect
16
+ "the year is " + humanize_list(@years)
17
+ end
18
+
19
+ # Encode the temporal expression into +codes+.
20
+ def encode(codes)
21
+ encode_list(codes, @years)
22
+ codes << encoding_token
23
+ end
24
+
25
+ def to_hash
26
+ build_hash do |b|
27
+ b.with @years
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'test/unit'
4
+ require 'date'
5
+ require 'texp'
6
+
7
+ class DayIntervalTest < Test::Unit::TestCase
8
+
9
+ def test_day_interval
10
+ te = TExp::DayInterval.new(Date.parse("Feb 10, 2008"), 3)
11
+ assert te.include?(Date.parse("Feb 10, 2008"))
12
+ assert ! te.include?(Date.parse("Feb 11, 2008"))
13
+ assert ! te.include?(Date.parse("Feb 12, 2008"))
14
+ assert te.include?(Date.parse("Feb 13, 2008"))
15
+ assert ! te.include?(Date.parse("Feb 14, 2008"))
16
+ assert ! te.include?(Date.parse("Feb 15, 2008"))
17
+ assert te.include?(Date.parse("Feb 16, 2008"))
18
+ assert ! te.include?(Date.parse("Feb 17, 2008"))
19
+ assert ! te.include?(Date.parse("Feb 18, 2008"))
20
+ end
21
+ end
22
+
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'date'
4
+ require 'test/unit'
5
+ require 'texp'
6
+
7
+ class DayOfMonthTest < Test::Unit::TestCase
8
+
9
+ def test_day_of_month_with_single_arg
10
+ te = TExp::DayOfMonth.new(14)
11
+ assert te.include?(Date.parse("Dec 14, 2006"))
12
+ assert te.include?(Date.parse("Feb 14, 2008"))
13
+ assert ! te.include?(Date.parse("Feb 15, 2008"))
14
+ end
15
+
16
+ def test_day_of_include_with_one_day
17
+ te = TExp::DayOfMonth.new([10, 14, 16])
18
+ assert te.include?(Date.parse("Feb 10, 2008"))
19
+ assert ! te.include?(Date.parse("Feb 11, 2008"))
20
+ assert ! te.include?(Date.parse("Feb 12, 2008"))
21
+ assert ! te.include?(Date.parse("Feb 13, 2008"))
22
+ assert te.include?(Date.parse("Feb 14, 2008"))
23
+ assert ! te.include?(Date.parse("Feb 15, 2008"))
24
+ assert te.include?(Date.parse("Feb 16, 2008"))
25
+ end
26
+ end
27
+
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'date'
4
+ require 'test/unit'
5
+ require 'texp'
6
+
7
+ class DayOfWeekTest < Test::Unit::TestCase
8
+
9
+ def test_day_of_week_include_with_one_day
10
+ te = TExp::DayOfWeek.new([1])
11
+ assert ! te.include?(Date.parse("Feb 10, 2008"))
12
+ assert te.include?(Date.parse("Feb 11, 2008"))
13
+ assert ! te.include?(Date.parse("Feb 12, 2008"))
14
+ assert ! te.include?(Date.parse("Feb 13, 2008"))
15
+ assert ! te.include?(Date.parse("Feb 14, 2008"))
16
+ assert ! te.include?(Date.parse("Feb 15, 2008"))
17
+ assert ! te.include?(Date.parse("Feb 16, 2008"))
18
+ end
19
+
20
+ def test_day_of_week_include_with_several_days
21
+ te = TExp::DayOfWeek.new([1, 3, 5])
22
+ assert ! te.include?(Date.parse("Feb 10, 2008"))
23
+ assert te.include?(Date.parse("Feb 11, 2008"))
24
+ assert ! te.include?(Date.parse("Feb 12, 2008"))
25
+ assert te.include?(Date.parse("Feb 13, 2008"))
26
+ assert ! te.include?(Date.parse("Feb 14, 2008"))
27
+ assert te.include?(Date.parse("Feb 15, 2008"))
28
+ assert ! te.include?(Date.parse("Feb 16, 2008"))
29
+ end
30
+ end
31
+
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'test/unit'
4
+ require 'date'
5
+
6
+ require 'texp'
7
+
8
+ class EveryDayTest < Test::Unit::TestCase
9
+
10
+ def test_every_day
11
+ te = TExp::EveryDay.new
12
+ assert te.include?(Date.parse("Feb 15, 2008"))
13
+ end
14
+ end
15
+
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'test/unit'
4
+ require 'date'
5
+ require 'texp'
6
+
7
+ module TExp
8
+
9
+ # Extensions is the standard module for TExp extensions.
10
+ module Extensions
11
+
12
+ # You should put your extensions inside your own namespace module.
13
+ # This will help avoid collisions with with other extensions.
14
+ module MyExt
15
+
16
+ # Create a Temporal Expression class. Inheriting from
17
+ # TExp::Base allows you to reuse some common functionality
18
+ # defined there. Other choices are:
19
+ #
20
+ # (1) TExp::TermsBase -- Defines a parsing method for handling
21
+ # lists of temporal expressions as argument lists.
22
+ #
23
+ class Never < Base
24
+
25
+ # Register your parsing token. Include your namespace as part
26
+ # of the token to avoid naming collisions with other
27
+ # extensions.
28
+ register_parse_callback("<MyExt::never>")
29
+
30
+ # Define how your temporal expression class handles dates.
31
+ # Our example is easy because we always return false.
32
+ def include?(date)
33
+ false
34
+ end
35
+
36
+ # Define an encoder for your temporal expression. Your
37
+ # encoder should push tokens onto +codes+ in a manner that
38
+ # your +parse_handler+ can handle them. Remember that the
39
+ # temporal expressions mini-language is a stack machine that
40
+ # pushs arguments onto the stack before your parse handler
41
+ # sees them.
42
+ def encode(codes)
43
+ codes << "<MyExt::never>"
44
+ end
45
+
46
+ class << self
47
+ # Define a parse handler that will handle your registered
48
+ # parse token. At the point the handler is called,
49
+ # arguments will have been pushed onto the stack. All you
50
+ # have to do is pop them off, construct your temporal
51
+ # expression and push it back onto the stack.
52
+ #
53
+ # See TExp::DayInterval for an example of an expression that
54
+ # handle arguments.
55
+ #
56
+ def parse_callback(stack)
57
+ stack.push new
58
+ end
59
+ end
60
+ end
61
+
62
+ end # module MyExt
63
+
64
+ end # module Extensions
65
+ end # module TExp
66
+
67
+ class ExtensionsTest < Test::Unit::TestCase
68
+ def test_never
69
+ te = TExp::Extensions::MyExt::Never.new
70
+ assert ! te.include?(Date.today)
71
+ end
72
+
73
+ def test_parse_never
74
+ te = TExp.parse("<MyExt::never>")
75
+ assert ! te.include?(Date.today)
76
+ end
77
+
78
+ def test_parsing_round_trip
79
+ assert_equal "<MyExt::never>", TExp.parse("<MyExt::never>").to_s
80
+ end
81
+ end
82
+