tab_keeper 0.1.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 +4 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/bin/console +6 -0
- data/lib/tab_keeper/daily.rb +30 -0
- data/lib/tab_keeper/generator.rb +15 -0
- data/lib/tab_keeper/hourly.rb +45 -0
- data/lib/tab_keeper/job_list.rb +41 -0
- data/lib/tab_keeper/log_redirection.rb +96 -0
- data/lib/tab_keeper/login_shell.rb +24 -0
- data/lib/tab_keeper/minutely.rb +25 -0
- data/lib/tab_keeper/monthly.rb +36 -0
- data/lib/tab_keeper/rails_runner.rb +25 -0
- data/lib/tab_keeper/version.rb +3 -0
- data/lib/tab_keeper/weekly.rb +41 -0
- data/lib/tab_keeper.rb +18 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/tab_keeper/daily_spec.rb +39 -0
- data/spec/tab_keeper/generator_spec.rb +34 -0
- data/spec/tab_keeper/hourly_spec.rb +45 -0
- data/spec/tab_keeper/job_list_spec.rb +51 -0
- data/spec/tab_keeper/log_redirection_spec.rb +95 -0
- data/spec/tab_keeper/login_shell_spec.rb +25 -0
- data/spec/tab_keeper/minutely_spec.rb +18 -0
- data/spec/tab_keeper/monthly_spec.rb +51 -0
- data/spec/tab_keeper/rails_runner_spec.rb +23 -0
- data/spec/tab_keeper/weekly_spec.rb +51 -0
- data/tab_keeper.gemspec +25 -0
- metadata +128 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 29235a8d42173d34b28beb02ec01bb82285aa12c
|
|
4
|
+
data.tar.gz: c3978f99e2fcc255ff2d7f68590e46e162a6c498
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 59272e872de28887507c2ec2a7712695d2a442e0d788c96a05327eadb5d8f1aff727e41c4c4ed9179780cea7fcf2507aa41fdf303a7276e3327a0c0c3d7ad415
|
|
7
|
+
data.tar.gz: f92e98d7503c162a0679a145c38e1d39e566137d4b7beb43bf7b164d6522f97053002c6ee6dbffc98a12f267690626b11930bf8ba656f476c4dbadf04ef46338
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Isaac Seymour
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# TabKeeper
|
|
2
|
+
|
|
3
|
+
Keep tabs on your crontab.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'tab_keeper'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install tab_keeper
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
TODO
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
|
|
27
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test`
|
|
28
|
+
to run the tests. You can also run `bin/console` for an interactive prompt that will allow
|
|
29
|
+
you to experiment.
|
|
30
|
+
|
|
31
|
+
## Contributing
|
|
32
|
+
|
|
33
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/isaacseymour/tab_keeper.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
38
|
+
|
data/bin/console
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class Daily
|
|
3
|
+
HOUR_VALUES = (0..23)
|
|
4
|
+
MINUTE_VALUES = (0..59)
|
|
5
|
+
|
|
6
|
+
def initialize(hour: nil, min: 0)
|
|
7
|
+
@hour = hour
|
|
8
|
+
@min = min
|
|
9
|
+
validate!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_s
|
|
13
|
+
"#{min} #{hour} * * *"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
attr_reader :hour, :min
|
|
19
|
+
|
|
20
|
+
def validate!
|
|
21
|
+
unless HOUR_VALUES.include?(hour)
|
|
22
|
+
raise ArgumentError, "hour must be between 0 and 23"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
unless MINUTE_VALUES.include?(min)
|
|
26
|
+
raise ArgumentError, "min must be between 0 and 59"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class Generator
|
|
3
|
+
def initialize(*pipeline)
|
|
4
|
+
@pipeline = pipeline.flatten
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def generate(job_list, **options)
|
|
8
|
+
job_list.map do |job, timing|
|
|
9
|
+
timing + " " + @pipeline.reduce(nil) do |previous, pipe|
|
|
10
|
+
pipe.new(previous, job: job, timing: timing, **options).to_s
|
|
11
|
+
end
|
|
12
|
+
end.join("\n\n") + "\n"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class Hourly
|
|
3
|
+
MINUTE_VALUES = (0..59)
|
|
4
|
+
# Other values don't divide 24, so result in weird behaviour
|
|
5
|
+
EVERY_VALUES = [nil, 2, 3, 4, 6, 8, 12]
|
|
6
|
+
HOUR_VALUES = (0..23)
|
|
7
|
+
|
|
8
|
+
def initialize(min: 0, every: nil, only: nil)
|
|
9
|
+
@min = min
|
|
10
|
+
@every = every
|
|
11
|
+
@only = only
|
|
12
|
+
verify!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
return "#{min} */#{every} * * *" if every
|
|
17
|
+
return "#{min} #{only.join(',')} * * *" if only
|
|
18
|
+
"#{min} * * * *"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :min, :every, :only
|
|
24
|
+
|
|
25
|
+
def verify!
|
|
26
|
+
unless MINUTE_VALUES.include?(min)
|
|
27
|
+
raise ArgumentError, "min must be between 0 and 59"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
unless EVERY_VALUES.include?(every)
|
|
31
|
+
raise ArgumentError, "every must be one of #{EVERY_VALUES.join(', ')}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return if only.nil?
|
|
35
|
+
raise ArgumentError, "every and only don't make sense together!" if every
|
|
36
|
+
|
|
37
|
+
raise ArgumentError, "only must be an array" unless only.is_a?(Array)
|
|
38
|
+
|
|
39
|
+
raise ArgumentError, "use Daily instead of a single `only` list" if only.one?
|
|
40
|
+
|
|
41
|
+
return if only.all? { |hour| HOUR_VALUES.include?(hour) }
|
|
42
|
+
raise ArgumentError, "only must be nil, or a list of hours"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class JobList
|
|
3
|
+
def initialize
|
|
4
|
+
@jobs = []
|
|
5
|
+
yield self if block_given?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def generate(generator, **options)
|
|
9
|
+
generator.generate(@jobs, **options)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_a
|
|
13
|
+
@jobs
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add(job, timer)
|
|
17
|
+
# TODO: validate `timer.to_s`
|
|
18
|
+
@jobs << [job, timer.to_s]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def daily(job, **options)
|
|
22
|
+
add(job, Daily.new(**options))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def hourly(job, **options)
|
|
26
|
+
add(job, Hourly.new(**options))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def minutely(job, **options)
|
|
30
|
+
add(job, Minutely.new(**options))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def monthly(job, **options)
|
|
34
|
+
add(job, Monthly.new(**options))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def weekly(job, **options)
|
|
38
|
+
add(job, Weekly.new(**options))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class LogRedirection
|
|
3
|
+
def initialize(previous, job: nil,
|
|
4
|
+
job_name_proc: ->(x) { x },
|
|
5
|
+
timing: nil,
|
|
6
|
+
log_directory: nil,
|
|
7
|
+
error_suffix: nil,
|
|
8
|
+
include_date_in_log_name: false,
|
|
9
|
+
**_options)
|
|
10
|
+
if previous.nil?
|
|
11
|
+
raise ArgumentError, "#{self.class.name} must not be first in the cron pipeline!"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if log_directory.nil?
|
|
15
|
+
raise ArgumentError, "log_directory must be set for #{self.class.name}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@previous = previous
|
|
19
|
+
@job = job
|
|
20
|
+
# Normalise the job name: if it's an array take the first thing, then get the last
|
|
21
|
+
# thing after a "::" (if it's a class name), or a "/" (if it's a script, and removes
|
|
22
|
+
# the need to escape yay)
|
|
23
|
+
job_name = Array(@job).first.to_s.split(/((::)|\/)/).last
|
|
24
|
+
@job_name = job_name_proc.call(job_name)
|
|
25
|
+
@timing = timing
|
|
26
|
+
@log_directory = log_directory
|
|
27
|
+
@error_suffix = error_suffix
|
|
28
|
+
@include_date_in_log_name = include_date_in_log_name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
"#{@previous} " \
|
|
33
|
+
">> #{@log_directory}/#{job_name}#{date_part}.log " \
|
|
34
|
+
"2>#{error_part}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :job_name
|
|
40
|
+
|
|
41
|
+
def error_part
|
|
42
|
+
return "&1" unless @error_suffix
|
|
43
|
+
"> #{@log_directory}/#{job_name}#{date_part}.#{@error_suffix}.log"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def date_part
|
|
47
|
+
return unless include_date?
|
|
48
|
+
"_`date +#{date_format}`"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def include_date?
|
|
52
|
+
@include_date_in_log_name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def date_format
|
|
56
|
+
[
|
|
57
|
+
'%Y',
|
|
58
|
+
'%m',
|
|
59
|
+
day_component,
|
|
60
|
+
hour_component,
|
|
61
|
+
min_component,
|
|
62
|
+
second_component
|
|
63
|
+
].compact.join('-').chars.map { |char| char == '%' ? '\%' : char }.join
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def day_component
|
|
67
|
+
'%d' if more_than_once_a_month?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def hour_component
|
|
71
|
+
'%H' if more_than_once_a_day?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def min_component
|
|
75
|
+
'%m' if more_than_once_a_day?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def second_component
|
|
79
|
+
'%s' if more_than_once_an_hour?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
MULTIPLE_CHARACTERS = "*-/,".chars.map(&:freeze).freeze
|
|
83
|
+
|
|
84
|
+
def more_than_once_a_month?
|
|
85
|
+
MULTIPLE_CHARACTERS.any? { |char| @timing.split(' ')[2].include?(char) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def more_than_once_a_day?
|
|
89
|
+
MULTIPLE_CHARACTERS.any? { |char| @timing.split(' ').first(2).join.include?(char) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def more_than_once_an_hour?
|
|
93
|
+
MULTIPLE_CHARACTERS.any? { |char| @timing.split(' ').first.include?(char) }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class LoginShell
|
|
3
|
+
def initialize(previous, job: nil, code_directory: nil, **_options)
|
|
4
|
+
@command = previous || job
|
|
5
|
+
@code_directory = code_directory
|
|
6
|
+
return if code_directory
|
|
7
|
+
raise ArgumentError, "code `directory` must be configured for LoginShell"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_s
|
|
11
|
+
"/bin/bash -l -c 'cd #{escaped_path} && #{escaped_command}'"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def escaped_path
|
|
17
|
+
@code_directory.chars.map { |char| char == "'" ? "'\\''" : char }.join
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def escaped_command
|
|
21
|
+
@command.chars.map { |char| char == "'" ? "'\\''" : char }.join
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class Minutely
|
|
3
|
+
# Other values would lead to the non-evenly spaced runs, which is non-obvious
|
|
4
|
+
EVERY_VALUES = [2, 3, 4, 5, 6, 10, 12, 15, 20, 30]
|
|
5
|
+
|
|
6
|
+
def initialize(every: nil)
|
|
7
|
+
@every = every
|
|
8
|
+
verify!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
return "* * * * *" unless every
|
|
13
|
+
"*/#{every} * * * *"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
attr_reader :every
|
|
19
|
+
|
|
20
|
+
def verify!
|
|
21
|
+
return if every.nil? || EVERY_VALUES.include?(every)
|
|
22
|
+
raise ArgumentError, "every must be nil, or one of #{EVERY_VALUES.join(', ')}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class Monthly
|
|
3
|
+
MINUTE_VALUES = (0..59)
|
|
4
|
+
HOUR_VALUES = (0..23)
|
|
5
|
+
# Cron supports 29-31 too, but that's a recipe for sadness
|
|
6
|
+
DAY_VALUES = (0..28)
|
|
7
|
+
|
|
8
|
+
def initialize(day: nil, hour: nil, min: 0)
|
|
9
|
+
@day = day
|
|
10
|
+
@hour = hour
|
|
11
|
+
@min = min
|
|
12
|
+
verify!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
"#{min} #{hour} #{day} * *"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :day, :hour, :min
|
|
22
|
+
|
|
23
|
+
def verify!
|
|
24
|
+
unless MINUTE_VALUES.include?(min)
|
|
25
|
+
raise ArgumentError, "min must be between 0 and 59"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unless HOUR_VALUES.include?(hour)
|
|
29
|
+
raise ArgumentError, "hour must be between 0 and 23"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
return if DAY_VALUES.include?(day)
|
|
33
|
+
raise ArgumentError, "day must be between 0 and 28"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class RailsRunner
|
|
3
|
+
def initialize(previous, job: nil, rails_env: nil, **_options)
|
|
4
|
+
@to_run = previous || job
|
|
5
|
+
@rails_env = rails_env
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_s
|
|
9
|
+
["bin/rails runner", env_part, "'#{escaped_previous}'"].compact.join(" ")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
attr_reader :rails_env, :to_run
|
|
15
|
+
|
|
16
|
+
def env_part
|
|
17
|
+
return unless @rails_env
|
|
18
|
+
"-e #{rails_env}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def escaped_previous
|
|
22
|
+
to_run.chars.map { |char| char == "'" ? "'\\''" : char }.join
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module TabKeeper
|
|
2
|
+
class Weekly
|
|
3
|
+
MINUTE_VALUES = (0..59)
|
|
4
|
+
HOUR_VALUES = (0..23)
|
|
5
|
+
# Order matters here - it must match the ordering used by cron (i.e. 0 = Sunday)
|
|
6
|
+
DAY_VALUES = %i(sunday monday tuesday wednesday thursday friday saturday)
|
|
7
|
+
|
|
8
|
+
def initialize(day: nil, hour: nil, min: 0)
|
|
9
|
+
@day = day
|
|
10
|
+
@hour = hour
|
|
11
|
+
@min = min
|
|
12
|
+
verify!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_s
|
|
16
|
+
"#{min} #{hour} * * #{day_index}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :day, :hour, :min
|
|
22
|
+
|
|
23
|
+
def day_index
|
|
24
|
+
return day if day.is_a?(Fixnum)
|
|
25
|
+
DAY_VALUES.index(day)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def verify!
|
|
29
|
+
unless MINUTE_VALUES.include?(min)
|
|
30
|
+
raise ArgumentError, "min must be between 0 and 59"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
unless HOUR_VALUES.include?(hour)
|
|
34
|
+
raise ArgumentError, "hour must be between 0 and 23"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
return if DAY_VALUES.include?(day) || (day.is_a?(Fixnum) && DAY_VALUES[day])
|
|
38
|
+
raise ArgumentError, "day must be between 0 and 6, or a symbol day name"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/tab_keeper.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "tab_keeper/version"
|
|
2
|
+
|
|
3
|
+
module TabKeeper
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
require "tab_keeper/daily"
|
|
7
|
+
require "tab_keeper/hourly"
|
|
8
|
+
require "tab_keeper/minutely"
|
|
9
|
+
require "tab_keeper/monthly"
|
|
10
|
+
require "tab_keeper/weekly"
|
|
11
|
+
|
|
12
|
+
require "tab_keeper/job_list"
|
|
13
|
+
|
|
14
|
+
require "tab_keeper/log_redirection"
|
|
15
|
+
require "tab_keeper/login_shell"
|
|
16
|
+
require "tab_keeper/rails_runner"
|
|
17
|
+
|
|
18
|
+
require "tab_keeper/generator"
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
|
3
|
+
require "tab_keeper"
|
|
4
|
+
|
|
5
|
+
RSpec.configure do |config|
|
|
6
|
+
config.expect_with :rspec do |expectations|
|
|
7
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
config.mock_with :rspec do |mocks|
|
|
11
|
+
mocks.verify_partial_doubles = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
config.example_status_persistence_file_path = ".rspec_log"
|
|
15
|
+
|
|
16
|
+
config.disable_monkey_patching!
|
|
17
|
+
|
|
18
|
+
config.warnings = true
|
|
19
|
+
|
|
20
|
+
config.order = :random
|
|
21
|
+
|
|
22
|
+
Kernel.srand config.seed
|
|
23
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::Daily do
|
|
2
|
+
subject { described_class.new(hour: hour, min: min).to_s }
|
|
3
|
+
|
|
4
|
+
context "midnight" do
|
|
5
|
+
let(:hour) { 0 }
|
|
6
|
+
let(:min) { 0 }
|
|
7
|
+
it { is_expected.to eq("0 0 * * *") }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context "7:30am" do
|
|
11
|
+
let(:hour) { 7 }
|
|
12
|
+
let(:min) { 30 }
|
|
13
|
+
it { is_expected.to eq("30 7 * * *") }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "without min supplied" do
|
|
17
|
+
let(:hour) { 12 }
|
|
18
|
+
let(:min) { nil }
|
|
19
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "without hour supplied" do
|
|
23
|
+
let(:hour) { nil }
|
|
24
|
+
let(:min) { 2 }
|
|
25
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "with a weird hour" do
|
|
29
|
+
let(:hour) { 24 }
|
|
30
|
+
let(:min) { 0 }
|
|
31
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "with a weird minute" do
|
|
35
|
+
let(:hour) { 12 }
|
|
36
|
+
let(:min) { -1 }
|
|
37
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::Generator do
|
|
2
|
+
subject { described_class.new(*pipeline).generate(job_list, **options) }
|
|
3
|
+
let(:job_list) do
|
|
4
|
+
TabKeeper::JobList.new do |tab|
|
|
5
|
+
tab.minutely("FrequentJob.run", every: 5)
|
|
6
|
+
tab.daily("MidnightJob.run", hour: 0)
|
|
7
|
+
tab.daily("LunchtimeJob.run", hour: 12, min: 30)
|
|
8
|
+
tab.weekly("HappyMondayJob.run", day: :monday, hour: 7, min: 30)
|
|
9
|
+
tab.monthly("PaydayJob.run", day: 25, hour: 16, min: 15)
|
|
10
|
+
end.to_a
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
let(:pipeline) { [TabKeeper::RailsRunner, TabKeeper::LoginShell] }
|
|
14
|
+
let(:options) do
|
|
15
|
+
{
|
|
16
|
+
code_directory: '/path/to/code',
|
|
17
|
+
rails_env: :production
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it do
|
|
22
|
+
is_expected.to eq <<-CRONTAB
|
|
23
|
+
*/5 * * * * /bin/bash -l -c 'cd /path/to/code && bin/rails runner -e production '\\''FrequentJob.run'\\'''
|
|
24
|
+
|
|
25
|
+
0 0 * * * /bin/bash -l -c 'cd /path/to/code && bin/rails runner -e production '\\''MidnightJob.run'\\'''
|
|
26
|
+
|
|
27
|
+
30 12 * * * /bin/bash -l -c 'cd /path/to/code && bin/rails runner -e production '\\''LunchtimeJob.run'\\'''
|
|
28
|
+
|
|
29
|
+
30 7 * * 1 /bin/bash -l -c 'cd /path/to/code && bin/rails runner -e production '\\''HappyMondayJob.run'\\'''
|
|
30
|
+
|
|
31
|
+
15 16 25 * * /bin/bash -l -c 'cd /path/to/code && bin/rails runner -e production '\\''PaydayJob.run'\\'''
|
|
32
|
+
CRONTAB
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::Hourly do
|
|
2
|
+
subject { described_class.new(every: every, only: only, min: min).to_s }
|
|
3
|
+
let(:every) { nil }
|
|
4
|
+
let(:only) { nil }
|
|
5
|
+
|
|
6
|
+
context "every hour" do
|
|
7
|
+
let(:min) { 0 }
|
|
8
|
+
it { is_expected.to eq("0 * * * *") }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context "every half-past" do
|
|
12
|
+
let(:min) { 30 }
|
|
13
|
+
it { is_expected.to eq("30 * * * *") }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "every 3rd hour" do
|
|
17
|
+
let(:min) { 0 }
|
|
18
|
+
let(:every) { 3 }
|
|
19
|
+
it { is_expected.to eq("0 */3 * * *") }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "on specified hours" do
|
|
23
|
+
let(:min) { 15 }
|
|
24
|
+
let(:only) { [3, 9, 17] }
|
|
25
|
+
it { is_expected.to eq("15 3,9,17 * * *") }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "without the minute" do
|
|
29
|
+
let(:min) { nil }
|
|
30
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "with every and only specified" do
|
|
34
|
+
let(:min) { 0 }
|
|
35
|
+
let(:every) { 3 }
|
|
36
|
+
let(:only) { [1, 2, 3] }
|
|
37
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "with only as a single hour" do
|
|
41
|
+
let(:only) { 12 }
|
|
42
|
+
let(:min) { 0 }
|
|
43
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::JobList do
|
|
2
|
+
subject(:instance) { described_class.new }
|
|
3
|
+
|
|
4
|
+
it "yields itself" do
|
|
5
|
+
yielded = nil
|
|
6
|
+
returned = described_class.new { |tab| yielded = tab }
|
|
7
|
+
expect(yielded).to be(returned)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "#add" do
|
|
11
|
+
pending "rejects invalid timers" do
|
|
12
|
+
expect { instance.add("thing", "* *-2 50 * -1") }.to raise_error(ArgumentError)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "#daily" do
|
|
17
|
+
it "passes options through to Daily.new" do
|
|
18
|
+
expect(TabKeeper::Daily).to receive(:new).with(hour: 5).and_call_original
|
|
19
|
+
instance.daily("thing", hour: 5)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "#hourly" do
|
|
24
|
+
it "passes options through to Hourly.new" do
|
|
25
|
+
expect(TabKeeper::Hourly).to receive(:new).with(min: 5).and_call_original
|
|
26
|
+
instance.hourly("thing", min: 5)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#minutely" do
|
|
31
|
+
it "passes options through to Minutely.new" do
|
|
32
|
+
expect(TabKeeper::Minutely).to receive(:new).and_call_original
|
|
33
|
+
instance.minutely("thing")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "#monthly" do
|
|
38
|
+
it "passes options through to Monthly.new" do
|
|
39
|
+
expect(TabKeeper::Monthly).to receive(:new).with(day: 14, hour: 5).and_call_original
|
|
40
|
+
instance.monthly("thing", day: 14, hour: 5)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "#weekly" do
|
|
45
|
+
it "passes options through to Weekly.new" do
|
|
46
|
+
expect(TabKeeper::Weekly).to receive(:new).with(day: :monday, hour: 5).
|
|
47
|
+
and_call_original
|
|
48
|
+
instance.weekly("thing", day: :monday, hour: 5)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::LogRedirection do
|
|
2
|
+
let(:instance) do
|
|
3
|
+
described_class.new("scripts/thing",
|
|
4
|
+
job: "scripts/thing",
|
|
5
|
+
job_name_proc: job_name_proc,
|
|
6
|
+
timing: timing,
|
|
7
|
+
log_directory: "/path/to/logs",
|
|
8
|
+
include_date_in_log_name: include_date_in_log_name,
|
|
9
|
+
error_suffix: error_suffix)
|
|
10
|
+
end
|
|
11
|
+
subject { instance.to_s }
|
|
12
|
+
let(:job_name_proc) { ->(job) { job } }
|
|
13
|
+
let(:error_suffix) { nil }
|
|
14
|
+
let(:include_date_in_log_name) { true }
|
|
15
|
+
|
|
16
|
+
context "without date in the log file name" do
|
|
17
|
+
let(:include_date_in_log_name) { false }
|
|
18
|
+
let(:timing) { "* * * * *" }
|
|
19
|
+
it { is_expected.to eq("scripts/thing >> /path/to/logs/thing.log 2>&1") }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "with a separate error log" do
|
|
23
|
+
let(:include_date_in_log_name) { false }
|
|
24
|
+
let(:error_suffix) { "wah" }
|
|
25
|
+
let(:timing) { "* * * * *" }
|
|
26
|
+
it do
|
|
27
|
+
is_expected.to eq("scripts/thing >> /path/to/logs/thing.log " \
|
|
28
|
+
"2>> /path/to/logs/thing.wah.log")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "with minutely timing" do
|
|
33
|
+
let(:timing) { TabKeeper::Minutely.new(every: 5).to_s }
|
|
34
|
+
it do
|
|
35
|
+
is_expected.to eq(
|
|
36
|
+
"scripts/thing >> /path/to/logs/thing_" \
|
|
37
|
+
"`date +\\%Y-\\%m-\\%d-\\%H-\\%m-\\%s`.log 2>&1")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "with hourly timing" do
|
|
42
|
+
let(:timing) { TabKeeper::Hourly.new(every: 2).to_s }
|
|
43
|
+
it do
|
|
44
|
+
is_expected.to eq(
|
|
45
|
+
"scripts/thing >> /path/to/logs/thing_" \
|
|
46
|
+
"`date +\\%Y-\\%m-\\%d-\\%H-\\%m`.log 2>&1")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "with daily timing" do
|
|
51
|
+
let(:timing) { TabKeeper::Daily.new(hour: 14).to_s }
|
|
52
|
+
it do
|
|
53
|
+
is_expected.to eq(
|
|
54
|
+
"scripts/thing >> /path/to/logs/thing_" \
|
|
55
|
+
"`date +\\%Y-\\%m-\\%d`.log 2>&1")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context "with a separate error file" do
|
|
59
|
+
let(:error_suffix) { "error" }
|
|
60
|
+
it do
|
|
61
|
+
is_expected.to eq(
|
|
62
|
+
"scripts/thing " \
|
|
63
|
+
">> /path/to/logs/thing_`date +\\%Y-\\%m-\\%d`.log " \
|
|
64
|
+
"2>> /path/to/logs/thing_`date +\\%Y-\\%m-\\%d`.error.log")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "with a fun job name proc" do
|
|
69
|
+
let(:job_name_proc) { -> (job) { "#{job}_sadness" } }
|
|
70
|
+
it do
|
|
71
|
+
is_expected.to eq(
|
|
72
|
+
"scripts/thing >> /path/to/logs/thing_sadness_" \
|
|
73
|
+
"`date +\\%Y-\\%m-\\%d`.log 2>&1")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "with weekly timing" do
|
|
79
|
+
let(:timing) { TabKeeper::Weekly.new(hour: 14, day: :tuesday).to_s }
|
|
80
|
+
it do
|
|
81
|
+
is_expected.to eq(
|
|
82
|
+
"scripts/thing >> /path/to/logs/thing_" \
|
|
83
|
+
"`date +\\%Y-\\%m-\\%d`.log 2>&1")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "with monthly timing" do
|
|
88
|
+
let(:timing) { TabKeeper::Monthly.new(day: 5, hour: 12).to_s }
|
|
89
|
+
it do
|
|
90
|
+
is_expected.to eq(
|
|
91
|
+
"scripts/thing >> /path/to/logs/thing_" \
|
|
92
|
+
"`date +\\%Y-\\%m`.log 2>&1")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::LoginShell do
|
|
2
|
+
subject { described_class.new(previous, job: job, code_directory: code_directory).to_s }
|
|
3
|
+
let(:job) { "bin/some_script" }
|
|
4
|
+
let(:code_directory) { "/path/to/code" }
|
|
5
|
+
let(:previous) { nil }
|
|
6
|
+
|
|
7
|
+
it { is_expected.to eq("/bin/bash -l -c 'cd /path/to/code && bin/some_script'") }
|
|
8
|
+
|
|
9
|
+
context "with a previous command" do
|
|
10
|
+
let(:previous) { "SOMEVAR=value bin/some_script" }
|
|
11
|
+
it do
|
|
12
|
+
is_expected.
|
|
13
|
+
to eq("/bin/bash -l -c 'cd /path/to/code && SOMEVAR=value bin/some_script'")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "when the job needs escaping" do
|
|
18
|
+
let(:job) { "bin/run 'MyJob.run('\\''arg'\\'')'" }
|
|
19
|
+
it do
|
|
20
|
+
is_expected.to eq(
|
|
21
|
+
"/bin/bash -l -c 'cd /path/to/code && " \
|
|
22
|
+
"bin/run '\\''MyJob.run('\\''\\'\\'''\\''arg'\\''\\'\\'''\\'')'\\'''")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::Minutely do
|
|
2
|
+
subject { described_class.new(every: every).to_s }
|
|
3
|
+
|
|
4
|
+
context "5 minutes" do
|
|
5
|
+
let(:every) { 5 }
|
|
6
|
+
it { is_expected.to eq("*/5 * * * *") }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
context "every minute" do
|
|
10
|
+
let(:every) { nil }
|
|
11
|
+
it { is_expected.to eq("* * * * *") }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "every 30 seconds" do
|
|
15
|
+
let(:every) { 0.5 }
|
|
16
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::Monthly do
|
|
2
|
+
subject { described_class.new(day: day, hour: hour, min: min).to_s }
|
|
3
|
+
let(:day) { 1 }
|
|
4
|
+
let(:hour) { 13 }
|
|
5
|
+
let(:min) { 0 }
|
|
6
|
+
|
|
7
|
+
context "on the first of the month" do
|
|
8
|
+
let(:day) { 1 }
|
|
9
|
+
it { is_expected.to eq("0 13 1 * *") }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context "on the 15th of the month" do
|
|
13
|
+
let(:day) { 15 }
|
|
14
|
+
it { is_expected.to eq("0 13 15 * *") }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "without the minute" do
|
|
18
|
+
let(:min) { nil }
|
|
19
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "without the hour" do
|
|
23
|
+
let(:hour) { nil }
|
|
24
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "without the day" do
|
|
28
|
+
let(:day) { nil }
|
|
29
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "with a weird minute" do
|
|
33
|
+
let(:min) { 60 }
|
|
34
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "with a weird hour" do
|
|
38
|
+
let(:hour) { -1 }
|
|
39
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "with a weird day" do
|
|
43
|
+
let(:day) { 29 }
|
|
44
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "with a symbol day" do
|
|
48
|
+
let(:day) { :thursday }
|
|
49
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::RailsRunner do
|
|
2
|
+
subject { described_class.new(previous, job: "MyJob.run", rails_env: rails_env).to_s }
|
|
3
|
+
|
|
4
|
+
let(:previous) { "MyJob.run" }
|
|
5
|
+
let(:rails_env) { :staging }
|
|
6
|
+
|
|
7
|
+
it { is_expected.to eq("bin/rails runner -e staging 'MyJob.run'") }
|
|
8
|
+
|
|
9
|
+
context "when there are 's to escape" do
|
|
10
|
+
let(:previous) { "MyJob.run('arg')" }
|
|
11
|
+
it { is_expected.to eq("bin/rails runner -e staging 'MyJob.run('\\''arg'\\'')'") }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "when this is the first transformer in the pipeline" do
|
|
15
|
+
let(:previous) { nil }
|
|
16
|
+
it { is_expected.to eq("bin/rails runner -e staging 'MyJob.run'") }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "without a rails_env set" do
|
|
20
|
+
let(:rails_env) { nil }
|
|
21
|
+
it { is_expected.to eq("bin/rails runner 'MyJob.run'") }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
RSpec.describe TabKeeper::Weekly do
|
|
2
|
+
subject { described_class.new(day: day, hour: hour, min: min).to_s }
|
|
3
|
+
let(:day) { :wednesday }
|
|
4
|
+
let(:hour) { 13 }
|
|
5
|
+
let(:min) { 0 }
|
|
6
|
+
|
|
7
|
+
context "every Thursday" do
|
|
8
|
+
let(:day) { :thursday }
|
|
9
|
+
it { is_expected.to eq("0 13 * * 4") }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context "every 3rd day of the week" do
|
|
13
|
+
let(:day) { 3 }
|
|
14
|
+
it { is_expected.to eq("0 13 * * 3") }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "without the minute" do
|
|
18
|
+
let(:min) { nil }
|
|
19
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "without the hour" do
|
|
23
|
+
let(:hour) { nil }
|
|
24
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "without the day" do
|
|
28
|
+
let(:day) { nil }
|
|
29
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "with a weird minute" do
|
|
33
|
+
let(:min) { 60 }
|
|
34
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "with a weird hour" do
|
|
38
|
+
let(:hour) { -1 }
|
|
39
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "with a weird day" do
|
|
43
|
+
let(:day) { 8 }
|
|
44
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "with a string day" do
|
|
48
|
+
let(:day) { "thursday" }
|
|
49
|
+
specify { expect { subject }.to raise_error(ArgumentError) }
|
|
50
|
+
end
|
|
51
|
+
end
|
data/tab_keeper.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'tab_keeper/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "tab_keeper"
|
|
8
|
+
spec.version = TabKeeper::VERSION
|
|
9
|
+
spec.authors = ["Isaac Seymour"]
|
|
10
|
+
spec.email = ["i.seymour@oxon.org"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{Manage crons}
|
|
13
|
+
spec.homepage = "https://github.com/isaacseymour/tab_keeper"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.bindir = "exe"
|
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
|
23
|
+
spec.add_development_dependency "rspec", "~> 3"
|
|
24
|
+
spec.add_development_dependency "pry", "~> 0"
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tab_keeper
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Isaac Seymour
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-10-20 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.10'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.10'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: pry
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
description:
|
|
56
|
+
email:
|
|
57
|
+
- i.seymour@oxon.org
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".gitignore"
|
|
63
|
+
- ".rspec"
|
|
64
|
+
- ".travis.yml"
|
|
65
|
+
- Gemfile
|
|
66
|
+
- LICENSE.txt
|
|
67
|
+
- README.md
|
|
68
|
+
- bin/console
|
|
69
|
+
- lib/tab_keeper.rb
|
|
70
|
+
- lib/tab_keeper/daily.rb
|
|
71
|
+
- lib/tab_keeper/generator.rb
|
|
72
|
+
- lib/tab_keeper/hourly.rb
|
|
73
|
+
- lib/tab_keeper/job_list.rb
|
|
74
|
+
- lib/tab_keeper/log_redirection.rb
|
|
75
|
+
- lib/tab_keeper/login_shell.rb
|
|
76
|
+
- lib/tab_keeper/minutely.rb
|
|
77
|
+
- lib/tab_keeper/monthly.rb
|
|
78
|
+
- lib/tab_keeper/rails_runner.rb
|
|
79
|
+
- lib/tab_keeper/version.rb
|
|
80
|
+
- lib/tab_keeper/weekly.rb
|
|
81
|
+
- spec/spec_helper.rb
|
|
82
|
+
- spec/tab_keeper/daily_spec.rb
|
|
83
|
+
- spec/tab_keeper/generator_spec.rb
|
|
84
|
+
- spec/tab_keeper/hourly_spec.rb
|
|
85
|
+
- spec/tab_keeper/job_list_spec.rb
|
|
86
|
+
- spec/tab_keeper/log_redirection_spec.rb
|
|
87
|
+
- spec/tab_keeper/login_shell_spec.rb
|
|
88
|
+
- spec/tab_keeper/minutely_spec.rb
|
|
89
|
+
- spec/tab_keeper/monthly_spec.rb
|
|
90
|
+
- spec/tab_keeper/rails_runner_spec.rb
|
|
91
|
+
- spec/tab_keeper/weekly_spec.rb
|
|
92
|
+
- tab_keeper.gemspec
|
|
93
|
+
homepage: https://github.com/isaacseymour/tab_keeper
|
|
94
|
+
licenses:
|
|
95
|
+
- MIT
|
|
96
|
+
metadata: {}
|
|
97
|
+
post_install_message:
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
require_paths:
|
|
100
|
+
- lib
|
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
requirements: []
|
|
112
|
+
rubyforge_project:
|
|
113
|
+
rubygems_version: 2.4.5.1
|
|
114
|
+
signing_key:
|
|
115
|
+
specification_version: 4
|
|
116
|
+
summary: Manage crons
|
|
117
|
+
test_files:
|
|
118
|
+
- spec/spec_helper.rb
|
|
119
|
+
- spec/tab_keeper/daily_spec.rb
|
|
120
|
+
- spec/tab_keeper/generator_spec.rb
|
|
121
|
+
- spec/tab_keeper/hourly_spec.rb
|
|
122
|
+
- spec/tab_keeper/job_list_spec.rb
|
|
123
|
+
- spec/tab_keeper/log_redirection_spec.rb
|
|
124
|
+
- spec/tab_keeper/login_shell_spec.rb
|
|
125
|
+
- spec/tab_keeper/minutely_spec.rb
|
|
126
|
+
- spec/tab_keeper/monthly_spec.rb
|
|
127
|
+
- spec/tab_keeper/rails_runner_spec.rb
|
|
128
|
+
- spec/tab_keeper/weekly_spec.rb
|