ruby-measurement 1.2.2 → 1.2.3

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: 4d9f8dac16cbf7f18dfbebf4f9ed90b4ab1236bf
4
- data.tar.gz: 18689f648cb2a657e68db270e74442f0eb0aa5ee
3
+ metadata.gz: 31687bfdd62b83ca04ffba2a8d1c106d767f8ac4
4
+ data.tar.gz: 5eb61379bb4dc5b1eaf051c96cf1c55353bb4b0b
5
5
  SHA512:
6
- metadata.gz: 1c83f8edb8c522cbc94b1623b74166bb5afc44c71ceb5f149eb7c11bea352bf6bcbbbdcec1544e39874a80884b3ea30c823978e8f637afa015877676196e484e
7
- data.tar.gz: c68397ddd27017653b7de555a2ad99eaa3ba00eafdfa8060f9d46fde7636b37e7fa61b96a8000d4cfdd70d0b26a8c3120404c8fa96b34af731eefa10b340614c
6
+ metadata.gz: 25e76a22f35f3e5af25a1f79bf0f396eeba2273da6d6d8011dc11f34e02e868b374227aa1f95ee6a541ddaf1b98d6408ddb30f6898a49c43d12852128e8ba79f
7
+ data.tar.gz: e7f70d5166d49109632bbc8c8a0b8cf3c706fea85c5dc4b3abaddebc0af46581468394ffdae920fdea235bad66dcf1cc0d9b6aa9afe6ab52988ac593454d8d4c
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
+ sudo: false
2
3
 
3
4
  rvm:
4
5
  - 1.9.3
@@ -1,32 +1,35 @@
1
+ ## ruby-measurement 1.2.3 (Mar 27, 2016)
2
+
3
+ * Updated parsing to accept UTF-8 fractions (½, ⅓, etc.). *Matt Huggins*
4
+
1
5
  ## ruby-measurement 1.2.2 (Jul 13, 2014)
2
6
 
3
- * Fix `Measurement::Unit#==` to properly compare units. *Matt Huggins*
7
+ * Fixed `Measurement::Unit#==` to properly compare units. *Matt Huggins*
4
8
 
5
9
  ## ruby-measurement 1.2.1 (Jul 27, 2013)
6
10
 
7
- * Implement `Measurement.names` to list all available units. *Daniele Palombo*
11
+ * Added `Measurement.names` to list all available units. *Daniele Palombo*
8
12
 
9
13
  ## ruby-measurement 1.2.0 (Mar 16, 2013)
10
14
 
11
- * Implement `String#to_measurement`, `String#to_unit`,
15
+ * Added `String#to_measurement`, `String#to_unit`,
12
16
  `Symbol#to_unit`, and `Numeric#to_measurement`. *Matt Huggins*
13
17
 
14
18
  ## ruby-measurement 1.1.0 (Feb 6, 2013)
15
19
 
16
- * Move unit definition logic into `Unit` class. *Matt Huggins*
17
- * Create test suite. *Matt Huggins*
18
- * Fix parsing of `'` and `"` for feet and inches. *Matt Huggins*
19
- * Implement missing metric weights and U.S. customary capacities.
20
- *Matt Huggins*
21
- * Fix several conversions that were found to be miscalculating.
20
+ * Moved unit definition logic into `Unit` class. *Matt Huggins*
21
+ * Added test suite. *Matt Huggins*
22
+ * Fixed parsing of `'` and `"` for feet and inches. *Matt Huggins*
23
+ * Added missing metric weights and U.S. customary capacities. *Matt Huggins*
24
+ * Fixed several conversions that were found to be miscalculating.
22
25
  *Matt Huggins*
23
26
 
24
27
  ## ruby-measurement 1.0.0 (Jan 22, 2013)
25
28
 
26
- * Fix parsing of mixed fractions. *Matt Huggins*
27
- * Allow parsing of quantity & unit to work when not separated by a space.
29
+ * Fixed parsing of mixed fractions. *Matt Huggins*
30
+ * Updated parsing of quantity & unit to work when not separated by a space.
28
31
  *Matt Huggins*
29
- * Change `Measurement` initializer to accept quantity & unit instead of a
32
+ * Updated `Measurement` initializer to accept quantity & unit instead of a
30
33
  single string for parsing. *Matt Huggins*
