colonel 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rvmrc +5 -0
- data/.yardopts +7 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +66 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +4 -0
- data/bin/colonel +12 -0
- data/colonel.gemspec +26 -0
- data/lib/colonel.rb +18 -0
- data/lib/colonel/array.rb +9 -0
- data/lib/colonel/builder.rb +55 -0
- data/lib/colonel/crontab.rb +73 -0
- data/lib/colonel/job.rb +31 -0
- data/lib/colonel/parser.rb +248 -0
- data/lib/colonel/server.rb +74 -0
- data/lib/colonel/server/assets/coffeescript/application.coffee +64 -0
- data/lib/colonel/server/coffee_engine.rb +7 -0
- data/lib/colonel/server/helpers/roots_helper.rb +25 -0
- data/lib/colonel/server/helpers/views_helper.rb +32 -0
- data/lib/colonel/server/public/images/glyphicons-halflings-white.png +0 -0
- data/lib/colonel/server/public/images/glyphicons-halflings.png +0 -0
- data/lib/colonel/server/public/javascripts/bootstrap.js +2268 -0
- data/lib/colonel/server/public/javascripts/bootstrap.min.js +6 -0
- data/lib/colonel/server/public/javascripts/jquery-1.9.1.min.js +5 -0
- data/lib/colonel/server/public/stylesheets/application.css +42 -0
- data/lib/colonel/server/public/stylesheets/bootstrap-responsive.css +1109 -0
- data/lib/colonel/server/public/stylesheets/bootstrap-responsive.min.css +9 -0
- data/lib/colonel/server/public/stylesheets/bootstrap.css +6158 -0
- data/lib/colonel/server/public/stylesheets/bootstrap.min.css +9 -0
- data/lib/colonel/server/views/edit.haml +6 -0
- data/lib/colonel/server/views/form.haml +51 -0
- data/lib/colonel/server/views/index.haml +32 -0
- data/lib/colonel/server/views/layout.haml +12 -0
- data/lib/colonel/server/views/new.haml +6 -0
- data/lib/colonel/version.rb +3 -0
- data/pkg/colonel-0.1.1.gem +0 -0
- data/pkg/colonel-0.1.2.gem +0 -0
- data/pkg/colonel-0.1.3.gem +0 -0
- data/pkg/colonel-0.1.gem +0 -0
- data/screenshots/job_deleting.png +0 -0
- data/screenshots/job_editing.png +0 -0
- data/screenshots/jobs_list.png +0 -0
- data/screenshots/new_job_validating.png +0 -0
- metadata +194 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'sinatra'
|
4
|
+
gem 'actionpack'
|
5
|
+
gem 'shotgun'
|
6
|
+
gem 'thin'
|
7
|
+
gem 'sqlite3'
|
8
|
+
gem 'vegas'
|
9
|
+
gem 'rake'
|
10
|
+
|
11
|
+
# sinatra extensions
|
12
|
+
gem 'sinatra-authorization'
|
13
|
+
|
14
|
+
#front-end
|
15
|
+
gem 'haml'
|
16
|
+
gem 'coffee-script'
|
17
|
+
|
18
|
+
# Specify your gem's dependencies in rand.gemspec
|
19
|
+
gemspec
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
colonel (0.1.4)
|
5
|
+
coffee-script
|
6
|
+
haml
|
7
|
+
sinatra (>= 0.9.2)
|
8
|
+
vegas (~> 0.1.2)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
actionpack (2.3.2)
|
14
|
+
activesupport (= 2.3.2)
|
15
|
+
activesupport (2.3.2)
|
16
|
+
coffee-script (2.2.0)
|
17
|
+
coffee-script-source
|
18
|
+
execjs
|
19
|
+
coffee-script-source (1.4.0)
|
20
|
+
daemons (1.1.9)
|
21
|
+
eventmachine (1.0.0)
|
22
|
+
execjs (1.4.0)
|
23
|
+
multi_json (~> 1.0)
|
24
|
+
haml (4.0.0)
|
25
|
+
tilt
|
26
|
+
multi_json (1.6.1)
|
27
|
+
rack (1.5.2)
|
28
|
+
rack-protection (1.3.2)
|
29
|
+
rack
|
30
|
+
rake (10.0.3)
|
31
|
+
redcarpet (1.17.2)
|
32
|
+
shotgun (0.9)
|
33
|
+
rack (>= 1.0)
|
34
|
+
sinatra (1.3.4)
|
35
|
+
rack (~> 1.4)
|
36
|
+
rack-protection (~> 1.3)
|
37
|
+
tilt (~> 1.3, >= 1.3.3)
|
38
|
+
sinatra-authorization (1.0.0)
|
39
|
+
sinatra (>= 0.9.1.1)
|
40
|
+
sqlite3 (1.3.7)
|
41
|
+
thin (1.5.0)
|
42
|
+
daemons (>= 1.0.9)
|
43
|
+
eventmachine (>= 0.12.6)
|
44
|
+
rack (>= 1.0.0)
|
45
|
+
tilt (1.3.3)
|
46
|
+
vegas (0.1.11)
|
47
|
+
rack (>= 1.0.0)
|
48
|
+
yard (0.7.5)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
actionpack
|
55
|
+
coffee-script
|
56
|
+
colonel!
|
57
|
+
haml
|
58
|
+
rake
|
59
|
+
redcarpet (~> 1.17)
|
60
|
+
shotgun
|
61
|
+
sinatra
|
62
|
+
sinatra-authorization
|
63
|
+
sqlite3
|
64
|
+
thin
|
65
|
+
vegas
|
66
|
+
yard (~> 0.7.5)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 santaux
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
Colonel
|
2
|
+
=======
|
3
|
+
|
4
|
+
Gem for cron jobs editing by web GUI. Web GUI is based on Sinatra, Vegas
|
5
|
+
and Twitter Bootstrap.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Add colonel to your Gemfile:
|
10
|
+
|
11
|
+
gem 'colonel', :git => 'git@github.com:santaux/colonel.git'
|
12
|
+
|
13
|
+
Install it:
|
14
|
+
|
15
|
+
bundle
|
16
|
+
|
17
|
+
Run application into your terminal and use:
|
18
|
+
|
19
|
+
bundle exec colonel
|
20
|
+
|
21
|
+
Stop application when you want:
|
22
|
+
|
23
|
+
bundle exec colonel -K
|
24
|
+
|
25
|
+
Or use it directly through your irb/rails console:
|
26
|
+
|
27
|
+
require 'colonel'
|
28
|
+
builder = Colonel::Builder.new
|
29
|
+
jobs = builder.parse
|
30
|
+
|
31
|
+
## Screenshots
|
32
|
+

