trick_bag 0.57.0 → 0.58.0

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