trick_bag 0.57.0 → 0.58.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 012936fb2a08c5a0cf646ecf9e8895f8cb122aab
4
- data.tar.gz: 11402a3ba146571b4648829b922db87aca0b62bd
3
+ metadata.gz: 10d4086b2610d6e23ce59201321b731e05a74df3
4
+ data.tar.gz: f64bd3abeeb2ee2c948173d39fb2cb1e464d5111
5
5
  SHA512:
6
- metadata.gz: 416fdc0e04db515bcec0a0462308165a49e4b5c8a6c250c8624ec2bcd00811da27fd784cabecb72830e1cd90d5492aad495f22ab4e54439544097511f9519735
7
- data.tar.gz: ef625fb8bee7da8734ba483071f64c24d5b3c7abb3ca6a2a05c767b1d32f733e59ce7d5d3e07cc87a48d74843c5cdb8e6857b22bc2220f30c6ff4e7b8c80f11a
6
+ metadata.gz: f36e566103aee54e599824f739a23b02c2a9957cfd857d4cc2c91d5aa086d3371638d61628766439569c4562f09e9fa182a95b20128b3c1e23b8624ea54429d1
7
+ data.tar.gz: 43ba1d249627e9205dc853d9fbde3e0bed51b1931cc5a28819bb6da64dbafb48cda72ee063578969926265deff5ea7384a9014ab40e8984429dac56d8bcf3750
data/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v0.58.0
2
+
3
+ * Added KmbtNumericString, Formatters.thousands_separated.
4
+
5
+
1
6
  ## v0.57.0
2
7
 
3
8
  * Removed most class methods from Bitmap and put them in new BitMapping module.
@@ -133,6 +133,59 @@ module Formatters
133
133
  Diffy::Diff.new(string1, string2).to_s(format)
134
134
  end
135
135
 
136
+
137
+ # Outputs bytes verbosely one on a line for examination.
138
+ # The characters printed are 1 byte in length, so multibyte
139
+ # characters will not be output correctly. Output will look like:
140
+ #
141
+ # Index Decimal Hex Binary Character
142
+ # ----- ------- --- ------ ---------
143
+ # 0 97 61 x 110 0001 b a
144
+ # 1 49 31 x 11 0001 b 1
145
+ # 2 46 2e x 10 1110 b .
146
+ #
147
+ def string_to_verbose_char_list(a_string)
148
+
149
+ header = [
150
+ 'Index Decimal Hex Binary Character',
151
+ '----- ------- --- ------ ---------',
152
+ '' # 3rd string is just to force a 3rd \n when joined
153
+ ].join("\n")
154
+
155
+ sio = StringIO.new
156
+ sio << header
157
+
158
+ if a_string.empty?
159
+ sio << '(String is empty)'
160
+ else
161
+ format = '%5d %3d %+6s %+16s %c'
162
+ a_string.bytes.each_with_index do |byte, index|
163
+ hex_str = "#{byte.to_s(16)} x"
164
+
165
+ base2_str = "#{byte.to_s(2)} b"
166
+ if base2_str.size > 6
167
+ base2_str.insert(base2_str.size - 6, ' ')
168
+ end
169
+
170
+ sio << format % [index, byte, hex_str, base2_str, byte.chr] << "\n"
171
+ end
172
+ end
173
+ sio.string
174
+ end
175
+
176
+
177
+ # Returns a string representation of the Integer corresponding to the
178
+ # input parameter, with a customizable thousands separator.
179
+ # Does not (yet) support fractional values or decimal places.
180
+ def thousands_separated(number, separator = ',')
181
+ number = Integer(number)
182
+ triplets = []
183
+ source_chars = number.to_s.reverse.chars
184
+ source_chars.each_slice(3) do |slice|
185
+ triplets << slice.join
186
+ end
187
+ triplets.join(separator).reverse
188
+ end
136
189
  end
137
190
  end
138
191
 
@@ -56,14 +56,17 @@ module BitMapping
56
56
 
57
57
 
