tickle 1.0.2 → 2.0.0rc3
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.
- checksums.yaml +5 -5
- data/.gitignore +3 -1
- data/.travis.yml +12 -0
- data/CHANGES.md +25 -3
- data/Gemfile +14 -1
- data/LICENCE +1 -1
- data/README.md +1 -1
- data/Rakefile +3 -8
- data/benchmarks/main.rb +71 -0
- data/lib/ext/array.rb +6 -0
- data/lib/ext/date_and_time.rb +64 -0
- data/lib/ext/string.rb +39 -0
- data/lib/tickle.rb +23 -113
- data/lib/tickle/filters.rb +58 -0
- data/lib/tickle/handler.rb +223 -80
- data/lib/tickle/helpers.rb +51 -0
- data/lib/tickle/patterns.rb +115 -0
- data/lib/tickle/repeater.rb +232 -116
- data/lib/tickle/tickle.rb +97 -201
- data/lib/tickle/tickled.rb +173 -0
- data/lib/tickle/token.rb +94 -0
- data/lib/tickle/version.rb +2 -2
- data/spec/helpers_spec.rb +36 -0
- data/spec/patterns_spec.rb +240 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/tickle_spec.rb +551 -0
- data/spec/token_spec.rb +82 -0
- data/tickle.gemspec +4 -9
- metadata +34 -74
- data/lib/numerizer/numerizer.rb +0 -103
@@ -0,0 +1,115 @@
|
|
1
|
+
module Tickle
|
2
|
+
|
3
|
+
# A place to keep all the regular expressions.
|
4
|
+
module Patterns
|
5
|
+
|
6
|
+
PLURAL_OR_PRESENT_PARTICIPLE = /
|
7
|
+
s
|
8
|
+
|
|
9
|
+
ing
|
10
|
+
/x
|
11
|
+
|
12
|
+
ON_THE = /
|
13
|
+
\bon\b
|
14
|
+
(?:
|
15
|
+
\s+
|
16
|
+
the
|
17
|
+
)?
|
18
|
+
/x
|
19
|
+
|
20
|
+
END_OR_UNTIL = /
|
21
|
+
(?:
|
22
|
+
(?:\band\b\s+)?
|
23
|
+
\bend
|
24
|
+
(?: #{PLURAL_OR_PRESENT_PARTICIPLE} )?
|
25
|
+
(?:
|
26
|
+
\s+
|
27
|
+
#{ON_THE}
|
28
|
+
)?
|
29
|
+
)
|
30
|
+
|
|
31
|
+
(:?
|
32
|
+
until
|
33
|
+
(?:
|
34
|
+
\s+
|
35
|
+
\bthe\b
|
36
|
+
)?
|
37
|
+
)
|
38
|
+
/x
|
39
|
+
|
40
|
+
SET_IDENTIFIER = /
|
41
|
+
every
|
42
|
+
|
|
43
|
+
each
|
44
|
+
|
|
45
|
+
(?: #{ON_THE} )
|
46
|
+
/x
|
47
|
+
|
48
|
+
# This is here so we can check for repetition
|
49
|
+
# and set 'until' more easily. If so desired.
|
50
|
+
REPETITION = /
|
51
|
+
(?<repeat>
|
52
|
+
repeat
|
53
|
+
)
|
54
|
+
/x
|
55
|
+
|
56
|
+
START = /
|
57
|
+
start
|
58
|
+
(?: #{PLURAL_OR_PRESENT_PARTICIPLE} )?
|
59
|
+
/x
|
60
|
+
|
61
|
+
START_EVERY_REGEX = /^
|
62
|
+
(?:
|
63
|
+
#{START}
|
64
|
+
)
|
65
|
+
\s+
|
66
|
+
(?<start>.*?)
|
67
|
+
(?:
|
68
|
+
\s+
|
69
|
+
#{REPETITION}
|
70
|
+
)?
|
71
|
+
\s+
|
72
|
+
#{SET_IDENTIFIER}
|
73
|
+
\s+
|
74
|
+
(?<event>.*)
|
75
|
+
/ix
|
76
|
+
|
77
|
+
|
78
|
+
EVERY_START_REGEX = /^
|
79
|
+
(?: #{SET_IDENTIFIER} )
|
80
|
+
\s+
|
81
|
+
(?<event>.*)
|
82
|
+
(?:
|
83
|
+
\s+
|
84
|
+
#{START}
|
85
|
+
(?:
|
86
|
+
\s+
|
87
|
+
#{ON_THE}
|
88
|
+
)?
|
89
|
+
)
|
90
|
+
\s+
|
91
|
+
(?<start>.*)
|
92
|
+
/ix
|
93
|
+
|
94
|
+
START_ENDING_REGEX = /^
|
95
|
+
#{START}
|
96
|
+
\s+
|
97
|
+
(?<start>.*?)
|
98
|
+
(?:
|
99
|
+
\s+
|
100
|
+
#{END_OR_UNTIL}
|
101
|
+
)
|
102
|
+
\s+
|
103
|
+
(?<finish>.*)
|
104
|
+
/ix
|
105
|
+
|
106
|
+
PROCESS_FOR_ENDING = /^
|
107
|
+
(?<target>.*)
|
108
|
+
\s+
|
109
|
+
(?: #{END_OR_UNTIL})
|
110
|
+
\s+
|
111
|
+
(?<ending>.*)
|
112
|
+
/ix
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
data/lib/tickle/repeater.rb
CHANGED
@@ -1,136 +1,252 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
token = self.scan_for_day_names(token) unless token.type
|
11
|
-
token = self.scan_for_year_name(token) unless token.type
|
12
|
-
token = self.scan_for_special_text(token) unless token.type
|
13
|
-
token = self.scan_for_units(token) unless token.type
|
1
|
+
module Tickle
|
2
|
+
require_relative "../ext/string.rb"
|
3
|
+
class Repeater
|
4
|
+
require_relative "token.rb"
|
5
|
+
|
6
|
+
attr_reader :tokens
|
7
|
+
|
8
|
+
def initialize( tokens )
|
9
|
+
@tokens = tokens.map(&:clone)
|
14
10
|
end
|
15
|
-
tokens
|
16
|
-
end
|
17
11
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
|
13
|
+
SCANNING_METHODS = [
|
14
|
+
:scan_for_numbers,
|
15
|
+
:scan_for_ordinal_names,
|
16
|
+
:scan_for_ordinals,
|
17
|
+
:scan_for_month_names,
|
18
|
+
:scan_for_day_names,
|
19
|
+
:scan_for_year_name,
|
20
|
+
:scan_for_special_text,
|
21
|
+
:scan_for_units,
|
22
|
+
]
|
23
|
+
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
25
|
+
#
|
26
|
+
def scan!
|
27
|
+
# for each token
|
28
|
+
@tokens.each do |token|
|
29
|
+
new_details = catch(:token_found) {
|
30
|
+
SCANNING_METHODS.each{|meth|
|
31
|
+
send meth, token
|
32
|
+
}
|
33
|
+
nil # if nothing matched, set to nil
|
34
|
+
}
|
35
|
+
token.update! new_details if new_details
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def detection(token, scanner, &block )
|
42
|
+
scanner = [scanner] unless scanner.respond_to? :keys
|
43
|
+
scanner.each do |key,value|
|
44
|
+
if (md = key.match token.downcase) or (md = key.match token.word)
|
45
|
+
throw :token_found, block.call(md,key,value)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
nil # if it reaches here nothing was found so return nil
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
SCAN_FOR_NUMBERS = /
|
53
|
+
\b
|
54
|
+
(?<number>\d\d?)
|
55
|
+
\b
|
56
|
+
/x
|
57
|
+
|
58
|
+
def scan_for_numbers(token)
|
59
|
+
detection token, SCAN_FOR_NUMBERS do |md,key,value|
|
60
|
+
n = md[:number].to_i
|
61
|
+
{type: :number, start: n, interval: n }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
SCAN_FOR_ORDINAL_NAMES = {
|
67
|
+
/first/ => Ordinal.new( '1st' ),
|
68
|
+
/second\b/ => Ordinal.new( '2nd' ),
|
69
|
+
/third/ => Ordinal.new( '3rd' ),
|
70
|
+
/fourth/ => Ordinal.new( '4th' ),
|
71
|
+
/fifth/ => Ordinal.new( '5th' ),
|
72
|
+
/sixth/ => Ordinal.new( '6th' ),
|
73
|
+
/seventh/ => Ordinal.new( '7th' ),
|
74
|
+
/eighth/ => Ordinal.new( '8th' ),
|
75
|
+
/ninth/ => Ordinal.new( '9th' ),
|
76
|
+
/tenth/ => Ordinal.new( '10th' ),
|
77
|
+
/eleventh/ => Ordinal.new( '11th' ),
|
78
|
+
/twelfth/ => Ordinal.new( '12th' ),
|
79
|
+
/thirteenth/ => Ordinal.new( '13th' ),
|
80
|
+
/fourteenth/ => Ordinal.new( '14th' ),
|
81
|
+
/fifteenth/ => Ordinal.new( '15th' ),
|
82
|
+
/sixteenth/ => Ordinal.new( '16th' ),
|
83
|
+
/seventeenth/ => Ordinal.new( '17th' ),
|
84
|
+
/eighteenth/ => Ordinal.new( '18th' ),
|
85
|
+
/nineteenth/ => Ordinal.new( '19th' ),
|
86
|
+
/twentieth/ => Ordinal.new( '20th' ),
|
87
|
+
/thirtieth/ => Ordinal.new( '30th' ),
|
46
88
|
}
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
89
|
+
|
90
|
+
|
91
|
+
def scan_for_ordinal_names(token)
|
92
|
+
detection token, SCAN_FOR_ORDINAL_NAMES do |md,key,value|
|
93
|
+
{ :type => :ordinal,
|
94
|
+
:start => value.ordinal_as_number,
|
95
|
+
:interval => Tickle::Helpers.days_in_month( Tickle::Helpers.get_next_month( value.ordinal_as_number )),
|
96
|
+
}
|
51
97
|
end
|
52
98
|
end
|
53
|
-
token
|
54
|
-
end
|
55
99
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
100
|
+
|
101
|
+
SCAN_FOR_ORDINALS = /
|
102
|
+
\b
|
103
|
+
(?<number>\d+)
|
104
|
+
(?:
|
105
|
+
st
|
106
|
+
|
|
107
|
+
nd
|
108
|
+
|
|
109
|
+
rd
|
110
|
+
|th
|
111
|
+
)
|
112
|
+
\b
|
113
|
+
/x
|
114
|
+
|
115
|
+
def scan_for_ordinals(token)
|
116
|
+
detection token, SCAN_FOR_ORDINALS do |md,key,value|
|
117
|
+
number = Ordinal.new(md[:number])
|
118
|
+
{ :type => :ordinal,
|
119
|
+
:start => number.ordinal_as_number,
|
120
|
+
:interval => Tickle::Helpers.days_in_month(Tickle::Helpers.get_next_month number )
|
121
|
+
}
|
122
|
+
end
|
61
123
|
end
|
62
|
-
token
|
63
|
-
end
|
64
124
|
|
65
|
-
|
66
|
-
|
67
|
-
/^
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
token
|
125
|
+
|
126
|
+
def scan_for_month_names(token)
|
127
|
+
scanner = {/^jan\.?(uary)?$/ => 1,
|
128
|
+
/^feb\.?(ruary)?$/ => 2,
|
129
|
+
/^mar\.?(ch)?$/ => 3,
|
130
|
+
/^apr\.?(il)?$/ => 4,
|
131
|
+
/^may$/ => 5,
|
132
|
+
/^jun\.?e?$/ => 6,
|
133
|
+
/^jul\.?y?$/ => 7,
|
134
|
+
/^aug\.?(ust)?$/ => 8,
|
135
|
+
/^sep\.?(t\.?|tember)?$/ => 9,
|
136
|
+
/^oct\.?(ober)?$/ => 10,
|
137
|
+
/^nov\.?(ember)?$/ => 11,
|
138
|
+
/^dec\.?(ember)?$/ => 12}
|
139
|
+
detection token, scanner do |md,key,value|
|
140
|
+
{
|
141
|
+
:type => :month_name,
|
142
|
+
:start => value,
|
143
|
+
:interval => 30,
|
144
|
+
}
|
145
|
+
end
|
80
146
|
end
|
81
|
-
token
|
82
|
-
end
|
83
147
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
148
|
+
|
149
|
+
def scan_for_day_names(token)
|
150
|
+
scanner = {
|
151
|
+
/^m[ou]n(day)?$/ => :monday,
|
152
|
+
/^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
|
153
|
+
/^tue$/ => :tuesday,
|
154
|
+
/^we(dnes|nds|nns)day$/ => :wednesday,
|
155
|
+
/^wed$/ => :wednesday,
|
156
|
+
/^th(urs|ers)day$/ => :thursday,
|
157
|
+
/^thu$/ => :thursday,
|
158
|
+
/^fr[iy](day)?$/ => :friday,
|
159
|
+
/^sat(t?[ue]rday)?$/ => :saturday,
|
160
|
+
/^su[nm](day)?$/ => :sunday
|
161
|
+
}
|
162
|
+
detection token, scanner do |md,key,value|
|
163
|
+
{
|
164
|
+
:type => :weekday,
|
165
|
+
:start => value,
|
166
|
+
:interval => 7,
|
167
|
+
}
|
168
|
+
end
|
97
169
|
end
|
98
|
-
token
|
99
|
-
end
|
100
170
|
|
101
|
-
def self.scan_for_year_name(token)
|
102
|
-
regex = /\b\d{4}\b/
|
103
|
-
token.update(:specific_year, token.original.gsub(regex,'\1'), 365) if token.original =~ regex
|
104
|
-
token
|
105
|
-
end
|
106
171
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
172
|
+
def scan_for_year_name(token)
|
173
|
+
detection token, /\b(?<year>\d{4})\b/ do |md,key,value|
|
174
|
+
{
|
175
|
+
:type => :specific_year,
|
176
|
+
:start => md[:year],
|
177
|
+
:interval => 365,
|
178
|
+
}
|
179
|
+
end
|
115
180
|
end
|
116
|
-
token
|
117
|
-
end
|
118
181
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
182
|
+
|
183
|
+
def scan_for_special_text(token)
|
184
|
+
scanner = {
|
185
|
+
/^other$/ => :other,
|
186
|
+
/^begin(ing|ning)?$/ => :beginning,
|
187
|
+
/^start$/ => :beginning,
|
188
|
+
/^end$/ => :end,
|
189
|
+
/^mid(d)?le$/ => :middle
|
190
|
+
}
|
191
|
+
detection token, scanner do |md,key,value|
|
192
|
+
{
|
193
|
+
:type => :special,
|
194
|
+
:start => value,
|
195
|
+
:interval => 7,
|
196
|
+
}
|
130
197
|
end
|
131
198
|
end
|
132
|
-
token
|
133
|
-
end
|
134
199
|
|
135
200
|
|
136
|
-
|
201
|
+
def scan_for_units(token)
|
202
|
+
scanner = {
|
203
|
+
/^year(?:ly)?s?$/ => {
|
204
|
+
:type => :year,
|
205
|
+
:interval => 365,
|
206
|
+
:start => :today
|
207
|
+
},
|
208
|
+
/^month(?:ly|s)?$/ => {
|
209
|
+
:type => :month,
|
210
|
+
:interval => 30,
|
211
|
+
:start => :today
|
212
|
+
},
|
213
|
+
/^fortnights?$/ => {
|
214
|
+
:type => :fortnight,
|
215
|
+
:interval => 14,
|
216
|
+
:start => :today
|
217
|
+
},
|
218
|
+
/^week(?:ly|s)?$/ => {
|
219
|
+
:type => :week,
|
220
|
+
:interval => 7,
|
221
|
+
:start => :today
|
222
|
+
},
|
223
|
+
/^weekends?$/ => {
|
224
|
+
:type => :weekend,
|
225
|
+
:interval => 7,
|
226
|
+
:start => :saturday
|
227
|
+
},
|
228
|
+
/^days?$/ => {
|
229
|
+
:type => :day,
|
230
|
+
:interval => 1,
|
231
|
+
:start => :today
|
232
|
+
},
|
233
|
+
/^daily$/ => {
|
234
|
+
:type => :day,
|
235
|
+
:interval => 1,
|
236
|
+
:start => :today
|
237
|
+
},
|
238
|
+
/^sec(?:onds)?$/ => {
|
239
|
+
:type => :sec,
|
240
|
+
:interval => 1,
|
241
|
+
:start => :today
|
242
|
+
},
|
243
|
+
}
|
244
|
+
|
245
|
+
detection token, scanner do |md,key,value|
|
246
|
+
value
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|