trick_bag 0.30.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.
Files changed (38) hide show
  1. data/.gitignore +19 -0
  2. data/Gemfile +4 -0
  3. data/Guardfile +8 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +32 -0
  6. data/RELEASE_NOTES.md +3 -0
  7. data/Rakefile +1 -0
  8. data/lib/trick_bag.rb +7 -0
  9. data/lib/trick_bag/collections/linked_list.rb +97 -0
  10. data/lib/trick_bag/enumerables/buffered_enumerable.rb +90 -0
  11. data/lib/trick_bag/enumerables/compound_enumerable.rb +114 -0
  12. data/lib/trick_bag/enumerables/filtered_enumerable.rb +32 -0
  13. data/lib/trick_bag/io/temp_files.rb +22 -0
  14. data/lib/trick_bag/io/text_mode_status_updater.rb +59 -0
  15. data/lib/trick_bag/meta/classes.rb +87 -0
  16. data/lib/trick_bag/numeric/multi_counter.rb +44 -0
  17. data/lib/trick_bag/numeric/totals.rb +49 -0
  18. data/lib/trick_bag/operators/operators.rb +13 -0
  19. data/lib/trick_bag/timing/timing.rb +47 -0
  20. data/lib/trick_bag/validations/hash_validations.rb +21 -0
  21. data/lib/trick_bag/validations/object_validations.rb +26 -0
  22. data/lib/trick_bag/version.rb +3 -0
  23. data/spec/spec_helper.rb +11 -0
  24. data/spec/trick_bag/collections/linked_list_spec.rb +64 -0
  25. data/spec/trick_bag/enumerables/buffered_enumerable_spec.rb +117 -0
  26. data/spec/trick_bag/enumerables/compound_enumerable_spec.rb +141 -0
  27. data/spec/trick_bag/enumerables/filtered_enumerable_spec.rb +35 -0
  28. data/spec/trick_bag/io/temp_files_spec.rb +31 -0
  29. data/spec/trick_bag/io/text_mode_status_updater_spec.rb +24 -0
  30. data/spec/trick_bag/meta/classes_spec.rb +211 -0
  31. data/spec/trick_bag/numeric/multi_counter_spec.rb +28 -0
  32. data/spec/trick_bag/numeric/totals_spec.rb +38 -0
  33. data/spec/trick_bag/operators/operators_spec.rb +33 -0
  34. data/spec/trick_bag/timing/timing_spec.rb +17 -0
  35. data/spec/trick_bag/validations/hashes_validations_spec.rb +39 -0
  36. data/spec/trick_bag/validations/object_validations_spec.rb +23 -0
  37. data/trick_bag.gemspec +28 -0
  38. metadata +194 -0