58
58
  # Converts a number to an array of bit values, e.g. 9 => [1, 0, 0, 1]
59
- def number_to_bit_array(number)
59
+ def number_to_bit_array(number, minimum_binary_places = 0)
60
60
  assert_non_negative(number)
61
61
  array = []
62
62
  while number > 0
63
63
  array << (number & 1)
64
64
  number >>= 1
65
65
  end
66
- array.reverse
66
+ array.reverse!
67
+ zero_pad_count = minimum_binary_places - array.size
68
+ zero_pad_count.times { array.unshift(0) }
69
+ array
67
70
  end
68
71
 
69
72
 
@@ -105,9 +108,9 @@ module BitMapping
105
108
 
106
109
 
107
110
  # Converts a binary string to an array of bit values, e.g. "\x0C" => [1, 1, 0, 0]
108
- def binary_string_to_bit_array(string)
111
+ def binary_string_to_bit_array(string, minimum_binary_places = 0)
109
112
  number = binary_string_to_number(string)
110
- number_to_bit_array(number)
113
+ number_to_bit_array(number, minimum_binary_places)
111
114
  end
112
115
 
113
116
 
@@ -119,6 +122,16 @@ module BitMapping
119
122
  "but is #{number.inspect} (a #{number.class})")
120
123
  end
121
124
  end
125
+
126
+ # Reverses a binary string. Note that it is not enough to reverse
127
+ # the string itself because although the bytes would be reversed,
128
+ # the bits within each byte would not.
129
+ def reverse_binary_string_bits(binary_string)
130
+ binary_place_count = binary_string.size * 8
131
+ reversed_bit_array = binary_string_to_bit_array(binary_string, binary_place_count).reverse
132
+ number = bit_array_to_number(reversed_bit_array)
133
+ number_to_binary_string(number)
134
+ end
122
135
  end
123
136
  end
124
137
  end
@@ -0,0 +1,101 @@
1
+ module TrickBag
2
+ module Numeric
3
+
4
+
5
+ # Converts number strings with optional suffixes of [kKmMgGtT] to integers.
6
+ # Upper case letters represent powers of 1024, and lower case letters represent powers of 1000
7
+ # (see MULTIPLIERS below).
8
+ #
9
+ # Also supports ranges, e.g. '1k..5k' will be converted by to_range to (1000..5000).
10
+ #
11
+ # Methods are to_number, to_range, and to_number_or_range. See tests for usage examples.
12
+ module KmgtNumericString
13
+
14
+ MULTIPLIERS = {
15
+ 'k' => 1_000,
16
+ 'K' => 1_024,
17
+ 'm' => 1_000 ** 2,
18
+ 'M' => 1_024 ** 2,
19
+ 'g' => 1_000 ** 3,
20
+ 'G' => 1_024 ** 3,
21
+ 't' => 1_000 ** 4,
22
+ 'T' => 1_024 ** 4,
23
+ }
24
+
25
+ VALID_STRING_SUFFIXES = MULTIPLIERS.keys
26
+
27
+ VALID_STRING_REGEX = /^-?\d+/ # only digits with optional minus sign prefix
28
+
29
+ module_function
30
+
31
+ # Converts a numeric string (e.g. '3', '2k') to a number.
32
+ # If the passed parameter is not a string, it is returned unchanged.
33
+ # This eliminates the need for the caller to do their own type test.
34
+ def to_number(object)
35
+ object.is_a?(String) ? ToNumberConverter.new(object).to_number : object
36
+ end
37
+
38
+ # Returns whether or not this is a number range. Specifically, tests that
39
+ # the string contains only 2 numeric strings separated by '..'.
40
+ def range_string?(string)
41
+ number_strings = string.split('..')
42
+ number_strings.size == 2 && number_strings.all? { |str| VALID_STRING_REGEX.match(str) }
43
+ end
44
+
45
+ # Converts the passed string to a range (see range_string? above for the string's format).
46
+ # If the passed parameter is a Range already, returns it unchanged.
47
+ # This eliminates the need for the caller to do their own type test.
48
+ def to_range(object)
49
+ return object if object.is_a?(Range)
50
+ unless range_string?(object)
51
+ raise ArgumentError.new("Invalid argument (#{object}); Range must be 2 numbers separated by '..', e.g. 10..20 or 900K..1M")
52
+ end
53
+ numbers = object.split('..').map { |s| to_number(s) }
54
+ (numbers.first..numbers.last)
55
+ end
56
+
57
+ # Converts a string such as '3' or '1k..2k' into a number or range.
58
+ # If an Integer or Range is passed, it is returned unchanged.
59
+ # This eliminates the need for the caller to do their own type test.
60
+ def to_number_or_range(object)
61
+ case object
62
+ when Integer, Range
63
+ object
64
+ when String
65
+ range_string?(object) ? to_range(object) : to_number(object)
66
+ else
67
+ raise ArgumentError.new("Invalid argument, class is #{object.class}, object is (#{object}).")
68
+ end
69
+ end
70
+
71
+
72
+ # This class is here to simplify implementation and is not intended
73
+ # to be instantiated by users of this module.
74
+ class ToNumberConverter
75
+
76
+ def initialize(string)
77
+ @string = string.dup.gsub('_', '')
78
+ assert_valid_input(@string)
79
+ end
80
+
81
+ def assert_valid_input(string)
82
+ unless VALID_STRING_REGEX.match(string)
83
+ raise ArgumentError.new(
84
+ "Bad arg (#{string}); must be an integer, optionally followed by one of [" +
85
+ MULTIPLIERS.keys.join + '], optionally with underscores.')
86
+ end
87
+ end
88
+
89
+ def to_number
90
+ last_char = @string[-1]
91
+ if VALID_STRING_SUFFIXES.include?(last_char)
92
+ multiplier = MULTIPLIERS[last_char]
93
+ Integer(@string.chop) * multiplier
94
+ else
95
+ Integer(@string)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,3 +1,3 @@
1
1
  module TrickBag
