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,22 @@
1
+ module TrickBag
2
+ module Io
3
+ module TempFiles
4
+
5
+ module_function
6
+
7
+ def self.file_containing(text, file_prefix = '')
8
+ filespec = nil
9
+ begin
10
+ Tempfile.open(file_prefix) do |file|
11
+ file << text
12
+ filespec = file.path
13
+ end
14
+ yield(filespec)
15
+ ensure
16
+ File.delete filespec if filespec && File.exist?(filespec)
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,59 @@
1
+ module TrickBag
2
+ module Io
3
+
4
+ # Updates the terminal line with text, erasing the original content and displaying at the same place.
5
+ # Uses ANSI escape sequences for cursor positioning and clearing
6
+ # (see http://www.oldlinux.org/Linux.old/Ref-docs/ASCII/ANSI%20Escape%20Sequences.htm).
7
+ class TextModeStatusUpdater
8
+
9
+
10
+ def initialize(text_generator, outstream = $stdout, force_output_non_tty = false)
11
+ @text_generator = text_generator
12
+ @outstream = outstream
13
+ @force_output_non_tty = force_output_non_tty
14
+ @first_time = true
15
+ end
16
+
17
+ def clear_to_end_of_line_text
18
+ "\x1b[2K"
19
+ end
20
+
21
+ def save_cursor_position_text
22
+ "\x1b[s"
23
+ end
24
+
25
+ def go_to_start_of_line_text
26
+ "\x1b0`"
27
+ end
28
+
29
+ def move_cursor_left_text(num_chars)
30
+ "\x1b[#{num_chars}D"
31
+ end
32
+
33
+ def restore_cursor_position_text
34
+ "\x1b[u"
35
+ end
36
+
37
+ def insert_blank_chars_text(num_chars)
38
+ "\x1b[#{num_chars}@"
39
+ end
40
+
41
+ def print
42
+
43
+ # If output is being redirected, don't print anything; it will look like garbage;
44
+ # But if output was forced (e.g. to write to a string), then allow it.
45
+ return unless @outstream.tty? || @force_output_non_tty
46
+
47
+ if @first_time
48
+ @first_time = false
49
+ else
50
+ @outstream.print(move_cursor_left_text(@prev_text_length))
51
+ end
52
+ text = @text_generator.()
53
+ @prev_text_length = text.length
54
+ @outstream.print(clear_to_end_of_line_text + text)
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,87 @@
1
+ # Provides useful additions to class Class.
2
+
3
+ module TrickBag
4
+ module Meta
5
+ module Classes
6
+
7
+ VALID_ACCESS_MODES = [:public, :protected, :private, :none]
8
+
9
+ # Enables concise and flexible creation of getters and setters
10
+ # whose access modes for reading and writing may be different.
11
+ #
12
+ # Access modes can be: :public, :protected, :private, :none
13
+ #
14
+ # For example:
15
+ #
16
+ # attr_access(:private, :none, :foo, :bar)
17
+ #
18
+ # will create accessors for @foo and @bar, and make them private,
19
+ # but will not create any mutators.
20
+ def attr_access(read_access, write_access, *attrs)
21
+ validate_input = -> do
22
+ [read_access, write_access].each do |access|
23
+ unless VALID_ACCESS_MODES.include?(access)
24
+ raise "Access mode must be one of [:public, :protected, :private]."
25
+ end
26
+ end
27
+ end
28
+
29
+ validate_input.()
30
+
31
+ unless read_access == :none
32
+ attr_reader(*attrs)
33
+ send(read_access, *attrs)
34
+ end
35
+
36
+ unless write_access == :none
37
+ attr_writer(*attrs)
38
+ writers = attrs.map { |attr| "#{attr}=".to_sym }
39
+ send(write_access, *writers)
40
+ end
41
+ end
42
+
43
+
44
+ def private_attr_reader(*attrs)
45
+ attr_access(:private, :none, *attrs)
46
+ end
47
+
48
+
49
+ def private_attr_writer(*attrs)
50
+ attr_access(:none, :private, *attrs)
51
+ end
52
+
53
+
54
+ def private_attr_accessor(*attrs)
55
+ attr_access(:private, :private, *attrs)
56
+ end
57
+
58
+
59
+ def protected_attr_reader(*attrs)
60
+ attr_access(:protected, :none, *attrs)
61
+ end
62
+
63
+
64
+ def protected_attr_writer(*attrs)
65
+ attr_access(:none, :protected, *attrs)
66
+ end
67
+
68
+
69
+ def protected_attr_accessor(*attrs)
70
+ attr_access(:protected, :protected, *attrs)
71
+ end
72
+
73
+
74
+ def class?(name, scope = Object)
75
+ scope.const_defined?(name) && scope.const_get(name).is_a?(Class)
76
+ end; module_function :class?
77
+
78
+
79
+ def undef_class(name, scope = Object)
80
+ class_existed = class?(name, scope)
81
+ scope.send(:remove_const, name) if class_existed
82
+ class_existed
83
+ end; module_function :undef_class
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,44 @@
1
+ module TrickBag
2
+ module Numeric
3
+
4
+ # Counts the results of any resolver comparisons made.
5
+ # Like a hash, but does not allow []=; increment is the only way to modify a value.
6
+ class MultiCounter
7
+
8
+ attr_accessor :name
9
+
10
+ def initialize(name = '')
11
+ @name = name
12
+ @counts = Hash.new(0)
13
+ end
14
+
15
+ def add_keys(keys)
16
+ keys.each { |key| @counts[key] = 0 }
17
+ end
18
+
19
+ def increment(key)
20
+ @counts[key] += 1
21
+ end
22
+
23
+ def [](key)
24
+ @counts[key]
25
+ end
26
+
27
+ def keys
28
+ @counts.keys
29
+ end
30
+
31
+ def key_exists?(key)
32
+ keys.include?(key)
33
+ end
34
+
35
+ def to_hash
36
+ @counts.clone
37
+ end
38
+
39
+ def to_s
40
+ "#{self.class} '#{name}': #{@counts.to_s}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ module TrickBag
2
+ module Numeric
3
+ module Totals
4
+
5
+ module_function
6
+
7
+ # @inputs an enumerable of numbers
8
+ # @return a collection containing the corresponding fractions of total of those numbers
9
+ def map_fraction_of_total(inputs)
10
+ return [] if inputs.size == 0
11
+ sum = Float(inputs.inject(:+))
12
+ inputs.map { |n| n / sum }
13
+ end
14
+
15
+
16
+ # @inputs an enumerable of numbers
17
+ # @return a collection containing the corresponding percents of total of those numbers
18
+ def map_percent_of_total(inputs)
19
+ map_fraction_of_total(inputs).map { |n| n * 100 }
20
+ end
21
+
22
+
23
+ # Given a hash whose values are numbers, produces a new hash with the same keys
24
+ # as the original hash, but whose values are the % of total.
25
+ # Ex: for input { foo: 100, bar: 200, baz: 300, razz: 400 }, value returned would be
26
+ # { foo: 10.0, bar: 20.0, baz: 30.0, razz: 40.0 }.
27
+ def fraction_of_total_hash(the_hash)
28
+ new_hash = percent_of_total_hash(the_hash)
29
+ new_hash.keys.each do |key|
30
+ new_hash[key] = new_hash[key] / 100.0
31
+ end
32
+ new_hash
33
+ end
34
+
35
+
36
+ # Given a hash whose values are numbers, produces a new hash with the same keys
37
+ # as the original hash, but whose values are the % of total.
38
+ # Ex: for input { foo: 100, bar: 200, baz: 300, razz: 400 }, value returned would be
39
+ # { foo: 10.0, bar: 20.0, baz: 30.0, razz: 40.0 }.
40
+ def percent_of_total_hash(the_hash)
41
+ sum = Float(the_hash.values.inject(:+))
42
+ keys = the_hash.keys
43
+ keys.each_with_object({}) do |key, percent_total_hash|
44
+ percent_total_hash[key] = 100 * the_hash[key] / sum
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ module TrickBag
2
+ module Operators
3
+
4
+ module_function
5
+
6
+ # Returns whether or not all passed values are equal
7
+ def multi_eq(*values)
8
+ values = values.first if values.is_a?(Array) && values.size == 1
9
+ raise "Must be called with at least 2 parameters" if values.size < 2
10
+ values[1..-1].all? { |value| value == values.first }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require 'trick_bag/io/text_mode_status_updater'
2
+
3
+ module TrickBag
4
+ module Timing
5
+
6
+ module_function
7
+
8
+
9
+ # Calls a predicate proc repeatedly, sleeping the specified interval
10
+ # between calls, and giving up after the specified number of seconds.
11
+ # Displays elapsed and remaining times on the terminal.
12
+ #
13
+ # @param predicate something that can be called with .() or .call
14
+ # that returns a truthy value that indicates no further retries are necessary
15
+ # @param sleep_interval number of seconds (fractions ok) to wait between tries
16
+ # @param timeout_secs maximum number of seconds (fractions ok) during which to retry
17
+ def retry_until_true_or_timeout(
18
+ predicate, sleep_interval, timeout_secs, output_stream = $stdout)
19
+
20
+ success = false
21
+ start_time = Time.now
22
+ end_time = start_time + timeout_secs
23
+ time_elapsed = nil
24
+ time_to_go = nil
25
+ text_generator = ->() { "%9.3f %9.3f" % [time_elapsed, time_to_go] }
26
+ status_updater = ::TrickBag::Io::TextModeStatusUpdater.new(text_generator, output_stream)
27
+
28
+ loop do
29
+ now = Time.now
30
+ time_elapsed = now - start_time
31
+ time_to_go = end_time - now
32
+ time_up = now >= end_time
33
+
34
+ break if time_up
35
+
36
+ success = !! predicate.()
37
+ break if success
38
+
39
+ status_updater.print
40
+ sleep(sleep_interval)
41
+ end
42
+ print "\n"
43
+ success
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,21 @@
1
+ module TrickBag
2
+ module Validations
3
+ module HashValidations
4
+ module_function
5
+
6
+ # Looks to see which keys, if any, are missing from the hash.
7
+ # @return an array of missing keys (empty if none)
8
+ def missing_hash_entries(hash, keys)
9
+ keys.reject { |key| hash.keys.include?(key) }
10
+ end
11
+
12
+ # Looks to see which keys, if any, are missing from the hash.
13
+ # @return nil if none missing, else comma separated string of missing keys.
14
+ def missing_hash_entries_as_string(hash, keys)
15
+ missing_keys = missing_hash_entries(hash, keys)
16
+ missing_keys.empty? ? nil : missing_keys.join(', ')
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module TrickBag
2
+ module Validations
3
+ module ObjectValidations
4
+
5
+ module_function
6
+
7
+
8
+ # Returns an array containing each symbol in vars for which the
9
+ # corresponding instance variable in the specified object is nil.
10
+ def nil_instance_vars(object, vars)
11
+ vars = Array(vars)
12
+ vars.select { |var| object.instance_variable_get(var).nil? }
13
+ end
14
+
15
+ # For each symbol in vars, checks to see that the corresponding instance
16
+ # variable in the specified object is not nil.
17
+ # If any are nil, raises an error listing the nils.
18
+ def raise_on_nil_instance_vars(object, vars)
19
+ nil_vars = nil_instance_vars(object, vars)
20
+ unless nil_vars.empty?
21
+ raise "The following instance variables were nil: #{nil_vars.join(', ')}."
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module TrickBag
2
+ VERSION = "0.30.0"
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspec'
2
+ require 'pry'
3
+
4
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
5
+
6
+ RSpec.configure do |config|
7
+ config.expect_with :rspec do |c|
8
+ # disable the `should` syntax; it's deprecated and will later be removed
9
+ c.syntax = :expect
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ # Adapted from Test Unit test at https://github.com/neilparikh/ruby-linked-list.
2
+
3
+ require_relative '../../spec_helper'
4
+ require 'trick_bag/collections/linked_list'
5
+
6
+ module TrickBag
7
+ module Collections
8
+
9
+ describe LinkedList do
10
+
11
+ specify 'to_a should return an array equal to the array with which it was initialized' do
12
+ array = [1, 3, 5]
13
+ expect(LinkedList.new(*array).to_a).to eq(array)
14
+ end
15
+
16
+ it 'should push correctly' do
17
+ list = LinkedList.new(1)
18
+ list.push(2)
19
+ expect(list.to_a).to eq([1, 2])
20
+ end
21
+
22
+ it 'should pop from a list of multiple elements correctly' do
23
+ list = LinkedList.new(1, 2, 3)
24
+ expect(list.pop).to eq(3)
25
+ expect(list.to_a).to eq([1, 2])
26
+ end
27
+
28
+ it 'should pop from a list of 1 element correctly' do
29
+ list = LinkedList.new(1)
30
+ expect(list.pop).to eq(1)
31
+ expect(list.to_a).to eq([])
32
+ end
33
+
34
+ it 'should raise an error when popping from an empty list' do
35
+ list = LinkedList.new
36
+ expect(list.length).to eq(0)
37
+ expect(->{ list.pop }).to raise_error(RuntimeError)
38
+ end
39
+
40
+ it 'should unshift correctly' do
41
+ list = LinkedList.new(1)
42
+ list.unshift(0)
43
+ expect(list.to_a).to eq([0,1])
44
+ end
45
+
46
+ it 'should shift correctly from a list containing multiple elements' do
47
+ list = LinkedList.new(1, 3, 4)
48
+ expect(list.shift).to eq(1)
49
+ expect(list.to_a).to eq([3, 4])
50
+ end
51
+
52
+ it 'should shift correctly from a list containing 1 element' do
53
+ list = LinkedList.new(1)
54
+ expect(list.shift).to eq(1)
55
+ expect(list.to_a).to eq([])
56
+ end
57
+
58
+ it 'should raise an error when shifting from an empty list' do
59
+ list = LinkedList.new
60
+ expect(->{ list.shift }).to raise_error(RuntimeError)
61
+ end
62
+ end
63
+ end
64
+ end