more_core_extensions 3.1.1 → 3.2.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: 813c1118ca8bd1502d467502dbc10c1965cfccb8
4
- data.tar.gz: 44f901136763832691e94be69e73cc90e82d3a16
3
+ metadata.gz: a1add9ba7e2ca9ea1cd89792999f0a40b1ae00a0
4
+ data.tar.gz: 21149cb02025e1c91ad29e50a68bc4df68746808
5
5
  SHA512:
6
- metadata.gz: 82cbb225f4ba697fc29cc1b6b6ef0d1e00561de1fb6071d8078cae1981aa0209f983b843e787841706b4f584121b1a0c9d29078942ea1f606b682c58f79a4e65
7
- data.tar.gz: 94645bbb55387932f89711371041e09bc4c00a7d7c43fe6224d52acf2d9ce00bb7f416ff2eb308ef3a3a8d8f26789657c178e8e095fa58e4d4294769708347e9
6
+ metadata.gz: 40638498bbcb894d6722018c78c9ce10eecb32eb7a64b60233735c6f442cc8a18d3685020348db60fb658a0c8b6f3bcc7cd4e6ead1cb407355ff9298c1758374
7
+ data.tar.gz: 8644345821bb5bef19fecd89e4e5575dd36ff6210146cfcbd029a2dd9310c607b7ad4ca1bf0e6776ff1fa2ddbfb402ce6c052b4d08978cba5a9ee7fee4bbbf82
data/README.md CHANGED
@@ -59,6 +59,8 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
59
59
 
60
60
  #### Numeric
61
61
 
62
+ * core_ext/numeric/clamp.rb
63
+ * `#clamp` - Clamp a number to a minimum and/or maximum value
62
64
  * core_ext/numeric/math.rb
63
65
  * `#square` - Returns the square of a Numeric
64
66
  * core_ext/numeric/rounding.rb
@@ -82,6 +84,8 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
82
84
  * `#guid?` - Returns whether or not the String is a valid GUID
83
85
  * core_ext/string/hex_dump.rb
84
86
  * `#hex_dump` - Dumps the string in a hex editor style format
87
+ * core_ext/string/iec60027_2.rb
88
+ * `#iec_60027_2_to_i` - Convert strings with an IEC60027-2 suffix to an integer
85
89
 
86
90
  #### Shared
87
91
 
@@ -31,59 +31,104 @@ module MoreCoreExtensions
31
31
  # Value3 | Value4
32
32
  #
33
33
  def tableize(options = {})
34
- case self.first
35
- when Array; tableize_arrays(options)
36
- when Hash; tableize_hashes(options)
37
- else raise "must be an Array of Arrays or Array of Hashes"
38
- end
34
+ Tableizer.new(self, options).tableize
39
35
  end
40
36
 
41
- private
37
+ # This class is a private implementation and not part of the public API.
38
+ class Tableizer
39
+ attr_accessor :target, :options
42
40
 
43
- def tableize_arrays(options)
44
- options[:header] = true unless options.has_key?(:header)
41
+ def initialize(target, options)
42
+ @target = target
43
+ @options = options
44
+ end
45
45
 
46
- widths = []
47
- justifications = []
48
- self.each do |row|
49
- row.each_with_index do |field, field_i|
50
- widths[field_i] = [widths[field_i].to_i, field.to_s.length].max
51
- widths[field_i] = [options[:max_width], widths[field_i].to_i].min if options[:max_width]
46
+ def tableize
47
+ case target.first
48
+ when Array then tableize_arrays
49
+ when Hash then tableize_hashes
50
+ else raise "must be an Array of Arrays or Array of Hashes"
51
+ end
52
+ end
52
53
 
