ruby-measurement 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/CI.yml +30 -0
- data/CHANGELOG.md +25 -10
- data/README.md +13 -7
- data/lib/ruby-measurement/core_ext/string.rb +2 -2
- data/lib/ruby-measurement/core_ext/symbol.rb +1 -1
- data/lib/ruby-measurement/measurement.rb +101 -42
- data/lib/ruby-measurement/unit.rb +34 -28
- data/lib/ruby-measurement/version.rb +1 -1
- data/ruby-measurement.gemspec +5 -4
- data/spec/ruby-measurement/core_ext/numeric_spec.rb +2 -2
- data/spec/ruby-measurement/core_ext/string_spec.rb +12 -12
- data/spec/ruby-measurement/core_ext/symbol_spec.rb +3 -3
- data/spec/ruby-measurement/definitions/metric/area_spec.rb +32 -32
- data/spec/ruby-measurement/definitions/metric/capacity_spec.rb +18 -18
- data/spec/ruby-measurement/definitions/metric/length_spec.rb +128 -128
- data/spec/ruby-measurement/definitions/metric/volume_spec.rb +128 -128
- data/spec/ruby-measurement/definitions/metric/weight_spec.rb +160 -160
- data/spec/ruby-measurement/definitions/us_customary/area_spec.rb +72 -72
- data/spec/ruby-measurement/definitions/us_customary/capacity_spec.rb +32 -32
- data/spec/ruby-measurement/definitions/us_customary/length_spec.rb +128 -128
- data/spec/ruby-measurement/definitions/us_customary/volume_spec.rb +98 -98
- data/spec/ruby-measurement/definitions/us_customary/weight_spec.rb +72 -72
- data/spec/ruby-measurement/measurement_spec.rb +238 -164
- data/spec/ruby-measurement/unit_builder_spec.rb +35 -35
- data/spec/ruby-measurement/unit_spec.rb +84 -41
- data/tasks/rspec.rake +1 -1
- metadata +29 -25
- 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
|
-
*
|
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
|
-
*
|
9
|
-
*
|
10
|
-
*
|
11
|
-
*
|
12
|
-
|
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
|
-
*
|
19
|
-
*
|
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
|
-
*
|
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://
|
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
|
@@ -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
|
61
|
-
obj.
|
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
|
-
|
83
|
-
str =
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
102
|
-
|
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) &&
|
49
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|