trick_bag 0.37.0 → 0.38.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/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.38.0
2
+
3
+ * Added documentation.
4
+ * Changed string returned by missing_hash_entries_as_string to be array.inspect.
5
+ * TextModeStatusUpdater.print now calls to_s on lambda's return value.
6
+ * Modified EndlessLastEnumerable so it could be initialized with any Enumerable, not only an Array.
7
+ * Added documentation, tests that enumerators return enumerables when yield is called without a block.
8
+ * Changed FilteredEnumerable default filter to be a lambda that always returns true.
9
+ * Added LinkedList#to_ary.
10
+
11
+
1
12
  ## v0.37.0
2
13
 
3
14
  * Fixed: 'os' gem dependency was not properly specified in gemspec.
@@ -3,7 +3,6 @@ module Collections
3
3
 
4
4
  # Linked List based on git@github.com:neilparikh/ruby-linked-list.git,
5
5
  # but modified somewhat.
6
-
7
6
  class LinkedList
8
7
 
9
8
  class Node
@@ -18,18 +17,21 @@ class LinkedList
18
17
  attr_accessor :first
19
18
  attr_reader :length
20
19
 
20
+
21
+ # @param items items with which to initialize the list
21
22
  def initialize(*items)
22
23
  @length = items.length
23
24
  @first = Node.new(items.shift)
24
25
  items.each { |item| push(item) }
25
26
  end
26
27
 
27
- # adds value to end of list
28
- # returns self
28
+
29
+ # @param value value to add to end of list
30
+ # @return self
29
31
  def push(value)
30
32
  node = Node.new(value)
31
33
  current_node = @first
32
- while current_node.next != nil
34
+ while current_node.next
33
35
  current_node = current_node.next
34
36
  end
35
37
  current_node.next = node
@@ -37,8 +39,9 @@ class LinkedList
37
39
  self
38
40
  end
39
41
 
40
- # Removes last element from list
41
- # returns that element's value
42
+
43
+ # Returns the last element from the list and removes it
44
+ # @return the last element's value
42
45
  def pop
43
46
  case(@length)
44
47
 
@@ -64,8 +67,9 @@ class LinkedList
64
67
  end
65
68
 
66
69
 
67
- # adds value to beginning of list
68
- # returns self
70
+ # Adds a value to the beginning of the list
71
+ # @param value value to add to beginning of list
72
+ # @return self
69
73
  def unshift(value)
70
74
  node = Node.new(value, @first)
71
75
  @first = node
@@ -73,16 +77,19 @@ class LinkedList
73
77
  self
74
78
  end
75
79
 
76
- # Removes first element from list
77
- # returns that element's value
80
+
81
+ # Removes the first element from the list
82
+ # @return the first element's value
78
83
  def shift
79
84
  raise "List is empty" if @length < 1
80
- to_return = @first.value
85
+ return_value = @first.value
81
86
  @first = @first.next
82
87
  @length -= 1
83
- to_return
88
+ return_value
84
89
  end
85
90
 
91
+
92
+ # @return the values in this list as an array
86
93
  def to_a
87
94
  current_node = @first
88
95
  array = []
@@ -92,6 +99,12 @@ class LinkedList
92
99
  end
93
100
  array
94
101
  end
102
+
103
+
104
+ # @return the values in this list as an array
105
+ def to_ary
106
+ to_a
107
+ end
95
108
  end
96
109
  end
97
110
  end
@@ -1,6 +1,20 @@
1
+
2
+ require 'trick_bag/meta/classes'
3
+
4
+ module TrickBag
5
+ module Enumerables
6
+
1
7
  # Provides the plumbing for an enumerator that, in order to serve objects in each(),
2
8
  # fetches them in chunks.
3
9
  #
10
+ # This class knows nothing about how to fetch anything; that behavior is provided
11
+ # by either subclassing this class, or calling .create_with_lambdas and passing
12
+ # a lambda that knows how to do that.
13
+ #
14
+ # Also supported is an optional fetch notification, a method or lambda that will
15
+ # be called whenever a fetch is done. This can be useful to update counters,
16
+ # provide user feedback (e.g. a progress bar)
17
+ #
4
18
  # This is useful, for example, in network requests, when multiple requests can be sent
5
19
  # one immediately after another, and the responses can be collected as a group,