53
- justifications[field_i] = field.kind_of?(Numeric) ? "" : "-"
54
+ private
55
+
56
+ def tableize_hashes
57
+ # Convert the target to an Array of Arrays
58
+ keys = options[:columns] || columns_from_hash_keys
59
+ self.target = target.collect { |h| h.values_at(*keys) }.unshift(keys)
60
+ options[:header] = true
61
+
62
+ tableize_arrays
63
+ end
64
+
65
+ def columns_from_hash_keys
66
+ target.first.keys.sort_by(&:to_s).tap do |keys|
67
+ apply_leading_columns!(keys)
68
+ apply_trailing_columns!(keys)
54
69
  end
55
70
  end
56
71
 
57
- header_separator = widths.collect { |w| "-" * (w + 2) }.join("+")
72
+ def apply_leading_columns!(keys)
73
+ return unless options[:leading_columns]
74
+ options[:leading_columns].reverse_each { |h| keys.unshift(keys.delete(h)) }
75
+ end
76
+
77
+ def apply_trailing_columns!(keys)
78
+ return unless options[:trailing_columns]
79
+ options[:trailing_columns].each { |h| keys.push(keys.delete(h)) }
80
+ end
81
+
82
+ def tableize_arrays
83
+ options[:header] = true unless options.key?(:header)
58
84
 
59
- table = []
60
- self.each_with_index do |row, row_i|
61
- r = []
62
- row.each_with_index do |field, field_i|
63
- r << sprintf("%0#{justifications[field_i]}#{widths[field_i]}s", field.to_s.gsub(/\n|\r/, '').slice(0, widths[field_i]))
85
+ widths, justifications = widths_and_justifications
86
+ table = target.collect { |row| format_row(row, widths, justifications) }
87
+ format_table(table, widths)
88
+ end
89
+
90
+ def widths_and_justifications
91
+ widths = []
92
+ justifications = []
93
+
94
+ target.each do |row|
95
+ row.each.with_index do |field, field_i|
96
+ apply_width!(widths, field, field_i)
97
+ apply_justification!(justifications, field, field_i)
98
+ end
64
99
  end
65
- r = " #{r.join(' | ')} ".rstrip
66
100
 
67
- table << r
68
- table << header_separator if row_i == 0 && options[:header]
101
+ return widths, justifications
102
+ end
103
+
104
+ def apply_width!(widths, field, field_i)
105
+ widths[field_i] = [widths[field_i].to_i, field.to_s.length].max
106
+ widths[field_i] = [options[:max_width], widths[field_i].to_i].min if options[:max_width]
107
+ end
108
+
109
+ def apply_justification!(justifications, field, field_i)
110
+ justifications[field_i] = field.kind_of?(Numeric) ? "" : "-"
111
+ end
112
+
113
+ def format_row(row, widths, justifications)
114
+ formatted_fields = row.collect.with_index do |field, field_i|
115
+ format_field(field, widths[field_i], justifications[field_i])
116
+ end
117
+ " #{formatted_fields.join(' | ')} ".rstrip
69
118
  end
70
- table.join("\n") << "\n"
71
- end
72
119
 
73
- def tableize_hashes(options)
74
- if options[:columns]
75
- keys = options[:columns]
76
- elsif options[:leading_columns] || options[:trailing_columns]
77
- keys = self.first.keys.sort_by(&:to_s)
78
- options[:leading_columns].reverse.each { |h| keys.unshift(keys.delete(h)) } if options[:leading_columns]
79
- options[:trailing_columns].each { |h| keys.push(keys.delete(h)) } if options[:trailing_columns]
80
- else
81
- keys = self.first.keys.sort_by(&:to_s)
120
+ def format_field(field, width, justification)
121
+ field = field.to_s.gsub(/\n|\r/, '').slice(0, width)
122
+ "%0#{justification}#{width}s" % field
82
123
  end
83
124
 
84
- options = options.dup
85
- options[:header] = true
86
- self.collect { |h| h.values_at(*keys) }.unshift(keys).tableize(options)
125
+ def format_table(table, widths)
126
+ if options[:header] && table.size > 1
127
+ header_separator = widths.collect { |w| "-" * (w + 2) }.join("+")
128
+ table.insert(1, header_separator)
129
+ end
130
+ table.join("\n") << "\n"
131
+ end
87
132
  end
