midnight 0.0.1.pre
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 +17 -0
- data/.rubocop_todo.yml +278 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +339 -0
- data/LICENSE.txt +22 -0
- data/README.md +30 -0
- data/README.textile +20 -0
- data/Rakefile +12 -0
- data/lib/midnight.rb +42 -0
- data/lib/midnight/converter.rb +152 -0
- data/lib/midnight/cron_expression.rb +38 -0
- data/lib/midnight/handler.rb +104 -0
- data/lib/midnight/midnight.rb +91 -0
- data/lib/midnight/repeater.rb +153 -0
- data/lib/midnight/version.rb +3 -0
- data/lib/numerizer/numerizer.rb +103 -0
- data/midnight.gemspec +24 -0
- data/test/helper.rb +10 -0
- data/test/test_cron_expression.rb +18 -0
- data/test/test_parsing.rb +68 -0
- data/todo.txt +21 -0
- metadata +112 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
class Midnight::Repeater < Chronic::Tag #:nodoc:
|
2
|
+
#
|
3
|
+
def self.scan(tokens)
|
4
|
+
# for each token
|
5
|
+
tokens.each do |token|
|
6
|
+
token = self.scan_for_24h_times(token)
|
7
|
+
token = self.scan_for_numbers(token)
|
8
|
+
token = self.scan_for_month_names(token)
|
9
|
+
token = self.scan_for_day_names(token)
|
10
|
+
token = self.scan_for_special_text(token)
|
11
|
+
token = self.scan_for_units(token)
|
12
|
+
end
|
13
|
+
|
14
|
+
tokens = self.split_time_tokens(tokens)
|
15
|
+
tokens
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.scan_for_numbers(token)
|
19
|
+
num = Float(token.word) rescue nil
|
20
|
+
token.update(:number, nil, num.to_i) if num
|
21
|
+
token
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.scan_for_24h_times(token)
|
25
|
+
if token.word =~ /(2[0-3]|[01]?[0-9]):([0-5]?[0-9])/
|
26
|
+
token.update(:time, nil, nil)
|
27
|
+
end
|
28
|
+
token
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.scan_for_month_names(token)
|
32
|
+
scanner = {
|
33
|
+
/^jan\.?(uary)?$/ => :january,
|
34
|
+
/^feb\.?(ruary)?$/ => :february,
|
35
|
+
/^mar\.?(ch)?$/ => :march,
|
36
|
+
/^apr\.?(il)?$/ => :april,
|
37
|
+
/^may$/ => :may,
|
38
|
+
/^jun\.?e?$/ => :june,
|
39
|
+
/^jul\.?y?$/ => :july,
|
40
|
+
/^aug\.?(ust)?$/ => :august,
|
41
|
+
/^sep\.?(t\.?|tember)?$/ => :september,
|
42
|
+
/^oct\.?(ober)?$/ => :october,
|
43
|
+
/^nov\.?(ember)?$/ => :november,
|
44
|
+
/^dec\.?(ember)?$/ => :december}
|
45
|
+
|
46
|
+
month_sequence = {
|
47
|
+
:january => 1,
|
48
|
+
:february => 2,
|
49
|
+
:march => 3,
|
50
|
+
:april => 4,
|
51
|
+
:may => 5,
|
52
|
+
:june => 6,
|
53
|
+
:july => 7,
|
54
|
+
:august => 8,
|
55
|
+
:september => 9,
|
56
|
+
:october => 10,
|
57
|
+
:november => 11,
|
58
|
+
:december => 12
|
59
|
+
}
|
60
|
+
|
61
|
+
scanner.each do |scanner_item, month|
|
62
|
+
position_in_sequence = month_sequence[month]
|
63
|
+
token.update(:month_name, scanner[scanner_item], 30, position_in_sequence) if scanner_item =~ token.word
|
64
|
+
end
|
65
|
+
token
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.scan_for_day_names(token)
|
69
|
+
scanner = {
|
70
|
+
/^su[nm](day)?s?$/ => :sunday,
|
71
|
+
/^m[ou]n(day)?s?$/ => :monday,
|
72
|
+
/^t(ue|eu|oo|u|)s(day)?s?$/ => :tuesday,
|
73
|
+
/^tue$/ => :tuesday,
|
74
|
+
/^we(dnes|nds|nns)day?s?$/ => :wednesday,
|
75
|
+
/^wed$/ => :wednesday,
|
76
|
+
/^th(urs|ers)day?s?$/ => :thursday,
|
77
|
+
/^thu$/ => :thursday,
|
78
|
+
/^fr[iy](day)?s?$/ => :friday,
|
79
|
+
/^sat(t?[ue]rday)?s?$/ => :saturday
|
80
|
+
}
|
81
|
+
|
82
|
+
day_sequence = {
|
83
|
+
:sunday => 0,
|
84
|
+
:monday => 1,
|
85
|
+
:tuesday => 2,
|
86
|
+
:wednesday => 3,
|
87
|
+
:thursday => 4,
|
88
|
+
:friday => 5,
|
89
|
+
:saturday => 6
|
90
|
+
}
|
91
|
+
|
92
|
+
scanner.each do |scanner_item, day|
|
93
|
+
position_in_sequence = day_sequence[day]
|
94
|
+
token.update(:weekday, scanner[scanner_item], 7, position_in_sequence) if scanner_item =~ token.word
|
95
|
+
end
|
96
|
+
token
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.scan_for_special_text(token)
|
100
|
+
scanner = {
|
101
|
+
/^other$/ => :other,
|
102
|
+
/^begin(ing|ning)?$/ => :beginning,
|
103
|
+
/^start$/ => :beginning,
|
104
|
+
/^end$/ => :end,
|
105
|
+
/^mid(d)?le$/ => :middle}
|
106
|
+
scanner.keys.each do |scanner_item|
|
107
|
+
token.update(:special, scanner[scanner_item], 7) if scanner_item =~ token.word
|
108
|
+
end
|
109
|
+
token
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.scan_for_units(token)
|
113
|
+
scanner = {
|
114
|
+
/^year(ly)?s?|annually$/ => {:type => :year, :interval => 365, :start => :today},
|
115
|
+
/^month(ly)?s?$/ => {:type => :month, :interval => 30, :start => :today},
|
116
|
+
/^fortnights?$/ => {:type => :fortnight, :interval => 365, :start => :today},
|
117
|
+
/^week(ly)?s?$/ => {:type => :week, :interval => 7, :start => :today},
|
118
|
+
/^weekends?$/ => {:type => :weekend, :interval => 7, :start => :saturday},
|
119
|
+
/^days?$/ => {:type => :day, :interval => 1, :start => :today},
|
120
|
+
/^daily?$/ => {:type => :day, :interval => 1, :start => :today},
|
121
|
+
/^minutes?$/ => {:type => :minute_word, :start => :today},
|
122
|
+
/^hour(ly)?s?$/ => {:type => :hour, :start => :today},
|
123
|
+
/^am|pm$/ => {:type => :meridiem}
|
124
|
+
}
|
125
|
+
scanner.keys.each do |scanner_item|
|
126
|
+
if scanner_item =~ token.word
|
127
|
+
token.update(scanner[scanner_item][:type], scanner[scanner_item][:start], scanner[scanner_item][:interval]) if scanner_item =~ token.word
|
128
|
+
end
|
129
|
+
end
|
130
|
+
token
|
131
|
+
end
|
132
|
+
|
133
|
+
# Split time tokens into constituent hour and minute tokens
|
134
|
+
def self.split_time_tokens(tokens)
|
135
|
+
tokens_output = []
|
136
|
+
tokens.each do |token|
|
137
|
+
if (token.type == :time)
|
138
|
+
time_parts = token.word.split(':')
|
139
|
+
hour_token = Midnight::Token.new(time_parts[0].to_i)
|
140
|
+
minute_token = Midnight::Token.new(time_parts[1].to_i)
|
141
|
+
hour_token.update(:hour)
|
142
|
+
minute_token.update(:minute)
|
143
|
+
tokens_output << hour_token
|
144
|
+
tokens_output << minute_token
|
145
|
+
else
|
146
|
+
tokens_output << token
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
tokens_output
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
@@ -0,0 +1,103 @@
|
|
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
|
data/midnight.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'midnight/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "midnight"
|
8
|
+
spec.version = Midnight::VERSION
|
9
|
+
spec.authors = ["bluefuton"]
|
10
|
+
spec.email = ["chris@bluefuton.com"]
|
11
|
+
spec.summary = %q{Parse natural language date/time into a cron expression}
|
12
|
+
spec.description = ''
|
13
|
+
spec.homepage = 'https://github.com/bluefuton/midnight'
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake", '~> 0'
|
23
|
+
spec.add_dependency 'chronic', '=0.10.2'
|
24
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'midnight')
|
8
|
+
|
9
|
+
class Test::Unit::TestCase
|
10
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'time'
|
3
|
+
require 'test/unit'
|
4
|
+
require './lib/midnight/cron_expression'
|
5
|
+
|
6
|
+
class TestParsing < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_cron_expression
|
13
|
+
cron = Midnight::CronExpression.new
|
14
|
+
cron.minute = 30
|
15
|
+
assert_equal '30 * * * *', cron.to_s
|
16
|
+
assert_equal 30, cron.minute
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'time'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class TestParsing < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_parse
|
8
|
+
expected_results = {
|
9
|
+
'each day' => '0 0 * * *',
|
10
|
+
'every day' => '0 0 * * *',
|
11
|
+
'daily' => '0 0 * * *',
|
12
|
+
'every day at 3am' => '0 3 * * *',
|
13
|
+
'daily at 5am' => '0 5 * * *',
|
14
|
+
'every week' => '0 0 * * 0',
|
15
|
+
'weekly' => '0 0 * * 0',
|
16
|
+
'every minute' => '* * * * *',
|
17
|
+
'every 5 minutes' => '*/5 * * * *',
|
18
|
+
'every 30 minutes' => '*/30 * * * *',
|
19
|
+
'every month' => '0 0 1 * *',
|
20
|
+
'monthly' => '0 0 1 * *',
|
21
|
+
'every Monday' => '0 0 * * 1',
|
22
|
+
'every Wednesday' => '0 0 * * 3',
|
23
|
+
'every Friday' => '0 0 * * 5',
|
24
|
+
'this one should not return a result' => nil,
|
25
|
+
'every hour' => '0 * * * *',
|
26
|
+
'every 6 hours' => '0 */6 * * *',
|
27
|
+
'hourly' => '0 * * * *',
|
28
|
+
'every year' => '0 0 1 1 *',
|
29
|
+
'yearly' => '0 0 1 1 *',
|
30
|
+
'annually' => '0 0 1 1 *',
|
31
|
+
'every day at 9am' => '0 9 * * *',
|
32
|
+
'every day at 5pm' => '0 17 * * *',
|
33
|
+
'every day at 5:45pm' => '45 17 * * *',
|
34
|
+
'every day at 17:00' => '0 17 * * *',
|
35
|
+
'every day at 17:25' => '25 17 * * *',
|
36
|
+
'5:15am every Tuesday' => '15 5 * * 2',
|
37
|
+
'7pm every Thursday' => '0 19 * * 4',
|
38
|
+
'every May' => '0 0 1 5 *',
|
39
|
+
'every December' => '0 0 1 12 *',
|
40
|
+
'midnight' => '0 0 * * *',
|
41
|
+
'midnight on tuesdays' => '0 0 * * 2',
|
42
|
+
'every 5 minutes on Tuesdays' => '*/5 * * * 2',
|
43
|
+
'every other day' => nil, # other is currently unsupported
|
44
|
+
'noon' => '0 12 * * *'
|
45
|
+
}
|
46
|
+
|
47
|
+
expected_results.each do |search,cron_string|
|
48
|
+
assert_equal(cron_string, Midnight.parse(search).to_s, search)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_argument_validation
|
53
|
+
assert_raise(Midnight::InvalidArgumentException) do
|
54
|
+
time = Midnight.parse("may 27", :today => 'something odd')
|
55
|
+
end
|
56
|
+
|
57
|
+
assert_raise(Midnight::InvalidArgumentException) do
|
58
|
+
time = Midnight.parse("may 27", :foo => :bar)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def parse_now(string, options={})
|
64
|
+
out = Midnight.parse(string, {}.merge(options))
|
65
|
+
puts ("Midnight.parse('#{string}') #=> #{out}")
|
66
|
+
out
|
67
|
+
end
|
68
|
+
end
|
data/todo.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
every 3 days
|
2
|
+
every 3 weeks
|
3
|
+
every 3 months
|
4
|
+
every 3 years
|
5
|
+
|
6
|
+
every other day
|
7
|
+
every other week
|
8
|
+
every other month
|
9
|
+
every other year
|
10
|
+
every other day starting May 1st
|
11
|
+
every other week starting this Sunday
|
12
|
+
|
13
|
+
the 10th of the month
|
14
|
+
the tenth of the month
|
15
|
+
the 3rd Sunday of the month
|
16
|
+
out of bounds values e.g. 4:61pm
|
17
|
+
midday - 'noon' works, so Chronic could be patched
|
18
|
+
every 5 minutes on fridays
|
19
|
+
multiple days "every tuesday, wednesday and friday at 5pm"
|
20
|
+
multiple hours or minutes
|
21
|
+
ranges of minutes
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: midnight
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- bluefuton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: chronic
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.10.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.10.2
|
55
|
+
description: ''
|
56
|
+
email:
|
57
|
+
- chris@bluefuton.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rubocop_todo.yml"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- README.textile
|
70
|
+
- Rakefile
|
71
|
+
- lib/midnight.rb
|
72
|
+
- lib/midnight/converter.rb
|
73
|
+
- lib/midnight/cron_expression.rb
|
74
|
+
- lib/midnight/handler.rb
|
75
|
+
- lib/midnight/midnight.rb
|
76
|
+
- lib/midnight/repeater.rb
|
77
|
+
- lib/midnight/version.rb
|
78
|
+
- lib/numerizer/numerizer.rb
|
79
|
+
- midnight.gemspec
|
80
|
+
- test/helper.rb
|
81
|
+
- test/test_cron_expression.rb
|
82
|
+
- test/test_parsing.rb
|
83
|
+
- todo.txt
|
84
|
+
homepage: https://github.com/bluefuton/midnight
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.3.1
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.2.2
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Parse natural language date/time into a cron expression
|
108
|
+
test_files:
|
109
|
+
- test/helper.rb
|
110
|
+
- test/test_cron_expression.rb
|
111
|
+
- test/test_parsing.rb
|
112
|
+
has_rdoc:
|