ruby-measurement 1.2.2 → 1.2.3

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: 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 }