88
133
  end
89
134
  end
@@ -1,2 +1,3 @@
1
+ require 'more_core_extensions/core_ext/numeric/clamp'
1
2
  require 'more_core_extensions/core_ext/numeric/math'
2
3
  require 'more_core_extensions/core_ext/numeric/rounding'
@@ -0,0 +1,18 @@
1
+ module MoreCoreExtensions
2
+ module NumericClamp
3
+ #
4
+ # Clamp a number to a minimum and/or maximum value.
5
+ #
6
+ # 8.clamp(nil, nil) #=> 8
7
+ # 8.clamp(9, nil) #=> 9
8
+ # 8.clamp(nil, 6) #=> 6
9
+ def clamp(min, max)
10
+ value = self
11
+ value = [value, min].max if min
12
+ value = [value, max].min if max
13
+ value
14
+ end
15
+ end
16
+ end
17
+
18
+ Numeric.send(:prepend, MoreCoreExtensions::NumericClamp)
@@ -1,2 +1,3 @@
1
1
  require 'more_core_extensions/core_ext/string/formats'
2
2
  require 'more_core_extensions/core_ext/string/hex_dump'
3
+ require 'more_core_extensions/core_ext/string/iec60027_2'
@@ -1,7 +1,7 @@
1
1
  module MoreCoreExtensions
2
2
  module StringFormats
3
3
  # From: http://www.regular-expressions.info/email.html
4
- RE_EMAIL = %r{\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z}
4
+ RE_EMAIL = %r{\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z}i
5
5
 
6
6
  def email?
7
7
  !!(self =~ RE_EMAIL)
@@ -9,47 +9,59 @@ module MoreCoreExtensions
9
9
  # instead of returning as a string.
10
10
  # :meth:: Used in conjunction with _:obj_ to send each line to an object
11
11
  # instead of returning as a string.
12
- def hex_dump(*opts)
13
- opts = opts[0] if opts.empty? || (opts.length == 1 && opts[0].kind_of?(Hash))
14
- raise ArgumentError, "opts must be a Hash" unless opts.nil? || opts.kind_of?(Hash)
12
+ def hex_dump(options = {})
13
+ HexDumper.new(self, options).dump
14
+ end
15
+
16
+ # This class is a private implementation and not part of the public API.
17
+ class HexDumper
18
+ attr_reader :target, :options
15
19
 
16
- opts = {:grouping => 16, :newline => true, :start_pos => 0}.merge!(opts || {})
17
- obj, meth, grouping, newline, pos = opts.values_at(:obj, :meth, :grouping, :newline, :start_pos)
18
- raise ArgumentError, "obj and meth must both be set, or both not set" if (obj.nil? && !meth.nil?) || (!obj.nil? && meth.nil?)
20
+ DEFAULT_OPTIONS = {
21
+ :grouping => 16,
22
+ :newline => true,
23
+ :start_pos => 0
24
+ }.freeze
19
25
 
20
- row_format = "0x%08x #{"%02x " * grouping} "
26
+ def initialize(target, options)
27
+ @target = target
28
+ @options = DEFAULT_OPTIONS.merge(options)
21
29
 
22
- i = 0
23
- last_i = self.length - 1
30
+ if !!options[:obj] ^ !!options[:meth] # rubocop:disable Style/DoubleNegation
31
+ raise ArgumentError, "obj and meth must both be set, or both not set"
32
+ end
33
+ end
24
34
 
25
- ret = ''
26
- row_vals = []
27
- row_chars = ''
35
+ def dump
36
+ obj, meth, grouping, pos = options.values_at(:obj, :meth, :grouping, :start_pos)
28
37
 
29
- self.each_byte do |c|
30
- row_vals << c
31
- row_chars << (c < 0x20 || (c >= 0x7F && c < 0xA0) ? '.' : c.chr)
38
+ ret = ""
32
39
 
