trick_bag 0.37.0 → 0.38.0

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