trick_bag 0.59 → 0.62.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/RELEASE_NOTES.md +15 -0
- data/lib/trick_bag/collections/collection_access.rb +25 -18
- data/lib/trick_bag/timing/elapser.rb +67 -0
- data/lib/trick_bag/timing/timing.rb +26 -0
- data/lib/trick_bag/version.rb +1 -1
- data/spec/trick_bag/collections/collection_access_spec.rb +15 -6
- data/spec/trick_bag/timing/elapser_spec.rb +95 -0
- data/spec/trick_bag/timing/timing_spec.rb +10 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7febbc99a5bd373e0964dd23b7cc4b4dd8482b7
|
4
|
+
data.tar.gz: 8c0ea6ac86a0dff90774250edc1143b0af5a6d19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d86943b5df0ab4289411f2c641cd9670d44c907502ce68432e2e79987a81989930678ad59423127897777352d243b4cebef19fe979c16897b59f6f608144583e
|
7
|
+
data.tar.gz: 7aa3678b4694537560c327bab79f8f8d4279c97bae890cbeb42b23231ffda852e5ce3e339c516223fcae63ea7e5280edc73ff7b112f0b9edbe5c49e7da908a03
|
data/RELEASE_NOTES.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## v0.62.0
|
2
|
+
|
3
|
+
* Added Timing.try_with_timeout.
|
4
|
+
|
5
|
+
## v0.61.0
|
6
|
+
|
7
|
+
* Added Timing::Elapser.
|
8
|
+
|
9
|
+
|
10
|
+
## v0.60
|
11
|
+
|
12
|
+
* CollectionAccess methods now support an array of keys/subscripts instead of
|
13
|
+
a string.
|
14
|
+
|
15
|
+
|
1
16
|
## v0.59
|
2
17
|
|
3
18
|
* Timing.retry_until_true_or_timeout now optionally takes a code block instead of a lambda.
|
@@ -19,37 +19,41 @@ module CollectionAccess
|
|
19
19
|
#
|
20
20
|
# Error occurred processing key [x.1] in [x.1.2]: undefined method `[]' for nil:NilClass
|
21
21
|
#
|
22
|
-
# @param the collection to access
|
23
|
-
# @param
|
24
|
-
# @separator the string to use to separate the
|
25
|
-
def access(collection,
|
22
|
+
# @param collection the collection to access
|
23
|
+
# @param key_string_or_array the string or array representing the keys to use
|
24
|
+
# @param separator the string to use to separate the keys, defaults to '.'
|
25
|
+
def access(collection, key_string_or_array, separator = '.')
|
26
26
|
|
27
|
-
|
27
|
+
to_integer = ->(object) do
|
28
28
|
begin
|
29
|
-
Integer(
|
30
|
-
true
|
29
|
+
Integer(object)
|
31
30
|
rescue
|
32
|
-
|
31
|
+
raise "Key is not a number string: #{object}"
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
|
-
|
35
|
+
get_key_array = -> do
|
36
|
+
case key_string_or_array
|
37
|
+
when Array
|
38
|
+
key_string_or_array
|
39
|
+
when String
|
40
|
+
key_string_or_array.split(separator)
|
41
|
+
else
|
42
|
+
raise "Invalid data type: #{key_string_or_array.class}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
keys = get_key_array.()
|
37
47
|
return_object = collection
|
38
48
|
|
39
49
|
keys.each_with_index do |key, index|
|
40
|
-
|
41
|
-
if return_object.kind_of?(Array)
|
42
|
-
unless is_number_string.(key)
|
43
|
-
raise "Key is not a number string: #{key}"
|
44
|
-
end
|
45
|
-
key = key.to_i
|
46
|
-
end
|
50
|
+
key = to_integer.(key) if return_object.kind_of?(Array)
|
47
51
|
|
48
52
|
begin
|
49
53
|
return_object = return_object[key]
|
50
54
|
rescue => e
|
51
55
|
this_key = keys[0..index].join(separator)
|
52
|
-
raise "Error occurred processing key [#{this_key}] in [#{
|
56
|
+
raise "Error occurred processing key [#{this_key}] in [#{key_string_or_array}]: #{e}"
|
53
57
|
end
|
54
58
|
end
|
55
59
|
return_object
|
@@ -69,7 +73,10 @@ module CollectionAccess
|
|
69
73
|
# or
|
70
74
|
# accessor.('h.1') # => 'b'
|
71
75
|
def accessor(collection, separator = '.')
|
72
|
-
->(
|
76
|
+
->(*args) do
|
77
|
+
key_string = (args.size == 1) ? args.first : args.map(&:to_s).join(separator)
|
78
|
+
access(collection, key_string, separator)
|
79
|
+
end
|
73
80
|
end
|
74
81
|
|
75
82
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module TrickBag
|
2
|
+
module Timing
|
3
|
+
|
4
|
+
|
5
|
+
# Very simple class that enables you to specify an elapsed time in
|
6
|
+
# either seconds or by the time itself.
|
7
|
+
class Elapser
|
8
|
+
|
9
|
+
attr_reader :seconds, :end_time
|
10
|
+
attr_accessor :never_elapse
|
11
|
+
|
12
|
+
def self.never_elapser
|
13
|
+
@never_elapser ||= begin
|
14
|
+
instance = new(0)
|
15
|
+
instance.never_elapse = true
|
16
|
+
instance
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# Can be used to create an instance or return the passed instance (see test for example).
|
22
|
+
def self.from(object)
|
23
|
+
case object
|
24
|
+
when :never
|
25
|
+
never_elapser
|
26
|
+
when self
|
27
|
+
object
|
28
|
+
else
|
29
|
+
new(object)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Create the instance with the passed parameter. If it's a Time instance,
|
35
|
+
# it is assumed to be the end time at which elapsed? should return true.
|
36
|
+
# If it's a number, it's assumed to be a number of seconds after which
|
37
|
+
# elapsed? should return true.
|
38
|
+
def initialize(seconds_or_end_time)
|
39
|
+
case seconds_or_end_time
|
40
|
+
when Time
|
41
|
+
@end_time = seconds_or_end_time
|
42
|
+
@seconds = @end_time - Time.now
|
43
|
+
when ::Numeric
|
44
|
+
@seconds = seconds_or_end_time
|
45
|
+
@end_time = Time.now + @seconds
|
46
|
+
else
|
47
|
+
raise ArgumentError.new("Invalid parameter class: #{seconds_or_end_time.class}, object: #{seconds_or_end_time}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def elapsed?
|
52
|
+
never_elapse ? false : Time.now >= @end_time
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def hash
|
57
|
+
Integer(@seconds - @end_time.to_i)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def ==(other)
|
62
|
+
other.class == self.class && other.seconds == self.seconds && other.end_time == self.end_time
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -90,5 +90,31 @@ module Timing
|
|
90
90
|
return_value
|
91
91
|
end
|
92
92
|
|
93
|
+
|
94
|
+
# Runs the passed block in a new thread, ensuring that its execution time
|
95
|
+
# does not exceed the specified duration.
|
96
|
+
#
|
97
|
+
# @param max_seconds maximum number of seconds to wait for completion
|
98
|
+
# @param check_interval_in_secs interval in seconds at which to check for completion
|
99
|
+
# @block block of code to execute in the secondary thread
|
100
|
+
#
|
101
|
+
# @return [true, block return value] if the block completes before timeout
|
102
|
+
# or [false, nil] if the block is still active (i.e. the thread is still alive)
|
103
|
+
# when max_seconds is reached
|
104
|
+
def try_with_timeout(max_seconds, check_interval_in_secs, &block)
|
105
|
+
raise "Must pass block to this method" unless block_given?
|
106
|
+
end_time = Time.now + max_seconds
|
107
|
+
block_return_value = nil
|
108
|
+
thread = Thread.new { block_return_value = block.call }
|
109
|
+
while Time.now < end_time
|
110
|
+
unless thread.alive?
|
111
|
+
return [true, block_return_value]
|
112
|
+
end
|
113
|
+
sleep(check_interval_in_secs)
|
114
|
+
end
|
115
|
+
# thread.kill
|
116
|
+
[false, nil]
|
117
|
+
end
|
118
|
+
|
93
119
|
end
|
94
120
|
end
|
data/lib/trick_bag/version.rb
CHANGED
@@ -19,12 +19,14 @@ describe CollectionAccess do
|
|
19
19
|
h = { 'a' => { 'b' => 234 }}
|
20
20
|
# instead of h['a']['b']:
|
21
21
|
expect(CollectionAccess.access(h, 'a.b')).to eq(234)
|
22
|
+
expect(CollectionAccess.access(h, %w(a b))).to eq(234)
|
22
23
|
end
|
23
24
|
|
24
25
|
it 'works with 3 keys' do
|
25
26
|
h = { 'a' => { 'bb' => { 'ccc' => 345 }}}
|
26
27
|
# instead of h['a']['bb']['ccc']:
|
27
28
|
expect(CollectionAccess.access(h, 'a.bb.ccc')).to eq(345)
|
29
|
+
expect(CollectionAccess.access(h, %w(a bb ccc))).to eq(345)
|
28
30
|
end
|
29
31
|
|
30
32
|
it 'works with spaces as separators' do
|
@@ -41,21 +43,26 @@ describe CollectionAccess do
|
|
41
43
|
it 'works with numeric array subscripts 1 deep' do
|
42
44
|
a = ['a', 'b']
|
43
45
|
expect(CollectionAccess.access(a, '1')).to eq('b')
|
46
|
+
expect(CollectionAccess.access(a, [1])).to eq('b')
|
44
47
|
end
|
45
48
|
|
46
49
|
it 'works with a hash containing an array' do
|
47
50
|
h = { 'h' => ['a', 'b'] }
|
48
51
|
expect(CollectionAccess.access(h, 'h.1')).to eq('b')
|
52
|
+
expect(CollectionAccess.access(h, ['h', 1])).to eq('b')
|
53
|
+
expect(CollectionAccess.access(h, ['h', '1'])).to eq('b')
|
49
54
|
end
|
50
55
|
|
51
56
|
it 'raises an error when accessing an invalid key' do
|
52
57
|
h = { 'h' => ['a', 'b'] }
|
53
58
|
expect(-> { CollectionAccess.access(h, 'x.1.2') }).to raise_error
|
59
|
+
expect(-> { CollectionAccess.access(h, [x, 1, 2]) }).to raise_error
|
54
60
|
end
|
55
61
|
|
56
62
|
it 'raises an error when accessing a string that should be a number' do
|
57
63
|
h = { 'x' => ['a', 'b'] }
|
58
64
|
expect(-> { CollectionAccess.access(h, 'x.x') }).to raise_error
|
65
|
+
expect(-> { CollectionAccess.access(h, [x, x]) }).to raise_error
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
@@ -63,16 +70,18 @@ describe CollectionAccess do
|
|
63
70
|
context '#accessor' do
|
64
71
|
|
65
72
|
it 'works with a hash containing an array' do
|
66
|
-
h = { 'h' =>
|
73
|
+
h = { 'h' => %w(a b) }
|
67
74
|
accessor = CollectionAccess.accessor(h)
|
68
75
|
expect(accessor['h.1']).to eq('b')
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
72
76
|
|
77
|
+
# Test both [] and .() notations, with 1 as number and string:
|
78
|
+
expect(accessor[['h', 1]]).to eq('b')
|
79
|
+
expect(accessor.('h', 1)).to eq('b')
|
73
80
|
|
81
|
+
expect(accessor[['h', '1']]).to eq('b')
|
82
|
+
expect(accessor.(['h', '1'])).to eq('b')
|
83
|
+
end
|
84
|
+
end
|
74
85
|
end
|
75
|
-
|
76
|
-
|
77
86
|
end
|
78
87
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
require 'trick_bag/timing/elapser'
|
4
|
+
|
5
|
+
module TrickBag
|
6
|
+
module Timing
|
7
|
+
|
8
|
+
describe Elapser do
|
9
|
+
|
10
|
+
def test_number(number)
|
11
|
+
e = Elapser.new(number)
|
12
|
+
expect(e.class).to eq(Elapser)
|
13
|
+
expect(e.seconds).to eq(number)
|
14
|
+
expect(e.end_time - Time.now).to be_within(2.0).of(number)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
context 'instantiation' do
|
19
|
+
|
20
|
+
it 'can take an integer' do
|
21
|
+
test_number(30)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can take a float' do
|
25
|
+
test_number(30.0)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can take an end time' do
|
29
|
+
now = Time.now
|
30
|
+
e = Elapser.new(now + 30)
|
31
|
+
expect(e.class).to eq(Elapser)
|
32
|
+
expect(e.seconds).to be_within(0.1).of(30)
|
33
|
+
expect(e.end_time - now).to be_within(0.1).of(30)
|
34
|
+
end
|
35
|
+
|
36
|
+
specify 'an invalid parameter type raises an error' do
|
37
|
+
expect { Elapser.new(nil) }.to raise_error(ArgumentError)
|
38
|
+
expect { Elapser.new('30') }.to raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
context '#elapsed?' do
|
44
|
+
|
45
|
+
specify 'elapsed? returns true if seconds is negative' do
|
46
|
+
expect(Elapser.new(-1).elapsed?).to eq(true)
|
47
|
+
end
|
48
|
+
|
49
|
+
specify 'elapsed? returns false if seconds is positive' do
|
50
|
+
expect(Elapser.new(100).elapsed?).to eq(false)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
context '.from' do
|
56
|
+
specify 'calling with an Elapsed instance returns that instance' do
|
57
|
+
e = Elapser.new(30)
|
58
|
+
expect(Elapser.from(e)).to equal(e)
|
59
|
+
end
|
60
|
+
|
61
|
+
specify 'calling with a number or time works correctly' do
|
62
|
+
now = Time.now
|
63
|
+
secs = 45
|
64
|
+
|
65
|
+
expect(Elapser.from(secs).seconds - (Elapser.new(secs).seconds)).to be_within(1).of(0)
|
66
|
+
expect(Elapser.from(secs).end_time - (Elapser.new(secs).end_time)).to be_within(1).of(0)
|
67
|
+
|
68
|
+
expect(Elapser.from(now).seconds - (Elapser.new(now).seconds)).to be_within(1).of(0)
|
69
|
+
expect(Elapser.from(now).end_time - (Elapser.new(now).end_time)).to be_within(1).of(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
context '.never' do
|
77
|
+
|
78
|
+
specify 'never_elapser is intialized correctly' do
|
79
|
+
expect(Elapser.never_elapser.elapsed?).to eq(false)
|
80
|
+
expect(Elapser.never_elapser.never_elapse).to eq(true)
|
81
|
+
|
82
|
+
expect(Elapser.from(:never).elapsed?).to eq(false)
|
83
|
+
expect(Elapser.from(:never).never_elapse).to eq(true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context '#hash' do
|
88
|
+
specify '#hash returns an Integer' do
|
89
|
+
expect(Elapser.new(3).hash).to be_a(Integer)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
@@ -68,4 +68,14 @@ module TrickBag
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
|
72
|
+
context '.try_with_timeout' do
|
73
|
+
it 'should return true and the block return value when timeout does not occur' do
|
74
|
+
expect(Timing.try_with_timeout(0.2, 0.01) { 7 }).to eq([true, 7])
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should return false and nil when timeout occurs' do
|
78
|
+
expect(Timing.try_with_timeout(0.2, 0.01) { sleep 100; 7 }).to eq([false, nil])
|
79
|
+
end
|
80
|
+
end
|
71
81
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trick_bag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.62.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keith Bennett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: os
|
@@ -146,6 +146,7 @@ files:
|
|
146
146
|
- lib/trick_bag/numeric/totals.rb
|
147
147
|
- lib/trick_bag/operators/operators.rb
|
148
148
|
- lib/trick_bag/system.rb
|
149
|
+
- lib/trick_bag/timing/elapser.rb
|
149
150
|
- lib/trick_bag/timing/timing.rb
|
150
151
|
- lib/trick_bag/validations/gem_dependency_script.rb
|
151
152
|
- lib/trick_bag/validations/hash_validations.rb
|
@@ -176,6 +177,7 @@ files:
|
|
176
177
|
- spec/trick_bag/numeric/totals_spec.rb
|
177
178
|
- spec/trick_bag/operators/operators_spec.rb
|
178
179
|
- spec/trick_bag/system_spec.rb
|
180
|
+
- spec/trick_bag/timing/elapser_spec.rb
|
179
181
|
- spec/trick_bag/timing/timing_spec.rb
|
180
182
|
- spec/trick_bag/validations/hash_validations_spec.rb
|
181
183
|
- spec/trick_bag/validations/object_validations_spec.rb
|
@@ -202,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
202
204
|
version: '0'
|
203
205
|
requirements: []
|
204
206
|
rubyforge_project:
|
205
|
-
rubygems_version: 2.4.
|
207
|
+
rubygems_version: 2.4.6
|
206
208
|
signing_key:
|
207
209
|
specification_version: 4
|
208
210
|
summary: Miscellaneous general useful tools.
|
@@ -230,9 +232,9 @@ test_files:
|
|
230
232
|
- spec/trick_bag/numeric/totals_spec.rb
|
231
233
|
- spec/trick_bag/operators/operators_spec.rb
|
232
234
|
- spec/trick_bag/system_spec.rb
|
235
|
+
- spec/trick_bag/timing/elapser_spec.rb
|
233
236
|
- spec/trick_bag/timing/timing_spec.rb
|
234
237
|
- spec/trick_bag/validations/hash_validations_spec.rb
|
235
238
|
- spec/trick_bag/validations/object_validations_spec.rb
|
236
239
|
- spec/trick_bag/validations/other_validations_spec.rb
|
237
240
|
- spec/trick_bag/validations/regex_validations_spec.rb
|
238
|
-
has_rdoc:
|