trick_bag 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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