texp 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/texp.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'texp/core'
2
+ require 'texp/base'
3
+ require 'texp/parse'
4
+ require 'texp/day_of_week'
5
+ require 'texp/day_of_month'
6
+ require 'texp/week'
7
+ require 'texp/month'
8
+ require 'texp/year'
9
+ require 'texp/day_interval'
10
+ require 'texp/every_day'
11
+ require 'texp/window'
12
+ require 'texp/logic'
13
+ require 'texp/ext'
data/lib/texp/base.rb ADDED
@@ -0,0 +1,146 @@
1
+ require 'texp/hash_builder'
2
+
3
+ module TExp
4
+ # Abstract Base class for all Texp Temporal Expressions.
5
+ class Base
6
+
7
+ # Convert the temporal expression into an encoded string (that can
8
+ # be parsed by TExp.parse).
9
+ def to_s
10
+ codes = []
11
+ encode(codes)
12
+ codes.join("")
13
+ end
14
+
15
+ private
16
+
17
+ # Coerce +arg+ into a list (i.e. Array) if it is not one already.
18
+ def listize(arg)
19
+ case arg
20
+ when Array
21
+ arg
22
+ else
23
+ [arg]
24
+ end
25
+ end
26
+
27
+ # Encode the date into the codes receiver.
28
+ def encode_date(codes, date)
29
+ codes << date.strftime("%Y-%m-%d")
30
+ end
31
+
32
+ # Encode the list into the codes receiver. All
33
+ def encode_list(codes, list)
34
+ if list.empty?
35
+ codes << "[]"
36
+ elsif list.size == 1
37
+ codes << list.first
38
+ else
39
+ codes << "["
40
+ prev = nil
41
+ list.each do |item|
42
+ codes << "," if prev && ! prev.kind_of?(TExp::Base)
43
+ codes << item.to_s
44
+ prev = item
45
+ end
46
+ codes << "]"
47
+ end
48
+ end
49
+
50
+ # For the list of integers as a list of ordinal numbers. By
51
+ # default, use 'or' as a connectingin word. (e.g. [1,2,3] => "1st,
52
+ # 2nd, or 3rd")
53
+ def ordinal_list(list, connector='or')
54
+ humanize_list(list, connector) { |item| ordinal(item) }
55
+ end
56
+
57
+ # Format the list in a human readable format. By default, use
58
+ # "or" as a connecting word. (e.g. ['a', 'b', 'c'] => "a, b, or
59
+ # c")
60
+ def humanize_list(list, connector='or', &block)
61
+ block ||= lambda { |item| item.to_s }
62
+ list = list.sort if list.all? { |item| item.kind_of?(Integer) && item >= 0 }
63
+ case list.size
64
+ when 1
65
+ block.call(list.first)
66
+ else
67
+ list[0...-1].collect { |d|
68
+ block.call(d)
69
+ }.join(", ") +
70
+ " #{connector} " + block.call(list.last)
71
+ end
72
+ end
73
+
74
+ # Format +date+ in a standard, human-readable format.
75
+ def humanize_date(date)
76
+ date.strftime("%B %d, %Y")
77
+ end
78
+
79
+ # Hash of cardinal integers to ordinal suffixes.
80
+ SUFFIX = {
81
+ 1 => "st",
82
+ 2 => "nd",
83
+ 3 => "rd",
84
+ 4 => "th",
85
+ 5 => "th",
86
+ 6 => "th",
87
+ 7 => "th",
88
+ 8 => "th",
89
+ 9 => "th",
90
+ 0 => "th",
91
+ 11 => "th",
92
+ 12 => "th",
93
+ 13 => "th",
94
+ }
95
+
96
+ # Return the ordinal abbreviation for the integer +n+. (e.g. 1 =>
97
+ # "1st", 3 => "3rd")
98
+ def ordinal(n)
99
+ if n == -1
100
+ "last"
101
+ elsif n == -2
102
+ "next to the last"
103
+ elsif n < 0
104
+ ordinal(-n) + " from the last"
105
+ else
106
+ n.to_s + suffix(n)
107
+ end
108
+ end
109
+
110
+ # The ordinal suffex appropriate for the given number. (e.g. 1 =>
111
+ # "st", 2 => "nd")
112
+ def suffix(n)
113
+ SUFFIX[n % 100] || SUFFIX[n % 10]
114
+ end
115
+
116
+ # Convenience method for accessing the class encoding token.
117
+ def encoding_token
118
+ self.class.encoding_token
119
+ end
120
+
121
+ # Build a params hash for this temporal expression.
122
+ def build_hash
123
+ builder = HashBuilder.new(encoding_token)
124
+ yield builder if block_given?
125
+ builder.hash
126
+ end
127
+
128
+ class << self
129
+ # The token to be used for encoding this temporal expression.
130
+ attr_reader :encoding_token
131
+
132
+ # Register a parse callack for the encoding token for this
133
+ # class.
134
+ def register_parse_callback(token, callback=self)
135
+ @encoding_token = token if callback == self
136
+ TExp.register_parse_callback(token, callback)
137
+ end
138
+
139
+ # The default parsing callback for single argument time
140
+ # expressions. Override if you need anything more complicated.
141
+ def parse_callback(stack)
142
+ stack.push new(stack.pop)
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/texp/core.rb ADDED
@@ -0,0 +1,41 @@
1
+ module TExp
2
+ class << self
3
+ def from_db(string)
4
+ stack = []
5
+ string.split(":").each do |code|
6
+ case code
7
+ when /^\d+$/
8
+ stack.push(code.to_i)
9
+ when /^\d\d\d\d-\d\d-\d\d$/
10
+ stack.push(Date.parse(code))
11
+ when 'x'
12
+ n = stack.pop
13
+ args = []
14
+ n.times do args.unshift(stack.pop) end
15
+ stack.push args
16
+ when 'e'
17
+ stack.push TExp::EveryDay.new
18
+ when 'm'
19
+ stack.push TExp::DayOfMonth.new(stack.pop)
20
+ when 'w'
21
+ stack.push TExp::DayOfWeek.new(stack.pop)
22
+ when 'i'
23
+ interval = stack.pop
24
+ date = stack.pop
25
+ stack.push TExp::DayInterval.new(date, interval)
26
+ when 'a'
27
+ right = stack.pop
28
+ left = stack.pop
29
+ stack.push(TExp::And.new(left, right))
30
+ when 'o'
31
+ right = stack.pop
32
+ left = stack.pop
33
+ stack.push(TExp::Or.new(left, right))
34
+ else
35
+ fail "Unknown TExp DB Code"
36
+ end
37
+ end
38
+ stack.pop
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ module TExp
2
+ class DayInterval < Base
3
+ register_parse_callback('i')
4
+
5
+ def initialize(base_date, interval)
6
+ @base_date = base_date
7
+ @interval = interval
8
+ end
9
+
10
+ # Is +date+ included in the temporal expression.
11
+ def include?(date)
12
+ ((date.mjd - base_mjd) % @interval) == 0
13
+ end
14
+
15
+ # Human readable version of the temporal expression.
16
+ def inspect
17
+ if @interval == 1
18
+ "every day starting on #{humanize_date(@base_date)}"
19
+ else
20
+ "every #{ordinal(@interval)} day starting on #{humanize_date(@base_date)}"
21
+ end
22
+ end
23
+
24
+ # Encode the temporal expression into +codes+.
25
+ def encode(codes)
26
+ encode_date(codes, @base_date)
27
+ codes << ',' << @interval << encoding_token
28
+ end
29
+
30
+ def to_hash
31
+ build_hash do |b|
32
+ b.with(@base_date)
33
+ b.with(@interval)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def base_mjd
40
+ @base_date.mjd
41
+ end
42
+
43
+ def formatted_date
44
+ @base_date.strftime("%Y-%m-%d")
45
+ end
46
+
47
+ class << self
48
+ def parse_callback(stack)
49
+ interval = stack.pop
50
+ date = stack.pop
51
+ stack.push TExp::DayInterval.new(date, interval)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ module TExp
2
+ class DayOfMonth < Base
3
+ register_parse_callback('d')
4
+
5
+ def initialize(days)
6
+ @days = listize(days)
7
+ end
8
+
9
+ # Is +date+ included in the temporal expression.
10
+ def include?(date)
11
+ @days.include?(date.day)
12
+ end
13
+
14
+ # Human readable version of the temporal expression.
15
+ def inspect
16
+ "the day of the month is the " +
17
+ ordinal_list(@days)
18
+ end
19
+
20
+ # Encode the temporal expression into +codes+.
21
+ def encode(codes)
22
+ encode_list(codes, @days)
23
+ codes << encoding_token
24
+ end
25
+
26
+ def to_hash
27
+ build_hash do |b|
28
+ b.with(@days.map { |d| d.to_s })
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module TExp
2
+ class DayOfWeek < Base
3
+ register_parse_callback('w')
4
+
5
+ def initialize(days)
6
+ @days = listize(days)
7
+ end
8
+
9
+ # Is +date+ included in the temporal expression.
10
+ def include?(date)
11
+ @days.include?(date.wday)
12
+ end
13
+
14
+ # Human readable version of the temporal expression.
15
+ def inspect
16
+ "the day of the week is " +
17
+ humanize_list(@days) { |d| Date::DAYNAMES[d] }
18
+ end
19
+
20
+ def to_hash
21
+ build_hash do |b|
22
+ b.with(@days)
23
+ end
24
+ end
25
+
26
+ # Encode the temporal expression into +codes+.
27
+ def encode(codes)
28
+ encode_list(codes, @days)
29
+ codes << encoding_token
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module TExp
2
+ class EveryDay < Base
3
+ register_parse_callback('e')
4
+
5
+ # Is +date+ included in the temporal expression.
6
+ def include?(date)
7
+ true
8
+ end
9
+
10
+ # Human readable version of the temporal expression.
11
+ def inspect
12
+ "every day"
13
+ end
14
+
15
+ # Encode the temporal expression into +codes+.
16
+ def encode(codes)
17
+ codes << encoding_token
18
+ end
19
+
20
+ def to_hash
21
+ build_hash
22
+ end
23
+
24
+ class << self
25
+ def parse_callback(stack)
26
+ stack.push TExp::EveryDay.new
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/texp/ext.rb ADDED
@@ -0,0 +1,4 @@
1
+ module TExp
2
+ module Extensions
3
+ end
4
+ end
@@ -0,0 +1,44 @@
1
+ module TExp
2
+
3
+ # Build a temporal expression hash representation
4
+ #
5
+ # Usage:
6
+ #
7
+ # builder = HashBuilder.new(encoding_token)
8
+ # builder.with(the_first_paramter)
9
+ # builder.with(the_next_parameter)
10
+ # hash = builder.hash
11
+ #
12
+ class HashBuilder
13
+ # Convert the builder to a hash.
14
+ attr_reader :hash
15
+
16
+ # Create a temporal expression params hash builder.
17
+ def initialize(type)
18
+ @type = type
19
+ @hash = { 'type' => type }
20
+ @args = 0
21
+ end
22
+
23
+ # Add an argument to the hash.
24
+ def with(arg)
25
+ @args += 1
26
+ @hash["#{@type}#{@args}"] = simplify(arg)
27
+ self
28
+ end
29
+
30
+ # Simplify the value to be placed in the hash. Essentially we
31
+ # want only strings and lists.
32
+ def simplify(value)
33
+ case value
34
+ when String
35
+ value
36
+ when Array
37
+ value.map { |item| simplify(item) }
38
+ else
39
+ value.to_s
40
+ end
41
+ end
42
+ end
43
+
44
+ end
data/lib/texp/logic.rb ADDED
@@ -0,0 +1,96 @@
1
+ module TExp
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
+
16
+ # Logically AND a list of temporal expressions. A date is included
17
+ # only if it is included in all of the sub-expressions.
18
+ class And < TermsBase
19
+ register_parse_callback('a')
20
+
21
+ # Create an AND temporal expression.
22
+ def initialize(*terms)
23
+ @terms = terms
24
+ end
25
+
26
+ # Is +date+ included in the temporal expression.
27
+ def include?(date)
28
+ @terms.all? { |te| te.include?(date) }
29
+ end
30
+
31
+ # Human readable version of the temporal expression.
32
+ def inspect
33
+ humanize_list(@terms, "and") { |item| item.inspect }
34
+ end
35
+
36
+ # Encode the temporal expression into +codes+.
37
+ def encode(codes)
38
+ encode_list(codes, @terms)
39
+ codes << encoding_token
40
+ end
41
+ end
42
+
43
+ # Logically OR a list of temporal expressions. A date is included
44
+ # if it is included in any of the sub-expressions.
45
+ class Or < TermsBase
46
+ register_parse_callback('o')
47
+
48
+ # Create an OR temporal expression.
49
+ def initialize(*terms)
50
+ @terms = terms
51
+ end
52
+
53
+ # Is +date+ included in the temporal expression.
54
+ def include?(date)
55
+ @terms.any? { |te| te.include?(date) }
56
+ end
57
+
58
+ # Human readable version of the temporal expression.
59
+ def inspect
60
+ humanize_list(@terms) { |item| item.inspect }
61
+ end
62
+
63
+ # Encode the temporal expression into +codes+.
64
+ def encode(codes)
65
+ encode_list(codes, @terms)
66
+ codes << encoding_token
67
+ end
68
+ end
69
+
70
+ # Logically NEGATE a temporal expression. A date is included if it
71
+ # is not included in the sub-expression.
72
+ class Not < Base
73
+ register_parse_callback('n')
74
+
75
+ # Create a NOT temporal expression.
76
+ def initialize(term)
77
+ @term = term
78
+ end
79
+
80
+ # Is date included in the temporal expression.
81
+ def include?(date)
82
+ ! @term.include?(date)
83
+ end
84
+
85
+ # Human readable version of the temporal expression.
86
+ def inspect
87
+ "it is not the case that " + @term.inspect
88
+ end
89
+
90
+ # Encode the temporal expression into +codes+.
91
+ def encode(codes)
92
+ @term.encode(codes)
93
+ codes << encoding_token
94
+ end
95
+ end
96
+ end