ruby-measurement 1.2.0 → 1.3.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.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/CI.yml +30 -0
  3. data/CHANGELOG.md +25 -10
  4. data/README.md +13 -7
  5. data/lib/ruby-measurement/core_ext/string.rb +2 -2
  6. data/lib/ruby-measurement/core_ext/symbol.rb +1 -1
  7. data/lib/ruby-measurement/measurement.rb +101 -42
  8. data/lib/ruby-measurement/unit.rb +34 -28
  9. data/lib/ruby-measurement/version.rb +1 -1
  10. data/ruby-measurement.gemspec +5 -4
  11. data/spec/ruby-measurement/core_ext/numeric_spec.rb +2 -2
  12. data/spec/ruby-measurement/core_ext/string_spec.rb +12 -12
  13. data/spec/ruby-measurement/core_ext/symbol_spec.rb +3 -3
  14. data/spec/ruby-measurement/definitions/metric/area_spec.rb +32 -32
  15. data/spec/ruby-measurement/definitions/metric/capacity_spec.rb +18 -18
  16. data/spec/ruby-measurement/definitions/metric/length_spec.rb +128 -128
  17. data/spec/ruby-measurement/definitions/metric/volume_spec.rb +128 -128
  18. data/spec/ruby-measurement/definitions/metric/weight_spec.rb +160 -160
  19. data/spec/ruby-measurement/definitions/us_customary/area_spec.rb +72 -72
  20. data/spec/ruby-measurement/definitions/us_customary/capacity_spec.rb +32 -32
  21. data/spec/ruby-measurement/definitions/us_customary/length_spec.rb +128 -128
  22. data/spec/ruby-measurement/definitions/us_customary/volume_spec.rb +98 -98
  23. data/spec/ruby-measurement/definitions/us_customary/weight_spec.rb +72 -72
  24. data/spec/ruby-measurement/measurement_spec.rb +238 -164
  25. data/spec/ruby-measurement/unit_builder_spec.rb +35 -35
  26. data/spec/ruby-measurement/unit_spec.rb +84 -41
  27. data/tasks/rspec.rake +1 -1
  28. metadata +29 -25
  29. data/.travis.yml +0 -4
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e9ad871d0be538a1fe0b81b6f7e30d88c843a6d8c26b0682dd4ce68289ebfdf8
4
+ data.tar.gz: fad2d4f562bbca29e685fd64f36ce7473b2b470479daaeb3a02bd707d409c198
5
+ SHA512:
6
+ metadata.gz: 9476515fecd08634e5f194ec1333e8d23ed7978046c99f72aa1a120b462961b711c2c49948bd4927eacfea9add0f378dfd1da865aded3f0f4ec9cff345a0a562
7
+ data.tar.gz: b14f4807067541c3d498f021e0cf79108a768358b8969258ee2452988cb70a3cc06a1cec83bfc6236ecf399f69459845dbb0b98e9cdf9226396cdd28f173d9e5
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby:
16
+ - head
17
+ - '3.0'
18
+ - '2.7'
19
+ - '2.6'
20
+ - '2.5'
21
+ continue-on-error: ${{ matrix.ruby == 'head' }}
22
+ name: Ruby ${{ matrix.ruby }}
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - uses: ruby/setup-ruby@v1
26
+ with:
27
+ ruby-version: ${{ matrix.ruby }}
28
+ bundler-cache: true
29
+ - run: |
30
+ bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,24 +1,39 @@
1
+ ## ruby-measurement 1.3.0 (Aug 1, 2021)
2
+
3
+ * Added `Comparable` module to `Measurement`, supporting `<` and `>` operators. *Edmund Lee*
4
+
5
+ ## ruby-measurement 1.2.3 (Mar 27, 2016)
6
+
7
+ * Updated parsing to accept UTF-8 fractions (½, ⅓, etc.). *Matt Huggins*
8
+
9
+ ## ruby-measurement 1.2.2 (Jul 13, 2014)
10
+
11
+ * Fixed `Measurement::Unit#==` to properly compare units. *Matt Huggins*
12
+
13
+ ## ruby-measurement 1.2.1 (Jul 27, 2013)
14
+
15
+ * Added `Measurement.names` to list all available units. *Daniele Palombo*
16
+
1
17
  ## ruby-measurement 1.2.0 (Mar 16, 2013)
2
18
 
3
- * Implement `String#to_measurement`, `String#to_unit`,
19
+ * Added `String#to_measurement`, `String#to_unit`,
4
20
  `Symbol#to_unit`, and `Numeric#to_measurement`. *Matt Huggins*
5
21
 
6
22
  ## ruby-measurement 1.1.0 (Feb 6, 2013)
7
23
 