2
- VERSION = "0.57.0"
2
+ VERSION = "0.58.0"
3
3
  end
@@ -133,6 +133,53 @@ describe Formatters do
133
133
  expect(actual_text).to eq(expected_text)
134
134
  end
135
135
  end
136
+
137
+
138
+ context 'string_to_verbose_char_list' do
139
+
140
+ let(:result) { Formatters.string_to_verbose_char_list("a1.").split("\n") }
141
+
142
+ specify 'first line should have header text' do
143
+ required_words = %w(Index Decimal Hex Binary Character)
144
+ expect(required_words.all? { |word| result[0].include?(word) }).to eq(true)
145
+ end
146
+
147
+ specify 'second line should have only lines' do
148
+ non_blank_or_hyphens = result[1].chars.reject { |char| [' ', '-'].include?(char) }
149
+ expect(non_blank_or_hyphens).to be_empty
150
+ end
151
+
152
+ specify 'third line should include some required text' do
153
+ # 0 97 61 x 110 0001 b a
154
+ expect(result[2]).to match(/^\s*\d*\s* 97 \s* 61 x \s* 110 0001 b\s*a\s*$/)
155
+ end
156
+
157
+ specify 'empty string returns header with 3rd line saying (string is empty)' do
158
+ output = Formatters.string_to_verbose_char_list('')
159
+ expect(output).to include('(String is empty)')
160
+ end
161
+ end
162
+
163
+
164
+ context '.thousands_separated' do
165
+
166
+ specify 'no separators for <= 3 digit numbers' do
167
+ expect(Formatters.thousands_separated(123)).to eq('123')
168
+ expect(Formatters.thousands_separated(12)).to eq('12')
169
+ expect(Formatters.thousands_separated(1)).to eq('1')
170
+ end
171
+
172
+ specify 'separators are in the right places' do
173
+ expect(Formatters.thousands_separated(1_234)).to eq('1,234')
174
+ expect(Formatters.thousands_separated(12_345)).to eq('12,345')
175
+ expect(Formatters.thousands_separated(123_456)).to eq('123,456')
176
+ expect(Formatters.thousands_separated(1_234_567)).to eq('1,234,567')
177
+ end
178
+
179
+ specify 'custom separators work properly' do
180
+ expect(Formatters.thousands_separated(1_234_567, '.')).to eq('1.234.567')
181
+ end
182
+ end
136
183
  end
