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,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
|