31
34
 
32
35
  ## ruby-measurement 0.0.1 (Jan 21, 2013)
@@ -4,7 +4,7 @@ class String
4
4
  def to_measurement
5
5
  Measurement.parse(self)
6
6
  end
7
-
7
+
8
8
  def to_unit
9
9
  Measurement::Unit[self] or raise ArgumentError, "Invalid unit: #{self}"
10
10
  end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'ruby-measurement/unit'
2
4
  require 'ruby-measurement/version'
3
5
 
@@ -7,28 +9,46 @@ class Measurement
7
9
  SCIENTIFIC_REGEX = /\A#{SCIENTIFIC_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
8
10
  RATIONAL_REGEX = /\A([+-]?\d+\s+)?((\d+)\/(\d+))?\s*#{UNIT_REGEX}?\z/.freeze
9
11
  COMPLEX_REGEX = /\A#{SCIENTIFIC_NUMBER}?#{SCIENTIFIC_NUMBER}i\s*#{UNIT_REGEX}?\z/.freeze
10
-
12
+
13
+ RATIOS = {
14
+ '¼' => '1/4',
15
+ '½' => '1/2',
16
+ '¾' => '3/4',
17
+ '⅓' => '1/3',
18
+ '⅔' => '2/3',
19
+ '⅕' => '1/5',
20
+ '⅖' => '2/5',
21
+ '⅗' => '3/5',
22
+ '⅘' => '4/5',
23
+ '⅙' => '1/6',
24
+ '⅚' => '5/6',
25
+ '⅛' => '1/8',
26
+ '⅜' => '3/8',
27
+ '⅝' => '5/8',
28
+ '⅞' => '7/8',
29
+ }.freeze
30
+
11
31
  attr_reader :quantity, :unit
12
-
32
+
13
33
  def initialize(quantity, unit_name = :count)
14
34
  unit = unit_name
15
35
  unit = Unit[unit_name.to_s] if unit_name.kind_of?(Symbol) || unit_name.kind_of?(String)
16
-
36
+
17
37
  raise ArgumentError, "Invalid quantity: #{quantity}" unless quantity.kind_of?(Numeric)
18
38
  raise ArgumentError, "Invalid unit: #{unit_name}" unless unit.kind_of?(Unit)
19
-
39
+
20
40
  @quantity = quantity
21
41
  @unit = unit
22
42
  end
23
-
43
+
24
44
  def inspect
25
45
  to_s
26
46
  end
27
-
47
+
28
48
  def to_s
29
49
  "#{quantity} #{unit}"
30
50
  end
31
-
51
+
32
52
  %w(+ - * /).each do |operator|
33
53
  class_eval <<-END, __FILE__, __LINE__ + 1
34
54
  def #{operator}(obj)
@@ -47,7 +67,7 @@ class Measurement
47
67
  end
48
68
  END
49
69
  end
50
-
70
+
51
71
  def **(obj)
52
72
  case obj
53
73
  when Numeric
@@ -56,82 +76,96 @@ class Measurement
56
76
  raise ArgumentError, "Invalid arithmetic: #{self} ** #{obj}"
57
77
  end
58
78
  end
59
-
79
+
60
80
  def ==(obj)
61
81
  obj.kind_of?(self.class) && quantity == obj.quantity && unit == obj.unit
62
82
  end
63
-
83
+
64
84
  def convert_to(unit_name)
65
85
  unit = Unit[unit_name]
66
86
  raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit
67
-
87
+
68
88
  return dup if unit == @unit
69
-
89
+
70
90
  conversion = @unit.conversion(unit.name)
71
91
  raise ArgumentError, "Invalid conversion: '#@unit' to '#{unit.name}'" unless conversion
72
-
92
+
73
93
  self.class.new(conversion.call(@quantity), unit.name)
74
94
  end
75
-
95
+
76
96
  def convert_to!(unit_name)
77
97
  measurement = convert_to(unit_name)
78
98
  @unit, @quantity = measurement.unit, measurement.quantity
79
99
  self
80
100
  end
