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
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
19
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in trick_bag.gemspec
4
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ ## v0.30.0
2
+
3
+ * Initial public version.
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