tickle 0.1.7 → 1.2.0
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 +7 -0
- data/.gitignore +8 -8
- data/.travis.yml +12 -0
- data/CHANGES.md +47 -0
- data/Gemfile +10 -0
- data/{LICENSE → LICENCE} +2 -0
- data/{README.rdoc → README.md} +91 -75
- data/Rakefile +14 -43
- data/examples.rb +29 -0
- data/lib/tickle.rb +23 -17
- data/lib/tickle/tickle.rb +29 -11
- data/lib/tickle/version.rb +5 -0
- data/test/helper.rb +8 -1
- data/test/test_parsing.rb +24 -15
- data/tickle.gemspec +24 -55
- metadata +176 -73
- data/.rvmrc +0 -9
- data/VERSION +0 -1
- data/lib/numerizer/numerizer.rb +0 -103
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c9f24bf840e38c2196e61c1428caa9d84eeb7b5
|
4
|
+
data.tar.gz: 6e8111fb7b6098f3c285d537b2adb529ede9ad36
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 84a13f5b64bd9e49327bca5456bbcb197b7d66b90b0f349ea035f842ffe8aa440a263aefc690d163c5023ac24e13b398b395da9e6d7240e9a89220c7c4ca9433
|
7
|
+
data.tar.gz: 2514f63b04e43ec3610932dc115ad127b29b9a384e9b99fe6c52606982ebb2537cb20ac06beccf2cfcc25630a5c2f7c5021870119ab39395b7fe9f4361af5a1b
|
data/.gitignore
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
## MAC OS
|
2
1
|
.DS_Store
|
3
2
|
|
4
|
-
## TEXTMATE
|
5
3
|
*.tmproj
|
6
4
|
tmtags
|
7
5
|
|
8
|
-
## EMACS
|
9
6
|
*~
|
10
7
|
\#*
|
11
8
|
.\#*
|
12
9
|
|
13
|
-
## VIM
|
14
10
|
*.swp
|
15
|
-
|
16
|
-
## PROJECT::GENERAL
|
11
|
+
doc/
|
17
12
|
coverage
|
18
13
|
rdoc
|
19
14
|
pkg
|
20
|
-
|
21
|
-
|
15
|
+
bin/
|
16
|
+
vendor/
|
17
|
+
Gemfile.lock
|
18
|
+
spec/
|
19
|
+
.yardoc/
|
20
|
+
.bundle/
|
21
|
+
.rspec
|
data/.travis.yml
ADDED
data/CHANGES.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# CH CH CH CH CHANGES! #
|
2
|
+
|
3
|
+
## Friday the 18th of September 2020, v1.2.0
|
4
|
+
|
5
|
+
- Changed the dependency from chronic to gitlab-chronic
|
6
|
+
- Required numerizer explicitly to stop a bug
|
7
|
+
- Formatted the regex for legibility
|
8
|
+
- Fixed bug with "today and" in the tests
|
9
|
+
- Fixed a bug previously fixed on the v2 branch in 2f2a32ce9 with @start
|
10
|
+
- Added Timecop because we're beyond the date chosen in the tests. Tempus fugit!
|
11
|
+
|
12
|
+
----
|
13
|
+
|
14
|
+
## Wednesday the 15th of March 2017, v1.1.1 ##
|
15
|
+
|
16
|
+
* Bit of easier debugging added in.
|
17
|
+
* Bug fixes, thanks to https://github.com/dodomarocgenex for finding and helping with them.
|
18
|
+
|
19
|
+
----
|
20
|
+
|
21
|
+
## Wednesday the 22nd of February 2017, v1.1.0 ##
|
22
|
+
|
23
|
+
* Numerizer duplication removed. Thanks to https://github.com/bjonord.
|
24
|
+
* Some very minor changes to the project, no other code changes.
|
25
|
+
|
26
|
+
----
|
27
|
+
|
28
|
+
|
29
|
+
## Monday the 11th of November 2015, v1.0.2 ##
|
30
|
+
|
31
|
+
* Shoulda and simplecov aren't runtime dependencies, fixed that in the gemfile.
|
32
|
+
* Got the version number right this time ;-)
|
33
|
+
|
34
|
+
----
|
35
|
+
|
36
|
+
## Monday the 11th of November 2015, v1.0.1 ##
|
37
|
+
|
38
|
+
* Moved library to new maintainer [https://github.com/yb66/tickle](@yb66)
|
39
|
+
* Moved library to [http://semver.org/](semver).
|
40
|
+
* Merged in some changes from @dan335 and @JesseAldridge, thanks to them.
|
41
|
+
* Moved rdocs to markdown for niceness.
|
42
|
+
* Updated licences with dates and correct spelling ;)
|
43
|
+
* Fix incorporated for "NameError: uninitialized constant Module::Numerizer"
|
44
|
+
* Moved library to Bundler to make it easier to set up and develop against.
|
45
|
+
* Started using Yardoc for more niceness with documentation.
|
46
|
+
|
47
|
+
----
|
data/Gemfile
ADDED
data/{LICENSE → LICENCE}
RENAMED
data/{README.rdoc → README.md}
RENAMED
@@ -1,82 +1,71 @@
|
|
1
|
-
|
2
|
-
http://github.com/noctivityinc/tickle
|
3
|
-
by Joshua Lippiner, Noctivity
|
1
|
+
## Tickle ##
|
4
2
|
|
5
|
-
|
3
|
+
This is now the home of ***Tickle***, previously found at [github.com/noctivityinc/tickle](https://github.com/noctivityinc/tickle)
|
6
4
|
|
7
|
-
If you
|
5
|
+
If you wish to contribute then please take a look at the Contribution section further down the page, but I'd be really, *really* grateful if anyone wishes to contribute specs. Not unit tests, but specs. This library's internals will be changing a lot over the coming months, and it would be good to have integration tests - a black-box spec of the library - to work against. Even if you've never contributed to a library before, now is your chance! I'll help anyone through with what they may need, and I promise not to be the standard snarky Open Source dictator that a lot of projects have. We'll try and improve this library together.
|
8
6
|
|
9
|
-
|
7
|
+
Take a look at the `develop` branch where all this stuff will be happening.
|
10
8
|
|
11
|
-
Tickle is a natural language parser for recurring events.
|
12
|
-
|
13
|
-
Tickle is designed to be a compliment of Chronic and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc."
|
14
|
-
|
15
|
-
In a lot of ways Tickle is actually an enhancement of Chronic, handling a lot of things that Chronic can't, such as commas and US Holidays (yup - you can do Tickle.parse('Christmas Eve'))
|
16
9
|
|
17
|
-
|
10
|
+
### DESCRIPTION ###
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
Tickle
|
22
|
-
|
23
|
-
$ gem install tickle
|
24
|
-
|
25
|
-
== TINKERING
|
12
|
+
Tickle is a natural language parser for recurring events.
|
13
|
+
|
14
|
+
Tickle is designed to be a complement to [Chronic](https://rubygems.org/gems/chronic) and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc."
|
26
15
|
|
27
|
-
|
16
|
+
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.
|
28
17
|
|
29
|
-
|
18
|
+
### *LEGACY WARNING* ###
|
30
19
|
|
31
|
-
|
20
|
+
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.
|
32
21
|
|
33
|
-
thoughtbot's shoulda (gem install shoulda)
|
34
22
|
|
35
|
-
|
23
|
+
### USAGE ###
|
36
24
|
|
37
|
-
You can parse strings containing a natural language interval using the Tickle.parse method.
|
25
|
+
You can parse strings containing a natural language interval using the `Tickle.parse` method.
|
38
26
|
|
39
|
-
You can either pass a string prefixed with the word "every, each or
|
27
|
+
You can either pass a string prefixed with the word "every", "each" or "on the" or simply the timeframe.
|
40
28
|
|
41
29
|
Tickle.parse returns a hash containing the following keys:
|
42
|
-
* next
|
43
|
-
* starting
|
44
|
-
* until
|
45
|
-
* expression
|
30
|
+
* `next` the next occurrence of the event. This is NEVER today as its always the next date in the future.
|
31
|
+
* `starting` the date all calculations as based on. If not passed as an option, the start date is right now.
|
32
|
+
* `until` the last date you want this event to run until.
|
33
|
+
* `expression` this is the natural language expression to store to run through tickle later to get the next occurrence.
|
46
34
|
|
47
|
-
Tickle returns nil if it cannot parse the string
|
35
|
+
Tickle returns `nil` if it cannot parse the string.
|
48
36
|
|
49
|
-
Tickle
|
37
|
+
Tickle ***heavily*** uses Chronic for parsing both the event and the start date.
|
50
38
|
|
51
|
-
|
39
|
+
### OPTIONS ###
|
52
40
|
|
53
41
|
There are two ways to pass options: natural language or an options hash.
|
54
42
|
|
55
43
|
NATURAL LANGUAGE:
|
56
|
-
* Pass a start date with the word "starting, start, stars"
|
57
|
-
* Pass an end date with the word "until, end, ends, ending"
|
58
|
-
* Pass both at the same time
|
44
|
+
* Pass a start date with the word "starting", "start", or "stars" e.g. `Tickle.parse('every 3 days starting next friday')`
|
45
|
+
* Pass an end date with the word "until", "end", "ends", or "ending" e.g. `Tickle.parse('every 3 days until next friday')`
|
46
|
+
* Pass both at the same time e.g. `"starting May 5th repeat every other week until December 1"`
|
59
47
|
|
60
48
|
OPTIONS HASH
|
61
49
|
Valid options are:
|
62
|
-
* start - must be a valid date or Chronic date expression.
|
63
|
-
* until - must be a valid date or Chronic date expression.
|
64
|
-
* next_only - legacy switch to
|
50
|
+
* `start` - must be a valid date or Chronic date expression. e.g. `Tickle.parse('every other day', {:start => Date.new(2010,8,1) })`
|
51
|
+
* `until` - must be a valid date or Chronic date expression. e.g. `Tickle.parse('every other day', {:until => Date.new(2010,10,1) })`
|
52
|
+
* `next_only` - legacy switch to *only* return the next occurrence as a date and not return a hash
|
65
53
|
|
66
|
-
|
54
|
+
### SUPER IMPORTANT NOTE ABOUT NEXT OCCURRENCE & START DATE ###
|
67
55
|
|
68
|
-
You may notice when parsing an expression with a start date that the next occurrence
|
56
|
+
You may notice when parsing an expression with a start date that the next occurrence **is** the start date passed. This is **designed behaviour**.
|
69
57
|
|
70
58
|
Here's why - assume your user says "remind me every 3 weeks starting Dec 1" and today is May 8th. Well the first reminder needs to be sent on Dec 1, not Dec 21 (three weeks later).
|
71
59
|
|
72
|
-
If you don't like that, fork and have fun but don't say
|
60
|
+
If you don't like that, fork and have fun but don't say you weren't warned.
|
73
61
|
|
74
|
-
=== EXAMPLES
|
75
62
|
|
76
|
-
|
77
|
-
|
63
|
+
### EXAMPLES ###
|
64
|
+
|
65
|
+
require 'tickle'
|
66
|
+
|
67
|
+
#### SIMPLE ####
|
78
68
|
|
79
|
-
SIMPLE
|
80
69
|
Tickle.parse('day')
|
81
70
|
#=> {:next=>2010-05-10 20:57:36 -0400, :expression=>"day", :starting=>2010-05-09 20:57:36 -0400, :until=>nil}
|
82
71
|
|
@@ -230,7 +219,8 @@ SIMPLE
|
|
230
219
|
Tickle.parse('the twenty first of the month', {:start=>#<Date: 2020-04-01 (4917881/2,0,2299161)>, :now=>#<Date: 2020-04-01 (4917881/2,0,2299161)>})
|
231
220
|
#=> {:next=>2020-04-21 00:00:00 -0400, :expression=>"the twenty first of the month", :starting=>2020-04-01 00:00:00 -0400, :until=>nil}
|
232
221
|
|
233
|
-
COMPLEX
|
222
|
+
#### COMPLEX ####
|
223
|
+
|
234
224
|
Tickle.parse('starting today and ending one week from now')
|
235
225
|
#=> {:next=>2010-05-10 22:30:00 -0400, :expression=>"day", :starting=>2010-05-09 22:30:00 -0400, :until=>2010-05-16 20:57:35 -0400}
|
236
226
|
|
@@ -283,7 +273,8 @@ COMPLEX
|
|
283
273
|
#=> {:next=>2010-05-12 12:00:00 -0400, :expression=>"week", :starting=>2010-05-12 12:00:00 -0400, :until=>2010-05-13 12:00:00 -0400}
|
284
274
|
|
285
275
|
|
286
|
-
OPTIONS HASH
|
276
|
+
#### OPTIONS HASH ####
|
277
|
+
|
287
278
|
Tickle.parse('May 1st 2020', {:next_only=>true})
|
288
279
|
#=> 2020-05-01 00:00:00 -0400
|
289
280
|
|
@@ -308,7 +299,7 @@ OPTIONS HASH
|
|
308
299
|
Tickle.parse('3 months', {:until=>2010-10-09 00:00:00 -0400})
|
309
300
|
#=> {:next=>2010-08-09 20:57:36 -0400, :expression=>"3 months", :starting=>2010-05-09 20:57:36 -0400, :until=>2010-10-09 00:00:00 -0400}
|
310
301
|
|
311
|
-
US HOLIDAYS
|
302
|
+
#### US HOLIDAYS ####
|
312
303
|
|
313
304
|
Tickle.parse('New Years Day', {:start=>#<Date: 2020-01-01 (4917699/2,0,2299161)>, :now=>#<Date: 2020-01-01 (4917699/2,0,2299161)>})
|
314
305
|
#=> {:next=>2021-01-01 12:00:00 -0500, :expression=>"january 1, 2021", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}
|
@@ -389,48 +380,73 @@ US HOLIDAYS
|
|
389
380
|
#=> {:next=>2021-01-01 12:00:00 -0500, :expression=>"january 1, 2021", :starting=>2020-01-01 00:00:00 -0500, :until=>nil}
|
390
381
|
|
391
382
|
|
392
|
-
|
383
|
+
### USING IN APP ###
|
393
384
|
|
394
385
|
To use in your app, we recommend adding two attributes to your database model:
|
395
|
-
* next_occurrence
|
396
|
-
* tickle_expression
|
397
386
|
|
398
|
-
|
399
|
-
|
387
|
+
* `next_occurrence`
|
388
|
+
* `tickle_expression`
|
400
389
|
|
401
|
-
|
390
|
+
Then call `Tickle.parse("date expression goes here")` when you need to and save the results accordingly. In your
|
391
|
+
code, each day, simply check to see if today is `>= next_occurrence` and, if so, run your block.
|
402
392
|
|
393
|
+
After it completes, call `next_occurrence = Tickle.parse(tickle_expression)` again to update the next occurrence of the event.
|
403
394
|
|
404
|
-
== TESTING
|
405
395
|
|
406
|
-
|
396
|
+
### INSTALLATION ###
|
407
397
|
|
408
|
-
|
409
|
-
|
410
|
-
|
398
|
+
Tickle can be installed via RubyGems:
|
399
|
+
|
400
|
+
gem install tickle
|
411
401
|
|
412
|
-
|
402
|
+
or if you're using Bundler, add this to the Gemfile:
|
403
|
+
|
404
|
+
gem "tickle"
|
405
|
+
|
406
|
+
### DEPENDENCIES ###
|
407
|
+
|
408
|
+
Chronic gem:
|
409
|
+
|
410
|
+
`gem install gitlab-chronic`
|
411
|
+
|
412
|
+
thoughtbot's [shoulda](https://rubygems.org/gems/shoulda):
|
413
|
+
|
414
|
+
`gem install shoulda`
|
415
|
+
|
416
|
+
or just run `bundle install`.
|
417
|
+
|
418
|
+
|
419
|
+
### LIMITATIONS ###
|
413
420
|
|
414
421
|
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.
|
415
422
|
|
416
|
-
== CREDIT
|
417
423
|
|
418
|
-
|
424
|
+
### CONTRIBUTING ###
|
425
|
+
|
426
|
+
Fork it, create a new branch for your changes, and send in a pull request.
|
427
|
+
|
428
|
+
* Only tested code gets in.
|
429
|
+
* Document it.
|
430
|
+
* If you want to work on something but aren't sure whether it'll get in, create a new branch and open a pull request before you've done any code. That will open an issue and we can discuss it.
|
431
|
+
* Do not mess with the Rakefile, version, or history (if you want to have your own version, that is fine but do it on a separate branch from the one I'm going to merge.)
|
432
|
+
|
433
|
+
### TESTING ###
|
434
|
+
|
435
|
+
Tickle comes with a full testing suite for simple, complex, options hash and invalid arguments.
|
436
|
+
|
437
|
+
You also have some command line options:
|
438
|
+
|
439
|
+
* --v verbose output like the examples above
|
440
|
+
* --d debug output showing the guts of a date expression
|
419
441
|
|
420
|
-
|
442
|
+
### CREDIT ###
|
421
443
|
|
422
|
-
|
444
|
+
The original work on the library was done by *Joshua Lippiner* a.k.a. [Noctivity](https://github.com/noctivityinc).
|
423
445
|
|
446
|
+
***HUGE*** shout-out to both the creator of Chronic, *Tom Preston-Werner* as well as *Brian Brownling* who maintains a Github version at [github.com/mojombo/chronic](https://github.com/mojombo/chronic).
|
424
447
|
|
425
|
-
|
426
|
-
* Fork the project.
|
427
|
-
* Make your feature addition or bug fix.
|
428
|
-
* Add tests for it. This is important so I don't break it in a
|
429
|
-
future version unintentionally.
|
430
|
-
* Commit, do not mess with rakefile, version, or history.
|
431
|
-
(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)
|
432
|
-
* Send me a pull request. Bonus points for time-based branches.
|
448
|
+
As always, ***BIG*** shout-out to the RVM Master himself, *Wayne Seguin*, for putting up with me and Ruby from day one. Ask Wayne to make you some ciabatta bread next time you see him.
|
433
449
|
|
434
|
-
|
450
|
+
### LICENCE ###
|
435
451
|
|
436
|
-
|
452
|
+
See the LICENCE file.
|
data/Rakefile
CHANGED
@@ -1,25 +1,3 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "tickle"
|
8
|
-
gem.summary = 'natural language parser for recurring events'
|
9
|
-
gem.description = 'Tickle is a date/time helper gem to help parse natural language into a recurring pattern. Tickle is designed to be a compliment of Chronic and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc.'
|
10
|
-
gem.email = "jlippiner@noctivity.com"
|
11
|
-
gem.homepage = "http://github.com/noctivityinc/tickle"
|
12
|
-
gem.authors = ["Joshua Lippiner"]
|
13
|
-
gem.add_dependency('chronic', '>= 0.2.3')
|
14
|
-
gem.add_development_dependency "shoulda", ">= 2.10.3"
|
15
|
-
|
16
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
-
end
|
18
|
-
Jeweler::GemcutterTasks.new
|
19
|
-
rescue LoadError
|
20
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
-
end
|
22
|
-
|
23
1
|
require 'rake/testtask'
|
24
2
|
Rake::TestTask.new(:test) do |test|
|
25
3
|
test.libs << 'lib' << 'test'
|
@@ -27,29 +5,22 @@ Rake::TestTask.new(:test) do |test|
|
|
27
5
|
test.verbose = true
|
28
6
|
end
|
29
7
|
|
30
|
-
|
31
|
-
require 'rcov/rcovtask'
|
32
|
-
Rcov::RcovTask.new do |test|
|
33
|
-
test.libs << 'test'
|
34
|
-
test.pattern = 'test/**/test_*.rb'
|
35
|
-
test.verbose = true
|
36
|
-
end
|
37
|
-
rescue LoadError
|
38
|
-
task :rcov do
|
39
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
-
end
|
41
|
-
end
|
8
|
+
task :default => :test
|
42
9
|
|
43
|
-
task :test => :check_dependencies
|
44
10
|
|
45
|
-
|
11
|
+
desc "(Re-) generate documentation and place it in the docs/ dir. Open the index.html file in there to read it."
|
12
|
+
task :docs => [:"docs:environment", :"docs:yard"]
|
13
|
+
namespace :docs do
|
14
|
+
|
15
|
+
task :environment do
|
16
|
+
ENV["RACK_ENV"] = "documentation"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'yard'
|
46
20
|
|
47
|
-
|
48
|
-
|
49
|
-
|
21
|
+
YARD::Rake::YardocTask.new :yard do |t|
|
22
|
+
t.files = ['lib/**/*.rb']
|
23
|
+
t.options = ['-odoc/'] # optional
|
24
|
+
end
|
50
25
|
|
51
|
-
rdoc.rdoc_dir = 'rdoc'
|
52
|
-
rdoc.title = "tickle #{version}"
|
53
|
-
rdoc.rdoc_files.include('README*')
|
54
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
26
|
end
|
data/examples.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), 'lib', 'tickle')
|
3
|
+
|
4
|
+
=begin
|
5
|
+
|
6
|
+
Tickle creates standard Ruby Time objects.
|
7
|
+
|
8
|
+
Time is ignored if there's also a date, unless the date is 'tomorrow'(?)
|
9
|
+
|
10
|
+
Tickle creates times in the servers local time zone.
|
11
|
+
To go from server time to user's local time...
|
12
|
+
user_time = server_time + (user_time_offset - server_time_offset)
|
13
|
+
eg (5:00PM Central) = (6:00PM Eastern + (-6 - -5))
|
14
|
+
|
15
|
+
Tickle makes 'May 30th' at 0:00, but 'June 18, 2011' and 'Christmas' at 12:00.
|
16
|
+
=end
|
17
|
+
|
18
|
+
|
19
|
+
server_offset = Time.now.utc_offset / 60 / 60
|
20
|
+
|
21
|
+
['May 30th', '6:30 PM',
|
22
|
+
'tomorrow at 6:00', 'June 18, 2011', 'Christmas'].each {|s|
|
23
|
+
time = Tickle.parse(s)[:next]
|
24
|
+
if s == 'Christmas'
|
25
|
+
time -= 12 * 60 * 60
|
26
|
+
end
|
27
|
+
print s, " --> server date: ", time
|
28
|
+
puts
|
29
|
+
}
|
data/lib/tickle.rb
CHANGED
@@ -9,16 +9,22 @@
|
|
9
9
|
|
10
10
|
$LOAD_PATH.unshift(File.dirname(__FILE__)) # For use/testing when no gem is installed
|
11
11
|
|
12
|
+
if ENV["DEBUG"]
|
13
|
+
warn "DEBUG MODE ON"
|
14
|
+
require 'pry-byebug'
|
15
|
+
require 'pry-state'
|
16
|
+
binding.pry
|
17
|
+
end
|
18
|
+
|
12
19
|
require 'date'
|
13
20
|
require 'time'
|
14
|
-
require 'chronic'
|
21
|
+
require 'gitlab-chronic'
|
15
22
|
|
16
23
|
require 'tickle/tickle'
|
17
24
|
require 'tickle/handler'
|
18
25
|
require 'tickle/repeater'
|
19
26
|
|
20
27
|
module Tickle #:nodoc:
|
21
|
-
VERSION = "0.1.7"
|
22
28
|
|
23
29
|
def self.debug=(val); @debug = val; end
|
24
30
|
|
@@ -48,20 +54,20 @@ class Date #:nodoc:
|
|
48
54
|
amount ||= 1
|
49
55
|
case attr
|
50
56
|
when :day then
|
51
|
-
Date.civil(self.year, self.month, self.day
|
57
|
+
Date.civil(self.year, self.month, self.day).next_day(amount)
|
52
58
|
when :wday then
|
53
59
|
amount = Date::ABBR_DAYNAMES.index(amount) if amount.is_a?(String)
|
54
60
|
raise Exception, "specified day of week invalid. Use #{Date::ABBR_DAYNAMES}" unless amount
|
55
61
|
diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
|
56
|
-
Date.civil(self.year, self.month, self.day
|
62
|
+
Date.civil(self.year, self.month, self.day).next_day(diff)
|
57
63
|
when :week then
|
58
|
-
Date.civil(self.year, self.month, self.day
|
64
|
+
Date.civil(self.year, self.month, self.day).next_day(7*amount)
|
59
65
|
when :month then
|
60
|
-
Date.civil(self.year, self.month
|
66
|
+
Date.civil(self.year, self.month, self.day).next_month(amount)
|
61
67
|
when :year then
|
62
|
-
Date.civil(self.year
|
68
|
+
Date.civil(self.year, self.month, self.day).next_year(amount)
|
63
69
|
else
|
64
|
-
|
70
|
+
raise Exception, "type \"#{attr}\" not supported."
|
65
71
|
end
|
66
72
|
end
|
67
73
|
end
|
@@ -71,27 +77,27 @@ class Time #:nodoc:
|
|
71
77
|
amount ||= 1
|
72
78
|
case attr
|
73
79
|
when :sec then
|
74
|
-
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec + amount
|
80
|
+
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + amount
|
75
81
|
when :min then
|
76
|
-
Time.local(self.year, self.month, self.day, self.hour, self.min
|
82
|
+
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + (amount * 60)
|
77
83
|
when :hour then
|
78
|
-
Time.local(self.year, self.month, self.day, self.hour
|
84
|
+
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + (amount * 60 * 60)
|
79
85
|
when :day then
|
80
|
-
Time.local(self.year, self.month, self.day
|
86
|
+
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + (amount * 60 * 60 * 24)
|
81
87
|
when :wday then
|
82
88
|
amount = Time::RFC2822_DAY_NAME.index(amount) if amount.is_a?(String)
|
83
89
|
raise Exception, "specified day of week invalid. Use #{Time::RFC2822_DAY_NAME}" unless amount
|
84
90
|
diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
|
85
|
-
|
91
|
+
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_day(diff)
|
86
92
|
when :week then
|
87
|
-
|
93
|
+
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_day(amount * 7)
|
88
94
|
when :month then
|
89
|
-
|
95
|
+
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_month(amount)
|
90
96
|
when :year then
|
91
|
-
|
97
|
+
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_year(amount)
|
92
98
|
else
|
93
99
|
raise Exception, "type \"#{attr}\" not supported."
|
94
|
-
end
|
100
|
+
end.to_time.localtime
|
95
101
|
end
|
96
102
|
end
|
97
103
|
|
data/lib/tickle/tickle.rb
CHANGED
@@ -19,8 +19,10 @@
|
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
require 'numerizer'
|
23
|
+
|
24
|
+
module Tickle
|
25
|
+
class << self
|
24
26
|
# == Configuration options
|
25
27
|
#
|
26
28
|
# * +start+ - start date for future occurrences. Must be in valid date format.
|
@@ -105,9 +107,25 @@ module Tickle #:nodoc:
|
|
105
107
|
def scan_expression(text, options)
|
106
108
|
starting = ending = nil
|
107
109
|
|
108
|
-
start_every_regex = /^
|
110
|
+
start_every_regex = /^
|
111
|
+
(start(?:s|ing)?) # 0
|
112
|
+
\s
|
113
|
+
(.*)
|
114
|
+
(\s(?:every|each|\bon\b|repeat) # 1
|
115
|
+
(?:s|ing)?) # 2
|
116
|
+
(.*) # 3
|
117
|
+
/ix
|
109
118
|
every_start_regex = /^(every|each|\bon\b|repeat(?:the)?)\s(.*)(\s(?:start)(?:s|ing)?)(.*)/i
|
110
|
-
start_ending_regex = /^
|
119
|
+
start_ending_regex = /^
|
120
|
+
(start(?:s|ing)?) # 0
|
121
|
+
\s+
|
122
|
+
(.*?)(?:\s+and)? # 1
|
123
|
+
(\s
|
124
|
+
(?:\bend|until)
|
125
|
+
(?:s|ing)?
|
126
|
+
) # 2
|
127
|
+
(.*) # 3
|
128
|
+
/ix
|
111
129
|
if text =~ start_every_regex
|
112
130
|
starting = text.match(start_every_regex)[2].strip
|
113
131
|
text = text.match(start_every_regex)[4].strip
|
@@ -117,8 +135,9 @@ module Tickle #:nodoc:
|
|
117
135
|
text = text.match(every_start_regex)[4].strip
|
118
136
|
starting, ending = process_for_ending(text)
|
119
137
|
elsif text =~ start_ending_regex
|
120
|
-
|
121
|
-
|
138
|
+
md = text.match start_ending_regex
|
139
|
+
starting = md.captures[1]
|
140
|
+
ending = md.captures.last.strip
|
122
141
|
event = 'day'
|
123
142
|
else
|
124
143
|
event, ending = process_for_ending(text)
|
@@ -127,6 +146,7 @@ module Tickle #:nodoc:
|
|
127
146
|
# they gave a phrase so if we can't interpret then we need to raise an error
|
128
147
|
if starting
|
129
148
|
Tickle.dwrite("starting: #{starting}")
|
149
|
+
@start ||= nil # initialize the variable to quell warnings
|
130
150
|
@start = chronic_parse(pre_filter(starting))
|
131
151
|
if @start
|
132
152
|
@start.to_time
|
@@ -172,8 +192,7 @@ module Tickle #:nodoc:
|
|
172
192
|
text.gsub!(/repeat(s|ing)?(\s)?/, '')
|
173
193
|
text.gsub!(/on the(\s)?/, '')
|
174
194
|
text.gsub!(/([^\w\d\s])+/, '')
|
175
|
-
text.downcase.strip
|
176
|
-
text = normalize_us_holidays(text)
|
195
|
+
normalize_us_holidays(text.downcase.strip)
|
177
196
|
end
|
178
197
|
|
179
198
|
# Split the text on spaces and convert each word into
|
@@ -254,8 +273,6 @@ module Tickle #:nodoc:
|
|
254
273
|
@tokens.map(&:type)
|
255
274
|
end
|
256
275
|
|
257
|
-
protected
|
258
|
-
|
259
276
|
# Returns the next available month based on the current day of the month.
|
260
277
|
# For example, if get_next_month(15) is called and the start date is the 10th, then it will return the 15th of this month.
|
261
278
|
# However, if get_next_month(15) is called and the start date is the 18th, it will return the 15th of next month.
|
@@ -264,7 +281,8 @@ module Tickle #:nodoc:
|
|
264
281
|
end
|
265
282
|
|
266
283
|
def next_appropriate_year(month, day)
|
267
|
-
|
284
|
+
start = @start || Date.today
|
285
|
+
year = (Date.new(start.year.to_i, month.to_i, day.to_i) == start.to_date) ? start.year + 1 : start.year
|
268
286
|
return year
|
269
287
|
end
|
270
288
|
|
data/test/helper.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
require '
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/vendor/"
|
4
|
+
add_filter "/bin/"
|
5
|
+
end
|
6
|
+
|
2
7
|
require 'test/unit'
|
3
8
|
require 'shoulda'
|
9
|
+
require 'timecop'
|
10
|
+
|
4
11
|
|
5
12
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
13
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
data/test/test_parsing.rb
CHANGED
@@ -1,19 +1,25 @@
|
|
1
|
-
|
1
|
+
require_relative './helper.rb'
|
2
2
|
require 'time'
|
3
3
|
require 'test/unit'
|
4
4
|
|
5
5
|
class TestParsing < Test::Unit::TestCase
|
6
6
|
|
7
|
+
Time_now = Time.parse "2010-05-09 20:57:36 +0000"
|
7
8
|
def setup
|
8
9
|
Tickle.debug = (ARGV.detect {|a| a == '--d'})
|
9
10
|
@verbose = (ARGV.detect {|a| a == '--v'})
|
10
|
-
|
11
|
-
|
12
|
-
p Time.now
|
11
|
+
Timecop.freeze Time_now
|
12
|
+
ENV["TZ"] = "UTC"
|
13
13
|
|
14
14
|
@date = Date.today
|
15
|
+
@tz = ENV["TZ"]
|
15
16
|
end
|
16
17
|
|
18
|
+
def teardown
|
19
|
+
Timecop.return
|
20
|
+
ENV["TZ"] = @tz
|
21
|
+
end
|
22
|
+
|
17
23
|
def test_parse_best_guess_simple
|
18
24
|
start = Date.new(2020, 04, 01)
|
19
25
|
|
@@ -85,11 +91,14 @@ class TestParsing < Test::Unit::TestCase
|
|
85
91
|
assert_date_match(Date.new(2020, 04, 21), 'the twenty first of the month', {:start => start, :now => start})
|
86
92
|
end
|
87
93
|
|
88
|
-
def
|
89
|
-
start = Date.new(2020, 04, 01)
|
90
|
-
|
94
|
+
def test_parse_best_guess_complex_and
|
91
95
|
assert_tickle_match(@date.bump(:day, 1), @date, @date.bump(:week, 1), 'day', 'starting today and ending one week from now') if Time.now.hour < 21 # => demonstrates leaving out the actual time period and implying it as daily
|
92
96
|
assert_tickle_match(@date.bump(:day, 1), @date.bump(:day, 1), @date.bump(:week, 1), 'day','starting tomorrow and ending one week from now') # => demonstrates leaving out the actual time period and implying it as daily.
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def test_parse_best_guess_complex
|
101
|
+
start = Date.new(2020, 04, 01)
|
93
102
|
|
94
103
|
assert_tickle_match(@date.bump(:wday, 'Mon'), @date.bump(:wday, 'Mon'), nil, 'month', 'starting Monday repeat every month')
|
95
104
|
|
@@ -165,30 +174,30 @@ class TestParsing < Test::Unit::TestCase
|
|
165
174
|
|
166
175
|
def test_argument_validation
|
167
176
|
assert_raise(Tickle::InvalidArgumentException) do
|
168
|
-
|
177
|
+
Tickle.parse("may 27", :today => 'something odd')
|
169
178
|
end
|
170
179
|
|
171
180
|
assert_raise(Tickle::InvalidArgumentException) do
|
172
|
-
|
181
|
+
Tickle.parse("may 27", :foo => :bar)
|
173
182
|
end
|
174
183
|
|
175
184
|
assert_raise(Tickle::InvalidArgumentException) do
|
176
|
-
|
185
|
+
Tickle.parse(nil)
|
177
186
|
end
|
178
187
|
|
179
188
|
assert_raise(Tickle::InvalidDateExpression) do
|
180
189
|
past_date = Date.civil(Date.today.year, Date.today.month, Date.today.day - 1)
|
181
|
-
|
190
|
+
Tickle.parse("every other day", {:start => past_date})
|
182
191
|
end
|
183
192
|
|
184
193
|
assert_raise(Tickle::InvalidDateExpression) do
|
185
|
-
start_date = Date.civil(Date.today.year, Date.today.month
|
186
|
-
end_date = Date.civil(Date.today.year, Date.today.month
|
187
|
-
|
194
|
+
start_date = Date.civil(Date.today.year, Date.today.month).next_day(10)
|
195
|
+
end_date = Date.civil(Date.today.year, Date.today.month).next_day(5)
|
196
|
+
Tickle.parse("every other day", :start => start_date, :until => end_date)
|
188
197
|
end
|
189
198
|
|
190
199
|
assert_raise(Tickle::InvalidDateExpression) do
|
191
|
-
end_date = Date.civil(Date.today.year, Date.today.month
|
200
|
+
end_date = Date.civil(Date.today.year, Date.today.month, Date.today.day).next_month(2)
|
192
201
|
parse_now('every 3 months', {:until => end_date})
|
193
202
|
end
|
194
203
|
end
|
data/tickle.gemspec
CHANGED
@@ -1,65 +1,34 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tickle/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tickle}
|
8
|
-
s.version =
|
8
|
+
s.version = Tickle::VERSION
|
9
9
|
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.date = %q{2010-05-10}
|
10
|
+
s.authors = ["Joshua Lippiner", "Iain Barnett"]
|
11
|
+
s.email = %q{iainspeed@gmail.com}
|
13
12
|
s.description = %q{Tickle is a date/time helper gem to help parse natural language into a recurring pattern. Tickle is designed to be a compliment of Chronic and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc.}
|
14
|
-
s.email = %q{jlippiner@noctivity.com}
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE",
|
17
|
-
"README.rdoc"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
".gitignore",
|
22
|
-
".rvmrc",
|
23
|
-
"LICENSE",
|
24
|
-
"README.rdoc",
|
25
|
-
"Rakefile",
|
26
|
-
"SCENARIOS.rdoc",
|
27
|
-
"VERSION",
|
28
|
-
"git-flow-version",
|
29
|
-
"lib/numerizer/numerizer.rb",
|
30
|
-
"lib/tickle.rb",
|
31
|
-
"lib/tickle/handler.rb",
|
32
|
-
"lib/tickle/repeater.rb",
|
33
|
-
"lib/tickle/tickle.rb",
|
34
|
-
"test/git-flow-version",
|
35
|
-
"test/helper.rb",
|
36
|
-
"test/test_parsing.rb",
|
37
|
-
"tickle.gemspec"
|
38
|
-
]
|
39
|
-
s.homepage = %q{http://github.com/noctivityinc/tickle}
|
40
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
-
s.require_paths = ["lib"]
|
42
|
-
s.rubygems_version = %q{1.3.6}
|
43
13
|
s.summary = %q{natural language parser for recurring events}
|
44
|
-
s.
|
45
|
-
|
46
|
-
"test/test_parsing.rb"
|
47
|
-
]
|
14
|
+
s.homepage = %q{http://github.com/yb66/tickle}
|
15
|
+
s.license = "MIT"
|
48
16
|
|
49
|
-
|
50
|
-
|
51
|
-
|
17
|
+
s.files = `git ls-files`.split($/)
|
18
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
|
+
|
21
|
+
s.require_paths = ["lib"]
|
52
22
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
23
|
+
s.add_dependency "gitlab-chronic", "~> 0.10.6"
|
24
|
+
s.add_dependency "numerizer", "~> 0.2.0"
|
25
|
+
s.add_development_dependency "shoulda", "~> 2.10.3"
|
26
|
+
s.add_development_dependency "simplecov"
|
27
|
+
s.add_development_dependency "test-unit"
|
28
|
+
s.add_development_dependency "bundler"
|
29
|
+
s.add_development_dependency "rake"
|
30
|
+
s.add_development_dependency "yard"
|
31
|
+
s.add_development_dependency "maruku"
|
32
|
+
s.add_development_dependency "timecop"
|
64
33
|
end
|
65
34
|
|
metadata
CHANGED
@@ -1,108 +1,211 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: tickle
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
segments:
|
6
|
-
- 0
|
7
|
-
- 1
|
8
|
-
- 7
|
9
|
-
version: 0.1.7
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
10
5
|
platform: ruby
|
11
|
-
authors:
|
6
|
+
authors:
|
12
7
|
- Joshua Lippiner
|
8
|
+
- Iain Barnett
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
date: 2020-09-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: gitlab-chronic
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.10.6
|
21
|
+
type: :runtime
|
22
22
|
prerelease: false
|
23
|
-
|
24
|
-
requirements:
|
25
|
-
- - "
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.10.6
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: numerizer
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.2.0
|
32
35
|
type: :runtime
|
33
|
-
|
34
|
-
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.2.0
|
42
|
+
- !ruby/object:Gem::Dependency
|
35
43
|
name: shoulda
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 2.10.3
|
49
|
+
type: :development
|
36
50
|
prerelease: false
|
37
|
-
|
38
|
-
requirements:
|
39
|
-
- - "
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
segments:
|
42
|
-
- 2
|
43
|
-
- 10
|
44
|
-
- 3
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
45
55
|
version: 2.10.3
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: simplecov
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: test-unit
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
46
77
|
type: :development
|
47
|
-
|
48
|
-
|
49
|
-
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: bundler
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rake
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: yard
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: maruku
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: timecop
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
description: Tickle is a date/time helper gem to help parse natural language into
|
155
|
+
a recurring pattern. Tickle is designed to be a compliment of Chronic and can interpret
|
156
|
+
things such as "every 2 days, every Sunday, Sundays, Weekly, etc.
|
157
|
+
email: iainspeed@gmail.com
|
50
158
|
executables: []
|
51
|
-
|
52
159
|
extensions: []
|
53
|
-
|
54
|
-
|
55
|
-
-
|
56
|
-
-
|
57
|
-
|
58
|
-
- .
|
59
|
-
-
|
60
|
-
-
|
61
|
-
-
|
62
|
-
- README.rdoc
|
160
|
+
extra_rdoc_files: []
|
161
|
+
files:
|
162
|
+
- ".document"
|
163
|
+
- ".gitignore"
|
164
|
+
- ".travis.yml"
|
165
|
+
- CHANGES.md
|
166
|
+
- Gemfile
|
167
|
+
- LICENCE
|
168
|
+
- README.md
|
63
169
|
- Rakefile
|
64
170
|
- SCENARIOS.rdoc
|
65
|
-
-
|
171
|
+
- examples.rb
|
66
172
|
- git-flow-version
|
67
|
-
- lib/numerizer/numerizer.rb
|
68
173
|
- lib/tickle.rb
|
69
174
|
- lib/tickle/handler.rb
|
70
175
|
- lib/tickle/repeater.rb
|
71
176
|
- lib/tickle/tickle.rb
|
177
|
+
- lib/tickle/version.rb
|
178
|
+
- spec/spec_helper.rb
|
72
179
|
- test/git-flow-version
|
73
180
|
- test/helper.rb
|
74
181
|
- test/test_parsing.rb
|
75
182
|
- tickle.gemspec
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
183
|
+
homepage: http://github.com/yb66/tickle
|
184
|
+
licenses:
|
185
|
+
- MIT
|
186
|
+
metadata: {}
|
80
187
|
post_install_message:
|
81
|
-
rdoc_options:
|
82
|
-
|
83
|
-
require_paths:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
84
190
|
- lib
|
85
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
87
193
|
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
94
198
|
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
|
97
|
-
- 0
|
98
|
-
version: "0"
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
99
201
|
requirements: []
|
100
|
-
|
101
202
|
rubyforge_project:
|
102
|
-
rubygems_version:
|
203
|
+
rubygems_version: 2.5.2.3
|
103
204
|
signing_key:
|
104
|
-
specification_version:
|
205
|
+
specification_version: 4
|
105
206
|
summary: natural language parser for recurring events
|
106
|
-
test_files:
|
207
|
+
test_files:
|
208
|
+
- spec/spec_helper.rb
|
209
|
+
- test/git-flow-version
|
107
210
|
- test/helper.rb
|
108
211
|
- test/test_parsing.rb
|
data/.rvmrc
DELETED
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.1.7
|
data/lib/numerizer/numerizer.rb
DELETED
@@ -1,103 +0,0 @@
|
|
1
|
-
require 'strscan'
|
2
|
-
|
3
|
-
class Numerizer
|
4
|
-
|
5
|
-
DIRECT_NUMS = [
|
6
|
-
['eleven', '11'],
|
7
|
-
['twelve', '12'],
|
8
|
-
['thirteen', '13'],
|
9
|
-
['fourteen', '14'],
|
10
|
-
['fifteen', '15'],
|
11
|
-
['sixteen', '16'],
|
12
|
-
['seventeen', '17'],
|
13
|
-
['eighteen', '18'],
|
14
|
-
['nineteen', '19'],
|
15
|
-
['ninteen', '19'], # Common mis-spelling
|
16
|
-
['zero', '0'],
|
17
|
-
['one', '1'],
|
18
|
-
['two', '2'],
|
19
|
-
['three', '3'],
|
20
|
-
['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
|
21
|
-
['five', '5'],
|
22
|
-
['six(\W|$)', '6\1'],
|
23
|
-
['seven(\W|$)', '7\1'],
|
24
|
-
['eight(\W|$)', '8\1'],
|
25
|
-
['nine(\W|$)', '9\1'],
|
26
|
-
['ten', '10'],
|
27
|
-
['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
|
28
|
-
]
|
29
|
-
|
30
|
-
TEN_PREFIXES = [ ['twenty', 20],
|
31
|
-
['thirty', 30],
|
32
|
-
['fourty', 40],
|
33
|
-
['fifty', 50],
|
34
|
-
['sixty', 60],
|
35
|
-
['seventy', 70],
|
36
|
-
['eighty', 80],
|
37
|
-
['ninety', 90]
|
38
|
-
]
|
39
|
-
|
40
|
-
BIG_PREFIXES = [ ['hundred', 100],
|
41
|
-
['thousand', 1000],
|
42
|
-
['million', 1_000_000],
|
43
|
-
['billion', 1_000_000_000],
|
44
|
-
['trillion', 1_000_000_000_000],
|
45
|
-
]
|
46
|
-
|
47
|
-
class << self
|
48
|
-
def numerize(string)
|
49
|
-
string = string.dup
|
50
|
-
|
51
|
-
# preprocess
|
52
|
-
string.gsub!(/ +|([^\d])-([^d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
|
53
|
-
string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
|
54
|
-
|
55
|
-
# easy/direct replacements
|
56
|
-
|
57
|
-
DIRECT_NUMS.each do |dn|
|
58
|
-
string.gsub!(/#{dn[0]}/i, dn[1])
|
59
|
-
end
|
60
|
-
|
61
|
-
# ten, twenty, etc.
|
62
|
-
|
63
|
-
TEN_PREFIXES.each do |tp|
|
64
|
-
string.gsub!(/(?:#{tp[0]})( *\d(?=[^\d]|$))*/i) { (tp[1] + $1.to_i).to_s }
|
65
|
-
end
|
66
|
-
|
67
|
-
# hundreds, thousands, millions, etc.
|
68
|
-
|
69
|
-
BIG_PREFIXES.each do |bp|
|
70
|
-
string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
|
71
|
-
andition(string)
|
72
|
-
#combine_numbers(string) # Should to be more efficient way to do this
|
73
|
-
end
|
74
|
-
|
75
|
-
# fractional addition
|
76
|
-
# I'm not combining this with the previous block as using float addition complicates the strings
|
77
|
-
# (with extraneous .0's and such )
|
78
|
-
string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
|
79
|
-
|
80
|
-
string
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
def andition(string)
|
85
|
-
sc = StringScanner.new(string)
|
86
|
-
while(sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i))
|
87
|
-
if sc[2] =~ /and/ || sc[1].size > sc[3].size
|
88
|
-
string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[3].to_i).to_s
|
89
|
-
sc.reset
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# def combine_numbers(string)
|
95
|
-
# sc = StringScanner.new(string)
|
96
|
-
# while(sc.scan_until(/(\d+)(?: | and |-)(\d+)(?=[^\w]|$)/i))
|
97
|
-
# string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[2].to_i).to_s
|
98
|
-
# sc.reset
|
99
|
-
# end
|
100
|
-
# end
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|