8
- * Move unit definition logic into `Unit` class. *Matt Huggins*
9
- * Create test suite. *Matt Huggins*
10
- * Fix parsing of `'` and `"` for feet and inches. *Matt Huggins*
11
- * Implement missing metric weights and U.S. customary capacities.
12
- *Matt Huggins*
13
- * Fix several conversions that were found to be miscalculating.
24
+ * Moved unit definition logic into `Unit` class. *Matt Huggins*
25
+ * Added test suite. *Matt Huggins*
26
+ * Fixed parsing of `'` and `"` for feet and inches. *Matt Huggins*
27
+ * Added missing metric weights and U.S. customary capacities. *Matt Huggins*
28
+ * Fixed several conversions that were found to be miscalculating.
14
29
  *Matt Huggins*
15
30
 
16
31
  ## ruby-measurement 1.0.0 (Jan 22, 2013)
17
32
 
18
- * Fix parsing of mixed fractions. *Matt Huggins*
19
- * Allow parsing of quantity & unit to work when not separated by a space.
33
+ * Fixed parsing of mixed fractions. *Matt Huggins*
34
+ * Updated parsing of quantity & unit to work when not separated by a space.
20
35
  *Matt Huggins*
21
- * Change `Measurement` initializer to accept quantity & unit instead of a
36
+ * Updated `Measurement` initializer to accept quantity & unit instead of a
22
37
  single string for parsing. *Matt Huggins*
23
38
 
