unitwise 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,21 @@
|
|
1
|
+
module Unitwise::Standard
|
2
|
+
class BaseUnit < Base
|
3
|
+
|
4
|
+
def self.remote_key
|
5
|
+
"base_unit"
|
6
|
+
end
|
7
|
+
|
8
|
+
def property
|
9
|
+
attributes["property"].to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def dim
|
13
|
+
attributes["@dim"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
super.merge property: property, dim: dim
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Unitwise::Standard
|
2
|
+
class DerivedUnit < Base
|
3
|
+
|
4
|
+
def self.remote_key
|
5
|
+
"unit"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.local_key
|
9
|
+
"derived_unit"
|
10
|
+
end
|
11
|
+
|
12
|
+
def property
|
13
|
+
attributes["property"].to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def scale
|
17
|
+
Scale.new(attributes["value"]) unless special?
|
18
|
+
end
|
19
|
+
|
20
|
+
def function
|
21
|
+
Function.new(attributes["value"]) if special?
|
22
|
+
end
|
23
|
+
|
24
|
+
def classification
|
25
|
+
attributes["@class"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def metric?
|
29
|
+
attributes["@isMetric"] == 'yes'
|
30
|
+
end
|
31
|
+
|
32
|
+
def special?
|
33
|
+
attributes["@isSpecial"] == 'yes'
|
34
|
+
end
|
35
|
+
|
36
|
+
def arbitrary?
|
37
|
+
attributes["@isArbitrary"] == 'yes'
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_hash
|
41
|
+
hash = super()
|
42
|
+
hash[:scale] = (special? ? function.to_hash : scale.to_hash)
|
43
|
+
hash.merge({classification: classification,
|
44
|
+
property: property, metric: metric?,
|
45
|
+
special: special?, arbitrary: arbitrary?})
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Unitwise::Standard
|
2
|
+
module Extras
|
3
|
+
def hash_to_markup(hash)
|
4
|
+
hash.map do |k,v|
|
5
|
+
if v.respond_to?(:to_xml)
|
6
|
+
"<#{k}>#{v.to_xml}</#{k}>"
|
7
|
+
elsif v.respond_to?(:map)
|
8
|
+
v.map do |i|
|
9
|
+
"<#{k}>#{i}</#{k}>"
|
10
|
+
end.join('')
|
11
|
+
else
|
12
|
+
"<#{k}>#{v}</#{k}>"
|
13
|
+
end
|
14
|
+
end.join('')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Unitwise::Standard
|
2
|
+
class Function
|
3
|
+
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
@attributes = attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
attributes["function"]["@name"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def value
|
15
|
+
attributes["function"]["@value"].to_f
|
16
|
+
end
|
17
|
+
|
18
|
+
def unit
|
19
|
+
attributes["function"]["@Unit"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def primary
|
23
|
+
attributes["@Unit"].gsub(/\(.*\)/, '')
|
24
|
+
end
|
25
|
+
|
26
|
+
def secondary
|
27
|
+
attributes["@UNIT"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
{function_code: primary, value: value, unit_code: unit}
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Unitwise::Standard
|
2
|
+
class Scale
|
3
|
+
attr_accessor :nori
|
4
|
+
|
5
|
+
def initialize(nori)
|
6
|
+
@nori = nori
|
7
|
+
end
|
8
|
+
|
9
|
+
def value
|
10
|
+
nori.attributes["value"].to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
def primary_unit_code
|
14
|
+
nori.attributes["Unit"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def secondary_unit_code
|
18
|
+
nori.attributes["UNIT"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
{value: value, unit_code: primary_unit_code}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'signed_multiset'
|
2
|
+
module Unitwise
|
3
|
+
class Term
|
4
|
+
attr_writer :atom_code, :prefix_code, :atom, :prefix
|
5
|
+
attr_writer :factor, :exponent
|
6
|
+
attr_accessor :annotation
|
7
|
+
|
8
|
+
include Unitwise::Composable
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
attributes.each do |k,v|
|
12
|
+
public_send :"#{k}=", v
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefix_code
|
17
|
+
@prefix_code ||= (@prefix.primary_code if @prefix)
|
18
|
+
end
|
19
|
+
|
20
|
+
def prefix
|
21
|
+
@prefix ||= (Prefix.find(@prefix_code) if @prefix_code)
|
22
|
+
end
|
23
|
+
|
24
|
+
def atom_code
|
25
|
+
@atom_code ||= (@atom.primary_code if @atom)
|
26
|
+
end
|
27
|
+
|
28
|
+
def atom
|
29
|
+
@atom ||= (Atom.find(@atom_code) if @atom_code)
|
30
|
+
end
|
31
|
+
|
32
|
+
def special?
|
33
|
+
atom.special rescue false
|
34
|
+
end
|
35
|
+
|
36
|
+
def depth
|
37
|
+
atom ? atom.depth + 1 : 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def terminal?
|
41
|
+
depth <= 3
|
42
|
+
end
|
43
|
+
|
44
|
+
def factor
|
45
|
+
@factor ||= 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def exponent
|
49
|
+
@exponent ||= 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def scalar
|
53
|
+
(factor * (prefix ? prefix.scalar : 1) * (atom ? atom.scalar : 1)) ** exponent
|
54
|
+
end
|
55
|
+
|
56
|
+
def functional(value, direction=1)
|
57
|
+
(factor * (prefix ? prefix.scalar : 1)) * (atom ? atom.functional(value, direction) : 1) ** exponent
|
58
|
+
end
|
59
|
+
|
60
|
+
def root_terms
|
61
|
+
if terminal?
|
62
|
+
[self]
|
63
|
+
else
|
64
|
+
atom.scale.root_terms.map do |t|
|
65
|
+
self.class.new(atom: t.atom, exponent: t.exponent * exponent)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_hash
|
71
|
+
[:prefix, :atom, :exponent, :factor, :annotation].inject({}) do |h, a|
|
72
|
+
h[a] = send a; h
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def *(other)
|
77
|
+
if other.respond_to?(:terms)
|
78
|
+
Unit.new(other.terms << self)
|
79
|
+
else
|
80
|
+
Unit.new([self, other])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def /(other)
|
85
|
+
if other.respond_to?(:terms)
|
86
|
+
Unit.new(other.terms.map{|t| t ** -1} << self)
|
87
|
+
else
|
88
|
+
Unit.new([self, other ** -1])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def **(integer)
|
93
|
+
self.class.new(to_hash.merge(exponent: exponent * integer))
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
[(factor if factor != 1), prefix_code,
|
98
|
+
atom_code, (exponent if exponent != 1)].compact.join('')
|
99
|
+
end
|
100
|
+
|
101
|
+
def inspect
|
102
|
+
"<#{self.class} #{to_s}>"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Unit
|
3
|
+
include Unitwise::Composable
|
4
|
+
attr_writer :terms, :expression
|
5
|
+
|
6
|
+
def initialize(input)
|
7
|
+
if input.respond_to?(:expression)
|
8
|
+
@expression = input.expression
|
9
|
+
elsif input.respond_to?(:each)
|
10
|
+
@terms = input
|
11
|
+
else
|
12
|
+
@expression = input.to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def expression
|
17
|
+
@expression ||= (Expression.compose(@terms) if @terms)
|
18
|
+
end
|
19
|
+
|
20
|
+
def terms
|
21
|
+
@terms ||= (Expression.decompose(@expression) if @expression)
|
22
|
+
end
|
23
|
+
|
24
|
+
def atoms
|
25
|
+
terms.map(&:atom)
|
26
|
+
end
|
27
|
+
|
28
|
+
def special?
|
29
|
+
terms.count == 1 && terms.all?(&:special?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def functional(value, direction=1)
|
33
|
+
terms.first.functional(value, direction)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dup
|
37
|
+
self.class.new(expression)
|
38
|
+
end
|
39
|
+
|
40
|
+
def depth
|
41
|
+
terms.map(&:depth).max + 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def terminal?
|
45
|
+
depth <= 3
|
46
|
+
end
|
47
|
+
|
48
|
+
def root_terms
|
49
|
+
terms.flat_map(&:root_terms)
|
50
|
+
end
|
51
|
+
|
52
|
+
def scalar
|
53
|
+
if terms.empty?
|
54
|
+
1
|
55
|
+
else
|
56
|
+
terms.map(&:scalar).inject(&:*)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def *(other)
|
61
|
+
if other.respond_to?(:terms)
|
62
|
+
self.class.new(terms + other.terms)
|
63
|
+
else
|
64
|
+
raise TypeError, "Can't multiply #{inspect} by #{other}."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def /(other)
|
69
|
+
if other.respond_to?(:terms)
|
70
|
+
self.class.new(terms + other.terms.map{ |t| t ** -1})
|
71
|
+
else
|
72
|
+
raise TypeError, "Can't divide #{inspect} by #{other}."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def **(number)
|
77
|
+
self.class.new(terms.map{ |t| t ** number })
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
expression
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
"<#{self.class} #{to_s}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Atom do
|
4
|
+
subject { Unitwise::Atom }
|
5
|
+
describe "::data" do
|
6
|
+
it "must have data" do
|
7
|
+
subject.data.must_be_instance_of Array
|
8
|
+
subject.data.count.must_be :>, 0
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "::all" do
|
13
|
+
it "must be an Array of instances" do
|
14
|
+
subject.all.must_be_instance_of Array
|
15
|
+
subject.all.first.must_be_instance_of Unitwise::Atom
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "::find" do
|
20
|
+
it "must find atoms" do
|
21
|
+
subject.find("m").must_be_instance_of Unitwise::Atom
|
22
|
+
subject.find("V").must_be_instance_of Unitwise::Atom
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:second) { Unitwise::Atom.find("s") }
|
27
|
+
let(:yard) { Unitwise::Atom.find("[yd_i]")}
|
28
|
+
let(:pi) { Unitwise::Atom.find("[pi]")}
|
29
|
+
let(:celsius) { Unitwise::Atom.find("Cel")}
|
30
|
+
let(:pfu) { Unitwise::Atom.find("[PFU]")}
|
31
|
+
describe "#scale" do
|
32
|
+
it "must be nil for base atoms" do
|
33
|
+
second.scale.must_equal nil
|
34
|
+
end
|
35
|
+
it "sould be a Scale object for derived atoms" do
|
36
|
+
yard.scale.must_be_instance_of Unitwise::Scale
|
37
|
+
end
|
38
|
+
it "must be a FunctionalScale object for special atoms" do
|
39
|
+
celsius.scale.must_be_instance_of Unitwise::Functional
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#base?" do
|
44
|
+
it "must be true for base atoms" do
|
45
|
+
second.base?.must_equal true
|
46
|
+
end
|
47
|
+
it "must be false for derived atoms" do
|
48
|
+
yard.base?.must_equal false
|
49
|
+
pi.base?.must_equal false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#derived?" do
|
54
|
+
it "must be false for base atoms" do
|
55
|
+
second.derived?.must_equal false
|
56
|
+
end
|
57
|
+
it "must be true for derived atoms" do
|
58
|
+
yard.derived?.must_equal true
|
59
|
+
celsius.derived?.must_equal true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#metric?" do
|
64
|
+
it "must be true for base atoms" do
|
65
|
+
second.metric?.must_equal true
|
66
|
+
end
|
67
|
+
it "must be false for english atoms" do
|
68
|
+
yard.metric?.must_equal false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#special?" do
|
73
|
+
it "must be true for special atoms" do
|
74
|
+
celsius.special?.must_equal true
|
75
|
+
end
|
76
|
+
it "must be false for non-special atoms" do
|
77
|
+
second.special?.must_equal false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#arbitrary?" do
|
82
|
+
it "must be true for arbitrary atoms" do
|
83
|
+
pfu.arbitrary?.must_equal true
|
84
|
+
end
|
85
|
+
it "must be false for non-arbitrary atoms" do
|
86
|
+
yard.arbitrary?.must_equal false
|
87
|
+
celsius.arbitrary?.must_equal false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#terminal?" do
|
92
|
+
it "must be true for atoms without a valid measurement atom" do
|
93
|
+
second.terminal?.must_equal true
|
94
|
+
pi.terminal?.must_equal true
|
95
|
+
end
|
96
|
+
it "must be false for child atoms" do
|
97
|
+
yard.terminal?.must_equal false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#scalar" do
|
102
|
+
it "must return scalar relative to terminal atom" do
|
103
|
+
second.scalar.must_equal 1
|
104
|
+
yard.scalar.must_equal 0.9144000000000001
|
105
|
+
pi.scalar.must_equal 3.141592653589793
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#key" do
|
110
|
+
it "must return the dim or the property" do
|
111
|
+
second.key.must_equal "T"
|
112
|
+
pi.key.must_equal "number"
|
113
|
+
celsius.key.must_equal "temperature"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#measurement=" do
|
118
|
+
it "must create a new measurement object and set attributes" do
|
119
|
+
skip("need to figure out mocking and stubbing with minitest")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|