6
20
  # for improved performance.
@@ -10,15 +24,10 @@
10
24
  # needlessly copying arrays,
11
25
  # and to eliminate the need for garbage collecting many array objects
12
26
  # (though the latter is not that important).
13
-
14
- require 'trick_bag/meta/classes'
15
-
16
- module TrickBag
17
- module Enumerables
18
-
19
27
  class BufferedEnumerable
20
28
 
21
29
  include Enumerable
30
+
22
31
  extend ::TrickBag::Meta::Classes
23
32
 
24
33
  attr_accessor :fetcher, :fetch_notifier
@@ -28,6 +37,10 @@ class BufferedEnumerable
28
37
  attr_access :public, :private, :chunk_count, :fetch_count, :yield_count
29
38
 
30
39
 
40
+ # Creates an instance with lambdas for fetch and fetch notify behaviors.
41
+ # @param chunk_size the maximum number of objects to be buffered
42
+ # @param fetcher lambda to be called to fetch to fill the buffer
43
+ # @param fetch_notifier lambda to be called to when a fetch is done
31
44
  def self.create_with_lambdas(chunk_size, fetcher, fetch_notifier)
32
45
  instance = self.new(chunk_size)
33
46
  instance.fetcher = fetcher
@@ -35,9 +48,9 @@ class BufferedEnumerable
35
48
  instance
36
49
  end
37
50
 
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;
51
+ # @param fetcher lambda to be called to fetch to fill the buffer
52
+ # @param chunk_size the maximum number of objects to be buffered
53
+ # @param fetch_notifier lambda to be called to when a fetch is done
41
54
  # in case the caller wants to receive notification, update counters, etc.
42
55
  # It's passed the array of objects just fetched, whose size may be
43
56
  # less than chunk size.
@@ -64,6 +77,10 @@ class BufferedEnumerable
64
77
  end
65
78
 
66
79
 
80
+ # Enumerable.each method.
81
+ # Note that, like all Enumerable.each methods, if you pass it without a block,
82
+ # it will return an enumerator, and any cleanup that would normally be done
83
+ # after each's loop has completed will not happen.
67
84
  def each
68
85
  return to_enum unless block_given?
69
86
 
@@ -47,6 +47,8 @@ class CompoundEnumerable
47
47
  end
48
48
 
49
49
 
50
+ # @param mode determines whether each should yield hashes or arrays.
51
+ # Permissible values are [:yields_arrays, :yields_hashes].
50
52
  def initialize(mode, keys, *enumerables)
51
53
 
52
54
  validate_inputs = ->do
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module TrickBag
2
4
  module Enumerables
3
5
 
@@ -23,16 +25,16 @@ module Enumerables
23
25
 
24
26
  include Enumerable
25
27
 
26
- attr_reader :array
28
+ attr_reader :inner_enumerable
27
29
 
28
- def initialize(array_or_number)
29
- @array = case array_or_number
30
- when Array
31
- array_or_number
30
+ def initialize(enumerable_or_number)
31
+ @inner_enumerable = case enumerable_or_number
32
+ when Enumerable
33
+ enumerable_or_number
32
34
  when Numeric
33
- [array_or_number]
35
+ Array(enumerable_or_number)
34
36
  else
35
- raise RuntimeError.new("Unsupported data type (#{array_or_number.class}.")
37
+ raise RuntimeError.new("Unsupported data type (#{enumerable_or_number.class}.")
36
38
  end
37
39
  @pos = 0
38
40
  end
@@ -40,20 +42,21 @@ module Enumerables
40
42
 
41
43
  def each
42
44
  return to_enum unless block_given?
45
+ last_number = nil
46
+
47
+ inner_enumerable.each do |number|
48
+ yield(number)
49
+ last_number = number
50
+ end
51
+
43
52
  loop do
44
- if @pos >= @array.size
45
- value = @array.last
46
- else
47
- value = @array[@pos]
48
- @pos += 1
49
- end
50
- yield(value)
53
+ yield(last_number)
51
54
  end
52
55
  end
53
56
 
54
57
 
55
58
  def ==(other) # mostly for testing
56
- other.is_a?(self.class) && other.array == array
59
+ other.is_a?(self.class) && other.inner_enumerable == inner_enumerable
57
60
  end
