unitwise 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +110 -0
- data/Rakefile +21 -0
- data/data/base_unit.yaml +43 -0
- data/data/derived_unit.yaml +3394 -0
- data/data/prefix.yaml +121 -0
- data/lib/unitwise.rb +25 -0
- data/lib/unitwise/atom.rb +84 -0
- data/lib/unitwise/base.rb +45 -0
- data/lib/unitwise/composable.rb +25 -0
- data/lib/unitwise/errors.rb +7 -0
- data/lib/unitwise/expression.rb +25 -0
- data/lib/unitwise/expression/composer.rb +41 -0
- data/lib/unitwise/expression/decomposer.rb +42 -0
- data/lib/unitwise/expression/matcher.rb +41 -0
- data/lib/unitwise/expression/parser.rb +53 -0
- data/lib/unitwise/expression/transformer.rb +22 -0
- data/lib/unitwise/ext.rb +2 -0
- data/lib/unitwise/ext/numeric.rb +13 -0
- data/lib/unitwise/ext/string.rb +5 -0
- data/lib/unitwise/function.rb +51 -0
- data/lib/unitwise/functional.rb +21 -0
- data/lib/unitwise/measurement.rb +89 -0
- data/lib/unitwise/prefix.rb +17 -0
- data/lib/unitwise/scale.rb +61 -0
- data/lib/unitwise/standard.rb +26 -0
- data/lib/unitwise/standard/base.rb +73 -0
- data/lib/unitwise/standard/base_unit.rb +21 -0
- data/lib/unitwise/standard/derived_unit.rb +49 -0
- data/lib/unitwise/standard/extras.rb +17 -0
- data/lib/unitwise/standard/function.rb +35 -0
- data/lib/unitwise/standard/prefix.rb +17 -0
- data/lib/unitwise/standard/scale.rb +25 -0
- data/lib/unitwise/term.rb +106 -0
- data/lib/unitwise/unit.rb +89 -0
- data/lib/unitwise/version.rb +3 -0
- data/test/test_helper.rb +7 -0
- data/test/unitwise/atom_test.rb +122 -0
- data/test/unitwise/base_test.rb +6 -0
- data/test/unitwise/expression/decomposer_test.rb +36 -0
- data/test/unitwise/expression/matcher_test.rb +42 -0
- data/test/unitwise/expression/parser_test.rb +91 -0
- data/test/unitwise/ext/numeric_test.rb +46 -0
- data/test/unitwise/ext/string_test.rb +13 -0
- data/test/unitwise/function_test.rb +42 -0
- data/test/unitwise/measurement_test.rb +168 -0
- data/test/unitwise/prefix_test.rb +25 -0
- data/test/unitwise/term_test.rb +44 -0
- data/test/unitwise/unit_test.rb +57 -0
- data/test/unitwise_test.rb +7 -0
- data/unitwise.gemspec +28 -0
- metadata +213 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Expression::Decomposer do
|
4
|
+
subject { Unitwise::Expression::Decomposer }
|
5
|
+
|
6
|
+
describe "PARSERS" do
|
7
|
+
it "should return multiple parsers" do
|
8
|
+
subject::PARSERS.must_be_instance_of Array
|
9
|
+
subject::PARSERS.sample.must_be_instance_of Unitwise::Expression::Parser
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#terms" do
|
14
|
+
it "should accept codes" do
|
15
|
+
fts = subject.new("[ft_i]/s").terms
|
16
|
+
fts.count.must_equal 2
|
17
|
+
end
|
18
|
+
it "should accept names" do
|
19
|
+
kms = subject.new("kilometer/second").terms
|
20
|
+
kms.count.must_equal 2
|
21
|
+
end
|
22
|
+
it "should accept spaced names" do
|
23
|
+
ncg = subject.new("Newtonian constant of gravitation").terms
|
24
|
+
ncg.count.must_equal 1
|
25
|
+
end
|
26
|
+
it "should accept parameterized names" do
|
27
|
+
pc = subject.new("planck_constant").terms
|
28
|
+
pc.count.must_equal 1
|
29
|
+
end
|
30
|
+
it "should accept symbols" do
|
31
|
+
saff = subject.new("<i>g<sub>n</sub></i>").terms
|
32
|
+
saff.count.must_equal 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Expression::Matcher do
|
4
|
+
describe "::atom(:codes)" do
|
5
|
+
subject { Unitwise::Expression::Matcher.atom(:primary_code)}
|
6
|
+
it "must be an Alternative list" do
|
7
|
+
subject.must_be_instance_of Parslet::Atoms::Alternative
|
8
|
+
end
|
9
|
+
it "must parse [in_i]" do
|
10
|
+
subject.parse("[in_i]").must_equal("[in_i]")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
describe "::metric_atom(:names)" do
|
14
|
+
subject { Unitwise::Expression::Matcher.metric_atom(:names)}
|
15
|
+
it "must be an Alternative list of names" do
|
16
|
+
subject.must_be_instance_of Parslet::Atoms::Alternative
|
17
|
+
end
|
18
|
+
it "must parse 'Joule'" do
|
19
|
+
subject.parse('Joule').must_equal('Joule')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "::atom(:slugs)" do
|
24
|
+
subject { Unitwise::Expression::Matcher.atom(:slugs)}
|
25
|
+
it "must be an Alternative list of slugs" do
|
26
|
+
subject.must_be_instance_of Parslet::Atoms::Alternative
|
27
|
+
end
|
28
|
+
it "must match 'georgian_year'" do
|
29
|
+
subject.parse("mean_gregorian_year").must_equal("mean_gregorian_year")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "::prefix(:symbol)" do
|
34
|
+
subject { Unitwise::Expression::Matcher.prefix(:symbol)}
|
35
|
+
it "must be an Alternative list of symbols" do
|
36
|
+
subject.must_be_instance_of Parslet::Atoms::Alternative
|
37
|
+
end
|
38
|
+
it "must parse 'h'" do
|
39
|
+
subject.parse('h').must_equal('h')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Expression::Parser do
|
4
|
+
subject { Unitwise::Expression::Parser.new}
|
5
|
+
describe '#metric_atom' do
|
6
|
+
it "must match 'N'" do
|
7
|
+
subject.metric_atom.parse('N')[:atom_code].must_equal('N')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#atom' do
|
12
|
+
it "must match '[in_i]'" do
|
13
|
+
subject.atom.parse('[in_i]')[:atom_code].must_equal('[in_i]')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#prefix' do
|
18
|
+
it "must match 'k'" do
|
19
|
+
subject.prefix.parse('k')[:prefix_code].must_equal('k')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#annotation' do
|
24
|
+
it "must match '{foobar}'" do
|
25
|
+
subject.annotation.parse('{foobar}')[:annotation].must_equal('foobar')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#factor" do
|
30
|
+
it "must match positives and fixnums" do
|
31
|
+
subject.factor.parse('3.2')[:factor].must_equal(fixnum: '3.2')
|
32
|
+
end
|
33
|
+
it "must match negatives and integers" do
|
34
|
+
subject.factor.parse('-5')[:factor].must_equal(integer: '-5')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#exponent" do
|
39
|
+
it "must match positives and integers" do
|
40
|
+
subject.exponent.parse('4')[:exponent].must_equal(integer: '4')
|
41
|
+
end
|
42
|
+
it "must match negatives and fixnums" do
|
43
|
+
subject.exponent.parse('-5.4')[:exponent].must_equal(fixnum: '-5.4')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "term" do
|
48
|
+
it "must match basic atoms" do
|
49
|
+
subject.term.parse('[in_i]')[:term][:atom][:atom_code].must_equal('[in_i]')
|
50
|
+
end
|
51
|
+
it "must match prefixed atoms" do
|
52
|
+
match = subject.term.parse('ks')[:term]
|
53
|
+
match[:atom][:atom_code].must_equal('s')
|
54
|
+
match[:prefix][:prefix_code].must_equal('k')
|
55
|
+
end
|
56
|
+
it "must match exponential atoms" do
|
57
|
+
match = subject.term.parse('cm3')[:term]
|
58
|
+
match[:atom][:atom_code].must_equal 'm'
|
59
|
+
match[:prefix][:prefix_code].must_equal 'c'
|
60
|
+
match[:exponent][:integer].must_equal '3'
|
61
|
+
end
|
62
|
+
it "must match factors" do
|
63
|
+
subject.term.parse('3.2')[:term][:factor][:fixnum].must_equal '3.2'
|
64
|
+
end
|
65
|
+
it "must match annotations" do
|
66
|
+
match = subject.term.parse('N{Normal}')[:term]
|
67
|
+
match[:atom][:atom_code].must_equal 'N'
|
68
|
+
match[:annotation].must_equal 'Normal'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#group' do
|
73
|
+
it "must match parentheses with a term" do
|
74
|
+
match = subject.group.parse('(s2)')[:group][:nested][:left][:term]
|
75
|
+
match[:atom][:atom_code].must_equal 's'
|
76
|
+
match[:exponent][:integer].must_equal '2'
|
77
|
+
end
|
78
|
+
it "must match nested groups" do
|
79
|
+
match = subject.group.parse('((kg))')[:group][:nested][:left][:group][:nested][:left][:term]
|
80
|
+
match[:atom][:atom_code].must_equal 'g'
|
81
|
+
match[:prefix][:prefix_code].must_equal 'k'
|
82
|
+
end
|
83
|
+
it "must pass exponents down" do
|
84
|
+
match = subject.group.parse('([in_i])3')[:group]
|
85
|
+
match[:exponent][:integer].must_equal '3'
|
86
|
+
match[:nested][:left][:term][:atom][:atom_code].must_equal '[in_i]'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Numeric do
|
4
|
+
|
5
|
+
describe "#convert" do
|
6
|
+
it "must work for Integer" do
|
7
|
+
measurement = 22.convert("kg")
|
8
|
+
measurement.must_be_instance_of(Unitwise::Measurement)
|
9
|
+
measurement.value.must_equal 22
|
10
|
+
end
|
11
|
+
it "must work for Fixnum" do
|
12
|
+
measurement = 24.25.convert("[ft_i]")
|
13
|
+
measurement.must_be_instance_of(Unitwise::Measurement)
|
14
|
+
measurement.value.must_equal 24.25
|
15
|
+
end
|
16
|
+
it "must work for Float" do
|
17
|
+
measurement = (22.0/7).convert("[mi_i]")
|
18
|
+
measurement.must_be_instance_of(Unitwise::Measurement)
|
19
|
+
measurement.value.must_equal 3.142857142857143
|
20
|
+
end
|
21
|
+
it "must work for Rational" do
|
22
|
+
measurement = Rational(22/7).convert("N/m2")
|
23
|
+
measurement.must_be_instance_of(Unitwise::Measurement)
|
24
|
+
measurement.value.must_equal Rational(22/7)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#method_missing" do
|
29
|
+
it "must match mm" do
|
30
|
+
mm = 2.5.mm
|
31
|
+
mm.must_be_instance_of(Unitwise::Measurement)
|
32
|
+
mm.value.must_equal 2.5
|
33
|
+
end
|
34
|
+
it "must match foot" do
|
35
|
+
ft = 4.foot
|
36
|
+
ft.must_be_instance_of(Unitwise::Measurement)
|
37
|
+
ft.value.must_equal 4
|
38
|
+
end
|
39
|
+
it "must not match 'foo'" do
|
40
|
+
->{ 1.foo }.must_raise NoMethodError
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe String do
|
4
|
+
describe '#to_slug' do
|
5
|
+
it "should convert 'Pascal' to 'pascal'" do
|
6
|
+
"Pascal".to_slug.must_equal 'pascal'
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should convert 'degree Celsius' to 'degree_celsius'" do
|
10
|
+
"degree Celsius".to_slug.must_equal 'degree_celsius'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Function do
|
4
|
+
subject { Unitwise::Function }
|
5
|
+
|
6
|
+
it '::all must be an Array' do
|
7
|
+
subject.all.must_be_instance_of Array
|
8
|
+
end
|
9
|
+
|
10
|
+
it '::add must add to :all' do
|
11
|
+
defined = subject.add('foo(2 3N)', ->{ 4 }, ->{ 6 })
|
12
|
+
subject.all.must_include(defined)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "::find must find by name" do
|
16
|
+
defined = subject.add("foo", -> { 3 }, -> { 7 })
|
17
|
+
subject.find("foo").must_equal(defined)
|
18
|
+
end
|
19
|
+
|
20
|
+
let (:fp) { Unitwise::Function.new('foo(1 4J)', ->(x){ x + 1}, ->(x){ x - 1 }) }
|
21
|
+
|
22
|
+
it 'must have a #name' do
|
23
|
+
fp.name.must_equal('foo(1 4J)')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'must have a direct lambda' do
|
27
|
+
fp.direct.call(1).must_equal 2
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'must have a inverse lambda' do
|
31
|
+
fp.inverse.call(1).must_equal 0
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'must have a direct scalar' do
|
35
|
+
fp.functional(1, 1).must_equal 2
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'must have an inverse scalar' do
|
39
|
+
fp.functional(1, -1).must_equal 0
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Measurement do
|
4
|
+
subject { Unitwise::Measurement.new(1, 'm/s') }
|
5
|
+
describe "#new" do
|
6
|
+
it "must set attributes" do
|
7
|
+
subject.value.must_equal(1)
|
8
|
+
subject.unit.to_s.must_equal('m/s')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#unit" do
|
13
|
+
it "must be a unit" do
|
14
|
+
subject.must_respond_to(:unit)
|
15
|
+
subject.unit.must_be_instance_of(Unitwise::Unit)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#root_terms" do
|
20
|
+
it "must be a collection of terms" do
|
21
|
+
subject.must_respond_to(:root_terms)
|
22
|
+
subject.root_terms.must_be_kind_of Enumerable
|
23
|
+
subject.root_terms.sample.must_be_instance_of(Unitwise::Term)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#dup" do
|
28
|
+
it "must return a new instance" do
|
29
|
+
subject.must_respond_to(:dup)
|
30
|
+
subject.dup.must_be_instance_of(Unitwise::Measurement)
|
31
|
+
subject.dup.value.must_equal subject.value
|
32
|
+
subject.dup.unit.to_s.must_equal subject.unit.to_s
|
33
|
+
subject.dup.object_id.wont_equal subject.dup.object_id
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:mph) { Unitwise::Measurement.new(60, '[mi_i]/h') }
|
38
|
+
let(:kmh) { Unitwise::Measurement.new(100, 'km/h') }
|
39
|
+
let(:mile) { Unitwise::Measurement.new(3, '[mi_i]') }
|
40
|
+
let(:hpm) { Unitwise::Measurement.new(6, 'h/[mi_i]') }
|
41
|
+
let(:cui) { Unitwise::Measurement.new(12, "[in_i]3") }
|
42
|
+
let(:cel) { Unitwise::Measurement.new(22, 'Cel') }
|
43
|
+
let(:k) {Unitwise::Measurement.new(373.15, 'K') }
|
44
|
+
let(:f) {Unitwise::Measurement.new(98.6, '[degF]')}
|
45
|
+
|
46
|
+
describe "#scalar" do
|
47
|
+
it "must return value relative to terminal atoms" do
|
48
|
+
subject.scalar.must_equal 1
|
49
|
+
mph.scalar.must_equal 26.8224
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#convert" do
|
54
|
+
it "must convert to a similar unit code" do
|
55
|
+
mph.convert('km/h').value.must_equal 96.56063999999999
|
56
|
+
end
|
57
|
+
it "must raise an error if the units aren't similar" do
|
58
|
+
->{ mph.convert('N') }.must_raise Unitwise::ConversionError
|
59
|
+
end
|
60
|
+
it "must convert special units to their base units" do
|
61
|
+
cel.convert('K').value.must_equal 295.15
|
62
|
+
end
|
63
|
+
it "must convert base units to special units" do
|
64
|
+
k.convert('Cel').value.must_equal 100
|
65
|
+
end
|
66
|
+
it "must convert special units to special units" do
|
67
|
+
f.convert('Cel').value.must_equal 37
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#*" do
|
72
|
+
it "must multiply by scalars" do
|
73
|
+
mult = mph * 4
|
74
|
+
mult.value.must_equal 240
|
75
|
+
mult.unit.must_equal Unitwise::Unit.new("[mi_i]/h")
|
76
|
+
end
|
77
|
+
it "must multiply similar units" do
|
78
|
+
mult = mph * kmh
|
79
|
+
mult.value.must_equal 3728.227153424004
|
80
|
+
mult.unit.must_equal Unitwise::Unit.new("([mi_i]/h).([mi_i]/h)")
|
81
|
+
end
|
82
|
+
it "must multiply unsimilar units" do
|
83
|
+
mult = mph * mile
|
84
|
+
mult.value.must_equal 180
|
85
|
+
mult.unit.must_equal Unitwise::Unit.new("[mi_i]2/h")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "must multiply canceling units" do
|
89
|
+
mult = mph * hpm
|
90
|
+
mult.value.must_equal 360
|
91
|
+
mult.unit.to_s.must_equal "1"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#/" do
|
96
|
+
it "must divide by scalars" do
|
97
|
+
div = kmh / 4
|
98
|
+
div.value.must_equal 25
|
99
|
+
div.unit.must_equal kmh.unit
|
100
|
+
end
|
101
|
+
it "must divide by the value of similar units" do
|
102
|
+
div = kmh / mph
|
103
|
+
div.value.must_equal 1.03561865372889
|
104
|
+
div.unit.to_s.must_equal '1'
|
105
|
+
end
|
106
|
+
it "must divide dissimilar units" do
|
107
|
+
div = mph / hpm
|
108
|
+
div.value.must_equal 10
|
109
|
+
div.unit.to_s.must_equal "[mi_i]2/h2"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#+" do
|
114
|
+
it "must add values when units are similar" do
|
115
|
+
added = mph + kmh
|
116
|
+
added.value.must_equal 122.13711922373341
|
117
|
+
added.unit.must_equal mph.unit
|
118
|
+
end
|
119
|
+
it "must raise an error when units are not similar" do
|
120
|
+
assert_raises(TypeError) { mph + hpm}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#-" do
|
125
|
+
it "must add values when units are similar" do
|
126
|
+
added = mph - kmh
|
127
|
+
added.value.must_equal -2.1371192237334
|
128
|
+
added.unit.must_equal mph.unit
|
129
|
+
end
|
130
|
+
it "must raise an error when units are not similar" do
|
131
|
+
assert_raises(TypeError) { mph - hpm}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#**" do
|
136
|
+
it "must raise to a power" do
|
137
|
+
exp = mile ** 3
|
138
|
+
exp.value.must_equal 27
|
139
|
+
exp.unit.to_s.must_equal "[mi_i]3"
|
140
|
+
end
|
141
|
+
it "must raise to a negative power" do
|
142
|
+
exp = mile ** -3
|
143
|
+
exp.value.must_equal 0.037037037037037035
|
144
|
+
exp.unit.to_s.must_equal "1/[mi_i]3"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#method_missing" do
|
149
|
+
let(:meter) { Unitwise::Measurement.new(1, 'm')}
|
150
|
+
it "must convert 'mm'" do
|
151
|
+
convert = meter.mm
|
152
|
+
convert.must_be_instance_of Unitwise::Measurement
|
153
|
+
convert.value.must_equal 1000
|
154
|
+
end
|
155
|
+
|
156
|
+
it "must convert 'foot'" do
|
157
|
+
convert = meter.foot
|
158
|
+
convert.must_be_instance_of Unitwise::Measurement
|
159
|
+
convert.value.must_equal 3.280839895013123
|
160
|
+
end
|
161
|
+
|
162
|
+
it "must not convert 'foo'" do
|
163
|
+
->{ meter.foo }.must_raise NoMethodError
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|