33
- if (i + 1) % grouping == 0 || i == last_i
34
- row_format = "0x%08x #{"%02x " * row_vals.length}#{" " * (grouping - row_vals.length)} " if i == last_i
40
+ target.each_byte.each_slice(grouping) do |bytes|
41
+ row = format_row(pos, bytes)
42
+ ret << row
35
43
 
36
- row_vals.unshift(pos)
37
- ret << (row_format % row_vals) << row_chars
38
- ret << "\n" if newline
39
44
  if obj
40
- obj.send(meth, ret)
41
- ret.replace('')
45
+ obj.send(meth, row)
46
+ ret = ""
42
47
  end
43
48
 
44
49
  pos += grouping
45
- row_vals.clear
46
- row_chars = ''
47
50
  end
48
51
 
49
- i += 1
52
+ ret
50
53
  end
51
54
 
52
- return ret
55
+ def format_row(pos, bytes)
56
+ padding = " " * (options[:grouping] - bytes.size)
57
+ byte_chars = bytes.collect { |byte| row_char(byte) }.join
58
+ newline = "\n" if options[:newline]
59
+ "0x%08x #{"%02x " * bytes.size}#{padding} " % [pos, *bytes] << "#{byte_chars}#{newline}"
60
+ end
61
+
62
+ def row_char(byte)
63
+ byte < 0x20 || (byte >= 0x7F && byte < 0xA0) ? '.' : byte.chr
64
+ end
53
65
  end
54
66
  end
55
67
  end
@@ -0,0 +1,19 @@
1
+ module MoreCoreExtensions
2
+ module IEC60027_2
3
+ # Support converting strings with an IEC60027-2 suffix to an integer
4
+ # http://physics.nist.gov/cuu/Units/binary.html
5
+ # Example: "1 Ki".iec_60027_2_to_i => 1024
6
+
7
+ IEC_60027_2_SIZE_SUFFIXES = %w(Ki Mi Gi Ti Pi Ei Zi Yi).freeze
8
+ def iec_60027_2_to_i
9
+ suffix_index = IEC_60027_2_SIZE_SUFFIXES.index(self[-2..-1])
10
+ if suffix_index.nil?
11
+ Integer(self)
12
+ else
13
+ Integer(self[0..-3]) * (2**10)**(suffix_index + 1)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ String.send(:prepend, MoreCoreExtensions::IEC60027_2)
@@ -1,3 +1,3 @@
1
1
  module MoreCoreExtensions
2
- VERSION = "3.1.1".freeze
2
+ VERSION = "3.2.0".freeze
3
3
  end
@@ -0,0 +1,17 @@
1
+ describe Numeric do
2
+ it "#clamp" do
3
+ expect(8.clamp(nil, nil)).to eq(8)
4
+ expect(8.clamp(3, nil)).to eq(8)
5
+ expect(8.clamp(13, nil)).to eq(13)
6
+ expect(8.clamp(nil, 6)).to eq(6)
7
+ expect(8.clamp(13, 16)).to eq(13)
8
+ expect(20.clamp(13, 16)).to eq(16)
9
+
10
+ expect(8.0.clamp(nil, nil)).to eq(8.0)
11
+ expect(8.0.clamp(3.0, nil)).to eq(8.0)
12
+ expect(8.0.clamp(13.0, nil)).to eq(13.0)
13
+ expect(8.0.clamp(nil, 6.0)).to eq(6.0)
14
+ expect(8.0.clamp(13.0, 16.0)).to eq(13.0)
15
+ expect(20.0.clamp(13.0, 16.0)).to eq(16.0)
16
+ end
17
+ end
@@ -4,6 +4,8 @@ describe String do
4
4
  expect("john.doe@example.com").to be_email
5
5
  expect("john.doe@my-company.prestidigitation").to be_email
6
6
  expect("john.o'doe@example.com").to be_email
7
+ expect("john.doe@EXAMPLE.COM").to be_email
8
+ expect("John.Doe@example.com").to be_email
7
9
 
8
10
  expect("john\ndoe@example.com").not_to be_email