24
39
  ## ruby-measurement 0.0.1 (Jan 21, 2013)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Measurement
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/mhuggins/ruby-measurement.png)](http://travis-ci.org/mhuggins/ruby-measurement)
3
+ [![Build Status](https://github.com/mhuggins/ruby-measurement/actions/workflows/CI.yml/badge.svg)](https://github.com/mhuggins/ruby-measurement/actions/workflows/CI.yml)
4
4
  [![Code Climate](https://codeclimate.com/github/mhuggins/ruby-measurement.png)](https://codeclimate.com/github/mhuggins/ruby-measurement)
5
5
 
6
6
  [ruby-measurement](https://github.com/mhuggins/ruby-measurement) is a simple
@@ -108,10 +108,10 @@ To include just one of these systems of measure, require the gem in per the
108
108
  following.
109
109
 
110
110
  require 'ruby-measurement/measurement'
111
-
111
+
112
112
  # Metric units/conversions
113
113
  require 'ruby-measurement/definitions/metric'
114
-
114
+
115
115
  # U.S. customary units/conversions
116
116
  require 'ruby-measurement/definitions/us_customary'
117
117
 
@@ -119,13 +119,13 @@ Additionally, specific categories of units of measure can be included per
119
119
  system.
120
120
 
121
121
  require 'ruby-measurement/measurement'
122
-
122
+
123
123
  # Metric units/conversions
124
124
  require 'ruby-measurement/definitions/metric/area'
125
125
  require 'ruby-measurement/definitions/metric/length'
126
126
  require 'ruby-measurement/definitions/metric/volume'
127
127
  require 'ruby-measurement/definitions/metric/weight'
128
-
128
+
129
129
  # U.S. customary units/conversions
130
130
  require 'ruby-measurement/definitions/us_customary/area'
131
131
  require 'ruby-measurement/definitions/us_customary/length'
@@ -142,13 +142,13 @@ default.
142
142
  unit.convert_to(:week) { |value| value / 7.0 }
143
143
  unit.convert_to(:year) { |value| value / 365.0 }
144
144
  end
145
-
145
+
146
146
  Measurement.define(:wk) do |unit|
147
147
  unit.alias :week, :weeks
148
148
  unit.convert_to(:day) { |value| value * 7.0 }
149
149
  unit.convert_to(:year) { |value| value / 52.0 }
150
150
  end
151
-
151
+
152
152
  Measurement.define(:yr) do |unit|
153
153
  unit.alias :year, :years
154
154
  unit.convert_to(:day) { |value| value * 365.0 }
@@ -162,6 +162,12 @@ that will be used when displaying a measurement.
162
162
  Measurement.parse('3 year') # => 3.0 yr
163
163
  Measurement.parse('3 years') # => 3.0 yr
164
164
 
165
+ ### List all units names defined
166
+
167
+ List all keys you can use as unit
168
+
169
+ Measurement::Unit.names # => ['count','doz','dozen',...]
170
+
165
171
  ## Contributing
166
172
 
167
173
  1. Fork it
@@ -4,8 +4,8 @@ class String
4
4
  def to_measurement
5
5
  Measurement.parse(self)
6
6
  end
7
-
7
+
8
8
  def to_unit
9
- Measurement::Unit[self] or raise ArgumentError, "Invalid unit: #{self}"
9
+ Measurement::Unit[self] or raise ArgumentError, "Invalid unit: '#{self}'"
10
10
  end
11
11
  end
@@ -2,6 +2,6 @@ require 'ruby-measurement/unit'
2
2
 
3
3
  class Symbol
4
4
  def to_unit
5
- Measurement::Unit[self] or raise ArgumentError, "Invalid unit: #{self}"
5
+ Measurement::Unit[self] or raise ArgumentError, "Invalid unit: '#{self}'"
6
6
  end
7
7
  end
@@ -1,34 +1,56 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'ruby-measurement/unit'
2
4
  require 'ruby-measurement/version'
3
5
 
4
6
  class Measurement
7
+ include Comparable
8
+
5
9
  UNIT_REGEX = /([^\d\s\/].*)/.freeze
6
10
  SCIENTIFIC_NUMBER = /([+-]?\d*\.?\d+(?:[Ee][+-]?)?\d*)/.freeze
7
11
  SCIENTIFIC_REGEX = /\A#{SCIENTIFIC_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
8
12
  RATIONAL_REGEX = /\A([+-]?\d+\s+)?((\d+)\/(\d+))?\s*#{UNIT_REGEX}?\z/.freeze
9
13
  COMPLEX_REGEX = /\A#{SCIENTIFIC_NUMBER}?#{SCIENTIFIC_NUMBER}i\s*#{UNIT_REGEX}?\z/.freeze
10
-
14
+
15
+ RATIOS = {
16
+ '¼' => '1/4',
17
+ '½' => '1/2',
18
+ '¾' => '3/4',
19
+ '⅓' => '1/3',
20
+ '⅔' => '2/3',
21
+ '⅕' => '1/5',
22
+ '⅖' => '2/5',
23
+ '⅗' => '3/5',
24
+ '⅘' => '4/5',
25
+ '⅙' => '1/6',
26
+ '⅚' => '5/6',
27
+ '⅛' => '1/8',
28
+ '⅜' => '3/8',
29
+ '⅝' => '5/8',
30
+ '⅞' => '7/8',
31
+ }.freeze
32
+
11
33
  attr_reader :quantity, :unit
12
-
34
+
13
35
  def initialize(quantity, unit_name = :count)
14
36
  unit = unit_name
15
37
  unit = Unit[unit_name.to_s] if unit_name.kind_of?(Symbol) || unit_name.kind_of?(String)
16
-
17
- raise ArgumentError, "Invalid quantity: #{quantity}" unless quantity.kind_of?(Numeric)
18
- raise ArgumentError, "Invalid unit: #{unit_name}" unless unit.kind_of?(Unit)
19
-
38
+
39
+ raise ArgumentError, "Invalid quantity: '#{quantity}'" unless quantity.kind_of?(Numeric)
40
+ raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit.kind_of?(Unit)
41
+
20
42
  @quantity = quantity
21
43
  @unit = unit
22
44
  end
23
-
45
+
24
46
  def inspect
25
47
  to_s
26
48
  end
27
-
49
+
28
50
  def to_s
29
51
  "#{quantity} #{unit}"
30
52
  end
31
-
53
+
32
54
  %w(+ - * /).each do |operator|
33
55
  class_eval <<-END, __FILE__, __LINE__ + 1
34
56
  def #{operator}(obj)
@@ -47,7 +69,7 @@ class Measurement
47
69
  end
48
70
  END
49
71
  end
50
-
72
+
51
73
  def **(obj)
52
74
  case obj
53
75
  when Numeric
@@ -56,41 +78,90 @@ class Measurement
56
78
  raise ArgumentError, "Invalid arithmetic: #{self} ** #{obj}"
57
79
  end
58
80
  end
59
-
60
- def ==(obj)
61
- obj.kind_of?(self.class) && quantity == obj.quantity && unit == obj.unit
81
+
82
+ def <=>(obj)
83
+ if !obj.is_a?(self.class) || obj.unit != self.unit
84
+ return nil
85
+ end
86
+
87
+ if quantity < obj.quantity
88
+ -1
89
+ elsif quantity > obj.quantity
90
+ 1
91
+ else
92
+ 0
93
+ end
62
94
  end
63
-
95
+
64
96
  def convert_to(unit_name)
65
97
  unit = Unit[unit_name]
66
98
  raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit
67
-
99
+
68
100
  return dup if unit == @unit
69
-
101
+
70
102
  conversion = @unit.conversion(unit.name)
71
103
  raise ArgumentError, "Invalid conversion: '#@unit' to '#{unit.name}'" unless conversion
72
-
104
+
73
105
  self.class.new(conversion.call(@quantity), unit.name)
74
106
  end
75
-
107
+
76
108
  def convert_to!(unit_name)
77
109
  measurement = convert_to(unit_name)
78
110
  @unit, @quantity = measurement.unit, measurement.quantity
79
111
  self
80
112
  end
81
-
82
- def self.parse(str = '0')
83
- str = str.strip
84
-
85
- if str =~ COMPLEX_REGEX
113
+
114
+ class << self
115
+ def parse(str = '0')
116
+ str = normalize(str)
117
+
118
+ case str
119
+ when COMPLEX_REGEX then unit_name, quantity = parse_complex(str)
120
+ when SCIENTIFIC_REGEX then unit_name, quantity = parse_scientific(str)
121
+ when RATIONAL_REGEX then unit_name, quantity = parse_rational(str)
122
+ else raise ArgumentError, "Unable to parse: '#{str}'"
123
+ end
124
+
125
+ unit_name ||= 'count'
126
+ unit = Unit[unit_name.strip.downcase]
127
+ raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit
128
+
129
+ new(quantity, unit)
130
+ end
131
+
132
+ def define(unit_name, &block)
133
+ Unit.define(unit_name, &block)
134
+ end
135
+
136
+ private
137
+
138
+ def normalize(string)
139
+ string.dup.tap do |str|
140
+ if str =~ Regexp.new(/(#{RATIOS.keys.join('|')})/)
141
+ RATIOS.each do |search, replace|
142
+ str.gsub!(search) { " #{replace}" }
143
+ end
144
+ end
145
+
146
+ str.strip!
147
+ end
148
+ end
149
+
150
+ def parse_complex(str)
86
151
  real, imaginary, unit_name = str.scan(COMPLEX_REGEX).first
87
152
  quantity = Complex(real.to_f, imaginary.to_f).to_f
88
- elsif str =~ SCIENTIFIC_REGEX
153
+ return unit_name, quantity
154
+ end
155
+
156
+ def parse_scientific(str)
89
157
  whole, unit_name = str.scan(SCIENTIFIC_REGEX).first
90
158
  quantity = whole.to_f
91
- elsif str =~ RATIONAL_REGEX
159
+ return unit_name, quantity
160
+ end
161
+
162
+ def parse_rational(str)
92
163
  whole, _, numerator, denominator, unit_name = str.scan(RATIONAL_REGEX).first
93
-
164
+
94
165
  if numerator && denominator
95
166
  numerator = numerator.to_f + (denominator.to_f * whole.to_f)
96
167
  denominator = denominator.to_f
@@ -98,27 +169,15 @@ class Measurement
98
169
  else
99
170
  quantity = whole.to_f
100
171
  end
101
- else
102
- raise ArgumentError, "Unable to parse: '#{str}'"
172
+
173
+ return unit_name, quantity
103
174
  end
104
-
105
- unit_name ||= 'count'
106
- unit = Unit[unit_name.strip.downcase]
107
- raise ArgumentError, "Invalid unit: '#{unit_name}'" unless unit
108
-
109
- new(quantity, unit)
110
175
  end
111
-
112
- def self.define(unit_name, &block)
113
- Unit.define(unit_name, &block)
114
- end
115
-
116
- private
117
-
176
+
118
177
  define(:count) do |unit|
119
178
  unit.convert_to(:dozen) { |value| value / 12.0 }
120
179
  end
121
-
180
+
122
181
  define(:doz) do |unit|
123
182
  unit.alias :dozen
124
183
  unit.convert_to(:count) { |value| value * 12.0 }
@@ -3,79 +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
- obj.kind_of?(self.class) && hash == obj.hash
49
- end
50
-
51
- def self.define(unit_name, &block)
52
- Builder.new(unit_name, &block)
53
- end
54
-
55
- def self.[](unit_name)
56
- @definitions[unit_name.to_s.downcase]
48
+ obj.kind_of?(self.class) && name == obj.name && aliases == obj.aliases && conversions.all? do |key, proc|
49
+ [-2.5, -1, 0, 1, 2.5].all? { |n| proc.call(n) == obj.conversions[key].call(n) }
50
+ end
57
51
  end
58
-
59
- private
60
-
61
- def self.[]=(unit_name, unit)
62
- @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
63
69
  end
64
-
70
+
65
71
  class Builder
66
72
  def initialize(unit_name, &block)
67
73
  @unit = Unit.new(unit_name)
68
74
  block.call(self) if block_given?
69
75
  end
70
-
76
+
71
77
  def alias(*args)
72
78
  @unit.add_alias(*args)
73
79
  end
74
-
80
+
75
81
  def convert_to(unit_name, &block)
76
82
  @unit.add_conversion(unit_name, &block)
77
83
  end
78
-
84
+
79
85
  def to_unit
80
86
  @unit
81
87
  end