texp 0.0.3

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/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
+