at-random 1.0.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.
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