137
184
 
138
185
  end
@@ -95,6 +95,10 @@ context '.number_to_bit_array' do
95
95
  specify 'a negative number should result in an error' do
96
96
  expect { BitMapping.number_to_bit_array(-1) }.to raise_error(ArgumentError)
97
97
  end
98
+
99
+ specify 'a minimum bit array size is respected' do
100
+ expect(BitMapping.number_to_bit_array(0, 3)).to eq([0, 0, 0])
101
+ end
98
102
  end
99
103
 
100
104
 
@@ -138,5 +142,23 @@ context '.set_bit_position_array_to_number' do
138
142
  expect(BitMapping.set_bit_position_array_to_number([])).to eq(nil)
139
143
  end
140
144
  end
145
+
146
+
147
+ context '.reverse_binary_string_bits' do
148
+ specify 'a single byte string should reverse correctly' do
149
+ hex_01 = "\x01".force_encoding(Encoding::ASCII_8BIT)
150
+ hex_80 = "\x80".force_encoding(Encoding::ASCII_8BIT)
151
+ expect(BitMapping.reverse_binary_string_bits("\x01").size).to eq(1)
152
+ expect(BitMapping.reverse_binary_string_bits(hex_01)).to eq(hex_80)
153
+ end
154
+
155
+ specify 'a multiple byte string should reverse correctly' do
156
+ hex_01 = "\x00\x01".force_encoding(Encoding::ASCII_8BIT)
157
+ hex_80 = "\x80\x00".force_encoding(Encoding::ASCII_8BIT)
158
+ expect(BitMapping.reverse_binary_string_bits("\x01").size).to eq(1)
159
+ expect(BitMapping.reverse_binary_string_bits(hex_01)).to eq(hex_80)
160
+ end
141
161
  end
142
162
  end
163
+ end
164
+
@@ -0,0 +1,84 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require 'trick_bag/numeric/kmgt_numeric_string'
4
+
5
+ module TrickBag
6
+ module Numeric
7
+ module KmgtNumericString
8
+
9
+ end
10
+ describe KmgtNumericString do
11
+
12
+ include KmgtNumericString
13
+
14
+ context '.to_number' do
15
+
16
+ specify 'ArgumentError should be raised on invalid inputs' do
17
+ expect { to_number('') }.to raise_error(ArgumentError)
18
+ expect { to_number('_') }.to raise_error(ArgumentError)
19
+ expect { to_number('-') }.to raise_error(ArgumentError)
20
+ expect { to_number('a') }.to raise_error(ArgumentError)
21
+ expect { to_number('A') }.to raise_error(ArgumentError)
22
+ expect { to_number('1a') }.to raise_error(ArgumentError)
23
+ expect { to_number('1A') }.to raise_error(ArgumentError)
24
+ expect { to_number('1.2') }.to raise_error(ArgumentError)
25
+ end
26
+
27
+ specify 'suffixes work correctly' do
28
+ expect(to_number('10k')).to eq(10 * 1000)
29
+ expect(to_number('10K')).to eq(10 * 1024)
30
+ expect(to_number('10m')).to eq(10 * (1000 ** 2))
31
+ expect(to_number('10M')).to eq(10 * (1024 ** 2))
32
+ expect(to_number('10g')).to eq(10 * (1000 ** 3))
33
+ expect(to_number('10G')).to eq(10 * (1024 ** 3))
34
+ expect(to_number('10t')).to eq(10 * (1000 ** 4))
35
+ expect(to_number('10T')).to eq(10 * (1024 ** 4))
36
+ end
37
+
38
+ specify 'negative numbers work too' do
39
+ expect(to_number('-10k')).to eq(-10 * 1000)
40
+ expect(to_number('-10')).to eq(-10)
41
+ end
42
+ end
43
+
44
+
45
+ context '.is_range_string' do
46
+
47
+ specify 'invalid range strings should return false' do
48
+ expect(range_string?('')).to eq(false)
49
+ expect(range_string?('10')).to eq(false)
50
+ expect(range_string?('10K')).to eq(false)
51
+ expect(range_string?('..')).to eq(false)
52
+ expect(range_string?('2..')).to eq(false)
53
+ expect(range_string?('..3')).to eq(false)
54
+ end
55
+
56
+ specify 'valid range strings should return true' do
57
+ expect(range_string?('1..-1')).to eq(true)
58
+ expect(range_string?('-1..1')).to eq(true)
59
+ expect(range_string?('10K..10M')).to eq(true)
60
+ end
61
+ end
62
+
63
+
64
+ context '.to_range' do
65
+
66
+ specify 'descending ranges are supported' do
67
+ expect(to_range('9..3')).to eq((9..3))
68
+ end
69
+
70
+ specify 'ascending ranges are supported' do
71
+ expect(to_range('3k..4k')).to eq((3000..4000))
72
+ end
73
+ end
74
+
75
+ context '.to_number_or_range' do
76
+
77
+ specify 'correct determination of number vs. range' do
78
+ expect(to_number_or_range('12k')).to eq(12_000)
79
+ expect(to_number_or_range('12k..24k')).to eq(12_000..24_000)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
data/trick_bag.gemspec CHANGED
@@ -24,4 +24,9 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "bundler", "~> 1.3"
25
25
  spec.add_development_dependency "rake", '~> 10.1'
