scron 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012, Hugh Bien
1
+ Copyright (c) Hugh Bien
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without modification,
data/README.md CHANGED
@@ -49,6 +49,11 @@ failure and scron will attempt to re-run it again in 2 hours.
49
49
  `$HOME/.scronlog` has the stdout, timestamps, and exit status of last
50
50
  scheduled commands.
51
51
 
52
+ TODO
53
+ ====
54
+
55
+ * rename process to scron
56
+
52
57
  License
53
58
  =======
54
59
 
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'optparse'
3
- require File.expand_path('scron', File.dirname(__FILE__))
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
@@ -0,0 +1,5 @@
1
+ require 'date'
2
+ require_relative 'scron/app'
3
+ require_relative 'scron/schedule'
4
+ require_relative 'scron/history'
5
+ require_relative 'scron/version'
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.3
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: 2013-04-28 00:00:00.000000000 Z
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
- - ./scron
45
- homepage: https://github.com/hughbien/scron
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: 1.8.23
45
+ rubygems_version: 2.5.1
66
46
  signing_key:
67
- specification_version: 3
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
@@ -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