9
11
  expect("").not_to be_email
@@ -0,0 +1,13 @@
1
+ describe String do
2
+ it '#iec60027_2' do
3
+ expect("1 ".iec_60027_2_to_i).to eq(1)
4
+ expect("1 Ki".iec_60027_2_to_i).to eq(1_024)
5
+ expect("1 Mi".iec_60027_2_to_i).to eq(1_048_576)
6
+ expect("1 Gi".iec_60027_2_to_i).to eq(1_073_741_824)
7
+ expect("1 Ti".iec_60027_2_to_i).to eq(1_099_511_627_776)
8
+ expect("1 Pi".iec_60027_2_to_i).to eq(1_125_899_906_842_624)
9
+ expect("1 Ei".iec_60027_2_to_i).to eq(1_152_921_504_606_846_976)
10
+ expect("1 Zi".iec_60027_2_to_i).to eq(1_180_591_620_717_411_303_424)
11
+ expect("1 Yi".iec_60027_2_to_i).to eq(1_208_925_819_614_629_174_706_176)
12
+ end
13
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: more_core_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Frey
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-12-16 00:00:00.000000000 Z
12
+ date: 2017-03-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -111,6 +111,7 @@ files:
111
111
  - lib/more_core_extensions/core_ext/module.rb
112
112
  - lib/more_core_extensions/core_ext/module/namespace.rb
113
113
  - lib/more_core_extensions/core_ext/numeric.rb
114
+ - lib/more_core_extensions/core_ext/numeric/clamp.rb
114
115
  - lib/more_core_extensions/core_ext/numeric/math.rb
115
116
  - lib/more_core_extensions/core_ext/numeric/rounding.rb
116
117
  - lib/more_core_extensions/core_ext/object.rb
@@ -119,6 +120,7 @@ files:
119
120
  - lib/more_core_extensions/core_ext/string.rb
120
121
  - lib/more_core_extensions/core_ext/string/formats.rb
121
122
  - lib/more_core_extensions/core_ext/string/hex_dump.rb
123
+ - lib/more_core_extensions/core_ext/string/iec60027_2.rb
122
124
  - lib/more_core_extensions/version.rb
123
125
  - spec/core_ext/array/deletes_spec.rb
124
126
  - spec/core_ext/array/duplicates_spec.rb
@@ -131,11 +133,13 @@ files:
131
133
  - spec/core_ext/array/tableize_spec.rb
132
134
  - spec/core_ext/hash/deletes_spec.rb
133
135
  - spec/core_ext/hash/nested_spec.rb
136
+ - spec/core_ext/numeric/clamp_spec.rb
134
137
  - spec/core_ext/numeric/math_spec.rb
135
138
  - spec/core_ext/numeric/rounding_spec.rb
136
139
  - spec/core_ext/object/namespace_spec.rb
137
140
  - spec/core_ext/string/formats_spec.rb
138
141
  - spec/core_ext/string/hex_dump_spec.rb
142
+ - spec/core_ext/string/iec60027_2_spec.rb
139
143
  - spec/spec_helper.rb
140
144
  homepage: http://github.com/ManageIQ/more_core_extensions
141
145
  licenses:
@@ -174,10 +178,12 @@ test_files:
174
178
  - spec/core_ext/array/tableize_spec.rb
175
179
  - spec/core_ext/hash/deletes_spec.rb
176
180
  - spec/core_ext/hash/nested_spec.rb
181
+ - spec/core_ext/numeric/clamp_spec.rb
177
182
  - spec/core_ext/numeric/math_spec.rb
178
183
  - spec/core_ext/numeric/rounding_spec.rb
179
184
  - spec/core_ext/object/namespace_spec.rb
180
185
  - spec/core_ext/string/formats_spec.rb
181
186
  - spec/core_ext/string/hex_dump_spec.rb
187
+ - spec/core_ext/string/iec60027_2_spec.rb
182
188
  - spec/spec_helper.rb
183
189
  - ".rspec"