tickle 0.1.3 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +28 -28
- data/VERSION +1 -1
- data/git-flow-version +1 -1
- data/lib/tickle.rb +8 -5
- data/lib/tickle/handler.rb +6 -2
- data/lib/tickle/tickle.rb +44 -14
- data/tickle.gemspec +1 -1
- metadata +2 -2
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
http://github.com/noctivityinc/tickle
|
3
3
|
by Joshua Lippiner, Noctivity
|
4
4
|
|
5
|
-
|
5
|
+
== *LEGACY WARNING*
|
6
6
|
|
7
7
|
If you starting using Tickle pre version 0.1.X, you will need to update your code to either include the :next_only => true option or read correctly from the options hash. Sorry.
|
8
8
|
|
9
|
-
|
9
|
+
== DESCRIPTION
|
10
10
|
|
11
11
|
Tickle is a natural language parser for recurring events.
|
12
12
|
|
@@ -14,17 +14,17 @@ Tickle is designed to be a compliment of Chronic and can interpret things such a
|
|
14
14
|
|
15
15
|
Tickle has one main method, "Tickle.parse," which returns the next time the event should occur, at which point you simply call Tickle.parse again.
|
16
16
|
|
17
|
-
|
17
|
+
== INSTALLATION
|
18
18
|
|
19
19
|
Tickle can be installed via RubyGems:
|
20
20
|
|
21
21
|
$ gem install tickle
|
22
22
|
|
23
|
-
|
23
|
+
== TINKERING
|
24
24
|
|
25
25
|
Everything's at Github - http://github.com/noctivityinc/tickle
|
26
26
|
|
27
|
-
|
27
|
+
== DEPENDENCIES
|
28
28
|
|
29
29
|
chronic gem (gem install chronic)
|
30
30
|
|
@@ -37,31 +37,31 @@ You can parse strings containing a natural language interval using the Tickle.pa
|
|
37
37
|
You can either pass a string prefixed with the word "every, each or 'on the'" or simply the time frame.
|
38
38
|
|
39
39
|
Tickle.parse returns a hash containing the following keys:
|
40
|
-
* next = the next occurrence of the event. This is NEVER today as its always the next date in the future.
|
41
|
-
* starting = the date all calculations as based on. If not passed as an option, the start date is right now.
|
42
|
-
* until = the last date you want this event to run until.
|
43
|
-
* expression = this is the natural language expression to store to run through tickle later to get the next occurrence.
|
40
|
+
* next = the next occurrence of the event. This is NEVER today as its always the next date in the future.
|
41
|
+
* starting = the date all calculations as based on. If not passed as an option, the start date is right now.
|
42
|
+
* until = the last date you want this event to run until.
|
43
|
+
* expression = this is the natural language expression to store to run through tickle later to get the next occurrence.
|
44
44
|
|
45
45
|
Tickle returns nil if it cannot parse the string cannot be parsed.
|
46
46
|
|
47
47
|
Tickle HEAVILY uses chronic for parsing both the event and the start date.
|
48
48
|
|
49
|
-
|
49
|
+
=== OPTIONS
|
50
50
|
|
51
51
|
There are two ways to pass options: natural language or an options hash.
|
52
52
|
|
53
53
|
NATURAL LANGUAGE:
|
54
|
-
Pass a start date with the word "starting, start, stars" (e.g. Tickle.parse('every 3 days starting next friday'))
|
55
|
-
Pass an end date with the word "until, end, ends, ending" (e.g. Tickle.parse('every 3 days until next friday'))
|
56
|
-
Pass both at the same time (e.g. "starting May 5th repeat every other week until December 1")
|
54
|
+
* Pass a start date with the word "starting, start, stars" (e.g. Tickle.parse('every 3 days starting next friday'))
|
55
|
+
* Pass an end date with the word "until, end, ends, ending" (e.g. Tickle.parse('every 3 days until next friday'))
|
56
|
+
* Pass both at the same time (e.g. "starting May 5th repeat every other week until December 1")
|
57
57
|
|
58
58
|
OPTIONS HASH
|
59
59
|
Valid options are:
|
60
|
-
* start - must be a valid date. (e.g. Tickle.parse('every other day', {:start => Date.new(2010,8,1) }))
|
61
|
-
* until - must be a valid date. (e.g. Tickle.parse('every other day', {:until => Date.new(2010,10,1) }))
|
62
|
-
* next_only - legacy switch to ONLY return the next occurrence as a date and not return a hash
|
60
|
+
* start - must be a valid date. (e.g. Tickle.parse('every other day', {:start => Date.new(2010,8,1) }))
|
61
|
+
* until - must be a valid date. (e.g. Tickle.parse('every other day', {:until => Date.new(2010,10,1) }))
|
62
|
+
* next_only - legacy switch to ONLY return the next occurrence as a date and not return a hash
|
63
63
|
|
64
|
-
|
64
|
+
=== SUPER IMPORTANT NOTE ABOUT NEXT OCCURRENCE & START DATE
|
65
65
|
|
66
66
|
You may notice when parsing an expression with a start date that the next occurrence IS the start date passed. This is DESIGNED BEHAVIOR.
|
67
67
|
|
@@ -69,7 +69,7 @@ Here's why - assume your user says "remind me every 3 weeks starting Dec 1" and
|
|
69
69
|
|
70
70
|
If you don't like that, fork and have fun but don't say I didn't warn ya.
|
71
71
|
|
72
|
-
|
72
|
+
=== EXAMPLES
|
73
73
|
|
74
74
|
require 'rubygems'
|
75
75
|
require 'tickle'
|
@@ -153,7 +153,7 @@ WITH OPTIONS HASH
|
|
153
153
|
Tickle.parse('3 months, {:until=>Thu, 07 Oct 2010}') #=> {:next=>2010-08-08 14:01:19 -0400, :recurrence_expression=>"3 months", :starting=>2010-05-07 14:01:19 -0400, :until=>2010-10-07 00:00:00 -0400}
|
154
154
|
.
|
155
155
|
|
156
|
-
|
156
|
+
== USING IN APP
|
157
157
|
|
158
158
|
To use in your app, we recommend adding two attributes to your database model:
|
159
159
|
* next_occurrence
|
@@ -165,14 +165,14 @@ code, each day, simply check to see if today is >= next_occurrence and, if so, r
|
|
165
165
|
After it completes, call Tickle.parse(tickle_expression) again to update the next occurrence of the event.
|
166
166
|
|
167
167
|
|
168
|
-
|
168
|
+
== TESTING
|
169
169
|
|
170
170
|
Tickle comes with a full testing suite that tests and shows sample output for simple, complex, options hash and invalid arguments.
|
171
171
|
|
172
172
|
Please note, the tests were designed to output the parsed expression and not return true. This allows (and sorry, forces you) to have to skim through the
|
173
173
|
results and queries to ensure they are working correctly. You can also add your own for fun.
|
174
174
|
|
175
|
-
|
175
|
+
== LIMITATIONS
|
176
176
|
|
177
177
|
Currently, Tickle only works for day intervals but feel free to fork and add time-based interval support or send me a note if you really want me to add it.
|
178
178
|
|
@@ -187,13 +187,13 @@ As always, BIG shout-out to the RVM Master himself, Wayne Seguin, for putting up
|
|
187
187
|
|
188
188
|
== Note on Patches/Pull Requests
|
189
189
|
|
190
|
-
* Fork the project.
|
191
|
-
* Make your feature addition or bug fix.
|
192
|
-
* Add tests for it. This is important so I don't break it in a
|
193
|
-
|
194
|
-
* Commit, do not mess with rakefile, version, or history.
|
195
|
-
|
196
|
-
* Send me a pull request. Bonus points for time-based branches.
|
190
|
+
* Fork the project.
|
191
|
+
* Make your feature addition or bug fix.
|
192
|
+
* Add tests for it. This is important so I don't break it in a
|
193
|
+
future version unintentionally.
|
194
|
+
* Commit, do not mess with rakefile, version, or history.
|
195
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
196
|
+
* Send me a pull request. Bonus points for time-based branches.
|
197
197
|
|
198
198
|
== Copyright
|
199
199
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/git-flow-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
GITFLOW_VERSION=0.1.
|
1
|
+
GITFLOW_VERSION=0.1.5
|
data/lib/tickle.rb
CHANGED
@@ -17,8 +17,8 @@ require 'tickle/tickle'
|
|
17
17
|
require 'tickle/handler'
|
18
18
|
require 'tickle/repeater'
|
19
19
|
|
20
|
-
module Tickle
|
21
|
-
VERSION = "0.1.
|
20
|
+
module Tickle #:nodoc:
|
21
|
+
VERSION = "0.1.5"
|
22
22
|
|
23
23
|
def self.debug; false; end
|
24
24
|
|
@@ -36,7 +36,8 @@ module Tickle
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
class Date
|
39
|
+
class Date #:nodoc:
|
40
|
+
# returns the days in the sending month
|
40
41
|
def days_in_month
|
41
42
|
d,m,y = mday,month,year
|
42
43
|
d += 1 while Date.valid_civil?(y,m,d)
|
@@ -44,7 +45,8 @@ class Date
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
|
-
class String
|
48
|
+
class String #:nodoc:
|
49
|
+
# returns true if the sending string is a text or numeric ordinal (e.g. first or 1st)
|
48
50
|
def is_ordinal?
|
49
51
|
scanner = %w{first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth eighteenth nineteenth twenty thirty thirtieth}
|
50
52
|
regex = /\b(\d*)(st|nd|rd|th)\b/
|
@@ -52,7 +54,8 @@ class String
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
|
-
class Array
|
57
|
+
class Array #:nodoc:
|
58
|
+
# compares two arrays to determine if they both contain the same elements
|
56
59
|
def same?(y)
|
57
60
|
self.sort == y.sort
|
58
61
|
end
|
data/lib/tickle/handler.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
module Tickle
|
2
|
-
class << self
|
1
|
+
module Tickle #:nodoc:
|
2
|
+
class << self #:nodoc:
|
3
3
|
|
4
|
+
# The heavy lifting. Goes through each token groupings to determine what natural language should either by
|
5
|
+
# parsed by Chronic or returned. This methodology makes extension fairly simple, as new token types can be
|
6
|
+
# easily added in repeater and then processed by the guess method
|
7
|
+
#
|
4
8
|
def guess()
|
5
9
|
guess_unit_types
|
6
10
|
guess_weekday unless @next
|
data/lib/tickle/tickle.rb
CHANGED
@@ -1,6 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# Copyright (c) 2010 Joshua Lippiner
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
module Tickle #:nodoc:
|
23
|
+
class << self #:nodoc:
|
24
|
+
# == Configuration options
|
25
|
+
#
|
26
|
+
# * +start+ - start date for future occurrences. Must be in valid date format.
|
27
|
+
# * +until+ - last date to run occurrences until. Must be in valid date format.
|
28
|
+
#
|
29
|
+
# Use by calling Tickle.parse and passing natural language with or without options.
|
30
|
+
#
|
31
|
+
# def get_next_occurrence
|
32
|
+
# results = Tickle.parse('every Wednesday starting June 1st until Dec 15th')
|
33
|
+
# return results[:next] if results
|
34
|
+
# end
|
35
|
+
#
|
4
36
|
def parse(text, specified_options = {})
|
5
37
|
# get options and set defaults if necessary
|
6
38
|
default_options = {:start => Time.now, :next_only => false, :until => nil}
|
@@ -59,10 +91,7 @@ module Tickle
|
|
59
91
|
if !best_guess
|
60
92
|
return nil
|
61
93
|
elsif options[:next_only] != true
|
62
|
-
|
63
|
-
h.merge!({:starting => @start.to_time}) if @start
|
64
|
-
h.merge!({:until => @until.to_time}) if @until
|
65
|
-
return h
|
94
|
+
return {:next => best_guess.to_time, :expression => event.strip, :starting => @start, :until => @until}
|
66
95
|
else
|
67
96
|
return best_guess
|
68
97
|
end
|
@@ -86,16 +115,13 @@ module Tickle
|
|
86
115
|
event, ending = process_for_ending(text)
|
87
116
|
end
|
88
117
|
|
89
|
-
@start = (starting && Tickle.parse(pre_filter(starting), {:next_only => true}) || options[:start])
|
118
|
+
@start = (starting && Tickle.parse(pre_filter(starting), {:next_only => true}) || options[:start]).to_time
|
90
119
|
@until = (ending && Tickle.parse(pre_filter(ending), {:next_only => true}) || options[:until])
|
120
|
+
@until = @until.to_time if @until
|
91
121
|
@next = nil
|
92
122
|
return event
|
93
123
|
end
|
94
124
|
|
95
|
-
def inspect_matches
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
125
|
# process the remaining expression to see if an until, end, ending is specified
|
100
126
|
def process_for_ending(text)
|
101
127
|
regex = /^(.*)(\s(?:end|until)(?:s|ing)?)(.*)/i
|
@@ -179,10 +205,15 @@ module Tickle
|
|
179
205
|
|
180
206
|
protected
|
181
207
|
|
208
|
+
# Returns the next available month based on the current day of the month.
|
209
|
+
# For example, if get_next_month(15) is called and today is the 10th, then it will return the 15th of this month.
|
210
|
+
# However, if get_next_month(15) is called and today is the 18th, it will return the 15th of next month.
|
182
211
|
def get_next_month(number)
|
183
212
|
month = number.to_i < Date.today.day ? (Date.today.month == 12 ? 1 : Date.today.month + 1) : Date.today.month
|
184
213
|
end
|
185
214
|
|
215
|
+
# Return the number of days in a specified month.
|
216
|
+
# If no month is specified, current month is used.
|
186
217
|
def days_in_month(month=nil)
|
187
218
|
month ||= Date.today.month
|
188
219
|
days_in_mon = Date.civil(Date.today.year, month, -1).day
|
@@ -200,6 +231,7 @@ module Tickle
|
|
200
231
|
@start = start
|
201
232
|
end
|
202
233
|
|
234
|
+
# Updates an existing token. Mostly used by the repeater class.
|
203
235
|
def update(type, start=nil, interval=nil)
|
204
236
|
@start = start
|
205
237
|
@type = type
|
@@ -210,12 +242,10 @@ module Tickle
|
|
210
242
|
# This exception is raised if an invalid argument is provided to
|
211
243
|
# any of Tickle's methods
|
212
244
|
class InvalidArgumentException < Exception
|
213
|
-
|
214
245
|
end
|
215
246
|
|
216
247
|
# This exception is raised if there is an issue with the parsing
|
217
248
|
# output from the date expression provided
|
218
249
|
class InvalidDateExpression < Exception
|
219
|
-
|
220
250
|
end
|
221
251
|
end
|
data/tickle.gemspec
CHANGED