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.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/RELEASE_NOTES.md +3 -0
- data/Rakefile +1 -0
- data/lib/trick_bag.rb +7 -0
- data/lib/trick_bag/collections/linked_list.rb +97 -0
- data/lib/trick_bag/enumerables/buffered_enumerable.rb +90 -0
- data/lib/trick_bag/enumerables/compound_enumerable.rb +114 -0
- data/lib/trick_bag/enumerables/filtered_enumerable.rb +32 -0
- data/lib/trick_bag/io/temp_files.rb +22 -0
- data/lib/trick_bag/io/text_mode_status_updater.rb +59 -0
- data/lib/trick_bag/meta/classes.rb +87 -0
- data/lib/trick_bag/numeric/multi_counter.rb +44 -0
- data/lib/trick_bag/numeric/totals.rb +49 -0
- data/lib/trick_bag/operators/operators.rb +13 -0
- data/lib/trick_bag/timing/timing.rb +47 -0
- data/lib/trick_bag/validations/hash_validations.rb +21 -0
- data/lib/trick_bag/validations/object_validations.rb +26 -0
- data/lib/trick_bag/version.rb +3 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/trick_bag/collections/linked_list_spec.rb +64 -0
- data/spec/trick_bag/enumerables/buffered_enumerable_spec.rb +117 -0
- data/spec/trick_bag/enumerables/compound_enumerable_spec.rb +141 -0
- data/spec/trick_bag/enumerables/filtered_enumerable_spec.rb +35 -0
- data/spec/trick_bag/io/temp_files_spec.rb +31 -0
- data/spec/trick_bag/io/text_mode_status_updater_spec.rb +24 -0
- data/spec/trick_bag/meta/classes_spec.rb +211 -0
- data/spec/trick_bag/numeric/multi_counter_spec.rb +28 -0
- data/spec/trick_bag/numeric/totals_spec.rb +38 -0
- data/spec/trick_bag/operators/operators_spec.rb +33 -0
- data/spec/trick_bag/timing/timing_spec.rb +17 -0
- data/spec/trick_bag/validations/hashes_validations_spec.rb +39 -0
- data/spec/trick_bag/validations/object_validations_spec.rb +23 -0
- data/trick_bag.gemspec +28 -0
- metadata +194 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# For running Guard to rerun tests whenever files change.
|
2
|
+
# Run 'bundle exec guard' from project root.
|
3
|
+
guard :rspec do
|
4
|
+
watch(%r{^spec/.+_spec\.rb$})
|
5
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
6
|
+
watch('spec/spec_helper.rb') { "spec" }
|
7
|
+
end
|
8
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Keith Bennett
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# TrickBag
|
2
|
+
|
3
|
+
This gem is a collection of useful classes and modules for Ruby development.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'trick_bag'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install trick_bag
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
See the unit tests in the spec directory tree for usage examples.
|
23
|
+
|
24
|
+
TODO: Write usage instructions here
|
25
|
+
|
26
|
+
## Contributing
|
27
|
+
|
28
|
+
1. Fork it
|
29
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
30
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
31
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
32
|
+
5. Create new Pull Request
|
data/RELEASE_NOTES.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/trick_bag.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Load all *.rb files in lib/trick_bag and below.
|
2
|
+
# Use a lambda so that the intermediate variables do not survive this file.
|
3
|
+
->() {
|
4
|
+
start_dir = File.join(File.dirname(__FILE__), 'trick_bag') # the lib directory
|
5
|
+
file_mask = "#{start_dir}/**/*.rb"
|
6
|
+
Dir[file_mask].each { |file| require file }
|
7
|
+
}.()
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module TrickBag
|
2
|
+
module Collections
|
3
|
+
|
4
|
+
# Linked List based on git@github.com:neilparikh/ruby-linked-list.git,
|
5
|
+
# but modified somewhat.
|
6
|
+
|
7
|
+
class LinkedList
|
8
|
+
|
9
|
+
class Node
|
10
|
+
attr_accessor :value, :next
|
11
|
+
|
12
|
+
def initialize(value, next_node = nil)
|
13
|
+
@value = value
|
14
|
+
@next = next_node
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :first
|
19
|
+
attr_reader :length
|
20
|
+
|
21
|
+
def initialize(*items)
|
22
|
+
@length = items.length
|
23
|
+
@first = Node.new(items.shift)
|
24
|
+
items.each { |item| push(item) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# adds value to end of list
|
28
|
+
# returns self
|
29
|
+
def push(value)
|
30
|
+
node = Node.new(value)
|
31
|
+
current_node = @first
|
32
|
+
while current_node.next != nil
|
33
|
+
current_node = current_node.next
|
34
|
+
end
|
35
|
+
current_node.next = node
|
36
|
+
@length += 1
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes last element from list
|
41
|
+
# returns that element's value
|
42
|
+
def pop
|
43
|
+
case(@length)
|
44
|
+
|
45
|
+
when 0
|
46
|
+
raise "List is empty"
|
47
|
+
|
48
|
+
when 1
|
49
|
+
@length = 0
|
50
|
+
value = @first.value
|
51
|
+
@first = nil
|
52
|
+
value
|
53
|
+
|
54
|
+
else
|
55
|
+
current = @first
|
56
|
+
while current.next && current.next.next
|
57
|
+
current = current.next
|
58
|
+
end
|
59
|
+
value = current.next.value
|
60
|
+
current.next = nil
|
61
|
+
@length -= 1
|
62
|
+
value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# adds value to beginning of list
|
68
|
+
# returns self
|
69
|
+
def unshift(value)
|
70
|
+
node = Node.new(value, @first)
|
71
|
+
@first = node
|
72
|
+
@length += 1
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Removes first element from list
|
77
|
+
# returns that element's value
|
78
|
+
def shift
|
79
|
+
raise "List is empty" if @length < 1
|
80
|
+
to_return = @first.value
|
81
|
+
@first = @first.next
|
82
|
+
@length -= 1
|
83
|
+
to_return
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_a
|
87
|
+
current_node = @first
|
88
|
+
array = []
|
89
|
+
while current_node != nil
|
90
|
+
array << current_node.value
|
91
|
+
current_node = current_node.next
|
92
|
+
end
|
93
|
+
array
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Provides the plumbing for an enumerator that, in order to serve objects in each(),
|
2
|
+
# fetches them in chunks.
|
3
|
+
#
|
4
|
+
# This is useful, for example, in network requests, when multiple requests can be sent
|
5
|
+
# one immediately after another, and the responses can be collected as a group,
|
6
|
+
# for improved performance.
|
7
|
+
#
|
8
|
+
# The fetch method and fetcher lambda modify the instance's data array directly,
|
9
|
+
# to avoid the need to allow the lambda to modify the data array reference,
|
10
|
+
# needlessly copying arrays,
|
11
|
+
# and to eliminate the need for garbage collecting many array objects
|
12
|
+
# (though the latter is not that important).
|
13
|
+
|
14
|
+
require 'trick_bag/meta/classes'
|
15
|
+
|
16
|
+
module TrickBag
|
17
|
+
module Enumerables
|
18
|
+
|
19
|
+
class BufferedEnumerable
|
20
|
+
|
21
|
+
include Enumerable
|
22
|
+
extend ::TrickBag::Meta::Classes
|
23
|
+
|
24
|
+
attr_accessor :fetcher, :fetch_notifier
|
25
|
+
attr_reader :chunk_size
|
26
|
+
|
27
|
+
attr_access :protected, :protected, :data
|
28
|
+
attr_access :public, :private, :chunk_count, :fetch_count, :yield_count
|
29
|
+
|
30
|
+
|
31
|
+
def self.create_with_lambdas(chunk_size, fetcher, fetch_notifier)
|
32
|
+
instance = self.new(chunk_size)
|
33
|
+
instance.fetcher = fetcher
|
34
|
+
instance.fetch_notifier = fetch_notifier
|
35
|
+
instance
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param fetcher a lambda/proc taking the chunk size as its parameter
|
39
|
+
# @param chunk_size how many objects to fetch at a time
|
40
|
+
# @param fetch_notifier a lambda/proc to be called when a fetch is done;
|
41
|
+
# in case the caller wants to receive notification, update counters, etc.
|
42
|
+
# It's passed the array of objects just fetched, whose size may be
|
43
|
+
# less than chunk size.
|
44
|
+
def initialize(chunk_size)
|
45
|
+
@chunk_size = chunk_size
|
46
|
+
@data = []
|
47
|
+
@chunk_count = 0
|
48
|
+
@fetch_count = 0
|
49
|
+
@yield_count = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Unless you use self.create_with_lambdas to create your instance,
|
54
|
+
# you'll need to override this method in your subclass.
|
55
|
+
def fetch
|
56
|
+
fetcher.(data, chunk_size) if fetcher
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Unless you use self.create_with_lambdas to create your instance,
|
61
|
+
# you'll need to override this method in your subclass.
|
62
|
+
def fetch_notify
|
63
|
+
fetch_notifier.(data) if fetch_notifier
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def each
|
68
|
+
return to_enum unless block_given?
|
69
|
+
|
70
|
+
last_chunk = false
|
71
|
+
loop do
|
72
|
+
if data.empty?
|
73
|
+
return if last_chunk
|
74
|
+
fetch
|
75
|
+
@chunk_count += 1
|
76
|
+
return if data.empty?
|
77
|
+
self.fetch_count = self.fetch_count + data.size
|
78
|
+
last_chunk = true if data.size < chunk_size
|
79
|
+
fetch_notify
|
80
|
+
end
|
81
|
+
|
82
|
+
self.yield_count = self.yield_count + 1
|
83
|
+
object_to_yield = data.shift
|
84
|
+
yield(object_to_yield)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'trick_bag/collections/linked_list'
|
2
|
+
require 'trick_bag/meta/classes'
|
3
|
+
|
4
|
+
module TrickBag
|
5
|
+
module Enumerables
|
6
|
+
|
7
|
+
# An enumerator Used to provide all combinations of a set of Enumerables.
|
8
|
+
|
9
|
+
# For example, for blood types [:a, :b, :ab, :o] and rh [:+, :-],
|
10
|
+
# provides an enumerator whose 'each' method gives:
|
11
|
+
#
|
12
|
+
# [:a, :+]
|
13
|
+
# [:a, :-]
|
14
|
+
# [:b, :+]
|
15
|
+
# [:b, :-]
|
16
|
+
# [:ab, :+]
|
17
|
+
# [:ab, :-]
|
18
|
+
# [:o, :+]
|
19
|
+
# [:o, :-]
|
20
|
+
#
|
21
|
+
# Initialized with enumerables whose 'each' method returns an Enumerator
|
22
|
+
# when no block is provided.
|
23
|
+
#
|
24
|
+
# Guaranteed to follow the order as shown above, that is, each value of
|
25
|
+
# the first enumerable will be processed in its entirety before advancing
|
26
|
+
# to the next value.
|
27
|
+
#
|
28
|
+
# Can be used with an arbitrary number of enumerables.
|
29
|
+
class CompoundEnumerable
|
30
|
+
|
31
|
+
include Enumerable
|
32
|
+
extend ::TrickBag::Meta::Classes
|
33
|
+
|
34
|
+
attr_reader :enum_count, :keys
|
35
|
+
private_attr_reader :enumerables
|
36
|
+
attr_reader :mode
|
37
|
+
|
38
|
+
# Creates a compound enumerable that returns a hash whenever 'each' is called
|
39
|
+
def self.hash_enumerable(keys, *enumerables)
|
40
|
+
self.new(:yields_hashes, keys, *enumerables)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Creates a compound enumerable that returns an array whenever 'each' is called
|
45
|
+
def self.array_enumerable(*enumerables)
|
46
|
+
self.new(:yields_arrays, nil, *enumerables)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
def initialize(mode, keys, *enumerables)
|
52
|
+
|
53
|
+
validate_inputs = ->do
|
54
|
+
raise "Mode must be either :yields_arrays or :yields_hashes" unless [:yields_arrays, :yields_hashes].include?(mode)
|
55
|
+
raise "Keys not provided" if mode == :yields_hashes && (! keys.is_a?(Array))
|
56
|
+
raise "No enumerables provided" if enumerables.empty?
|
57
|
+
|
58
|
+
if mode == :yields_hashes && (keys.size != enumerables.size)
|
59
|
+
raise "Key array size (#{keys.size}) is different from enumerables size (#{enumerables.size})."
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
validate_inputs.()
|
65
|
+
@enumerables = enumerables
|
66
|
+
@enum_count = enumerables.size
|
67
|
+
@mode = mode
|
68
|
+
@keys = keys
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def each(&block)
|
73
|
+
return to_enum unless block_given?
|
74
|
+
return if enum_count == 0
|
75
|
+
initial_value = mode == :yields_arrays ? [] : {}
|
76
|
+
each_multi_enumerable(0, ::TrickBag::Collections::LinkedList.new(*@enumerables).first, initial_value, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# This method will be called recursively down the list of enumerables.
|
81
|
+
# @param node will advance to the next node for each recursive call
|
82
|
+
# @param values will be a list of values collected on the stack
|
83
|
+
def each_multi_enumerable(depth, node, values, &block)
|
84
|
+
enumerable = node.value
|
85
|
+
is_deepest_enumerable = node.next.nil?
|
86
|
+
|
87
|
+
as_array = ->(thing) do
|
88
|
+
new_value_array = values + [thing]
|
89
|
+
if is_deepest_enumerable
|
90
|
+
yield *new_value_array
|
91
|
+
else
|
92
|
+
each_multi_enumerable(depth + 1, node.next, new_value_array, &block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
as_hash = ->(thing) do
|
97
|
+
key = keys[depth]
|
98
|
+
new_values = values.clone
|
99
|
+
new_values[key] = thing
|
100
|
+
if is_deepest_enumerable
|
101
|
+
yield new_values
|
102
|
+
else
|
103
|
+
each_multi_enumerable(depth + 1, node.next, new_values, &block)
|
104
|
+
end
|
105
|
+
new_values
|
106
|
+
end
|
107
|
+
|
108
|
+
enumerable.each do |thing|
|
109
|
+
mode == :yields_arrays ? as_array.(thing) : as_hash.(thing)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module TrickBag
|
2
|
+
module Enumerables
|
3
|
+
|
4
|
+
|
5
|
+
# Decorator that wraps an Enumerable and takes a filter predicate
|
6
|
+
# to determine whether or not objects from the wrapped enumerator
|
7
|
+
# should be processed.
|
8
|
+
#
|
9
|
+
# The filter is an optional parameter on the constructor, but can
|
10
|
+
# also be set after creation with a mutator.
|
11
|
+
class FilteredEnumerable
|
12
|
+
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
attr_reader :inner_enum
|
16
|
+
attr_accessor :filter
|
17
|
+
|
18
|
+
def initialize(inner_enum, filter = nil)
|
19
|
+
@inner_enum = inner_enum
|
20
|
+
@filter = filter
|
21
|
+
end
|
22
|
+
|
23
|
+
def each
|
24
|
+
return to_enum unless block_given?
|
25
|
+
|
26
|
+
inner_enum.each do |thing|
|
27
|
+
yield thing if filter.nil? || filter.(thing)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|