58
61
  end
59
62
  end
@@ -9,6 +9,13 @@ module Enumerables
9
9
  #
10
10
  # If calling each without a block to get an enumerator, call enumerator.close when done
11
11
  # if not all values have been exhausted, so that the input file will be closed.
12
+ #
13
+ # Supports specifying a starting position and maximum count. For example
14
+ # if the starting position is 2, then the first 2 valid lines will be
15
+ # discarded, and yielding will begin with the next valid line.
16
+ #
17
+ # Similarly, specifying a maximum count will cause yielding to end after
18
+ # max_count objects have been yielded.
12
19
  class FileLineReader
13
20
 
14
21
  include Enumerable
@@ -24,22 +31,27 @@ class FileLineReader
24
31
  @start_and_max = TrickBag::Numeric::StartAndMax.new(start_pos, max_count)
25
32
  end
26
33
 
27
-
34
+ # @return the position of the first valid object to be yielded.
28
35
  def start_pos
29
36
  start_and_max.start_pos
30
37
  end
31
38
 
32
39
 
40
+ # @return the maximum number of objects to be yielded
33
41
  def max_count
34
42
  start_and_max.max_count
35
43
  end
36
44
 
37
45
 
46
+ # @param line the line to test
47
+ # @return whether or not this line is valid (eligible for yielding),
48
+ # specifically not empty and not a comment
38
49
  def line_valid?(line)
