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
@@ -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