|
33
|
+

|
34
|
+

|
35
|
+

|
data/Rakefile
ADDED
data/bin/colonel
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
begin
|
5
|
+
require 'vegas'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'vegas'
|
9
|
+
end
|
10
|
+
require 'colonel/server'
|
11
|
+
|
12
|
+
Vegas::Runner.new(Colonel::Server, 'colonel')
|
data/colonel.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'colonel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "colonel"
|
8
|
+
gem.version = Colonel::VERSION
|
9
|
+
gem.authors = ["santaux"]
|
10
|
+
gem.email = ["santaux@gmail.com"]
|
11
|
+
gem.description = %q{Gem for managing cron jobs with web GUI}
|
12
|
+
gem.summary = %q{Gem for managing cron jobs with web GUI}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "vegas", "~> 0.1.2"
|
21
|
+
gem.add_dependency "sinatra", ">= 0.9.2"
|
22
|
+
gem.add_dependency "haml"
|
23
|
+
gem.add_dependency "coffee-script"
|
24
|
+
gem.add_development_dependency "redcarpet", "~> 1.17"
|
25
|
+
gem.add_development_dependency "yard", "~> 0.7.5"
|
26
|
+
end
|
data/lib/colonel.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "colonel/version"
|
2
|
+
require 'colonel/array'
|
3
|
+
|
4
|
+
# TODO: Rewrite views with helpers
|
5
|
+
# TODO: Add ActiveModel methods to classes
|
6
|
+
# TODO: Add ability to import/export crontab files
|
7
|
+
# TODO: Move it to gem as rails engine
|
8
|
+
# TODO: Make import/output of templates
|
9
|
+
module Colonel
|
10
|
+
require 'colonel/builder'
|
11
|
+
require 'colonel/crontab'
|
12
|
+
require 'colonel/job'
|
13
|
+
require 'colonel/parser'
|
14
|
+
end
|
15
|
+
|
16
|
+
class Array
|
17
|
+
include Colonel::Array
|
18
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# example:
|
2
|
+
# builder = Colonel::Builder.new
|
3
|
+
# jobs = builder.parse
|
4
|
+
module Colonel
|
5
|
+
class Builder
|
6
|
+
def initialize(opts={})
|
7
|
+
Job.clear_amount
|
8
|
+
@all_flag = opts[:all_flag].nil? ? true : opts[:all_flag]
|
9
|
+
end
|
10
|
+
|
11
|
+
def user
|
12
|
+
@_user ||= `whoami`.chomp
|
13
|
+
end
|
14
|
+
|
15
|
+
def crontab
|
16
|
+
@_crontab ||= Crontab.new(user)
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse
|
20
|
+
@jobs ||= []
|
21
|
+
crontab.reject_useless.each do |line|
|
22
|
+
@jobs << Parser.new(line, :all_flag => @all_flag).execute
|
23
|
+
end
|
24
|
+
@jobs
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_crontab
|
28
|
+
crontab.update(@jobs)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_job(id)
|
32
|
+
@jobs.select { |j| j.id == id.to_i }.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_job_index(id)
|
36
|
+
@jobs.each_with_index { |j,i| return i if j.id == id.to_i }
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: Refactor it! To complicated!
|
40
|
+
def update_job(opts={})
|
41
|
+
index = get_job_index(opts[:id])
|
42
|
+
job = find_job(opts[:id])
|
43
|
+
@jobs[index] = job.update(opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_job(opts={})
|
47
|
+
@jobs << Job.new( Parser::Schedule.new(opts[:schedule]), Parser::Command.new(opts[:command]))
|
48
|
+
end
|
49
|
+
|
50
|
+
def destroy_job(id)
|
51
|
+
index = get_job_index(id)
|
52
|
+
@jobs.delete_at index
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Colonel
|
4
|
+
class Crontab
|
5
|
+
def initialize(user)
|
6
|
+
@user = user
|
7
|
+
end
|
8
|
+
|
9
|
+
def read
|
10
|
+
@tab = `crontab -l`
|
11
|
+
end
|
12
|
+
|
13
|
+
def lines
|
14
|
+
@_lines ||= read.split(/\n/)
|
15
|
+
end
|
16
|
+
|
17
|
+
def reject_useless
|
18
|
+
lines.reject { |line| reject_if_wrong(line) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def reject_if_wrong(line)
|
22
|
+
!line.match /^(\d+|\*)/
|
23
|
+
end
|
24
|
+
|
25
|
+
def update(jobs)
|
26
|
+
update_tab(jobs)
|
27
|
+
write
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_tab(jobs)
|
31
|
+
tab_lines = jobs.map do |j|
|
32
|
+
[
|
33
|
+
TimeCreator.new(
|
34
|
+
minutes: j.schedule.minutes,
|
35
|
+
hours: j.schedule.hours,
|
36
|
+
days: j.schedule.days,
|
37
|
+
months: j.schedule.months,
|
38
|
+
weekdays: j.schedule.weekdays,
|
39
|
+
).generate,
|
40
|
+
j.command.get
|
41
|
+
].join("\t\t\t")
|
42
|
+
end
|
43
|
+
tab_lines
|
44
|
+
@tab = "# --- Updated with Colonel ---\n" + tab_lines.join("\n") + "\n# --- \n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_to_temp
|
48
|
+
@tempfile = Tempfile.new("#{@user}_crontab_temp")
|
49
|
+
@tempfile.write(@tab)
|
50
|
+
@tempfile.close
|
51
|
+
end
|
52
|
+
|
53
|
+
def write
|
54
|
+
write_to_temp
|
55
|
+
`crontab #{@tempfile.path}`
|
56
|
+
@tempfile.delete
|
57
|
+
end
|
58
|
+
|
59
|
+
class TimeCreator
|
60
|
+
def initialize(opts={})
|
61
|
+
@minutes = opts[:minutes]
|
62
|
+
@hours = opts[:hours]
|
63
|
+
@days = opts[:days]
|
64
|
+
@months = opts[:months]
|
65
|
+
@weekdays = opts[:weekdays]
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate
|
69
|
+
[@minutes, @hours, @days, @months, @weekdays].map { |t| t.is_a?(Array) ? t.join(",") : t }.join(" ")#.join("\t")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/colonel/job.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Colonel
|
2
|
+
class Job
|
3
|
+
|
4
|
+
attr_reader :id, :schedule, :command
|
5
|
+
|
6
|
+
def initialize(schedule, command)
|
7
|
+
@schedule = schedule
|
8
|
+
@command = command
|
9
|
+
|
10
|
+
increase_amount
|
11
|
+
@id = @@amount
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.clear_amount
|
15
|
+
@@amount = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(opts={})
|
19
|
+
@schedule = Parser::Schedule.new(opts[:schedule])
|
20
|
+
@command = Parser::Command.new(opts[:command])
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def increase_amount
|
28
|
+
@@amount = @@amount ? @@amount+1 : 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
module Colonel
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
SCHEDULE_SIZE = 5
|
5
|
+
PERIOD = /@hourly|@daily|@weekly|@monthly|@yearly/
|
6
|
+
|
7
|
+
def initialize(text, opts={})
|
8
|
+
@all_flag = opts[:all_flag].nil? ? true : opts[:all_flag]
|
9
|
+
filtered_text = replace_periods(text)
|
10
|
+
@tokens = scan_tokens(filtered_text)
|
11
|
+
end
|
12
|
+
|
13
|
+
def scan_tokens(text)
|
14
|
+
text.scan(/[\S]+/)
|
15
|
+
end
|
16
|
+
|
17
|
+
def replace_periods(text)
|
18
|
+
period = text.scan(PERIOD).first
|
19
|
+
if period
|
20
|
+
time_string = case period
|
21
|
+
when "@hourly"
|
22
|
+
"0 * * * *"
|
23
|
+
when "@daily"
|
24
|
+
"0 0 * * *"
|
25
|
+
when "@weekly"
|
26
|
+
"0 0 * * 0"
|
27
|
+
when "@monthly"
|
28
|
+
"0 0 1 * *"
|
29
|
+
when "@yearly"
|
30
|
+
"0 0 1 1 *"
|
31
|
+
end
|
32
|
+
text.gsub(/^(#{PERIOD})/, time_string)
|
33
|
+
else
|
34
|
+
text
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def schedule_tokens
|
39
|
+
@tokens.first(SCHEDULE_SIZE)
|
40
|
+
end
|
41
|
+
|
42
|
+
def command_tokens
|
43
|
+
@tokens.last(@tokens.size - SCHEDULE_SIZE)
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
Job.new Schedule.new(schedule_tokens: schedule_tokens, all_flag: @all_flag), Command.new(command_tokens)
|
48
|
+
end
|
49
|
+
|
50
|
+
class Command
|
51
|
+
def initialize(data)
|
52
|
+
if data.is_a?(Array)
|
53
|
+
@command_tokens = data
|
54
|
+
else
|
55
|
+
@_string = data
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def get
|
60
|
+
@_string ||= @command_tokens.join(' ')
|
61
|
+
end
|
62
|
+
|
63
|
+
def exist?
|
64
|
+
@_string.size > 0
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Class to store parsed time fields values
|
69
|
+
class Schedule
|
70
|
+
|
71
|
+
def initialize(opts={})
|
72
|
+
@schedule_tokens = opts[:schedule_tokens]
|
73
|
+
@all_flag = opts[:all_flag].nil? ? true : opts[:all_flag]
|
74
|
+
|
75
|
+
@_minutes = opts[:minutes]
|
76
|
+
@_hours = opts[:hours]
|
77
|
+
@_days = opts[:days]
|
78
|
+
@_months = opts[:months]
|
79
|
+
@_weekdays = opts[:weekdays]
|
80
|
+
end
|
81
|
+
|
82
|
+
def minutes
|
83
|
+
@_minutes ||= TimeParser.new(@schedule_tokens.first, :minute, @all_flag).result
|
84
|
+
end
|
85
|
+
|
86
|
+
def hours
|
87
|
+
@_hours ||= TimeParser.new(@schedule_tokens.second, :hour, @all_flag).result
|
88
|
+
end
|
89
|
+
|
90
|
+
def days
|
91
|
+
@_days ||= TimeParser.new(@schedule_tokens.third, :day, @all_flag).result
|
92
|
+
end
|
93
|
+
|
94
|
+
def months
|
95
|
+
@_months ||= TimeParser.new(@schedule_tokens.fourth, :month, @all_flag).result
|
96
|
+
end
|
97
|
+
|
98
|
+
def weekdays
|
99
|
+
@_weekdays ||= TimeParser.new(@schedule_tokens.fifth, :weekday, @all_flag).result
|
100
|
+
end
|
101
|
+
|
102
|
+
%w[minutes hours days months weekdays].each do |method|
|
103
|
+
define_method "#{method}_string" do
|
104
|
+
result = send(method)
|
105
|
+
result.is_a?(Array) ? result.join(', ') : result.to_s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Class to parse time fields from crontab line.
|
111
|
+
class TimeParser
|
112
|
+
DIGIT = /\d{1,2}/
|
113
|
+
ALL = /\*/
|
114
|
+
COMMA = /\,/
|
115
|
+
DASH = /\-/
|
116
|
+
RANGE = /#{DIGIT}#{DASH}#{DIGIT}/
|
117
|
+
DEVIDE = /#{ALL}\/#{DIGIT}|#{RANGE}\/#{DIGIT}/
|
118
|
+
DEVIDER = /\//
|
119
|
+
|
120
|
+
def tokens
|
121
|
+
@tokens
|
122
|
+
end
|
123
|
+
|
124
|
+
# if all_flag is true -> return :all result if all?
|
125
|
+
def initialize(time_expression, time_type, all_flag=true)
|
126
|
+
@time_type = time_type
|
127
|
+
@tokens = time_expression.scan(/#{COMMA}|#{DEVIDE}|#{RANGE}|#{DIGIT}|#{ALL}/)
|
128
|
+
@all_flag = all_flag
|
129
|
+
end
|
130
|
+
|
131
|
+
def ast
|
132
|
+
@_ast ||= expression
|
133
|
+
end
|
134
|
+
|
135
|
+
def result
|
136
|
+
unique = ast.flatten.uniq
|
137
|
+
unique.pop if unique.last.nil? #remove nil element
|
138
|
+
if all?(unique) && @all_flag
|
139
|
+
:all
|
140
|
+
else
|
141
|
+
unique
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def min_time_value
|
146
|
+
@_min_time_value ||= case @time_type
|
147
|
+
when :minute
|
148
|
+
0
|
149
|
+
when :hour
|
150
|
+
0
|
151
|
+
when :day
|
152
|
+
1
|
153
|
+
when :month
|
154
|
+
1
|
155
|
+
when :weekday
|
156
|
+
0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def max_time_value
|
161
|
+
@_max_time_value ||= case @time_type
|
162
|
+
when :minute
|
163
|
+
59
|
164
|
+
when :hour
|
165
|
+
23
|
166
|
+
when :day
|
167
|
+
31
|
168
|
+
when :month
|
169
|
+
12
|
170
|
+
when :weekday
|
171
|
+
6
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def next_token
|
178
|
+
@tokens.shift
|
179
|
+
end
|
180
|
+
|
181
|
+
def next_token_safe
|
182
|
+
@tokens.first
|
183
|
+
end
|
184
|
+
|
185
|
+
def second_token_safe
|
186
|
+
@tokens.second
|
187
|
+
end
|
188
|
+
|
189
|
+
def expression
|
190
|
+
token = next_token
|
191
|
+
|
192
|
+
case token
|
193
|
+
when nil
|
194
|
+
return nil
|
195
|
+
when DEVIDE
|
196
|
+
return [devide(token), expression]
|
197
|
+
when RANGE
|
198
|
+
return [range(token), expression]
|
199
|
+
when DIGIT
|
200
|
+
return [digit(token), expression]
|
201
|
+
when ALL
|
202
|
+
return [all(token), expression]
|
203
|
+
when COMMA
|
204
|
+
return expression
|
205
|
+
else
|
206
|
+
raise "Unknown token: #{token}!"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def digit(token)
|
211
|
+
return token
|
212
|
+
end
|
213
|
+
|
214
|
+
def all(token)
|
215
|
+
return (min_time_value..max_time_value).to_a
|
216
|
+
end
|
217
|
+
|
218
|
+
def devide(token)
|
219
|
+
case token
|
220
|
+
when /#{RANGE}#{DEVIDER}#{DIGIT}/
|
221
|
+
up, bottom = token.split(DEVIDER)
|
222
|
+
range(up).select do |x|
|
223
|
+
x % bottom.to_i == 0
|
224
|
+
end
|
225
|
+
when /#{ALL}#{DEVIDER}#{DIGIT}/
|
226
|
+
up, bottom = token.split(DEVIDER)
|
227
|
+
(min_time_value..max_time_value).select do |x|
|
228
|
+
x % bottom.to_i == 0
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def range(token)
|
234
|
+
min, max = token.split(DASH)
|
235
|
+
(min..max).to_a
|
236
|
+
end
|
237
|
+
|
238
|
+
def correct?(array)
|
239
|
+
array.all? { |el| el <= max_time_value } and array.all? { |el| el >= min_time_value }
|
240
|
+
end
|
241
|
+
|
242
|
+
def all?(array)
|
243
|
+
array.count-1 == max_time_value-min_time_value
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|