scron 1.0.3 → 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE.md +1 -1
- data/README.md +5 -0
- data/{scron → bin/scron} +3 -3
- data/lib/scron.rb +5 -0
- metadata +12 -32
- data/scron.rb +0 -140
- data/scron_test.rb +0 -126
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 586a0c5cedf3700fd66abdb7b24403aca01a4b68
|
4
|
+
data.tar.gz: 95f660518802bab1ddc320e5192dd111cbcca1c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed5492d51a2acd578e9bd791b3d740ea25d96a7b65a5a9e6ebb17cc9211110e0cfa6853cd5cd755c43b063d3c44f1c09c7a5a85350cbea1c3ac8e9397fafd26f
|
7
|
+
data.tar.gz: 35e8f7eadabb093c71a5af3440551efdef569f5c79df527dd06723f8102d498daff481209efd7ff515715ccfe471d97eecbbe17ac6ca8515cf388342f12d50f4
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
data/{scron → bin/scron}
RENAMED
@@ -1,13 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'optparse'
|
3
|
-
|
3
|
+
require_relative '../lib/scron'
|
4
4
|
|
5
5
|
ARGV.options do |o|
|
6
6
|
o.set_summary_indent(' ')
|
7
7
|
o.banner = "Usage: #{File.basename($0)} [OPTION]"
|
8
8
|
o.define_head "Scheduler for laptops/machines which aren't on 24/7"
|
9
|
-
o.on('-e', '--edit', 'edit jobs') { Scron.edit; exit }
|
10
|
-
o.on('-r', '--run', 'run jobs') { Scron.run; exit }
|
9
|
+
o.on('-e', '--edit', 'edit jobs') { Scron::App.edit; exit }
|
10
|
+
o.on('-r', '--run', 'run jobs') { Scron::App.run; exit }
|
11
11
|
o.on('-h', '--help', 'show this help message') { puts o; exit }
|
12
12
|
o.parse!
|
13
13
|
puts o
|
data/lib/scron.rb
ADDED
metadata
CHANGED
@@ -1,32 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.4
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Hugh Bien
|
9
8
|
autorequire:
|
10
|
-
bindir:
|
9
|
+
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: minitest
|
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'
|
11
|
+
date: 2020-04-06 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
30
13
|
description: Run commands at scheduled intervals. If an interval is missed, the command
|
31
14
|
will be run as soon as possible.
|
32
15
|
email:
|
@@ -36,34 +19,31 @@ executables:
|
|
36
19
|
extensions: []
|
37
20
|
extra_rdoc_files: []
|
38
21
|
files:
|
39
|
-
- scron_test.rb
|
40
|
-
- scron.rb
|
41
22
|
- LICENSE.md
|
42
23
|
- README.md
|
43
|
-
- scron
|
44
|
-
-
|
45
|
-
homepage: https://github.com/hughbien/
|
24
|
+
- bin/scron
|
25
|
+
- lib/scron.rb
|
26
|
+
homepage: https://github.com/hughbien/scronrb
|
46
27
|
licenses: []
|
28
|
+
metadata: {}
|
47
29
|
post_install_message:
|
48
30
|
rdoc_options: []
|
49
31
|
require_paths:
|
50
32
|
- lib
|
51
33
|
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
34
|
requirements:
|
54
|
-
- -
|
35
|
+
- - ">="
|
55
36
|
- !ruby/object:Gem::Version
|
56
37
|
version: '0'
|
57
38
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
39
|
requirements:
|
60
|
-
- -
|
40
|
+
- - ">="
|
61
41
|
- !ruby/object:Gem::Version
|
62
42
|
version: 1.3.6
|
63
43
|
requirements: []
|
64
44
|
rubyforge_project:
|
65
|
-
rubygems_version:
|
45
|
+
rubygems_version: 2.5.1
|
66
46
|
signing_key:
|
67
|
-
specification_version:
|
47
|
+
specification_version: 4
|
68
48
|
summary: Scheduler for laptops/machines which aren't on 24/7
|
69
49
|
test_files: []
|
data/scron.rb
DELETED
@@ -1,140 +0,0 @@
|
|
1
|
-
require 'date'
|
2
|
-
|
3
|
-
class Scron
|
4
|
-
VERSION = '1.0.3'
|
5
|
-
SCHEDULE_FILE = "#{ENV['HOME']}/.scron"
|
6
|
-
HISTORY_FILE = "#{ENV['HOME']}/.scrondb"
|
7
|
-
LOG_FILE = "#{ENV['HOME']}/.scronlog"
|
8
|
-
EDITOR = ENV['EDITOR'] || 'vi'
|
9
|
-
attr_reader :history, :schedules
|
10
|
-
|
11
|
-
def initialize(text, history_text)
|
12
|
-
@history = History.new(history_text)
|
13
|
-
@schedules = text.split("\n").
|
14
|
-
reject {|l| l =~ /^\s+$/}.
|
15
|
-
map {|l| Schedule.new(l, @history)}
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.run
|
19
|
-
scron = Scron.new(read(SCHEDULE_FILE), read(HISTORY_FILE))
|
20
|
-
overdue = scron.schedules.select {|s| s.overdue?}
|
21
|
-
nowstr = now.strftime(History::FORMAT)
|
22
|
-
|
23
|
-
logger = File.open(LOG_FILE, "a")
|
24
|
-
logger.puts("=> #{nowstr} running")
|
25
|
-
return if overdue.size == 0
|
26
|
-
|
27
|
-
overdue.each do |schedule|
|
28
|
-
logger.puts("=> #{nowstr} #{schedule.command} (start)")
|
29
|
-
output = safe_cmd(schedule.command)
|
30
|
-
logger.puts("=> #{nowstr} #{schedule.command} (exit=#{$?.to_i})")
|
31
|
-
logger.puts(output) unless output == ''
|
32
|
-
if $?.to_i == 0
|
33
|
-
scron.history.touch(schedule.command)
|
34
|
-
File.open(HISTORY_FILE, "w") {|f| f.puts scron.history.to_s}
|
35
|
-
end
|
36
|
-
end
|
37
|
-
ensure
|
38
|
-
logger.close
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.now
|
42
|
-
@now ||= begin
|
43
|
-
now = DateTime.now
|
44
|
-
now += now.offset
|
45
|
-
now = now.new_offset('+00:00')
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.edit
|
50
|
-
`#{EDITOR} #{SCHEDULE_FILE} < \`tty\` > \`tty\``
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
def self.safe_cmd(command)
|
55
|
-
`#{command}`
|
56
|
-
rescue StandardError => error
|
57
|
-
error.to_s
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.read(filename)
|
61
|
-
File.exist?(filename) ? File.read(filename) : ''
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class Schedule
|
66
|
-
attr_reader :interval, :command
|
67
|
-
WEEKDAYS = {'Mo' => 1, 'Tu' => 2, 'We' => 3, 'Th' => 4, 'Fr' => 5,
|
68
|
-
'Sa' => 6, 'Su' => 7}
|
69
|
-
|
70
|
-
def initialize(line, history)
|
71
|
-
interval, command = line.split(/\s+/, 2)
|
72
|
-
@interval = interval.split(',').map {|i| parse_days(i)}.min
|
73
|
-
@command = command.strip
|
74
|
-
@overdue = history[command].nil? ||
|
75
|
-
(Scron.now - history[command]).to_f > @interval
|
76
|
-
end
|
77
|
-
|
78
|
-
def overdue?
|
79
|
-
!!@overdue
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
def parse_days(interval)
|
84
|
-
now = Scron.now
|
85
|
-
if WEEKDAYS[interval]
|
86
|
-
(now.cwday - WEEKDAYS[interval]) % 7 + 1
|
87
|
-
elsif interval =~ /^\d+(st|nd|rd|th)$/
|
88
|
-
day = interval.to_i
|
89
|
-
delta = now.day >= day ?
|
90
|
-
now.day - day :
|
91
|
-
now - last_month(day)
|
92
|
-
delta.to_i + 1
|
93
|
-
elsif interval =~ /^(\d+)\/(\d+)$/
|
94
|
-
year, month, day = Scron.now.year, $1.to_i, $2.to_i
|
95
|
-
year -= 1 if Scron.now.month < month ||
|
96
|
-
(Scron.now.month == month && Scron.now.day < day)
|
97
|
-
(Scron.now - DateTime.new(year, month, day)).to_i + 1
|
98
|
-
elsif interval =~ /^\d+d$/
|
99
|
-
interval.to_i
|
100
|
-
else
|
101
|
-
raise ArgumentError.new("Unable to parse: #{interval}")
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def last_month(day)
|
106
|
-
last = Scron.now << 1
|
107
|
-
[day, 30, 29, 28].each do |d|
|
108
|
-
date = DateTime.new(last.year, last.month, d) rescue nil
|
109
|
-
return date if date
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
class History
|
115
|
-
FORMAT = '%Y-%m-%d.%H:%M'
|
116
|
-
|
117
|
-
def initialize(text)
|
118
|
-
@history = {}
|
119
|
-
text.split("\n").reject {|l| l =~ /^\s+$/}.each do |line|
|
120
|
-
timestamp, command = line.split(/\s+/, 2)
|
121
|
-
@history[command.strip] = DateTime.parse(timestamp, FORMAT)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def [](command)
|
126
|
-
@history[command]
|
127
|
-
end
|
128
|
-
|
129
|
-
def touch(command)
|
130
|
-
@history[command] = Scron.now
|
131
|
-
end
|
132
|
-
|
133
|
-
def to_s
|
134
|
-
lines = []
|
135
|
-
@history.each do |command, timestamp|
|
136
|
-
lines << "#{timestamp.strftime(FORMAT)} #{command}"
|
137
|
-
end
|
138
|
-
lines.join("\n")
|
139
|
-
end
|
140
|
-
end
|
data/scron_test.rb
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require "#{File.dirname(__FILE__)}/scron"
|
3
|
-
require 'minitest/autorun'
|
4
|
-
|
5
|
-
class ScronTest < MiniTest::Unit::TestCase
|
6
|
-
def setup
|
7
|
-
Scron.instance_variable_set(:@now, DateTime.new(2010, 3, 15))
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class AppTest < ScronTest
|
12
|
-
def test_files
|
13
|
-
assert_equal("#{ENV['HOME']}/.scron", Scron::SCHEDULE_FILE)
|
14
|
-
assert_equal("#{ENV['HOME']}/.scrondb", Scron::HISTORY_FILE)
|
15
|
-
assert_equal("#{ENV['HOME']}/.scronlog", Scron::LOG_FILE)
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_editor
|
19
|
-
assert_includes([ENV['EDITOR'] || 'vi'], Scron::EDITOR)
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_empty
|
23
|
-
assert_equal('', Scron.send(:read, './non-existent-file'))
|
24
|
-
refute_equal('', Scron.send(:read, 'README.md'))
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_no_schedules
|
28
|
-
scron = Scron.new('', '')
|
29
|
-
assert_equal([], scron.schedules)
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_initialize_schedules
|
33
|
-
scron = Scron.new(
|
34
|
-
"30d cmd arg1 arg2\n" +
|
35
|
-
"7d /path/to/script.rb\n" +
|
36
|
-
"1d /path/to/script2.rb",
|
37
|
-
"2100-01-01.01:00 cmd arg1 arg2\n" +
|
38
|
-
"2000-01-01.01:00 /path/to/script.rb")
|
39
|
-
assert_equal(3, scron.schedules.size)
|
40
|
-
refute(scron.schedules[0].overdue?)
|
41
|
-
assert(scron.schedules[1].overdue?)
|
42
|
-
assert(scron.schedules[2].overdue?)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class ScheduleTest < ScronTest
|
47
|
-
def test_parse_day_interval
|
48
|
-
sched = Schedule.new('1d c', History.new(''))
|
49
|
-
assert_equal(1, sched.send(:parse_days, '1d'))
|
50
|
-
assert_equal(30, sched.send(:parse_days, '30d'))
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_parse_day_of_week
|
54
|
-
sched = Schedule.new('1d c', History.new(''))
|
55
|
-
assert_equal(1, sched.send(:parse_days, 'Mo'))
|
56
|
-
assert_equal(7, sched.send(:parse_days, 'Tu'))
|
57
|
-
assert_equal(6, sched.send(:parse_days, 'We'))
|
58
|
-
assert_equal(5, sched.send(:parse_days, 'Th'))
|
59
|
-
assert_equal(4, sched.send(:parse_days, 'Fr'))
|
60
|
-
assert_equal(3, sched.send(:parse_days, 'Sa'))
|
61
|
-
assert_equal(2, sched.send(:parse_days, 'Su'))
|
62
|
-
end
|
63
|
-
|
64
|
-
def test_parse_day_of_month
|
65
|
-
sched = Schedule.new('1d c', History.new(''))
|
66
|
-
assert_equal(15, sched.send(:parse_days, '1st'))
|
67
|
-
assert_equal(1, sched.send(:parse_days, '15th'))
|
68
|
-
assert_equal(21, sched.send(:parse_days, '23rd'))
|
69
|
-
assert_equal(16, sched.send(:parse_days, '31st'))
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_parse_day_of_year
|
73
|
-
sched = Schedule.new('1d c', History.new(''))
|
74
|
-
assert_equal(74, sched.send(:parse_days, '1/1'))
|
75
|
-
assert_equal(1, sched.send(:parse_days, '3/15'))
|
76
|
-
assert_equal(81, sched.send(:parse_days, '12/25'))
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_initialize_command
|
80
|
-
sched = Schedule.new('30d cmd arg1 arg2', History.new(''))
|
81
|
-
assert_equal('cmd arg1 arg2', sched.command)
|
82
|
-
assert_equal(30, sched.interval)
|
83
|
-
assert(sched.overdue?)
|
84
|
-
end
|
85
|
-
|
86
|
-
def test_bad_date
|
87
|
-
sched = Schedule.new('1d c', History.new(''))
|
88
|
-
assert_raises(ArgumentError) { sched.send(:parse_days, '2/31') }
|
89
|
-
assert_raises(ArgumentError) { sched.send(:parse_days, '1') }
|
90
|
-
end
|
91
|
-
|
92
|
-
def test_multiple_intervals
|
93
|
-
assert_equal(1, Schedule.new('1d,2d,3d cmd', History.new('')).interval)
|
94
|
-
assert_equal(2, Schedule.new('Fr,Sa,Su cmd', History.new('')).interval)
|
95
|
-
assert_equal(15, Schedule.new('1st,23rd cmd', History.new('')).interval)
|
96
|
-
end
|
97
|
-
|
98
|
-
def test_overdue_history
|
99
|
-
sched = Schedule.new('30d cmd', History.new('2000-01-01.01:00 cmd'))
|
100
|
-
assert(sched.overdue?)
|
101
|
-
end
|
102
|
-
|
103
|
-
def test_recent_history
|
104
|
-
sched = Schedule.new('30d cmd', History.new('2100-01-01.01:00 cmd'))
|
105
|
-
refute(sched.overdue?)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
class HistoryTest < ScronTest
|
110
|
-
def test_initialize
|
111
|
-
history = History.new('2100-01-01.01:00 cmd arg1 arg2')
|
112
|
-
assert_equal(DateTime.new(2100, 1, 1, 1, 0), history['cmd arg1 arg2'])
|
113
|
-
end
|
114
|
-
|
115
|
-
def test_update_command
|
116
|
-
history = History.new('')
|
117
|
-
history.touch('cmd')
|
118
|
-
assert_kind_of(DateTime, history['cmd'])
|
119
|
-
end
|
120
|
-
|
121
|
-
def test_output
|
122
|
-
history = History.new('')
|
123
|
-
history.touch('cmd')
|
124
|
-
assert_match(/^20\d{2}-\d{2}-\d{2}.\d{2}:\d{2} cmd$/, history.to_s)
|
125
|
-
end
|
126
|
-
end
|