@@ -0,0 +1,117 @@
1
+ require_relative '../../spec_helper'
2
+ require 'trick_bag/enumerables/buffered_enumerable'
3
+
4
+ module TrickBag
5
+ module Enumerables
6
+
7
+ describe BufferedEnumerable do
8
+
9
+ context 'when created with lambdas' do
10
+ # Returns an object that returns chunks of incrementing integers.
11
+ let(:fetcher) do
12
+ object = 0
13
+ ->(array, chunk_size) do
14
+ chunk_size.times do
15
+ object += 1
16
+ array << object
17
+ end
18
+ end
19
+ end
20
+
21
+ specify 'the values and number of fetches are correct' do
22
+ chunk_fetch_calls = 0
23
+ object_count = 0
24
+
25
+ fetch_notifier = ->(fetched_objects) do
26
+ chunk_fetch_calls += 1
27
+ object_count += fetched_objects.size
28
+ end
29
+
30
+ e = BufferedEnumerable.create_with_lambdas(4, fetcher, fetch_notifier).to_enum
31
+ (1..10).each do |n|
32
+ expect(e.next).to eq(n)
33
+ end
34
+ expect(chunk_fetch_calls).to eq(3)
35
+ expect(object_count).to eq(12)
36
+ end
37
+ end
38
+
39
+
40
+ context "when instantiating a subclass" do
41
+ specify 'the values and number of fetches are correct' do
42
+ create_test_class = ->() do
43
+ class BufferedEnumerableSubclass < BufferedEnumerable
44
+
45
+ attr_accessor :chunk_fetch_calls, :object_count
46
+
47
+ def initialize(chunk_size)
48
+ super
49
+ @chunk_fetch_calls = 0
50
+ @object_count = 0
51
+ end
52
+
53
+ def fetch_notify
54
+ @chunk_fetch_calls += 1
55
+ @object_count += data.size
56
+ end
57
+
58
+ def fetch
59
+ @object ||= 0
60
+ chunk_size.times do
61
+ @object += 1
62
+ self.data << @object
63
+ end
64
+ require 'pry'; binding.pry if self.data.is_a?(Fixnum)
65
+ end
66
+ end
67
+ end
68
+
69
+ create_test_class.()
70
+ enumerable = BufferedEnumerableSubclass.new(4)
71
+ enumerator = enumerable.each
72
+
73
+ (1..10).each do |n|
74
+ expect(enumerator.next).to eq(n)
75
+ end
76
+ expect(enumerable.chunk_fetch_calls).to eq(3)
77
+ expect(enumerable.object_count).to eq(12)
78
+ ::TrickBag::Meta::Classes.undef_class(:BufferedEnumerableSubclass, TrickBag)
79
+ end
80
+ end
81
+
82
+
83
+ context "an Array implementation" do
84
+
85
+ create_array_subclass = -> do
86
+ class ArrayBufferedEnumerable < BufferedEnumerable
87
+
88
+ def initialize(chunk_size, array)
89
+ super(chunk_size)
90
+ @array = array
91
+ end
92
+
93
+ def fetch
94
+ num_times = [chunk_size, @array.size].min
95
+ num_times.times { @data << @array.shift }
96
+ end
97
+ end
98
+ end
99
+
100
+ it 'should fulfill its basic functions' do
101
+ create_array_subclass.()
102
+ enumerable = ArrayBufferedEnumerable.new(4, (0..5).to_a)
103
+ enumerator = enumerable.each
104
+
105
+ (0..5).to_a.each do |n|
106
+ expect(enumerator.next).to eq(n)
107
+ end
108
+ expect(->{ enumerator.next }).to raise_error(StopIteration)
109
+ expect(enumerable.chunk_count).to eq(2)
110
+
111
+ ::TrickBag::Meta::Classes.undef_class(:ArrayBufferedEnumerable, TrickBag)
112
+
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,141 @@
1
+ require_relative '../../spec_helper'
2
+ require 'trick_bag/enumerables/compound_enumerable'
3
+
4
+ module TrickBag
5
+ module Enumerables
6
+
7
+ describe CompoundEnumerable do
8
+
9
+ context 'input validations' do
10
+
11
+ specify 'an initialization error will be raised if no enumerables are specified' do
12
+ expect(->{ CompoundEnumerable.array_enumerable() }).to raise_error
13
+ expect(->{ CompoundEnumerable.hash_enumerable([:key]) }).to raise_error
14
+ end
15
+
16
+ specify 'an initialization error will be raised if mode is not :yields_arrays or :yields_hashes' do
17
+ expect(->{ CompoundEnumerable.new(:bad_mode, [], [])}).to raise_error
18
+ end
19
+
20
+ specify 'an initialization error will be raised if key array size != enumerables size in :yields_hashes mode' do
21
+ expect(->{ CompoundEnumerable.new(:yields_hashes, [:key1, :key2], [])}).to raise_error
22
+ expect(->{ CompoundEnumerable.hash_enumerable([:key1, :key2], [])}).to raise_error
23
+ end
24
+ end
25
+
26
+
27
+
28
+ context "as array" do
29
+
30
+ context 'with 1 enumerable' do
31
+ specify 'Gets its values then raises a StopIteration error' do
32
+ array = [1, 2]
33
+ e = CompoundEnumerable.array_enumerable(array).each
34
+ expect(e.next).to eq(1)
35
+ expect(e.next).to eq(2)
36
+ expect(->{ e.next }).to raise_error(StopIteration)
37
+ end
38
+ end
39
+
40
+
41
+ context 'with 2 enumerables' do
42
+ specify 'offers correct values in the correct order' do
43
+ outer = %w(A B)
44
+ inner = [1, 2]
45
+ e = CompoundEnumerable.array_enumerable(outer, inner).each
46
+ expect(e.next).to eq(['A', 1])
47
+ expect(e.next).to eq(['A', 2])
48
+ expect(e.next).to eq(['B', 1])
49
+ expect(e.next).to eq(['B', 2])
50
+ expect(->{ e.next }).to raise_error(StopIteration)
51
+ end
52
+ end
53
+
54
+
55
+ context 'with 3 enumerables' do
56
+ specify 'offers correct values in the correct order' do
57
+ domains = %w(aaa.com zzz.com)
58
+ qtypes = %w(A NS)
59
+ qclasses = %w(IN CH)
60
+ e = CompoundEnumerable.array_enumerable(domains, qtypes, qclasses).each
61
+
62
+ expect(e.next).to eq(['aaa.com', 'A', 'IN'])
63
+ expect(e.next).to eq(['aaa.com', 'A', 'CH'])
64
+ expect(e.next).to eq(['aaa.com', 'NS', 'IN'])
65
+ expect(e.next).to eq(['aaa.com', 'NS', 'CH'])
66
+ expect(e.next).to eq(['zzz.com', 'A', 'IN'])
67
+ expect(e.next).to eq(['zzz.com', 'A', 'CH'])
68
+ expect(e.next).to eq(['zzz.com', 'NS', 'IN'])
69
+ expect(e.next).to eq(['zzz.com', 'NS', 'CH'])
70
+ expect(->{ e.next }).to raise_error(StopIteration)
71
+ end
72
+ end
73
+
74
+ context 'is an Enumerable' do
75
+ it "provides the 'take' method" do
76
+ expect(->{ CompoundEnumerable.array_enumerable([1, 2, 3]).take(2)}).not_to raise_error
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ context "as hash" do
83
+
84
+ context 'with 1 enumerable' do
85
+ specify 'Gets its values then raises a StopIteration error' do
86
+ array = [1, 2]
87
+ e = CompoundEnumerable.hash_enumerable([:number], array).each
88
+ expect(e.next).to eq({ number: 1 })
89
+ expect(e.next).to eq({ number: 2 })
90
+ expect(->{ e.next }).to raise_error(StopIteration)
91
+ end
92
+ end
93
+
94
+
95
+ context 'with 2 enumerables' do
96
+ specify 'offers correct values in the correct order' do
97
+ outer = %w(A B)
98
+ inner = [1, 2]
99
+ e = CompoundEnumerable.hash_enumerable([:outer, :inner], outer, inner).each
100
+ expect(e.next).to eq({ outer: 'A', inner: 1 })
101
+ expect(e.next).to eq({ outer: 'A', inner: 2 })
102
+ expect(e.next).to eq({ outer: 'B', inner: 1 })
103
+ expect(e.next).to eq({ outer: 'B', inner: 2 })
104
+ expect(->{ e.next }).to raise_error(StopIteration)
105
+ end
106
+ end
107
+
108
+
109
+ context 'with 3 enumerables' do
110
+ specify 'offers correct values in the correct order' do
111
+ domains = %w(aaa.com zzz.com)
112
+ qtypes = %w(A NS)
113
+ qclasses = %w(IN CH)
114
+ keys = [:domain, :qtype, :qclass]
115
+ e = CompoundEnumerable.hash_enumerable(keys, domains, qtypes, qclasses).each
116
+
117
+ expect(e.next).to eq({ domain: 'aaa.com', qtype: 'A', qclass: 'IN' })
118
+ expect(e.next).to eq({ domain: 'aaa.com', qtype: 'A', qclass: 'CH' })
119
+ expect(e.next).to eq({ domain: 'aaa.com', qtype: 'NS', qclass: 'IN' })
120
+ expect(e.next).to eq({ domain: 'aaa.com', qtype: 'NS', qclass: 'CH' })
121
+
122
+ expect(e.next).to eq({ domain: 'zzz.com', qtype: 'A', qclass: 'IN' })
123
+ expect(e.next).to eq({ domain: 'zzz.com', qtype: 'A', qclass: 'CH' })
124
+ expect(e.next).to eq({ domain: 'zzz.com', qtype: 'NS', qclass: 'IN' })
125
+ expect(e.next).to eq({ domain: 'zzz.com', qtype: 'NS', qclass: 'CH' })
126
+ expect(->{ e.next }).to raise_error(StopIteration)
127
+ end
128
+ end
129
+
130
+ context 'is an Enumerable' do
131
+ it "provides the 'take' method" do
132
+ expect(->{ CompoundEnumerable.hash_enumerable([:num], [1, 2, 3]).take(2)}).not_to raise_error
133
+
134
+ values = CompoundEnumerable.hash_enumerable([:num], [1, 2, 3]).take(2)
135
+ expect(values).to eq([{ num: 1 }, { num: 2 }])
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../../spec_helper'
2
+ require 'trick_bag/enumerables/filtered_enumerable'
3
+
4
+ module TrickBag
5
+ module Enumerables
6
+
7
+ describe FilteredEnumerable do
8
+
9
+ let (:even_filter) { ->(n) { n.even? } }
10
+ specify 'with no filter it behaves as a regular enumberable' do
11
+ e = FilteredEnumerable.new([1,2,3])
12
+ expect(e.to_a).to eq([1, 2, 3])
13
+ end
14
+
15
+ specify 'an even number filter works correctly when filter passed to constructor' do
16
+ e = FilteredEnumerable.new((1..10).to_a, even_filter)
17
+ expect(e.to_a).to eq([2, 4, 6, 8, 10])
18
+ end
19
+
20
+ specify 'an even number filter works correctly when filter passed after creation' do
21
+ e = FilteredEnumerable.new((1..10).to_a)
22
+ e.filter = even_filter
23
+ expect(e.to_a).to eq([2, 4, 6, 8, 10])
24
+ end
25
+
26
+ specify 'works as an enumerator' do
27
+ e = FilteredEnumerable.new((1..5).to_a, even_filter)
28
+ enumerator = e.each
29
+ expect(enumerator.next).to eq(2)
30
+ expect(enumerator.next).to eq(4)
31
+ expect(-> { enumerator.next }).to raise_error(StopIteration)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require 'trick_bag/io/temp_files'
4
+
5
+ module TrickBag::Io
6
+
7
+ describe TempFiles do
8
+
9
+ it 'creates a readable file with the correct content' do
10
+ TempFiles.file_containing('foo') do |filespec|
11
+ expect(File.read(filespec)).to eq('foo')
12
+ end
13
+ end
14
+
15
+ it 'deletes the file when done' do
16
+ filespec = nil
17
+ TempFiles.file_containing('foo') do |fspec|
18
+ filespec = fspec
19
+ end
20
+ expect(File.exist?(filespec)).to be_false
21
+ end
22
+
23
+ it 'uses the prefix in the filespec' do
24
+ prefix = 'jsiapewrqms'
25
+ TempFiles.file_containing('', prefix) do |filespec|
26
+ filename = File.split(filespec).last
27
+ expect(filename.start_with?(prefix)).to be_true
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../../spec_helper'
2
+ require 'trick_bag/io/text_mode_status_updater'
3
+
4
+ module TrickBag
5
+ module Io
6
+ describe TextModeStatusUpdater do
7
+
8
+ it 'instantiates without error' do
9
+ expect(->{ TextModeStatusUpdater.new(->{ '' }) }).not_to raise_error
10
+ end
11
+
12
+ it 'creates a TextModeStatusUpdater' do
13
+ expect(TextModeStatusUpdater.new(->{ '' })).to be_a(TextModeStatusUpdater)
14
+ end
15
+
16
+ specify 'the output string contains the content provided by the passed lambda' do
17
+ string_io = StringIO.new
18
+ updater = TextModeStatusUpdater.new(->{ 'status' }, string_io, true)
19
+ updater.print
20
+ expect(string_io.string).to match(/status/)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,211 @@
1
+ require_relative '../../spec_helper'
2
+ require 'trick_bag/meta/classes'
3
+
4
+ module TrickBag
5
+ module Meta
6
+
7
+ describe Classes do
8
+
9
+ after(:each) { Classes.undef_class('ClassPatchTestClass') }
10
+
11
+
12
+ ####################################################### class?
13
+
14
+ context 'class?' do
15
+
16
+ it 'should recognize String as a class' do
17
+ expect(Classes.class?('String')).to be_true
18
+ end
19
+
20
+ it 'should recognize that RUBY_PLATFORM is defined but is not a class' do
21
+ expect(Classes.class?('RUBY_PLATFORM')).to be_false
22
+ end
23
+
24
+ it 'should recognize a nonexistent constant as not being a class' do
25
+ expect(Classes.class?('Afjkdiurqpweruwiqopurqpweriuqewprzvxcvzxcvzxvzvzvzvcxzvzv')).to be_false
26
+ end
27
+ end
28
+
29
+
30
+ ####################################################### undef_class
31
+
32
+ context 'undef_class' do
33
+ it 'should remove the class' do
34
+ fn = -> do
35
+ class ClassPatchTestClass; end
36
+ ClassPatchTestClass.new # to illustrate that it's available
37
+ Classes.undef_class('ClassPatchTestClass', TrickBag::Meta)
38
+ end
39
+ fn.()
40
+ expect(->{ ClassPatchTestClass.new }).to raise_error
41
+ end
42
+ end
43
+
44
+
45
+ ####################################################### private_attr_reader
46
+
47
+ context 'private_attr_reader' do
48
+
49
+ after(:each) { Classes.undef_class('ClassPatchTestClass') }
50
+
51
+ fn_create_class = ->do
52
+ class ClassPatchTestClass
53
+
54
+ extend Classes
55
+
56
+ private_attr_reader :foo
57
+
58
+ def initialize
59
+ @foo = 123
60
+ puts "foo = #{self.foo}"
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+ it "should be visible inside the class" do
67
+ expect(fn_create_class).not_to raise_error
68
+ end
69
+
70
+ it "should be invisible outside the class" do
71
+ fn_create_class.()
72
+ expect(->{ ClassPatchTestClass.new.foo }).to raise_error
73
+ end
74
+ end
75
+
76
+
77
+ ####################################################### private_attr_writer
78
+
79
+ context 'private_attr_writer' do
80
+
81
+ fn_create_class = ->do
82
+ class ClassPatchTestClass
83
+
84
+ extend Classes
85
+
86
+ private_attr_writer :foo
87
+
88
+ def initialize
89
+ self.foo = 123
90
+ end
91
+ end
92
+ end
93
+
94
+
95
+ it "should be visible inside the class" do
96
+ expect(fn_create_class).not_to raise_error
97
+ end
98
+
99
+ it "should be invisible outside the class" do
100
+ fn_create_class.()
101
+ expect(->{ ClassPatchTestClass.new.foo = 456}).to raise_error
102
+ end
103
+
104
+ end
105
+
106
+ ####################################################### private_attr_accessor
107
+
108
+ context 'private_attr_accessor' do
109
+
110
+ fn_create_class = ->do
111
+ class ClassPatchTestClass
112
+
113
+ extend Classes
114
+
115
+ private_attr_accessor :foo
116
+
117
+ def initialize
118
+ self.foo = 123
119
+ puts self.foo
120
+ end
121
+ end
122
+ end
123
+
124
+
125
+ it "should be visible inside the class" do
126
+ expect(fn_create_class).not_to raise_error
127
+ end
128
+
129
+ it "should be invisible outside the class" do
130
+ fn_create_class.()
131
+ expect(->{ ClassPatchTestClass.new.foo = 456}).to raise_error
132
+ expect(->{ ClassPatchTestClass.new.foo }).to raise_error
133
+ end
134
+
135
+ end
136
+
137
+
138
+ ####################################################### private_attr_writer_public_reader
139
+
140
+ context 'attr_access(:public, :private, :foo)' do
141
+
142
+ fn_create_class = ->do
143
+ class ClassPatchTestClass
144
+
145
+ extend Classes
146
+
147
+ attr_access :public, :private, :foo
148
+
149
+ def initialize
150
+ self.foo = 123
151
+ self.foo
152
+ end
153
+ end
154
+ end
155
+
156
+
157
+ it "should be readable and writable inside the class" do
158
+ expect(fn_create_class).not_to raise_error
159
+ end
160
+
161
+ it "should be readable outside the class" do
162
+ fn_create_class.()
163
+ expect(->{ ClassPatchTestClass.new.foo }).not_to raise_error
164
+ end
165
+
166
+ it "should not be writable outside the class" do
167
+ fn_create_class.()
168
+ expect(->{ ClassPatchTestClass.new.foo = 456}).to raise_error
169
+ end
170
+ end
171
+
172
+ context 'attr_access(:private, :none, :foo)' do
173
+
174
+ fn_create_class = ->do
175
+ class ClassPatchTestClass
176
+
177
+ extend Classes
178
+
179
+ attr_access :private, :none, :foo
180
+
181
+ def initialize
182
+ @foo = 1
183
+ self.foo
184
+ end
185
+ end
186
+ end
187
+
188
+
189
+ it "should be readable inside the class" do
190
+ expect(fn_create_class).not_to raise_error
191
+ end
192
+
193
+
194
+ it "should not be readable outside the class" do
195
+ fn_create_class.()
196
+ expect(->{ ClassPatchTestClass.new.foo }).to raise_error
197
+ end
198
+
199
+ it "should not be readable outside the class" do
200
+ fn_create_class.()
201
+ expect(->{ ClassPatchTestClass.new.foo }).to raise_error
202
+ end
203
+
204
+ it "should not be writable outside the class" do
205
+ fn_create_class.()
206
+ expect(->{ ClassPatchTestClass.new.foo = 456}).to raise_error
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end