39
50
  ! (line.empty? || /^#/ === line)
40
51
  end
41
52
 
42
53
 
54
+ # Closes the file if it is not null and has not already been closed
43
55
  def close_file(file)
44
56
  if file && (! file.closed?)
45
57
  file.close
@@ -47,12 +59,15 @@ class FileLineReader
47
59
  end
48
60
 
49
61
 
50
- # Any enum returned should have a close method to close the file from which lines are read.
51
- # Get the enumerator from the superclass, and add a close method to it.
62
+ # Any enumerator returned should have a close method to close the file
63
+ # from which lines are read. This method gets an enumerator from the superclass,
64
+ # and adds a 'file' attribute and a close method to it.
52
65
  def to_enum(file)
53
66
  enumerator = super()
54
67
  enumerator.instance_variable_set(:@file, file)
55
68
 
69
+ # This method is defined on the instance of the new enumerator.
70
+ # It closes the input file.
56
71
  def enumerator.close
57
72
  if @file && (! @file.closed?)
58
73
  @file.close
@@ -12,19 +12,23 @@ class FilteredEnumerable
12
12
 
13
13
  include Enumerable
14
14
 
15
- attr_reader :inner_enum
15
+ attr_reader :wrapped_enumerator
16
16
  attr_accessor :filter
17
17
 
18
- def initialize(inner_enum, filter = nil)
19
- @inner_enum = inner_enum
18
+ # @param wrapped_enumerator the enumerator to filter
19
+ # @param filter a lambda that returns whether or not to yield a given object,
20
+ # defaults to lambda always returning true
21
+ def initialize(wrapped_enumerator, filter = ->(_) { true })
22
+ @wrapped_enumerator = wrapped_enumerator
20
23
  @filter = filter
21
24
  end
22
25
 
26
+ # Standard Enumerable.each; returns an Enumerator if called without a block
23
27
  def each
24
28
  return to_enum unless block_given?
25
29
 
26
- inner_enum.each do |thing|
27
- yield thing if filter.nil? || filter.(thing)
30
+ wrapped_enumerator.each do |thing|
31
+ yield thing if filter.(thing)
28
32
  end
29
33
  end
30
34
  end
@@ -5,13 +5,20 @@ module Formatters
5
5
 
6
6
  module_function
7
7
 
8
+ # Formats a number of seconds as a succinct string, with days, hours,
9
+ # minutes, and seconds. Examples:
10
+ #
11
+ # duration_to_s(1_000_000) => "11 d, 13 h, 46 m, 40 s"
12
+ # duration_to_s(1_000) => "16 m, 40 s"
13
+ # duration_to_s(-1_000) => "-16 m, 40 s"
14
+ # duration_to_s(1.234567) => "1.234567 s"
15
+ # duration_to_s(1000.234567) => "16 m, 40.23456699999997 s"
8
16
  def duration_to_s(seconds)
9
17
 
10
18
  seconds_in_minute = 60
11
19
  seconds_in_hour = 60 * seconds_in_minute
12
20
  seconds_in_day = 24 *seconds_in_hour
13
21
 
14
- seconds = seconds.to_i
15
22
  str = ''
16
23
 
17
24
  if seconds < 0
@@ -53,6 +60,9 @@ module Formatters
53
60
  end
54
61
 
55
62
 
63
+ # Returns a timestamp string suitable for use in filespecs,
64
+ # whose string sort order will be the same as a chronological sort would be.
65
+ # @param datetime the date to format, defaults to DateTime.now
56
66
  def timestamp(datetime = DateTime.now)
57
67
  datetime.strftime('%Y-%m-%d_%H-%M-%S')
58
68
  end
@@ -60,7 +70,12 @@ module Formatters
60
70
 
61
71
  # Replaces all occurrences of marker with the current date/time in
62
72
  # YYYYMMDD-HHMMSS format.
63
- def replace_with_timestamp(string, marker = '{dt}', datetime = DateTime.now)
73
+ #
74
+ # Useful for creating filespecs with static content that will differ by date,
75
+ # for example:
76
+ #
77
+ # replace_with_timestamp('my-app-result-{dt}.txt') => "my-app-result-2014-03-24_15-25-57.txt"
78
+ def replace_with_timestamp(string, marker = '{dt}', datetime = DateTime.now)
64
79
  string.gsub(marker, timestamp(datetime))
65
80
  end
66
81
 
@@ -4,7 +4,15 @@ module TempFiles
4
4
 
5
5
  module_function
6
6
 
7
+ # Creates a temporary file containing the specified text,
8
+ # passes its filespec to the passed block, then deletes the file.
9
+ #
10
+ # @param text the text to write to the temporary file
11
+ # @param file_prefix optional prefix for the temporary file's name
12
+ # @yield filespec of the temporary file
7
13
  def self.file_containing(text, file_prefix = '')
14
+ raise "This method must be called with a code block." unless block_given?
15
+
8
16
  filespec = nil
9
17
  begin
10
18
  Tempfile.open(file_prefix) do |file|
@@ -4,6 +4,11 @@ module Io
4
4
  # Updates the terminal line with text, erasing the original content and displaying at the same place.
5
5
  # Uses ANSI escape sequences for cursor positioning and clearing
6
6
  # (see http://www.oldlinux.org/Linux.old/Ref-docs/ASCII/ANSI%20Escape%20Sequences.htm).
7
+ #
8
+ # Example:
9
+ #
10
+ # updater = TrickBag::Io::TextModeStatusUpdater.new(->{ Time.now })
11
+ # 5.times { updater.print; sleep(1) }
7
12
  class TextModeStatusUpdater
8
13
 
9
14
 
@@ -14,6 +19,33 @@ class TextModeStatusUpdater
14
19
  @first_time = true
15
20
  end
16
21
 
22
+
23
+ # Causes the text generator lambda to be called, calls to_s on its result,
24
+ # and outputs the resulting text to the output stream, moving the cursor
25
+ # left to the beginning of the previous call's output if not the first call to print.
26
+ #
27
+ # Since this method uses ASCII escape sequences that would look messy in a file,
28
+ # this method will silently return if the output stream is not a TTY, unless
29
+ # @force_output_non_tty has been set to true.
30
+ def print
31
+
32
+ # If output is being redirected, don't print anything; it will look like garbage;
33
+ # But if output was forced (e.g. to write to a string), then allow it.
34
+ return unless @outstream.tty? || @force_output_non_tty
35
+
36
+ if @first_time
37
+ @first_time = false
38
+ else
39
+ @outstream.print(move_cursor_left_text(@prev_text_length))
40
+ end
41
+ text = @text_generator.().to_s
42
+ @prev_text_length = text.length
43
+ @outstream.print(clear_to_end_of_line_text + text)
44
+ end
45
+
46
+ # The following methods are for cursor placement and text clearing:
47
+ private
48
+
17
49
  def clear_to_end_of_line_text
18
50
  "\x1b[2K"
19
51
  end
@@ -38,22 +70,6 @@ class TextModeStatusUpdater
38
70
  "\x1b[#{num_chars}@"
39
71
  end
40
72
 
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
73
  end
58
74
  end
59
75
  end
@@ -15,13 +15,14 @@ module Classes
15
15
  #
16
16
  # attr_access(:private, :none, :foo, :bar)
17
17
  #
18
- # will create accessors for @foo and @bar, and make them private,
19
- # but will not create any mutators.
18
+ # will create readers (aka accessors or getters) for @foo and @bar,
19
+ # and make them private, but will not create any writers
20
+ # (aka mutators or setters).
20
21
  def attr_access(read_access, write_access, *attrs)
21
22
  validate_input = -> do
22
23
  [read_access, write_access].each do |access|
23
24
  unless VALID_ACCESS_MODES.include?(access)
24
- raise "Access mode must be one of [:public, :protected, :private]."
25
+ raise "Access mode must be one of #{VALID_ACCESS_MODES.inspect}]."
25
26
  end
26
27
  end
27
28
  end
@@ -41,41 +42,60 @@ module Classes
41
42
  end
42
43
 
43
44
 
45
+ # Creates private readers and no writers for the specified attributes.
44
46
  def private_attr_reader(*attrs)
45
47
  attr_access(:private, :none, *attrs)
46
48
  end
47
49
 
48
50
 
51
+ # Creates private writers and no readers for the specified attributes.
49
52
  def private_attr_writer(*attrs)
50
53
  attr_access(:none, :private, *attrs)
51
54
  end
52
55
 
53
56
 
57
+ # Creates private readers and writers for the specified attributes.
54
58
  def private_attr_accessor(*attrs)
55
59
  attr_access(:private, :private, *attrs)
56
60
  end
57
61
 
58
62
 
63
+ # Creates protected readers and no writers for the specified attributes.
59
64
  def protected_attr_reader(*attrs)
60
65
  attr_access(:protected, :none, *attrs)
61
66
  end
62
67
 
63
68
 
69
+ # Creates protected writers and no readers for the specified attributes.
64
70
  def protected_attr_writer(*attrs)
65
71
  attr_access(:none, :protected, *attrs)
66
72
  end
67
73
 
68
74
 
75
+ # Creates protected readers and writers for the specified attributes.
69
76
  def protected_attr_accessor(*attrs)
70
77
  attr_access(:protected, :protected, *attrs)
71
78
  end
72
79
 
73
80
 
81
+ # Returns whether or not the specified name is a defined class.
82
+ # @param class class name (a string)
83
+ # @param scope scope in which the class would be defined, defaults to Object
84
+ # (e.g. could be MyModule::InnerModule)
85
+ #
86
+ # e.g. module M; class X; end; end; class?('X', M) => true
74
87
  def class?(name, scope = Object)
75
88
  scope.const_defined?(name) && scope.const_get(name).is_a?(Class)
76
89
  end; module_function :class?
77
90
 
78
91
 
92
+ # Undefines a class; useful for unit testing code that manipulates classes.
93
+ #
94
+ # @param name name of class to undefine
95
+ # @param scope scope in which the class is defined, defaults to Object
96
+ #
97
+ # e.g. module M; class X; end; end; class?('X', M) => true
98
+ # undef_class('X', M); class?('X', M) => false
79
99
  def undef_class(name, scope = Object)
80
100
  class_existed = class?(name, scope)
81
101
  scope.send(:remove_const, name) if class_existed
@@ -7,35 +7,47 @@ class MultiCounter
7
7
 
8
8
  attr_accessor :name
9
9
 
10
+ # Creates a multicounter.
11
+ #
12
+ # @param name optional name for printing in to_s
10
13
  def initialize(name = '')
11
14
  @name = name
12
15
  @counts = Hash.new(0)
13
16
  end
14
17
 
18
+ # Adds keys in the passed enumerable to this counter.
15
19
  def add_keys(keys)
16
20
  keys.each { |key| @counts[key] = 0 }
17
21
  end
18
22
 
23
+ # Increments the value corresponding to the specified key.
19
24
  def increment(key)
20
25
  @counts[key] += 1
21
26
  end
22
27
 
28
+ # Returns the count for the specified key.
23
29
  def [](key)
24
30
  @counts[key]
25
31
  end
26
32
 
33
+ # Returns this counter's keys.
27
34
  def keys
28
35
  @counts.keys
29
36
  end
30
37
 
38
+ # Returns whether or not the specified key exists in this counter.
31
39
  def key_exists?(key)
32
40
  keys.include?(key)
33
41
  end
34
42
 
43
+ # Creates a hash whose keys are this counter's keys, and whose values are
44
+ # their corresponding values.
35
45
  def to_hash
36
46
  @counts.clone
37
47
  end
38
48
 
49
+ # Returns a string representing this counter, including its values,
50
+ # and, if specified, its optional name.
39
51
  def to_s
40
52
  "#{self.class} '#{name}': #{@counts.to_s}"
41
53
  end
@@ -2,7 +2,7 @@ module TrickBag
2
2
  module Numeric
3
3
 
4
4
  # Like a Range, but includes useful functions with understandable names
5
- # that account for the absence of limits.
5
+ # that account for the limits or absences of limits.
6
6
  class StartAndMax
7
7
 
8
8
  attr_reader :start_pos, :max_count
@@ -11,22 +11,27 @@ module Numeric
11
11
  # @param max_count - the maximum number of records to be served by each(); or :infinite
12
12
  def initialize(start_pos = :first, max_count = :infinite)
13
13
  @start_pos = ([nil, :first].include?(start_pos) || start_pos <= 0) ? :first : start_pos
14
- @max_count = ([nil, :infinite].include?(max_count) || max_count <= 0) ? :infinite : max_count
14
+ @max_count = ([nil, :infinite].include?(max_count) || max_count <= 0) ? :infinite : max_count
15
15
  end
16
16
 
17
17
 
18
+ # If a starting position has been specified, returns whether or not the specified number
19
+ # >= that position. Else, returns true unconditionally.
18
20
  def start_position_reached?(num)
19
21
  @start_pos == :first || num >= @start_pos
20
22
  end
21
23
 
22
24
 
25
+ # If a maximum count has been specified, returns whether or not the specified number
26
+ # >= that count. Else, returns false unconditionally.
23
27
  def max_count_reached?(count)
24
28
  @max_count != :infinite && (count >= @max_count)
25
29
  end
26
30
 
27
31
 
32
+ # Returns string representation including starting position and maximum count.
28
33
  def to_s
29
- "#{self.class}: start position=#{start_pos}, max count=#{max_count}"
34
+ "#{self.class}: start position=#{start_pos.inspect}, max count=#{max_count.inspect}"
30
35
  end
31
36
 
32
37
  end
@@ -6,6 +6,8 @@ module Totals
6
6
 
7
7
  # @inputs an enumerable of numbers
8
8
  # @return a collection containing the corresponding fractions of total of those numbers
9
+ #
10
+ # e.g.: map_fraction_of_total [10,20,30] => [0.16666666666666666, 0.3333333333333333, 0.5]
9
11
  def map_fraction_of_total(inputs)
10
12
  return [] if inputs.size == 0
11
13
  sum = Float(inputs.inject(:+))
@@ -15,6 +17,8 @@ module Totals
15
17
 
16
18
  # @inputs an enumerable of numbers
17
19
  # @return a collection containing the corresponding percents of total of those numbers
20
+ #
21
+ # e.g. map_percent_of_total [10,20,30] => [16.666666666666664, 33.33333333333333, 50.0]
18
22
  def map_percent_of_total(inputs)
19
23
  map_fraction_of_total(inputs).map { |n| n * 100 }
20
24
  end
@@ -22,8 +26,9 @@ module Totals
22
26
 
23
27
  # Given a hash whose values are numbers, produces a new hash with the same keys
24
28
  # 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 }.
29
+ #
30
+ # Ex: fraction_of_total_hash({ foo: 10.0, bar: 20.0, baz: 30.0, razz: 40.0 })
31
+ # => {:foo=>0.1, :bar=>0.2, :baz=>0.3, :razz=>0.4}
27
32
  def fraction_of_total_hash(the_hash)
28
33
  new_hash = percent_of_total_hash(the_hash)
29
34
  new_hash.keys.each do |key|
@@ -35,8 +40,9 @@ module Totals
35
40
 
36
41
  # Given a hash whose values are numbers, produces a new hash with the same keys
37
42
  # 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 }.
43
+ #
44
+ # Ex: percent_of_total_hash({ foo: 10.0, bar: 20.0, baz: 30.0, razz: 40.0 })
45
+ # => {:foo=>10.0, :bar=>20.0, :baz=>30.0, :razz=>40.0}
40
46
  def percent_of_total_hash(the_hash)
41
47
  sum = Float(the_hash.values.inject(:+))
42
48
  keys = the_hash.keys
@@ -4,6 +4,8 @@ module Operators
4
4
  module_function
5
5
 
6
6
  # Returns whether or not all passed values are equal
7
+ #
8
+ # Ex: multi_eq(1, 1, 1, 2) => false; multi_eq(1, 1, 1, 1) => true
7
9
  def multi_eq(*values)
8
10
  values = values.first if values.is_a?(Array) && values.size == 1
9
11
  raise "Must be called with at least 2 parameters" if values.size < 2
@@ -14,6 +14,8 @@ module Timing
14
14
  # that returns a truthy value that indicates no further retries are necessary
15
15
  # @param sleep_interval number of seconds (fractions ok) to wait between tries
16
16
  # @param timeout_secs maximum number of seconds (fractions ok) during which to retry
17
+ #
18
+ # Ex: TrickBag::Timing.retry_until_true_or_timeout(->{false}, 1.0, 5)
17
19
  def retry_until_true_or_timeout(
18
20
  predicate, sleep_interval, timeout_secs, output_stream = $stdout)
19
21
 
@@ -14,11 +14,12 @@ module HashValidations
14
14
  keys.reject { |key| the_hash.keys.include?(key) }
15
15
  end
16
16
 
17
+
17
18
  # Looks to see which keys, if any, are missing from the hash.
18
19
  # @return nil if none missing, else comma separated string of missing keys.
19
20
  def missing_hash_entries_as_string(the_hash, *keys)
20
21
  missing_keys = missing_hash_entries(the_hash, *keys)
21
- missing_keys.empty? ? nil : missing_keys.join(', ')
22
+ missing_keys.empty? ? nil : missing_keys.inspect
22
23
  end
23
24
 
24
25
  # Checks to see that all passed keys are present in the hash.
@@ -1,3 +1,3 @@
1
1
  module TrickBag
2
- VERSION = "0.37.0"
2
+ VERSION = "0.38.0"
3
3
  end
@@ -8,9 +8,11 @@ module Collections
8
8
 
9
9
  describe LinkedList do
10
10
 
11
+ let(:sample_array) { [1, 2, 3] }
12
+ let(:sample_list) { LinkedList.new(*sample_array) }
13
+
11
14
  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)
