at-random 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rvmrc
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format=documentation
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake'
4
+
5
+ gemspec
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # at-random
2
+
3
+ at-random [--random-seed seed] [--from HH[:MM]] [--to [HH[:MM]] [at-args]
4
+
5
+ Picks some time
6
+ between `--from` and `--to`
7
+ (in 24-hour format)
8
+ uniformly at random.
9
+ Passes the remaining args
10
+ (namely `[-q queue] [-f file] [-m]`)
11
+ along with standard input
12
+ to `at(1)` to run
13
+ at that time.
14
+
15
+ If `--from` is in the past,
16
+ `at-random` will re-roll times
17
+ until it gets one in the future.
18
+ If `--to` is in the past,
19
+ it will print an error message
20
+ and exit nonzero.
21
+
22
+ If `--random-seed` is passed,
23
+ it will be used to seed the PRNG.
24
+
25
+
26
+ This script can be used to implement random job start times in `cron(8)` with
27
+ `crontab(5)` lines like the following:
28
+
29
+ # m h dom month dow command
30
+ 0 0 0 * 0 at-random --from 12:00 --to 17:00 -f /home/bob/reflect
31
+
32
+ which runs `at-random` at midnight every Sunday to tell `at(1)` to execute the
33
+ contents of `/home/bob/reflect` some time between noon and five PM.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/at-random.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/at-random/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Steve Dee"]
6
+ gem.email = ["steve@smartercode.net"]
7
+ gem.description = %q{Pick a random time subject to some constraints, and fork `at` with the remaining args.}
8
+ gem.summary = %q{Do things at random times}
9
+ gem.homepage = "https://github.com/mrdomino/at-random"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "at-random"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = AtRandom::VERSION
17
+
18
+ gem.add_development_dependency 'rspec', ['>= 2']
19
+ gem.add_development_dependency 'mocha'
20
+ end
data/bin/at-random ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'at-random'
5
+
6
+ exit AtRandom::App.new(ARGV).run
@@ -0,0 +1,44 @@
1
+ require 'at-random/at_cmd'
2
+ require 'at-random/pick_time'
3
+
4
+ module AtRandom
5
+ class App
6
+ def initialize(argv=[])
7
+ @pick_time_opts = {}
8
+ while argv[0] =~ /--(from|to|random-seed)/
9
+ arg = argv.shift
10
+ if arg =~ /--from/
11
+ from_str = arg.length > 6 ? arg[7..-1] : argv.shift
12
+ @pick_time_opts[:from] = from_str
13
+ elsif arg =~ /--to/
14
+ to_str = arg.length > 4 ? arg[5..-1] : argv.shift
15
+ @pick_time_opts[:to] = to_str
16
+ elsif arg =~ /--random-seed/
17
+ @random_seed = arg.length > 13 ? arg[14..-1].to_i : argv.shift
18
+ end
19
+ end
20
+
21
+ if @pick_time_opts[:from] && @pick_time_opts[:from].length == 2
22
+ @pick_time_opts[:from] += ':00'
23
+ end
24
+
25
+ if @pick_time_opts[:to] && @pick_time_opts[:to].length == 2
26
+ @pick_time_opts[:to] += ':59'
27
+ end
28
+
29
+ @at_args = argv
30
+ end
31
+
32
+ def run
33
+ Kernel.srand(@random_seed) if @random_seed
34
+ @picked_time = PickTime.new @pick_time_opts
35
+ begin
36
+ AtCmd.new(@picked_time.time_s, @at_args).exec
37
+ 0
38
+ rescue Exception => e
39
+ $stderr.puts e.message
40
+ -1
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ module AtRandom
2
+ class AtCmd
3
+ class InvalidTime < Exception; end
4
+
5
+ def initialize(*args)
6
+ if args[0]
7
+ timespec = args.shift
8
+ if !(timespec =~ /[0-2][0-9]:[0-5][0-9]/)
9
+ raise ArgumentError
10
+ end
11
+
12
+ if timespec < Time.now.strftime('%H:%M')
13
+ raise InvalidTime, "Timespec #{timespec} is earlier than now"
14
+ end
15
+
16
+ @exec_args = ['at', timespec, args].flatten
17
+ end
18
+ end
19
+
20
+ def exec
21
+ Process.exec(*@exec_args)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ module AtRandom
2
+ class PickTime
3
+ class ImpossibleRange < Exception; end
4
+
5
+ def initialize(opts={})
6
+ from_h, from_m = parse_time(opts[:from] || '00:00')
7
+ to_h, to_m = parse_time(opts[:to] || '23:59')
8
+ now_h, now_m = parse_time(Time.now.strftime('%H:%M'))
9
+
10
+ if now_h >= from_h
11
+ from_h = now_h
12
+ if now_m > from_m
13
+ from_m = now_m
14
+ end
15
+ end
16
+
17
+ if now_h > to_h || (now_h == to_h && now_m > to_m)
18
+ raise ImpossibleRange, 'Asked for :to sooner than now'
19
+ end
20
+
21
+ @hour = rand(to_h - from_h) + from_h
22
+ @minute = rand(to_m - from_m) + from_m
23
+ end
24
+
25
+ def time_s
26
+ format("%02d:%02d", @hour, @minute)
27
+ end
28
+
29
+ private
30
+ def parse_time(time_s)
31
+ time_s.split(':').map(&:to_i)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module AtRandom
2
+ VERSION = '1.0.0'
3
+ end
data/lib/at-random.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'at-random/app'
2
+ require 'at-random/pick_time'
3
+ require 'at-random/at_cmd'
4
+ require 'at-random/version'
@@ -0,0 +1,204 @@
1
+ require 'spec_helper'
2
+
3
+ require 'at-random/app'
4
+
5
+ describe AtRandom::App do
6
+ let!(:mock_picked_time) { mock('PickTime') }
7
+ let!(:mock_at_cmd) { mock('AtCmd') }
8
+
9
+ before do
10
+ $stderr.stubs(:puts)
11
+ Kernel.stubs(:srand)
12
+
13
+ AtRandom::AtCmd.stubs(:new).returns(mock_at_cmd)
14
+ mock_at_cmd.stubs(:exec)
15
+ AtRandom::PickTime.stubs(:new).returns(mock_picked_time)
16
+ mock_picked_time.stubs(:time_s)
17
+ end
18
+
19
+ describe '#run' do
20
+ def run_with_good_args
21
+ argv = %w[--random-seed=20 --from=12:34 --to=13 -q q -m]
22
+ AtRandom::App.new(argv).run
23
+ end
24
+
25
+ shared_examples_for 'successful run' do
26
+ it 'picks a time to pass to `at`' do
27
+ picked_time = '12:35'
28
+ mock_picked_time.expects(:time_s).returns(picked_time)
29
+ AtRandom::AtCmd.expects(:new).with(picked_time, any_parameters)
30
+ subject
31
+ end
32
+
33
+ it 'calls AtCmd#exec' do
34
+ mock_at_cmd.expects(:exec)
35
+ subject
36
+ end
37
+ end
38
+
39
+ context 'with good arguments' do
40
+ subject { run_with_good_args }
41
+
42
+ context 'when successful' do
43
+ it_behaves_like 'successful run'
44
+
45
+ it 'passes appropriate arguments' do
46
+ Kernel.expects(:srand).with(20)
47
+ AtRandom::PickTime.expects(:new).
48
+ with(:from => '12:34', :to => '13:59').
49
+ returns(mock_picked_time)
50
+ AtRandom::AtCmd.expects(:new).with(anything, ['-q', 'q', '-m'])
51
+ subject
52
+ end
53
+ end
54
+
55
+ context 'when AtCmd raises an exception' do
56
+ before do
57
+ AtRandom::AtCmd.stubs(:new).raises(Exception, 'boom')
58
+ end
59
+
60
+ it { lambda { subject }.should_not raise_error }
61
+
62
+ it 'prints an error message to stderr' do
63
+ $stderr.expects(:puts).with(includes('boom'))
64
+ subject
65
+ end
66
+
67
+ it 'returns a negative number' do
68
+ subject.should be < 0
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'without arguments' do
74
+ subject { AtRandom::App.new.run }
75
+
76
+ it_behaves_like 'successful run'
77
+ end
78
+ end
79
+
80
+ describe '.new' do
81
+ describe 'arguments' do
82
+ def run_with_arg(arg)
83
+ AtRandom::App.new([arg]).run
84
+ end
85
+
86
+ shared_examples_for 'parametrized argument' do |param|
87
+ it 'understands --arg=param' do
88
+ AtRandom::App.new(["#{subject}=#{param}"]).run
89
+ end
90
+
91
+ it 'understands --arg param' do
92
+ AtRandom::App.new([subject, param]).run
93
+ end
94
+ end
95
+
96
+ shared_examples_for 'time argument' do
97
+ it 'accepts HH' do
98
+ run_with_arg("#{subject}=23").should eq 0
99
+ end
100
+
101
+ it 'accepts HH:MM' do
102
+ run_with_arg("#{subject}=01:23").should eq 0
103
+ end
104
+ end
105
+
106
+ def run_with_time_arg(arg, time_s)
107
+ AtRandom::PickTime.expects(:new).
108
+ with(arg.to_sym => time_s).
109
+ returns(mock_picked_time)
110
+ run_with_arg("--#{arg}=#{time_s}")
111
+ end
112
+
113
+ describe '--from' do
114
+ subject { '--from' }
115
+
116
+ it_behaves_like 'time argument'
117
+
118
+ it_behaves_like 'parametrized argument', '01:23' do
119
+ before do
120
+ AtRandom::PickTime.expects(:new).
121
+ with(:from => '01:23').
122
+ returns(mock_picked_time)
123
+ end
124
+ end
125
+
126
+ it 'passes valid times to PickTime' do
127
+ run_with_time_arg 'from', '10:00'
128
+
129
+ run_with_time_arg 'from', '22:33'
130
+ end
131
+
132
+ it 'expands "HH" to "HH:00"' do
133
+ AtRandom::PickTime.expects(:new).
134
+ with(:from => '11:00').
135
+ returns(mock_picked_time)
136
+ run_with_arg("--from=11")
137
+ end
138
+ end
139
+
140
+ describe '--to' do
141
+ subject { '--to' }
142
+
143
+ it_behaves_like 'time argument'
144
+
145
+ it_behaves_like 'parametrized argument', '01:23' do
146
+ before do
147
+ AtRandom::PickTime.expects(:new).
148
+ with(:to => '01:23').
149
+ returns(mock_picked_time)
150
+ end
151
+ end
152
+
153
+ it 'passes to PickTime' do
154
+ run_with_time_arg 'to', '11:00'
155
+
156
+ run_with_time_arg 'to', '23:59'
157
+ end
158
+
159
+ it 'expands "HH" to "HH:59"' do
160
+ AtRandom::PickTime.expects(:new).
161
+ with(:to => '12:59').
162
+ returns(mock_picked_time)
163
+ run_with_arg("--to=12")
164
+ end
165
+ end
166
+
167
+ describe '--from and --to' do
168
+ specify 'both get passed to PickTime' do
169
+ AtRandom::PickTime.expects(:new).
170
+ with(:from => '23:05', :to => '23:04').
171
+ returns(mock_picked_time)
172
+ AtRandom::App.new(%w[--from=23:05 --to=23:04]).run
173
+ end
174
+ end
175
+
176
+ describe '--random-seed' do
177
+ subject { '--random-seed' }
178
+
179
+ it_behaves_like 'parametrized argument', 1234 do
180
+ before { Kernel.expects(:srand).with(1234) }
181
+ end
182
+
183
+ it 'seeds the rng' do
184
+ Kernel.expects(:srand).with(12345)
185
+ run_with_arg('--random-seed=12345')
186
+ end
187
+ end
188
+
189
+ describe '`at` args' do
190
+ specify 'get passed to `at`' do
191
+ AtRandom::AtCmd.expects(:new).with(anything, %w[-q a])
192
+ AtRandom::App.new(%w[-q a]).run
193
+ end
194
+
195
+ describe 'other `at-random` args' do
196
+ specify "aren't passed to `at`" do
197
+ AtRandom::AtCmd.expects(:new).with(anything, %w[-m])
198
+ AtRandom::App.new(%w[--from=10:00 --to=12:00 -m]).run
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ require 'at-random/at_cmd'
4
+
5
+ describe AtRandom::AtCmd do
6
+ let!(:frozen_now) { Time.local(2015, 05, 25) }
7
+ before do
8
+ Time.stubs(:now).returns(frozen_now)
9
+ Process.stubs(:exec)
10
+ end
11
+
12
+ let(:timespec) { '12:30' }
13
+ let(:at_args) { [] }
14
+
15
+ let(:at_with_args) { AtRandom::AtCmd.new(timespec, at_args) }
16
+
17
+ describe '.new' do
18
+ subject { at_with_args }
19
+
20
+ describe 'timespec argument' do
21
+ context 'a valid time string' do
22
+ let(:timespec) { '12:34' }
23
+
24
+ context 'after now' do
25
+ let(:frozen_now) { Time.local(2012, 05, 25, 12, 33) }
26
+
27
+ it { lambda { subject }.should_not raise_error }
28
+ end
29
+
30
+ context 'before now' do
31
+ let(:frozen_now) { Time.local(2012, 05, 25, 12, 35) }
32
+
33
+ it { lambda { subject }.
34
+ should raise_error AtRandom::AtCmd::InvalidTime }
35
+ end
36
+ end
37
+
38
+ context 'a non-time string' do
39
+ let(:timespec) { 'as:df' }
40
+
41
+ it { lambda { subject }.should raise_error ArgumentError }
42
+ end
43
+
44
+ context 'a non-string' do
45
+ let(:timespec) { Time.local(2012, 05, 25, 12, 32) }
46
+
47
+ it { lambda { subject }.should raise_error ArgumentError }
48
+ end
49
+ end
50
+
51
+ describe 'at_args argument' do
52
+ context 'an array of `at` args' do
53
+ let(:at_args) { ['-f', 'foo', '-mv'] }
54
+
55
+ it { lambda { subject }.should_not raise_error }
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#exec' do
61
+ subject { at_with_args.exec }
62
+
63
+ it 'execs `at`' do
64
+ Process.expects(:exec).with('at', any_parameters)
65
+ subject
66
+ end
67
+
68
+ context 'with at args' do
69
+ let(:at_args) { ['-f', 'foo'] }
70
+
71
+ it 'passes arguments to `at`' do
72
+ Process.expects(:exec).with('at', anything, '-f', 'foo')
73
+ subject
74
+ end
75
+ end
76
+
77
+ describe '`at` timespec' do
78
+ it 'is the first argument to AtCmd.new' do
79
+ Process.expects(:exec).with('at', timespec, any_parameters)
80
+ subject
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ require 'at-random/pick_time'
4
+
5
+ describe AtRandom::PickTime do
6
+ let(:frozen_now) { Time.local(1970, 1, 1, 0, 0) }
7
+
8
+ before do
9
+ Time.stubs(:now).returns(frozen_now)
10
+ end
11
+
12
+ describe '#time_s' do
13
+ it 'is a time string' do
14
+ subject.time_s.should =~ /[012][0-9]:[0-5][0-9]/
15
+ end
16
+
17
+ it 'is a function of Kernel.rand' do
18
+ (1..20).map do |i|
19
+ Kernel.srand(i)
20
+ AtRandom::PickTime.new.time_s
21
+ end.uniq.length.should be_within(1).of(19)
22
+ end
23
+
24
+ it 'is the same every time for a given PickTime' do
25
+ subject.time_s.should eq subject.time_s
26
+ end
27
+ end
28
+
29
+ describe '.new' do
30
+ context 'with :from => "00:34"' do
31
+ subject { AtRandom::PickTime.new :from => "00:34" }
32
+
33
+ its(:time_s) { should be > '00:34' }
34
+
35
+ context 'at 17:36pm local' do
36
+ let(:frozen_now) { Time.at(Time.local(2012, 05, 16, 17, 36)) }
37
+
38
+ its(:time_s) { should be > '17:36' }
39
+ end
40
+ end
41
+
42
+ context 'with :to => "12:34"' do
43
+ subject { AtRandom::PickTime.new :to => '12:34' }
44
+
45
+ its(:time_s) { should be <= '12:34' }
46
+
47
+ context 'at 12:36pm local' do
48
+ let(:frozen_now) { Time.local(2012, 05, 16, 12, 36) }
49
+
50
+ it { lambda { subject }.should raise_error }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ require 'at-random/version'
4
+
5
+ describe AtRandom::VERSION do
6
+ it { should =~ /^[0-9]+\.[0-9]+\.[0-9]+$/ }
7
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ require 'at-random'
4
+
5
+ describe AtRandom do
6
+ it 'includes submodules' do
7
+ subject.const_defined?('App').should be_true
8
+ subject.const_defined?('PickTime').should be_true
9
+ subject.const_defined?('AtCmd').should be_true
10
+ subject.const_defined?('VERSION').should be_true
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ RSpec.configure do |config|
5
+ config.mock_with :mocha
6
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: at-random
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Steve Dee
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '2'
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: '2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mocha
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
+ description: Pick a random time subject to some constraints, and fork `at` with the
47
+ remaining args.
48
+ email:
49
+ - steve@smartercode.net
50
+ executables:
51
+ - at-random
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - .rspec
57
+ - Gemfile
58
+ - README.md
59
+ - Rakefile
60
+ - at-random.gemspec
61
+ - bin/at-random
62
+ - lib/at-random.rb
63
+ - lib/at-random/app.rb
64
+ - lib/at-random/at_cmd.rb
65
+ - lib/at-random/pick_time.rb
66
+ - lib/at-random/version.rb
67
+ - spec/at-random/app_spec.rb
68
+ - spec/at-random/at_cmd_spec.rb
69
+ - spec/at-random/pick_time_spec.rb
70
+ - spec/at-random/version_spec.rb
71
+ - spec/at_random_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: https://github.com/mrdomino/at-random
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.24
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Do things at random times
97
+ test_files:
98
+ - spec/at-random/app_spec.rb
99
+ - spec/at-random/at_cmd_spec.rb
100
+ - spec/at-random/pick_time_spec.rb
101
+ - spec/at-random/version_spec.rb
102
+ - spec/at_random_spec.rb
103
+ - spec/spec_helper.rb