81
-
82
- def self.parse(str = '0')
83
- str = str.strip
84
-
85
- case str
86
- when COMPLEX_REGEX then unit_name, quantity = parse_complex(str)
87
- when SCIENTIFIC_REGEX then unit_name, quantity = parse_scientific(str)
88
- when RATIONAL_REGEX then unit_name, quantity = parse_rational(str)
89
- else raise ArgumentError, "Unable to parse: '#{str}'"
101
+
102
+ class << self
103
+ def parse(str = '0')
104
+ str = normalize(str)
105
+
106
+ case str
107
+ when COMPLEX_REGEX then unit_name, quantity = parse_complex(str)
108
+ when SCIENTIFIC_REGEX then unit_name, quantity = parse_scientific(str)
109
+ when RATIONAL_REGEX then unit_name, quantity = parse_rational(str)
110
+ else raise ArgumentError, "Unable to parse: '#{str}'"
111
+ end
112
+
113
+ unit_name ||= 'count'
114
+ unit = Unit[unit_name.strip.downcase]
115
+ raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit
116
+
117
+ new(quantity, unit)
90
118
  end
91
-
92
- unit_name ||= 'count'
93
- unit = Unit[unit_name.strip.downcase]
94
- raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit
95
-
96
- new(quantity, unit)
97
- end
98
-
99
- def self.define(unit_name, &block)
100
- Unit.define(unit_name, &block)
101
- end
102
-
103
- private
104
-
105
- def self.parse_complex(str)
106
- real, imaginary, unit_name = str.scan(COMPLEX_REGEX).first
107
- quantity = Complex(real.to_f, imaginary.to_f).to_f
108
- return unit_name, quantity
109
- end
110
-
111
- def self.parse_scientific(str)
112
- whole, unit_name = str.scan(SCIENTIFIC_REGEX).first
113
- quantity = whole.to_f
114
- return unit_name, quantity
115
- end
116
-
117
- def self.parse_rational(str)
118
- whole, _, numerator, denominator, unit_name = str.scan(RATIONAL_REGEX).first
119
-
120
- if numerator && denominator
121
- numerator = numerator.to_f + (denominator.to_f * whole.to_f)
122
- denominator = denominator.to_f
123
- quantity = Rational(numerator, denominator).to_f
124
- else
119
+
120
+ def define(unit_name, &block)
121
+ Unit.define(unit_name, &block)
122
+ end
123
+
124
+ private
125
+
126
+ def normalize(str)
127
+ str.dup.tap do |str|
128
+ if str =~ Regexp.new(/(#{RATIOS.keys.join('|')})/)
129
+ RATIOS.each do |search, replace|
130
+ str.gsub!(search) { " #{replace}" }
131
+ end
132
+ end
133
+
134
+ str.strip!
135
+ end
136
+ end
137
+
138
+ def parse_complex(str)
139
+ real, imaginary, unit_name = str.scan(COMPLEX_REGEX).first
140
+ quantity = Complex(real.to_f, imaginary.to_f).to_f
141
+ return unit_name, quantity
142
+ end
143
+
144
+ def parse_scientific(str)
145
+ whole, unit_name = str.scan(SCIENTIFIC_REGEX).first
125
146
  quantity = whole.to_f
147
+ return unit_name, quantity
148
+ end
149
+
150
+ def parse_rational(str)
151
+ whole, _, numerator, denominator, unit_name = str.scan(RATIONAL_REGEX).first
152
+
153
+ if numerator && denominator
154
+ numerator = numerator.to_f + (denominator.to_f * whole.to_f)
155
+ denominator = denominator.to_f
156
+ quantity = Rational(numerator, denominator).to_f
157
+ else
158
+ quantity = whole.to_f
159
+ end
160
+
161
+ return unit_name, quantity
126
162
  end
127
-
128
- return unit_name, quantity
129
163
  end
130
-
164
+
131
165
  define(:count) do |unit|
132
166
  unit.convert_to(:dozen) { |value| value / 12.0 }
133
167
  end
134
-
168
+
135
169
  define(:doz) do |unit|
136
170
  unit.alias :dozen
137
171
  unit.convert_to(:count) { |value| value * 12.0 }
@@ -3,85 +3,85 @@ require 'set'
3
3
  class Measurement
4
4
  class Unit
5
5
  attr_reader :name, :aliases, :conversions
6
-
6
+
7
7
  @definitions = {}
8
-
8
+
9
9
  def initialize(name)
10
10
  @name = name.to_s
11
11
  @aliases = Set.new
12
12
  @conversions = {}
13
13
  add_alias(name)
14
14
  end
15
-
15
+
16
16
  def add_alias(*args)
17
17
  args.each do |unit_alias|
18
18
  @aliases << unit_alias.to_s
19
19
  self.class[unit_alias] = self
20
20
  end
21
21
  end
22
-
22
+
23
23
  def add_conversion(unit_name, &block)
24
24
  @conversions[unit_name.to_s] = block
25
25
  end
26
-
26
+
27
27
  def conversion(unit_name)
28
28
  unit = self.class[unit_name]
29
29
  return nil unless unit
30
-
30
+
31
31
  unit.aliases.each do |unit_alias|
32
32
  conversion = @conversions[unit_alias.to_s]
33
33
  return conversion if conversion
34
34
  end
35
-
35
+
36
36
  nil
37
37
  end
38
-
38
+
39
39
  def inspect
40
40
  to_s
41
41
  end
42
-
42
+
43
43
  def to_s
44
44
  name
45
45
  end
46
-
46
+
47
47
  def ==(obj)
48
48
  obj.kind_of?(self.class) && name == obj.name && aliases == obj.aliases && conversions.all? do |key, proc|
49
49
  [-2.5, -1, 0, 1, 2.5].all? { |n| proc.call(n) == obj.conversions[key].call(n) }
50
50
  end
51
51
  end
52
-
53
- def self.define(unit_name, &block)
54
- Builder.new(unit_name, &block)
55
- end
56
-
57
- def self.[](unit_name)
58
- @definitions[unit_name.to_s.downcase]
59
- end
60
-
61
- def self.names
62
- @definitions.keys
63
- end
64
-
65
- private
66
-
67
- def self.[]=(unit_name, unit)
68
- @definitions[unit_name.to_s.downcase] = unit
52
+
53
+ class << self
54
+ def define(unit_name, &block)
55
+ Builder.new(unit_name, &block)
56
+ end
57
+
58
+ def [](unit_name)
59
+ @definitions[unit_name.to_s.downcase]
60
+ end
61
+
62
+ def []=(unit_name, unit)
63
+ @definitions[unit_name.to_s.downcase] = unit
64
+ end
65
+
66
+ def names
67
+ @definitions.keys
68
+ end
69
69
  end
70
-
70
+
71
71
  class Builder
72
72
  def initialize(unit_name, &block)
73
73
  @unit = Unit.new(unit_name)
74
74
  block.call(self) if block_given?
75
75
  end
76
-
76
+
77
77
  def alias(*args)
78
78
  @unit.add_alias(*args)
79
79
  end
80
-
80
+
81
81
  def convert_to(unit_name, &block)
82
82
  @unit.add_conversion(unit_name, &block)
83
83
  end
84
-
84
+
85
85
  def to_unit
86
86
  @unit
87
87
  end
@@ -1,3 +1,3 @@
1
1
  class Measurement
2
- VERSION = '1.2.2'
2
+ VERSION = '1.2.3'
3
3
  end
@@ -6,29 +6,29 @@ RSpec.describe String do
6
6
  subject { '3' }
7
7
  specify { expect(subject.to_measurement).to eq Measurement.new(3) }
8
8
  end
9
-
9
+
10
10
  describe 'with valid quantity and unit' do
11
11
  subject { '3 dozen' }
12
12
  specify { expect(subject.to_measurement).to eq Measurement.new(3, :dozen) }
13
13
  end
14
-
14
+
15
15
  describe 'with valid quantity and invalid unit' do
16
16
  subject { '3 people' }
17
17
  specify { expect { subject.to_measurement }.to raise_error }
18
18
  end
19
-
19
+
20
20
  describe 'with invalid input' do
21
21
  subject { 'foobar' }
22
22
  specify { expect { subject.to_measurement }.to raise_error }
23
23
  end
24
24
  end
25
-
25
+
26
26
  describe '#to_unit' do
27
27
  describe 'with valid unit' do
28
28
  subject { 'dozen' }
29
29
  specify { expect(subject.to_unit).to eq Measurement::Unit[:dozen] }
30
30
  end
31
-
31
+
32
32
  describe 'with invalid unit' do
33
33
  subject { 'person' }
34
34
  specify { expect { subject.to_unit }.to raise_error }