15
+ expect(LinkedList.new(*sample_array).to_a).to eq(sample_array)
14
16
  end
15
17
 
16
18
  it 'should push correctly' do
@@ -20,9 +22,8 @@ describe LinkedList do
20
22
  end
21
23
 
22
24
  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])
25
+ expect(sample_list.pop).to eq(3)
26
+ expect(sample_list.to_a).to eq([1, 2])
26
27
  end
27
28
 
28
29
  it 'should pop from a list of 1 element correctly' do
@@ -44,9 +45,8 @@ describe LinkedList do
44
45
  end
45
46
 
46
47
  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])
48
+ expect(sample_list.shift).to eq(1)
49
+ expect(sample_list.to_a).to eq([2, 3])
50
50
  end
51
51
 
52
52
  it 'should shift correctly from a list containing 1 element' do
@@ -59,6 +59,12 @@ describe LinkedList do
59
59
  list = LinkedList.new
60
60
  expect(->{ list.shift }).to raise_error(RuntimeError)
61
61
  end
62
+
63
+ specify 'to_ary should return an array identical to that returned by to_a' do
64
+ a = sample_list.to_a
65
+ ary = sample_list.to_ary
66
+ expect(ary).to eq(a)
67
+ end
62
68
  end
63
69
  end
64
70
  end
