measured 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/Rakefile +4 -1
- data/dev.yml +1 -1
- data/lib/measured/base.rb +2 -1
- data/lib/measured/conversion_table_builder.rb +80 -0
- data/lib/measured/unit.rb +1 -1
- data/lib/measured/unit_system.rb +2 -1
- data/lib/measured/version.rb +1 -1
- data/measured.gemspec +3 -3
- data/test/conversion_table_builder_test.rb +118 -0
- data/test/test_helper.rb +1 -1
- data/test/unit_test.rb +4 -0
- metadata +9 -7
- data/lib/measured/conversion_table.rb +0 -65
- data/test/conversion_table_test.rb +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b181078ed94c22d305d95b445287def5a9ee7386
|
4
|
+
data.tar.gz: 2694f75fc2085f93c93075435ad1955b5f872e36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 961ab0ba4ac6cec71002b1c4a65a179880db5e8336a722aef32849ab6bf1a1f3bb2b137687ca3a8e4fd651d90b76ab1616d6306f03e3a12d82e64c248ae377c7
|
7
|
+
data.tar.gz: 757eba32db074dfba1712fd1c9c751630d5f5bae413dbb9fea0735bc0255f63fd28ca1a8b39aea9057b308acc14d3de7330305b1711ef45f87d4bdcf4b8f879f
|
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -8,9 +8,12 @@ task default: :test
|
|
8
8
|
|
9
9
|
desc 'Run the test stuite'
|
10
10
|
Rake::TestTask.new do |t|
|
11
|
+
files = ARGV[1..-1]
|
12
|
+
files = "test/**/*_test.rb" if !files || files.length == 0
|
13
|
+
|
11
14
|
t.libs << "test"
|
12
15
|
t.libs << "lib/**/*"
|
13
|
-
t.test_files = FileList[
|
16
|
+
t.test_files = FileList[files]
|
14
17
|
t.verbose = true
|
15
18
|
end
|
16
19
|
|
data/dev.yml
CHANGED
data/lib/measured/base.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "forwardable"
|
1
2
|
require "measured/version"
|
2
3
|
require "active_support/all"
|
3
4
|
require "bigdecimal"
|
@@ -42,5 +43,5 @@ require "measured/parser"
|
|
42
43
|
require "measured/unit"
|
43
44
|
require "measured/unit_system"
|
44
45
|
require "measured/unit_system_builder"
|
45
|
-
require "measured/
|
46
|
+
require "measured/conversion_table_builder"
|
46
47
|
require "measured/measurable"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Measured::ConversionTableBuilder
|
2
|
+
attr_reader :units
|
3
|
+
|
4
|
+
def initialize(units)
|
5
|
+
@units = units
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_h
|
9
|
+
table = {}
|
10
|
+
|
11
|
+
units.map{|u| u.name}.each do |to_unit|
|
12
|
+
to_table = {to_unit => Rational(1, 1)}
|
13
|
+
|
14
|
+
table.each do |from_unit, from_table|
|
15
|
+
conversion = find_conversion(to: from_unit, from: to_unit)
|
16
|
+
to_table[from_unit] = conversion
|
17
|
+
from_table[to_unit] = 1 / conversion
|
18
|
+
end
|
19
|
+
|
20
|
+
table[to_unit] = to_table
|
21
|
+
end
|
22
|
+
|
23
|
+
table
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_conversion(to:, from:)
|
29
|
+
conversion = find_direct_conversion_cached(to: to, from: from) || find_tree_traversal_conversion(to: to, from: from)
|
30
|
+
|
31
|
+
raise Measured::UnitError, "Cannot find conversion path from #{ from } to #{ to }." unless conversion
|
32
|
+
|
33
|
+
conversion
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_direct_conversion_cached(to:, from:)
|
37
|
+
@cache ||= {}
|
38
|
+
@cache[to] ||= {}
|
39
|
+
|
40
|
+
if @cache[to].key?(from)
|
41
|
+
@cache[to][from]
|
42
|
+
else
|
43
|
+
@cache[to][from] = find_direct_conversion(to: to, from: from)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_direct_conversion(to:, from:)
|
48
|
+
units.each do |unit|
|
49
|
+
return unit.conversion_amount if unit.name == from && unit.conversion_unit == to
|
50
|
+
return unit.inverse_conversion_amount if unit.name == to && unit.conversion_unit == from
|
51
|
+
end
|
52
|
+
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_tree_traversal_conversion(to:, from:)
|
57
|
+
traverse(from: from, to: to, units_remaining: units.map(&:name), amount: 1)
|
58
|
+
end
|
59
|
+
|
60
|
+
def traverse(from:, to:, units_remaining:, amount:)
|
61
|
+
units_remaining = units_remaining - [from]
|
62
|
+
|
63
|
+
units_remaining.each do |name|
|
64
|
+
conversion = find_direct_conversion_cached(from: from, to: name)
|
65
|
+
|
66
|
+
if conversion
|
67
|
+
new_amount = amount * conversion
|
68
|
+
if name == to
|
69
|
+
return new_amount
|
70
|
+
else
|
71
|
+
result = traverse(from: name, to: to, units_remaining: units_remaining, amount: new_amount)
|
72
|
+
return result if result
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
data/lib/measured/unit.rb
CHANGED
data/lib/measured/unit_system.rb
CHANGED
@@ -3,6 +3,7 @@ class Measured::UnitSystem
|
|
3
3
|
|
4
4
|
def initialize(units)
|
5
5
|
@units = units.map { |unit| unit.with_unit_system(self) }
|
6
|
+
@conversion_table_builder = Measured::ConversionTableBuilder.new(@units)
|
6
7
|
end
|
7
8
|
|
8
9
|
def unit_names_with_aliases
|
@@ -43,7 +44,7 @@ class Measured::UnitSystem
|
|
43
44
|
protected
|
44
45
|
|
45
46
|
def conversion_table
|
46
|
-
@conversion_table ||=
|
47
|
+
@conversion_table ||= @conversion_table_builder.to_h
|
47
48
|
end
|
48
49
|
|
49
50
|
def unit_name_to_unit
|
data/lib/measured/version.rb
CHANGED
data/measured.gemspec
CHANGED
@@ -6,10 +6,10 @@ require 'measured/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "measured"
|
8
8
|
spec.version = Measured::VERSION
|
9
|
-
spec.authors = ["Kevin McPhillips"]
|
10
|
-
spec.email = ["
|
9
|
+
spec.authors = ["Kevin McPhillips", "Jason Gedge", "Javier Honduvilla Coto"]
|
10
|
+
spec.email = ["gems@shopify.com"]
|
11
11
|
spec.summary = %q{Encapsulate measurements with their units in Ruby}
|
12
|
-
spec.description = %q{Wrapper objects which encapsulate
|
12
|
+
spec.description = %q{Wrapper objects which encapsulate measurements and their associated units in Ruby.}
|
13
13
|
spec.homepage = "https://github.com/Shopify/measured"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class Measured::ConversionTableBuilderTest < ActiveSupport::TestCase
|
4
|
+
test "#initialize creates a new object with the units" do
|
5
|
+
units = [Measured::Unit.new(:test)]
|
6
|
+
|
7
|
+
assert_equal units, Measured::ConversionTableBuilder.new(units).units
|
8
|
+
end
|
9
|
+
|
10
|
+
test "#to_h should return a hash for the simple case" do
|
11
|
+
conversion_table = Measured::ConversionTableBuilder.new([Measured::Unit.new(:test)]).to_h
|
12
|
+
|
13
|
+
expected = {
|
14
|
+
"test" => {"test" => Rational(1, 1)}
|
15
|
+
}
|
16
|
+
|
17
|
+
assert_equal expected, conversion_table
|
18
|
+
assert_instance_of Rational, conversion_table.values.first.values.first
|
19
|
+
end
|
20
|
+
|
21
|
+
test "#to_h returns expected nested hashes with BigDecimal conversion factors in a tiny data set" do
|
22
|
+
conversion_table = Measured::ConversionTableBuilder.new([
|
23
|
+
Measured::Unit.new(:m),
|
24
|
+
Measured::Unit.new(:cm, value: "0.01 m"),
|
25
|
+
]).to_h
|
26
|
+
|
27
|
+
expected = {
|
28
|
+
"m" => {
|
29
|
+
"m" => Rational(1, 1),
|
30
|
+
"cm" => Rational(100, 1),
|
31
|
+
},
|
32
|
+
"cm" => {
|
33
|
+
"m" => Rational(1, 100),
|
34
|
+
"cm" => Rational(1, 1),
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
assert_equal expected, conversion_table
|
39
|
+
|
40
|
+
conversion_table.values.map(&:values).flatten.each do |value|
|
41
|
+
assert_instance_of Rational, value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
test "#to_h returns expected nested hashes factors" do
|
46
|
+
conversion_table = Measured::ConversionTableBuilder.new([
|
47
|
+
Measured::Unit.new(:m),
|
48
|
+
Measured::Unit.new(:cm, value: "0.01 m"),
|
49
|
+
Measured::Unit.new(:mm, value: "0.001 m"),
|
50
|
+
]).to_h
|
51
|
+
|
52
|
+
expected = {
|
53
|
+
"m" => {
|
54
|
+
"m" => Rational(1, 1),
|
55
|
+
"cm" => Rational(100, 1),
|
56
|
+
"mm" => Rational(1000, 1),
|
57
|
+
},
|
58
|
+
"cm" => {
|
59
|
+
"m" => Rational(1, 100),
|
60
|
+
"cm" => Rational(1, 1),
|
61
|
+
"mm" => Rational(10, 1),
|
62
|
+
},
|
63
|
+
"mm" => {
|
64
|
+
"m" => Rational(1, 1000),
|
65
|
+
"cm" => Rational(1, 10),
|
66
|
+
"mm" => Rational(1, 1),
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
assert_equal expected, conversion_table
|
71
|
+
|
72
|
+
conversion_table.values.map(&:values).flatten.each do |value|
|
73
|
+
assert_instance_of Rational, value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
test "#to_h returns expected nested hashes in an indrect path" do
|
78
|
+
conversion_table = Measured::ConversionTableBuilder.new([
|
79
|
+
Measured::Unit.new(:mm),
|
80
|
+
Measured::Unit.new(:cm, value: "10 mm"),
|
81
|
+
Measured::Unit.new(:dm, value: "10 cm"),
|
82
|
+
Measured::Unit.new(:m, value: "10 dm"),
|
83
|
+
]).to_h
|
84
|
+
|
85
|
+
expected = {
|
86
|
+
"m" => {
|
87
|
+
"m" => Rational(1, 1),
|
88
|
+
"dm" => Rational(10, 1),
|
89
|
+
"cm" => Rational(100, 1),
|
90
|
+
"mm" => Rational(1000, 1),
|
91
|
+
},
|
92
|
+
"cm" => {
|
93
|
+
"m" => Rational(1, 100),
|
94
|
+
"dm" => Rational(1, 10),
|
95
|
+
"cm" => Rational(1, 1),
|
96
|
+
"mm" => Rational(10, 1),
|
97
|
+
},
|
98
|
+
"dm" => {
|
99
|
+
"m" => Rational(1, 10),
|
100
|
+
"cm" => Rational(10, 1),
|
101
|
+
"dm" => Rational(1, 1),
|
102
|
+
"mm" => Rational(100, 1),
|
103
|
+
},
|
104
|
+
"mm" => {
|
105
|
+
"m" => Rational(1, 1000),
|
106
|
+
"dm" => Rational(1, 100),
|
107
|
+
"cm" => Rational(1, 10),
|
108
|
+
"mm" => Rational(1, 1),
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
assert_equal expected, conversion_table
|
113
|
+
|
114
|
+
conversion_table.values.map(&:values).flatten.each do |value|
|
115
|
+
assert_instance_of Rational, value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/test/test_helper.rb
CHANGED
data/test/unit_test.rb
CHANGED
@@ -78,4 +78,8 @@ class Measured::UnitTest < ActiveSupport::TestCase
|
|
78
78
|
test "#inverse_conversion_amount returns 1/amount" do
|
79
79
|
assert_equal Rational(1, 10), @unit.inverse_conversion_amount
|
80
80
|
end
|
81
|
+
|
82
|
+
test "#inverse_conversion_amount handles nil for base unit" do
|
83
|
+
assert_nil Measured::Unit.new(:pie).inverse_conversion_amount
|
84
|
+
end
|
81
85
|
end
|
metadata
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: measured
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin McPhillips
|
8
|
+
- Jason Gedge
|
9
|
+
- Javier Honduvilla Coto
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
13
|
+
date: 2018-01-24 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: activesupport
|
@@ -94,10 +96,10 @@ dependencies:
|
|
94
96
|
- - ">="
|
95
97
|
- !ruby/object:Gem::Version
|
96
98
|
version: '0'
|
97
|
-
description: Wrapper objects which encapsulate
|
99
|
+
description: Wrapper objects which encapsulate measurements and their associated units
|
98
100
|
in Ruby.
|
99
101
|
email:
|
100
|
-
-
|
102
|
+
- gems@shopify.com
|
101
103
|
executables: []
|
102
104
|
extensions: []
|
103
105
|
extra_rdoc_files: []
|
@@ -113,7 +115,7 @@ files:
|
|
113
115
|
- lib/measured.rb
|
114
116
|
- lib/measured/arithmetic.rb
|
115
117
|
- lib/measured/base.rb
|
116
|
-
- lib/measured/
|
118
|
+
- lib/measured/conversion_table_builder.rb
|
117
119
|
- lib/measured/measurable.rb
|
118
120
|
- lib/measured/parser.rb
|
119
121
|
- lib/measured/unit.rb
|
@@ -126,7 +128,7 @@ files:
|
|
126
128
|
- measured.gemspec
|
127
129
|
- shipit.rubygems.yml
|
128
130
|
- test/arithmetic_test.rb
|
129
|
-
- test/
|
131
|
+
- test/conversion_table_builder_test.rb
|
130
132
|
- test/measurable_test.rb
|
131
133
|
- test/parser_test.rb
|
132
134
|
- test/support/fake_system.rb
|
@@ -164,7 +166,7 @@ specification_version: 4
|
|
164
166
|
summary: Encapsulate measurements with their units in Ruby
|
165
167
|
test_files:
|
166
168
|
- test/arithmetic_test.rb
|
167
|
-
- test/
|
169
|
+
- test/conversion_table_builder_test.rb
|
168
170
|
- test/measurable_test.rb
|
169
171
|
- test/parser_test.rb
|
170
172
|
- test/support/fake_system.rb
|
@@ -1,65 +0,0 @@
|
|
1
|
-
module Measured::ConversionTable
|
2
|
-
extend self
|
3
|
-
|
4
|
-
def build(units)
|
5
|
-
table = {}
|
6
|
-
|
7
|
-
units.map{|u| u.name}.each do |to_unit|
|
8
|
-
to_table = {to_unit => BigDecimal("1")}
|
9
|
-
|
10
|
-
table.each do |from_unit, from_table|
|
11
|
-
to_table[from_unit] = find_conversion(units, to: from_unit, from: to_unit)
|
12
|
-
from_table[to_unit] = find_conversion(units, to: to_unit, from: from_unit)
|
13
|
-
end
|
14
|
-
|
15
|
-
table[to_unit] = to_table
|
16
|
-
end
|
17
|
-
|
18
|
-
table
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def find_conversion(units, to:, from:)
|
24
|
-
conversion = find_direct_conversion(units, to: to, from: from) || find_tree_traversal_conversion(units, to: to, from: from)
|
25
|
-
|
26
|
-
raise Measured::UnitError, "Cannot find conversion path from #{ from } to #{ to }." unless conversion
|
27
|
-
|
28
|
-
conversion
|
29
|
-
end
|
30
|
-
|
31
|
-
def find_direct_conversion(units, to:, from:)
|
32
|
-
units.each do |unit|
|
33
|
-
return unit.conversion_amount if unit.name == from && unit.conversion_unit == to
|
34
|
-
end
|
35
|
-
|
36
|
-
units.each do |unit|
|
37
|
-
return unit.inverse_conversion_amount if unit.name == to && unit.conversion_unit == from
|
38
|
-
end
|
39
|
-
|
40
|
-
nil
|
41
|
-
end
|
42
|
-
|
43
|
-
def find_tree_traversal_conversion(units, to:, from:)
|
44
|
-
traverse(units, from: from, to: to, unit_names: units.map(&:name), amount: 1)
|
45
|
-
end
|
46
|
-
|
47
|
-
def traverse(units, from:, to:, unit_names:, amount:)
|
48
|
-
unit_names = unit_names - [from]
|
49
|
-
|
50
|
-
unit_names.each do |name|
|
51
|
-
if conversion = find_direct_conversion(units, from: from, to: name)
|
52
|
-
new_amount = amount * conversion
|
53
|
-
if name == to
|
54
|
-
return new_amount
|
55
|
-
else
|
56
|
-
result = traverse(units, from: name, to: to, unit_names: unit_names, amount: new_amount)
|
57
|
-
return result if result
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class Measured::ConversionTableTest < ActiveSupport::TestCase
|
4
|
-
test ".build should return a hash for the simple case" do
|
5
|
-
expected = {
|
6
|
-
"test" => {"test" => BigDecimal("1")}
|
7
|
-
}
|
8
|
-
|
9
|
-
assert_equal expected, Measured::ConversionTable.build([Measured::Unit.new(:test)])
|
10
|
-
end
|
11
|
-
|
12
|
-
test ".build returns expected nested hashes with BigDecimal conversion factors in a tiny data set" do
|
13
|
-
conversion_table = Measured::ConversionTable.build([
|
14
|
-
Measured::Unit.new(:m),
|
15
|
-
Measured::Unit.new(:cm, value: "0.01 m"),
|
16
|
-
])
|
17
|
-
|
18
|
-
expected = {
|
19
|
-
"m" => {
|
20
|
-
"m" => BigDecimal("1"),
|
21
|
-
"cm" => BigDecimal("100"),
|
22
|
-
},
|
23
|
-
"cm" => {
|
24
|
-
"m" => BigDecimal("0.01"),
|
25
|
-
"cm" => BigDecimal("1"),
|
26
|
-
}
|
27
|
-
}
|
28
|
-
|
29
|
-
assert_equal expected, conversion_table
|
30
|
-
end
|
31
|
-
|
32
|
-
test ".build returns expected nested hashes with BigDecimal conversion factors" do
|
33
|
-
conversion_table = Measured::ConversionTable.build([
|
34
|
-
Measured::Unit.new(:m),
|
35
|
-
Measured::Unit.new(:cm, value: "0.01 m"),
|
36
|
-
Measured::Unit.new(:mm, value: "0.001 m"),
|
37
|
-
])
|
38
|
-
|
39
|
-
expected = {
|
40
|
-
"m" => {
|
41
|
-
"m" => BigDecimal("1"),
|
42
|
-
"cm" => BigDecimal("100"),
|
43
|
-
"mm" => BigDecimal("1000"),
|
44
|
-
},
|
45
|
-
"cm" => {
|
46
|
-
"m" => BigDecimal("0.01"),
|
47
|
-
"cm" => BigDecimal("1"),
|
48
|
-
"mm" => BigDecimal("10"),
|
49
|
-
},
|
50
|
-
"mm" => {
|
51
|
-
"m" => BigDecimal("0.001"),
|
52
|
-
"cm" => BigDecimal("0.1"),
|
53
|
-
"mm" => BigDecimal("1"),
|
54
|
-
}
|
55
|
-
}
|
56
|
-
|
57
|
-
assert_equal expected, conversion_table
|
58
|
-
end
|
59
|
-
|
60
|
-
test ".build returns expected nested hashes with BigDecimal conversion factors in an indrect path" do
|
61
|
-
conversion_table = Measured::ConversionTable.build([
|
62
|
-
Measured::Unit.new(:mm),
|
63
|
-
Measured::Unit.new(:cm, value: "10 mm"),
|
64
|
-
Measured::Unit.new(:dm, value: "10 cm"),
|
65
|
-
Measured::Unit.new(:m, value: "10 dm"),
|
66
|
-
])
|
67
|
-
|
68
|
-
expected = {
|
69
|
-
"m" => {
|
70
|
-
"m" => BigDecimal("1"),
|
71
|
-
"dm" => BigDecimal("10"),
|
72
|
-
"cm" => BigDecimal("100"),
|
73
|
-
"mm" => BigDecimal("1000"),
|
74
|
-
},
|
75
|
-
"cm" => {
|
76
|
-
"m" => BigDecimal("0.01"),
|
77
|
-
"dm" => BigDecimal("0.1"),
|
78
|
-
"cm" => BigDecimal("1"),
|
79
|
-
"mm" => BigDecimal("10"),
|
80
|
-
},
|
81
|
-
"dm" => {
|
82
|
-
"m" => BigDecimal("0.1"),
|
83
|
-
"cm" => BigDecimal("10"),
|
84
|
-
"dm" => BigDecimal("1"),
|
85
|
-
"mm" => BigDecimal("100"),
|
86
|
-
},
|
87
|
-
"mm" => {
|
88
|
-
"m" => BigDecimal("0.001"),
|
89
|
-
"dm" => BigDecimal("0.01"),
|
90
|
-
"cm" => BigDecimal("0.1"),
|
91
|
-
"mm" => BigDecimal("1"),
|
92
|
-
}
|
93
|
-
}
|
94
|
-
|
95
|
-
assert_equal expected, conversion_table
|
96
|
-
end
|
97
|
-
|
98
|
-
end
|