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/ChangeLog +15 -0
- data/INFO +33 -0
- data/MIT-LICENSE +21 -0
- data/README +2 -0
- data/Rakefile +86 -0
- data/doc/jamis.rb +591 -0
- data/lib/texp.rb +13 -0
- data/lib/texp/base.rb +146 -0
- data/lib/texp/core.rb +41 -0
- data/lib/texp/day_interval.rb +55 -0
- data/lib/texp/day_of_month.rb +32 -0
- data/lib/texp/day_of_week.rb +32 -0
- data/lib/texp/every_day.rb +30 -0
- data/lib/texp/ext.rb +4 -0
- data/lib/texp/hash_builder.rb +44 -0
- data/lib/texp/logic.rb +96 -0
- data/lib/texp/month.rb +32 -0
- data/lib/texp/parse.rb +112 -0
- data/lib/texp/week.rb +61 -0
- data/lib/texp/window.rb +52 -0
- data/lib/texp/year.rb +31 -0
- data/test/texp/day_interval_test.rb +22 -0
- data/test/texp/day_of_month_test.rb +27 -0
- data/test/texp/day_of_week_test.rb +31 -0
- data/test/texp/every_day_test.rb +15 -0
- data/test/texp/ext_test.rb +82 -0
- data/test/texp/hash_test.rb +26 -0
- data/test/texp/inspect_test.rb +65 -0
- data/test/texp/logic_test.rb +51 -0
- data/test/texp/logic_text_test.rb +0 -0
- data/test/texp/month_test.rb +20 -0
- data/test/texp/parse_test.rb +233 -0
- data/test/texp/week_test.rb +58 -0
- data/test/texp/window_test.rb +32 -0
- data/test/texp/year_test.rb +28 -0
- metadata +94 -0
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,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
|