@@ -73,6 +73,7 @@ module Enumerables
73
73
  (1..10).each do |n|
74
74
  expect(enumerator.next).to eq(n)
75
75
  end
76
+ expect(enumerator).to be_a(Enumerator)
76
77
  expect(enumerable.chunk_fetch_calls).to eq(3)
77
78
  expect(enumerable.object_count).to eq(12)
78
79
  ::TrickBag::Meta::Classes.undef_class(:BufferedEnumerableSubclass, TrickBag)
@@ -87,12 +88,12 @@ module Enumerables
87
88
 
88
89
  def initialize(chunk_size, array)
89
90
  super(chunk_size)
90
- @array = array
91
+ @inner_enumerable = array
91
92
  end
92
93
 
93
94
  def fetch
94
- num_times = [chunk_size, @array.size].min
95
- num_times.times { @data << @array.shift }
95
+ num_times = [chunk_size, @inner_enumerable.size].min
96
+ num_times.times { @data << @inner_enumerable.shift }
96
97
  end
97
98
  end
98
99
  end
@@ -75,6 +75,10 @@ module Enumerables
75
75
  it "provides the 'take' method" do
76
76
  expect(->{ CompoundEnumerable.array_enumerable([1, 2, 3]).take(2)}).not_to raise_error
77
77
  end
