cronscription 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/Rakefile +8 -0
- data/cronscription.gemspec +22 -0
- data/lib/cronscription.rb +18 -0
- data/lib/cronscription/entry.rb +91 -0
- data/lib/cronscription/tab.rb +27 -0
- data/lib/cronscription/version.rb +3 -0
- data/spec/cronscription/entry_spec.rb +178 -0
- data/spec/cronscription/tab_spec.rb +103 -0
- data/spec/cronscription_spec.rb +32 -0
- data/spec/spec_helper.rb +2 -0
- metadata +110 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cronscription/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cronscription"
|
7
|
+
s.version = Cronscription::VERSION
|
8
|
+
s.authors = ["Ben Feng"]
|
9
|
+
s.email = ["bfeng@enova.com"]
|
10
|
+
s.homepage = "https://github.com/enova/cronscription"
|
11
|
+
s.summary = %q{Cron parsing}
|
12
|
+
s.description = %q{Cron parsing}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency "rake"
|
20
|
+
s.add_development_dependency "rspec"
|
21
|
+
s.add_development_dependency "simplecov"
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'cronscription/tab'
|
2
|
+
require 'cronscription/version'
|
3
|
+
|
4
|
+
|
5
|
+
module Cronscription
|
6
|
+
class << self
|
7
|
+
# Convenient construction methods
|
8
|
+
def from_s(str)
|
9
|
+
Tab.new(str.lines.to_a)
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_filepath(path)
|
13
|
+
File.open(path) do |f|
|
14
|
+
Tab.new(f.readlines)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Cronscription
|
2
|
+
class Entry
|
3
|
+
ORDERED_KEYS = [:min, :hour, :day, :month, :wday]
|
4
|
+
FULL_RANGE = {
|
5
|
+
:min => (0..59).to_a,
|
6
|
+
:hour => (0..23).to_a,
|
7
|
+
:day => (1..31).to_a,
|
8
|
+
:month => (1..12).to_a,
|
9
|
+
:wday => (0..6).to_a,
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_reader :times, :command
|
13
|
+
|
14
|
+
def initialize(line)
|
15
|
+
@line = line
|
16
|
+
@times = {}
|
17
|
+
|
18
|
+
raw = {}
|
19
|
+
raw[:min], raw[:hour], raw[:day], raw[:month], raw[:wday], @command = line.split(nil, 6)
|
20
|
+
@command.gsub!(/#.*/, '')
|
21
|
+
|
22
|
+
raw.each do |key, val|
|
23
|
+
@times[key] = parse_column(val, FULL_RANGE[key])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
@line == other.instance_variable_get(:@line)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
@line
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.parsable?(str)
|
36
|
+
!!(str =~ /([*\d,-]+\s+){5}.*/)
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_column(column, default=[])
|
40
|
+
case column
|
41
|
+
when /\*\/(\d+)/ then default.select { |val| val % $1.to_i == 0 }
|
42
|
+
when /\*/ then default
|
43
|
+
when /,/ then column.split(',').map{|c| parse_column(c)}.flatten.uniq
|
44
|
+
when /-/ then Range.new(*column.split('-').map{|c| c.to_i}).to_a
|
45
|
+
else [column.to_i]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def match_command?(regex)
|
50
|
+
regex === @command
|
51
|
+
end
|
52
|
+
|
53
|
+
def times_to_execute(start, finish)
|
54
|
+
ret = []
|
55
|
+
|
56
|
+
incr_min = 60
|
57
|
+
incr_hour = incr_min*60
|
58
|
+
incr_day = incr_hour*24
|
59
|
+
incr = incr_min
|
60
|
+
|
61
|
+
current = nearest_minute(start)
|
62
|
+
while current <= finish
|
63
|
+
if ORDERED_KEYS.map{|k| @times[k].include?(current.send k)}.all?
|
64
|
+
ret << current
|
65
|
+
# If only I could goto into the middle of the loop, this wouldn't run every time.
|
66
|
+
# Optimizations to reduce execution time. No need to run minutely if there is only one minute.
|
67
|
+
if @times[:min].size == 1
|
68
|
+
if @times[:hour].size == 1
|
69
|
+
incr = incr_day
|
70
|
+
else
|
71
|
+
incr = incr_hour
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
current += incr
|
76
|
+
end
|
77
|
+
|
78
|
+
ret
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def nearest_minute(time)
|
83
|
+
if time.sec == 0
|
84
|
+
time
|
85
|
+
else
|
86
|
+
# Always round up
|
87
|
+
time + (60 - time.sec)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'cronscription/entry'
|
2
|
+
|
3
|
+
|
4
|
+
module Cronscription
|
5
|
+
class Tab
|
6
|
+
def initialize(cron_lines)
|
7
|
+
# Eliminate all lines starting with '#' since they are full comments
|
8
|
+
@entries = cron_lines.select{|l| Entry.parsable?(l)}.map{|e| Entry.new(e)}
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
@entries == other.instance_variable_get(:@entries)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(regex)
|
16
|
+
@entries.select{|e| e.match_command?(regex)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def sorted_merge(*arrs)
|
20
|
+
arrs.flatten.uniq.sort
|
21
|
+
end
|
22
|
+
|
23
|
+
def times_to_execute(regex, start, finish)
|
24
|
+
sorted_merge(find(regex).map{|e| e.times_to_execute(start, finish)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cronscription/entry'
|
3
|
+
|
4
|
+
|
5
|
+
describe Cronscription::Entry do
|
6
|
+
before :all do
|
7
|
+
# <minute> <hour> <day> <month> <day of week> <tags and command>
|
8
|
+
@line = '1 2 3 4 5 comm'
|
9
|
+
@entry = Cronscription::Entry.new(@line)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should be equal when created from same values' do
|
13
|
+
Cronscription::Entry.new(@line).should == @entry
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'from_s' do
|
17
|
+
it 'should map columns with correct fields' do
|
18
|
+
@entry.times.should == {
|
19
|
+
:min => [1],
|
20
|
+
:hour => [2],
|
21
|
+
:day => [3],
|
22
|
+
:month => [4],
|
23
|
+
:wday => [5],
|
24
|
+
}
|
25
|
+
@entry.command.should == 'comm'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should convert to original line on to_s' do
|
29
|
+
line = ' 1 2 * 4 5 fun today! # some comment'
|
30
|
+
entry = Cronscription::Entry.new(line)
|
31
|
+
entry.to_s.should == line
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should use FULL_RANGE for default values' do
|
35
|
+
entry = Cronscription::Entry.new('* * * * * comm')
|
36
|
+
entry.times.should == Cronscription::Entry::FULL_RANGE
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should understand compex command structure' do
|
40
|
+
command = 'comm/rad --gen=ro -s tail'
|
41
|
+
entry = Cronscription::Entry.new("* * * * * #{command}")
|
42
|
+
entry.command.should == command
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'parsable?' do
|
47
|
+
it 'should be true for basic lines' do
|
48
|
+
Cronscription::Entry.parsable?('* * * * * comm').should be_true
|
49
|
+
Cronscription::Entry.parsable?('1 2 3 4 5 comm').should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be true for compound time directives' do
|
53
|
+
Cronscription::Entry.parsable?('1-2 * * * * comm').should be_true
|
54
|
+
Cronscription::Entry.parsable?(' * 3,4 * * * comm').should be_true
|
55
|
+
Cronscription::Entry.parsable?(' * * 3,4-5 * * comm').should be_true
|
56
|
+
Cronscription::Entry.parsable?(' * * * 6-7,0,8-9 * comm').should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should be true for complex commands' do
|
60
|
+
Cronscription::Entry.parsable?('* * * * * comm/rad --gen=ro -s tail').should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should be false for bad time declaration' do
|
64
|
+
Cronscription::Entry.parsable?('* b * * * comm').should be_false
|
65
|
+
Cronscription::Entry.parsable?('* * * * . comm').should be_false
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should be false for dumb lines' do
|
69
|
+
Cronscription::Entry.parsable?(' # This is a comment').should be_false
|
70
|
+
Cronscription::Entry.parsable?('rawr!').should be_false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'parse_column' do
|
75
|
+
it 'should return fixed value as list of one' do
|
76
|
+
@entry.parse_column('1').should == [1]
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should return comma-separated as list of values' do
|
80
|
+
@entry.parse_column('5,2,8').should == [5, 2, 8]
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should return range as list of everything within the range' do
|
84
|
+
@entry.parse_column('4-6').should == [4, 5, 6]
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should return combination of range and comma-separated' do
|
88
|
+
@entry.parse_column('9,2-5,7').should == [9, 2, 3, 4, 5, 7]
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should use default value on asterisk' do
|
92
|
+
default = [1, 5, 9, 2, 6]
|
93
|
+
@entry.parse_column('*', default).should == default
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should find every n minutes from the default on */n' do
|
97
|
+
default = [*0..100]
|
98
|
+
@entry.parse_column('*/31', default).should == [0, 31, 62, 93]
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should return unique entries only' do
|
102
|
+
@entry.parse_column('1,1,1').should == [1]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'match_command?' do
|
107
|
+
it 'should match command by regex' do
|
108
|
+
entry = Cronscription::Entry.new('1 2 3 4 5 command one')
|
109
|
+
entry.match_command?(/m*and\s*on/).should be_true
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should not match command within comments' do
|
113
|
+
entry = Cronscription::Entry.new('1 2 3 4 5 command #herp')
|
114
|
+
entry.match_command?(/herp/).should be_false
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not match command within time directives' do
|
118
|
+
entry = Cronscription::Entry.new('1 2 3 4 5 command #herp')
|
119
|
+
entry.match_command?(/\d/).should be_false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'times_to_execute' do
|
124
|
+
it 'should return times based on minutes' do
|
125
|
+
entry = Cronscription::Entry.new("21-40 * * * * comm")
|
126
|
+
start = Time.local(2011, 1, 1, 0, 0)
|
127
|
+
finish = Time.local(2011, 1, 1, 0, 30)
|
128
|
+
|
129
|
+
times = entry.times_to_execute(start, finish)
|
130
|
+
times.should == (21..30).map{|m| Time.local(2011, 1, 1, 0, m)}
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should return times based on hours' do
|
134
|
+
entry = Cronscription::Entry.new("0 1-8 * * * comm")
|
135
|
+
start = Time.local(2011, 1, 1, 0, 0)
|
136
|
+
finish = Time.local(2011, 1, 1, 6, 0)
|
137
|
+
|
138
|
+
times = entry.times_to_execute(start, finish)
|
139
|
+
times.should == (1..6).map{|h| Time.local(2011, 1, 1, h, 0)}
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should return times based on days' do
|
143
|
+
entry = Cronscription::Entry.new("0 0 8-20 * * comm")
|
144
|
+
start = Time.local(2011, 1, 5, 0, 0)
|
145
|
+
finish = Time.local(2011, 1, 15, 0, 0)
|
146
|
+
|
147
|
+
times = entry.times_to_execute(start, finish)
|
148
|
+
times.should == (8..15).map{|d| Time.local(2011, 1, d, 0, 0)}
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should return times based on months' do
|
152
|
+
entry = Cronscription::Entry.new("0 0 1 1-5 * comm")
|
153
|
+
start = Time.local(2011, 4, 1, 0, 0)
|
154
|
+
finish = Time.local(2011, 12, 1, 0, 0)
|
155
|
+
|
156
|
+
times = entry.times_to_execute(start, finish)
|
157
|
+
times.should == (4..5).map{|m| Time.local(2011, m, 1, 0, 0)}
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should round start time up to the next minute' do
|
161
|
+
entry = Cronscription::Entry.new("* * * * * comm")
|
162
|
+
start = Time.local(2011, 1, 1, 0, 0, 45)
|
163
|
+
finish = Time.local(2011, 1, 1, 0, 1, 45)
|
164
|
+
|
165
|
+
times = entry.times_to_execute(start, finish)
|
166
|
+
times.should == [Time.local(2011, 1, 1, 0, 1)]
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should round start time up by bumping up necessary other fields' do
|
170
|
+
entry = Cronscription::Entry.new("* * * * * comm")
|
171
|
+
start = Time.local(2011, 12, 31, 23, 59, 45)
|
172
|
+
finish = Time.local(2012, 1, 1, 0, 0, 45)
|
173
|
+
|
174
|
+
times = entry.times_to_execute(start, finish)
|
175
|
+
times.should == [Time.local(2012, 1, 1, 0, 0)]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cronscription'
|
3
|
+
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
|
7
|
+
describe Cronscription::Tab do
|
8
|
+
before :all do
|
9
|
+
@cronstr = <<-END
|
10
|
+
# <minute> <hour> <day> <month> <day of week> <tags and command>
|
11
|
+
0 * * * * cron.hourly
|
12
|
+
0 0 * * * cron.daily
|
13
|
+
0 0 * * 0 cron.weekly
|
14
|
+
0 0 1 * * cron.monthly
|
15
|
+
1 2 3 4 5 0 # test trailing comment
|
16
|
+
END
|
17
|
+
@cron_lines = @cronstr.lines.to_a
|
18
|
+
@tab = Cronscription::Tab.new(@cron_lines)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should be equal when created from same values' do
|
22
|
+
Cronscription::Tab.new(@cron_lines).should == @tab
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'find' do
|
26
|
+
it 'should find the daily entry' do
|
27
|
+
entries = @tab.find(/daily/).map{|e| e.to_s}
|
28
|
+
entries.should == [@cron_lines[2]]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should find all cron.* entries' do
|
32
|
+
entries = @tab.find(/cron\..*/).map{|e| e.to_s}
|
33
|
+
entries.should == @cron_lines[1..4]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should ignore complete line comments' do
|
37
|
+
entries = @tab.find(/.*/).map{|e| e.to_s}
|
38
|
+
entries.should == @cron_lines[1..-1]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should ignore trailing comments' do
|
42
|
+
entries = @tab.find(/test trailing comment/).map{|e| e.to_s}
|
43
|
+
entries.should == []
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should ignore time directives' do
|
47
|
+
entries = @tab.find(/0/).map{|e| e.to_s}
|
48
|
+
entries.should == [@cron_lines[-1]]
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should attempt to use possible lines when encountering mangled garbage' do
|
52
|
+
cronstr = <<-END
|
53
|
+
# The history of all hitherto
|
54
|
+
existing society is the
|
55
|
+
* * * * * comm
|
56
|
+
history of class struggles.
|
57
|
+
END
|
58
|
+
cron_lines = cronstr.lines.to_a
|
59
|
+
tab = Cronscription::Tab.new(cron_lines)
|
60
|
+
entries = tab.find(/.*/).map{|e| e.to_s}
|
61
|
+
entries.should == [cron_lines[2]]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'sorted_merge' do
|
66
|
+
before :all do
|
67
|
+
@tab = Cronscription::Tab.new([])
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should merge in order' do
|
71
|
+
@tab.sorted_merge([1, 4], [2, 7, 9]).should == [1, 2, 4, 7, 9]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should merge while eliminating duplicates' do
|
75
|
+
@tab.sorted_merge([2, 2, 2, 6, 7], [7, 8]).should == [2, 6, 7, 8]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'times_to_execute' do
|
80
|
+
it 'should return merged times' do
|
81
|
+
hour1 = 3
|
82
|
+
min1 = 48
|
83
|
+
|
84
|
+
hour2 = 6
|
85
|
+
min2 = 21
|
86
|
+
cronfile = <<-END
|
87
|
+
#{min1} #{hour1} * * * common
|
88
|
+
#{min2} #{hour2} * * * common
|
89
|
+
END
|
90
|
+
tab = Cronscription::Tab.new(cronfile.lines.to_a)
|
91
|
+
|
92
|
+
start = Time.local(2011, 1, 1, 0, 0)
|
93
|
+
finish = Time.local(2011, 1, 3, 0, 0)
|
94
|
+
tab.times_to_execute(/common/, start, finish).should == [
|
95
|
+
Time.local(2011, 1, 1, hour1, min1),
|
96
|
+
Time.local(2011, 1, 1, hour2, min2),
|
97
|
+
Time.local(2011, 1, 2, hour1, min1),
|
98
|
+
Time.local(2011, 1, 2, hour2, min2),
|
99
|
+
]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cronscription'
|
3
|
+
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
|
7
|
+
describe Cronscription do
|
8
|
+
before(:all) do
|
9
|
+
@cronstr = <<-END
|
10
|
+
59 * 10 * * entry1
|
11
|
+
* 12 2 * 5 entry2
|
12
|
+
END
|
13
|
+
@tab = Cronscription::Tab.new(@cronstr.lines.to_a)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'convenient constructors' do
|
17
|
+
it 'should create from string' do
|
18
|
+
Cronscription.from_s(@cronstr).should == @tab
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should create from filepath' do
|
22
|
+
path = nil
|
23
|
+
Tempfile.open('cronscription') do |f|
|
24
|
+
f.write(@cronstr)
|
25
|
+
path = f.path
|
26
|
+
end
|
27
|
+
|
28
|
+
Cronscription.from_filepath(path).should == @tab
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cronscription
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben Feng
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: simplecov
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Cron parsing
|
63
|
+
email:
|
64
|
+
- bfeng@enova.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- Rakefile
|
72
|
+
- cronscription.gemspec
|
73
|
+
- lib/cronscription.rb
|
74
|
+
- lib/cronscription/entry.rb
|
75
|
+
- lib/cronscription/tab.rb
|
76
|
+
- lib/cronscription/version.rb
|
77
|
+
- spec/cronscription/entry_spec.rb
|
78
|
+
- spec/cronscription/tab_spec.rb
|
79
|
+
- spec/cronscription_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
homepage: https://github.com/enova/cronscription
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.23
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Cron parsing
|
105
|
+
test_files:
|
106
|
+
- spec/cronscription/entry_spec.rb
|
107
|
+
- spec/cronscription/tab_spec.rb
|
108
|
+
- spec/cronscription_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
has_rdoc:
|