26
26
  spec.add_development_dependency "rspec", '~> 3.0'
27
+
28
+ unless /java/ === RUBY_PLATFORM
29
+ spec.add_development_dependency 'pry', '~> 0.10'
30
+ spec.add_development_dependency 'pry-byebug', '~> 2.0' if RUBY_VERSION >= '2'
31
+ end
27
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trick_bag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.57.0
4
+ version: 0.58.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Bennett
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-16 00:00:00.000000000 Z
11
+ date: 2015-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: os
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.10'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.10'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry-byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.0'
83
111
  description: Miscellaneous general useful tools for general purpose programming.
84
112
  email:
85
113
  - keithrbennett@gmail.com
@@ -112,6 +140,7 @@ files:
112
140
  - lib/trick_bag/meta/classes.rb
113
141
  - lib/trick_bag/numeric/bit_mapping.rb
114
142
  - lib/trick_bag/numeric/bitmap.rb
143
+ - lib/trick_bag/numeric/kmgt_numeric_string.rb
115
144
  - lib/trick_bag/numeric/multi_counter.rb
116
145
  - lib/trick_bag/numeric/start_and_max.rb
117
146
  - lib/trick_bag/numeric/totals.rb
@@ -141,6 +170,7 @@ files:
141
170
  - spec/trick_bag/meta/classes_spec.rb
142
171
  - spec/trick_bag/numeric/bit_mapping_spec.rb
143
172
  - spec/trick_bag/numeric/bitmap_spec.rb
173
+ - spec/trick_bag/numeric/kmgt_numeric_string_spec.rb
144
174
  - spec/trick_bag/numeric/multi_counter_spec.rb
145
175
  - spec/trick_bag/numeric/start_and_max_spec.rb
146
176
  - spec/trick_bag/numeric/totals_spec.rb
@@ -194,6 +224,7 @@ test_files:
194
224
  - spec/trick_bag/meta/classes_spec.rb
195
225
  - spec/trick_bag/numeric/bit_mapping_spec.rb
196
226
  - spec/trick_bag/numeric/bitmap_spec.rb
227
+ - spec/trick_bag/numeric/kmgt_numeric_string_spec.rb
197
228
  - spec/trick_bag/numeric/multi_counter_spec.rb
198
229
  - spec/trick_bag/numeric/start_and_max_spec.rb
199
230
  - spec/trick_bag/numeric/totals_spec.rb