78
+
79
+ it 'returns an Enumerator when each is called without a block' do
80
+ expect(CompoundEnumerable.array_enumerable([1, 2, 3]).each).to be_a(Enumerator)
81
+ end
78
82
  end
79
83
  end
80
84
 
@@ -29,10 +29,10 @@ module TrickBag::Enumerables
29
29
  it "should return the original array and then repeat the last" do
30
30
  array = (0..7).to_a
31
31
  e = EndlessLastEnumerable.new(array).each
32
- expect(e.take(8)).to eq(array)
33
- expect(e.take(12)).to eq([7] * 12)
32
+ expect(e.take(20)).to eq(array + ([7] * 12))
34
33
  end
35
34
 
35
+
36
36
  it "should return == for 2 instances created with the same array" do
37
37
  array = (0..10).to_a
38
38
  e1 = EndlessLastEnumerable.new(array)
@@ -46,6 +46,22 @@ module TrickBag::Enumerables
46
46
  e2 = EndlessLastEnumerable.new([2, 1])
47
47
  expect(e1).not_to eq(e2)
48
48
  end
49
+
50
+ it 'returns an Enumerator when each is called without a block' do
51
+ expect(EndlessLastEnumerable.new([1]).each).to be_a(Enumerator)
52
+ end
53
+
54
+ context 'initializing with non-Array enumerables' do
55
+ specify 'it does not raise an error when initialized with a set' do
56
+ set = Set.new([1, 2])
57
+ expect(->() { EndlessLastEnumerable.new(set) }).not_to raise_error
58
+ end
59
+
60
+ specify 'it behaves correctly when initialized with a set' do
61
+ set = Set.new([1, 2])
62
+ expect(EndlessLastEnumerable.new(set).take(4)).to eq([1,2,2,2])
63
+ end
64
+ end
49
65
  end
