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