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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +110 -0
  8. data/Rakefile +21 -0
  9. data/data/base_unit.yaml +43 -0
  10. data/data/derived_unit.yaml +3394 -0
  11. data/data/prefix.yaml +121 -0
  12. data/lib/unitwise.rb +25 -0
  13. data/lib/unitwise/atom.rb +84 -0
  14. data/lib/unitwise/base.rb +45 -0
  15. data/lib/unitwise/composable.rb +25 -0
  16. data/lib/unitwise/errors.rb +7 -0
  17. data/lib/unitwise/expression.rb +25 -0
  18. data/lib/unitwise/expression/composer.rb +41 -0
  19. data/lib/unitwise/expression/decomposer.rb +42 -0
  20. data/lib/unitwise/expression/matcher.rb +41 -0
  21. data/lib/unitwise/expression/parser.rb +53 -0
  22. data/lib/unitwise/expression/transformer.rb +22 -0
  23. data/lib/unitwise/ext.rb +2 -0
  24. data/lib/unitwise/ext/numeric.rb +13 -0
  25. data/lib/unitwise/ext/string.rb +5 -0
  26. data/lib/unitwise/function.rb +51 -0
  27. data/lib/unitwise/functional.rb +21 -0
  28. data/lib/unitwise/measurement.rb +89 -0
  29. data/lib/unitwise/prefix.rb +17 -0
  30. data/lib/unitwise/scale.rb +61 -0
  31. data/lib/unitwise/standard.rb +26 -0
  32. data/lib/unitwise/standard/base.rb +73 -0
  33. data/lib/unitwise/standard/base_unit.rb +21 -0
  34. data/lib/unitwise/standard/derived_unit.rb +49 -0
  35. data/lib/unitwise/standard/extras.rb +17 -0
  36. data/lib/unitwise/standard/function.rb +35 -0
  37. data/lib/unitwise/standard/prefix.rb +17 -0
  38. data/lib/unitwise/standard/scale.rb +25 -0
  39. data/lib/unitwise/term.rb +106 -0
  40. data/lib/unitwise/unit.rb +89 -0
  41. data/lib/unitwise/version.rb +3 -0
  42. data/test/test_helper.rb +7 -0
  43. data/test/unitwise/atom_test.rb +122 -0
  44. data/test/unitwise/base_test.rb +6 -0
  45. data/test/unitwise/expression/decomposer_test.rb +36 -0
  46. data/test/unitwise/expression/matcher_test.rb +42 -0
  47. data/test/unitwise/expression/parser_test.rb +91 -0
  48. data/test/unitwise/ext/numeric_test.rb +46 -0
  49. data/test/unitwise/ext/string_test.rb +13 -0
  50. data/test/unitwise/function_test.rb +42 -0
  51. data/test/unitwise/measurement_test.rb +168 -0
  52. data/test/unitwise/prefix_test.rb +25 -0
  53. data/test/unitwise/term_test.rb +44 -0
  54. data/test/unitwise/unit_test.rb +57 -0
  55. data/test/unitwise_test.rb +7 -0
  56. data/unitwise.gemspec +28 -0
  57. metadata +213 -0
@@ -0,0 +1,6 @@
1
+ require 'test_helper'
2
+
3
+ describe Unitwise::Base do
4
+
5
+
6
+ end
@@ -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