tickle 0.1.3 → 0.1.5
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/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