50
66
 
51
67
  end
@@ -106,6 +106,9 @@ nbc.com
106
106
  expect(get_lsof_lines.().size).to eq(0)
107
107
  end
108
108
 
109
+ it 'returns an Enumerator when each is called without a block' do
110
+ expect(subject.each).to be_a(Enumerator)
111
+ end
109
112
 
110
113
  specify "calling the enumerator's close method multiple times will not raise an error" do
111
114
  enumerator = subject.each
@@ -30,6 +30,11 @@ module Enumerables
30
30
  expect(enumerator.next).to eq(4)
31
31
  expect(-> { enumerator.next }).to raise_error(StopIteration)
32
32
  end
33
+
34
+ it 'returns an Enumerator when each is called without a block' do
35
+ expect(FilteredEnumerable.new([]).each).to be_a(Enumerator)
36
+ end
37
+
33
38
  end
34
39
  end
35
40
  end
@@ -30,7 +30,7 @@ module Validations
30
30
  it 'should return a correct string listing missing keys' do
31
31
  h = { a: 1, c: 1, e: 1}
32
32
  keys_to_test = [:e, :d, :c, :b, :a]
33
- expect(missing_hash_entries_as_string(h, keys_to_test)).to eq('d, b')
33
+ expect(missing_hash_entries_as_string(h, keys_to_test)).to eq("[:d, :b]")
34
34
  end
35
35
 
36
36
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trick_bag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.0
4
+ version: 0.38.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-20 00:00:00.000000000 Z
12
+ date: 2014-03-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: os