trick_bag 0.59 → 0.62.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80de6b95f6465d84976cfff65217064adab423c6
4
- data.tar.gz: 6a91c5d2a35c666f85de817f64d78587978ace2a
3
+ metadata.gz: e7febbc99a5bd373e0964dd23b7cc4b4dd8482b7
4
+ data.tar.gz: 8c0ea6ac86a0dff90774250edc1143b0af5a6d19
5
5
  SHA512:
6
- metadata.gz: df097a1226f5ba6a5bdcaaf2200050fd8450e7a3cafdcd0827b6766ac15a8bfc5ef4f724b525c6d191891e0a272818348cabbee4a3ae27ed05e018e1f44eb493
7
- data.tar.gz: 6f25b512ee5485ee8a7e78c089764420f29519d4abcb6a1fd2fa6faa1c3cde512f7018b4f3cd26589a3e75da057a65be5a04bb2d5457e75e5bf50a065ed8bd10
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 key_string the string representing the keys to use
24
- # @separator the string to use to separate the
25
- def access(collection, key_string, separator = '.')
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
- is_number_string = ->(s) do
27
+ to_integer = ->(object) do
28
28
  begin
29
- Integer(s)
30
- true
29
+ Integer(object)
31
30
  rescue
32
- false
31
+ raise "Key is not a number string: #{object}"
33
32
  end
34
33
  end
35
34
 
36
- keys = key_string.split(separator)
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 [#{key_string}]: #{e}"
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
- ->(key_string) { access(collection, key_string, separator) }
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
@@ -1,3 +1,3 @@
1
1
  module TrickBag
2
- VERSION = "0.59"
2
+ VERSION = '0.62.0'
3
3
  end
@@ -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' => ['a', 'b'] }
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: '0.59'
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-03-23 00:00:00.000000000 Z
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.2
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: