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,53 @@
|
|
1
|
+
module Unitwise
|
2
|
+
module Expression
|
3
|
+
class Parser < Parslet::Parser
|
4
|
+
attr_reader :key
|
5
|
+
def initialize(key=:primary_code)
|
6
|
+
@key = key
|
7
|
+
end
|
8
|
+
|
9
|
+
root :expression
|
10
|
+
|
11
|
+
rule (:atom) { Matcher.atom(key).as(:atom_code) }
|
12
|
+
rule (:metric_atom) { Matcher.metric_atom(key).as(:atom_code) }
|
13
|
+
rule (:prefix) { Matcher.prefix(key).as(:prefix_code) }
|
14
|
+
|
15
|
+
rule (:simpleton) do
|
16
|
+
(prefix.as(:prefix) >> metric_atom.as(:atom) | atom.as(:atom))
|
17
|
+
end
|
18
|
+
|
19
|
+
rule (:annotation) do
|
20
|
+
str('{') >> match['^}'].repeat.as(:annotation) >> str('}')
|
21
|
+
end
|
22
|
+
|
23
|
+
rule (:digits) { match['0-9'].repeat(1) }
|
24
|
+
|
25
|
+
rule (:integer) { (str('-').maybe >> digits).as(:integer) }
|
26
|
+
|
27
|
+
rule (:fixnum) do
|
28
|
+
(str('-').maybe >> digits >> str('.') >> digits).as(:fixnum)
|
29
|
+
end
|
30
|
+
|
31
|
+
rule (:number) { fixnum | integer }
|
32
|
+
|
33
|
+
rule (:exponent) { number.as(:exponent) }
|
34
|
+
|
35
|
+
rule (:factor) { number.as(:factor) }
|
36
|
+
|
37
|
+
rule (:operator) { (str('.') | str('/')).as(:operator) }
|
38
|
+
|
39
|
+
rule (:term) do
|
40
|
+
((simpleton | factor) >> exponent.maybe >> annotation.maybe).as(:term)
|
41
|
+
end
|
42
|
+
|
43
|
+
rule (:group) do
|
44
|
+
(str('(') >> expression.as(:nested) >> str(')') >> exponent.maybe).as(:group)
|
45
|
+
end
|
46
|
+
|
47
|
+
rule (:expression) do
|
48
|
+
(group | term).as(:left) >> (operator >> expression.as(:right)).maybe
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Unitwise
|
2
|
+
module Expression
|
3
|
+
class Transformer < Parslet::Transform
|
4
|
+
rule(integer: simple(:i)) { i.to_i }
|
5
|
+
rule(fixnum: simple(:f)) { f.to_f }
|
6
|
+
|
7
|
+
rule(prefix_code: simple(:c)) { Prefix.find(c) }
|
8
|
+
rule(atom_code: simple(:c)) { Atom.find(c) }
|
9
|
+
rule(term: subtree(:h)) { Term.new(h) }
|
10
|
+
|
11
|
+
rule(left: simple(:l), operator: simple(:o), right: simple(:r)) do
|
12
|
+
o == '/' ? l / r : l * r
|
13
|
+
end
|
14
|
+
|
15
|
+
rule(left: simple(:l)) { l }
|
16
|
+
|
17
|
+
rule(group: { nested: simple(:n) , exponent: simple(:e)}) { n ** e }
|
18
|
+
|
19
|
+
rule(group: { nested: simple(:n) }) { n }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/unitwise/ext.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Function
|
3
|
+
include Math
|
4
|
+
class << self
|
5
|
+
def all
|
6
|
+
@all ||= defaults
|
7
|
+
end
|
8
|
+
|
9
|
+
def defaults
|
10
|
+
[ ["cel", ->(x){ x - 273.15}, ->(x){ x + 273.15} ],
|
11
|
+
["degf", ->(x){9.0 * x / 5.0 - 459.67},->(x){ 5.0/9 * (x + 459.67)}],
|
12
|
+
["hpX", ->(x){ -log10(x) }, ->(x){ 10 ** -x } ],
|
13
|
+
["hpC", ->(x){ -log(x) / log(100) }, ->(x){ 100 ** -x } ],
|
14
|
+
["tan100",->(x){ 100 * tan(x) }, ->(x){ atan(x / 100) } ],
|
15
|
+
["ph", ->(x){ -log10(x) }, ->(x){ 10 ** -x } ],
|
16
|
+
["ld", ->(x){ log2(x) }, ->(x){ 2 ** -x } ],
|
17
|
+
["ln", ->(x){ log(x) }, ->(x){ Math::E ** x } ],
|
18
|
+
["lg", ->(x){ log10(x) }, ->(x){ 10 ** x } ],
|
19
|
+
["2lg", ->(x){ 2 * log10(x) }, ->(x){ (10 ** x) / 2 } ]
|
20
|
+
].map {|args| new(*args) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def add(*args)
|
24
|
+
new_instance = self.new(*args)
|
25
|
+
all << new_instance
|
26
|
+
new_instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(name)
|
30
|
+
all.find{|fp| fp.name == name}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :name, :direct, :inverse
|
35
|
+
|
36
|
+
def initialize(name, direct, inverse)
|
37
|
+
@name = name
|
38
|
+
@direct = direct
|
39
|
+
@inverse = inverse
|
40
|
+
end
|
41
|
+
|
42
|
+
def functional(x, direction=1)
|
43
|
+
if direction == 1
|
44
|
+
direct.call(x)
|
45
|
+
elsif direction == -1
|
46
|
+
inverse.call(x)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Functional < Scale
|
3
|
+
|
4
|
+
attr_reader :function
|
5
|
+
|
6
|
+
def initialize(value, unit, function_name)
|
7
|
+
super(value, unit)
|
8
|
+
@function = Function.find(function_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def scalar
|
12
|
+
puts "Warning: Mathematical operations with special units should be used with caution."
|
13
|
+
super()
|
14
|
+
end
|
15
|
+
|
16
|
+
def functional(x, direction=1)
|
17
|
+
function.functional(x, direction)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Measurement < Scale
|
3
|
+
|
4
|
+
def convert(other_unit)
|
5
|
+
other_unit = Unit.new(other_unit)
|
6
|
+
if similar_to?(other_unit)
|
7
|
+
new(converted_value(other_unit), other_unit)
|
8
|
+
else
|
9
|
+
raise ConversionError, "Can't convert #{inspect} to #{other_unit}."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def *(other)
|
14
|
+
operate(:*, other) || raise(TypeError, "Can't multiply #{inspect} by #{other}.")
|
15
|
+
end
|
16
|
+
|
17
|
+
def /(other)
|
18
|
+
operate(:/, other) || raise(TypeError, "Can't divide #{inspect} by #{other}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def +(other)
|
22
|
+
combine(:+, other) || raise(TypeError, "Can't add #{other} to #{inspect}.")
|
23
|
+
end
|
24
|
+
|
25
|
+
def -(other)
|
26
|
+
combine(:-, other) || raise(TypeError, "Can't subtract #{other} from #{inspect}.")
|
27
|
+
end
|
28
|
+
|
29
|
+
def **(number)
|
30
|
+
if number.is_a?(Numeric)
|
31
|
+
new( value ** number, unit ** number )
|
32
|
+
else
|
33
|
+
raise TypeError, "Can't raise #{inspect} to #{number} power."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(meth, *args, &block)
|
38
|
+
if Unitwise::Expression.decompose(meth)
|
39
|
+
self.convert(meth)
|
40
|
+
else
|
41
|
+
super(meth, *args, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def new(*args)
|
48
|
+
self.class.new(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def converted_value(other_unit)
|
52
|
+
if unit.special?
|
53
|
+
if other_unit.special?
|
54
|
+
other_unit.functional functional(value, -1)
|
55
|
+
else
|
56
|
+
functional(value, -1)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
if other_unit.special?
|
60
|
+
other_unit.functional(value)
|
61
|
+
else
|
62
|
+
scalar / other_unit.scalar
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# add or subtract other unit
|
68
|
+
def combine(operator, other)
|
69
|
+
if similar_to?(other)
|
70
|
+
new(value.send(operator, other.convert(unit).value), unit)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# multiply or divide other unit
|
75
|
+
def operate(operator, other)
|
76
|
+
if other.is_a?(Numeric)
|
77
|
+
new(value.send(operator, other), unit)
|
78
|
+
elsif other.respond_to?(:composition)
|
79
|
+
if similar_to?(other)
|
80
|
+
converted = other.convert(unit)
|
81
|
+
new(value.send(operator, converted.value), unit.send(operator, converted.unit))
|
82
|
+
else
|
83
|
+
new(value.send(operator, other.value), unit.send(operator, other.unit))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Prefix < Base
|
3
|
+
attr_reader :scalar
|
4
|
+
|
5
|
+
def self.data
|
6
|
+
@data ||= YAML::load File.open(data_file)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.data_file
|
10
|
+
Unitwise.data_file 'prefix'
|
11
|
+
end
|
12
|
+
|
13
|
+
def scalar=(value)
|
14
|
+
@scalar = value.to_f
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Scale
|
3
|
+
attr_reader :value
|
4
|
+
|
5
|
+
include Unitwise::Composable
|
6
|
+
|
7
|
+
def initialize(value, unit)
|
8
|
+
@value = value
|
9
|
+
if unit.is_a?(Unit)
|
10
|
+
@unit = unit.dup
|
11
|
+
else
|
12
|
+
@unit = Unit.new(unit.to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def dup
|
17
|
+
self.class.new(value, unit)
|
18
|
+
end
|
19
|
+
|
20
|
+
def atoms
|
21
|
+
unit.atoms
|
22
|
+
end
|
23
|
+
|
24
|
+
def special?
|
25
|
+
unit.special?
|
26
|
+
end
|
27
|
+
|
28
|
+
def functional(value, direction=1)
|
29
|
+
unit.functional(value, direction)
|
30
|
+
end
|
31
|
+
|
32
|
+
def scalar
|
33
|
+
value * unit.scalar
|
34
|
+
end
|
35
|
+
|
36
|
+
def unit
|
37
|
+
@unit ||= Unit.new(@unit_code)
|
38
|
+
end
|
39
|
+
|
40
|
+
def root_terms
|
41
|
+
unit.root_terms
|
42
|
+
end
|
43
|
+
|
44
|
+
def depth
|
45
|
+
unit.depth + 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def terminal?
|
49
|
+
depth <= 3
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"#{value} #{unit}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"<#{self.class} #{to_s}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'nori'
|
3
|
+
require 'unitwise/standard/base'
|
4
|
+
require 'unitwise/standard/prefix'
|
5
|
+
require 'unitwise/standard/base_unit'
|
6
|
+
require 'unitwise/standard/derived_unit'
|
7
|
+
require 'unitwise/standard/scale'
|
8
|
+
require 'unitwise/standard/function'
|
9
|
+
|
10
|
+
module Unitwise
|
11
|
+
module Standard
|
12
|
+
HOST = "unitsofmeasure.org"
|
13
|
+
PATH = "/ucum-essence.xml"
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def body
|
17
|
+
@body ||= Net::HTTP.get HOST, PATH
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
Nori.new.parse(body)["root"]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'unitwise/standard/extras'
|
2
|
+
module Unitwise::Standard
|
3
|
+
class Base
|
4
|
+
include Unitwise::Standard::Extras
|
5
|
+
|
6
|
+
attr_accessor :attributes
|
7
|
+
|
8
|
+
def self.local_key
|
9
|
+
remote_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.all
|
13
|
+
@all ||= read
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.read
|
17
|
+
Unitwise::Standard.hash[remote_key].inject([]){|a,h| a << self.new(h)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.hash
|
21
|
+
self.all.map(&:to_hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.path
|
25
|
+
Unitwise.data_file(local_key)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.write
|
29
|
+
File.open(path, 'w') do |f|
|
30
|
+
f.write hash.to_yaml
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(attributes)
|
35
|
+
@attributes = attributes
|
36
|
+
end
|
37
|
+
|
38
|
+
def names
|
39
|
+
if attributes["name"].respond_to?(:map)
|
40
|
+
attributes["name"].map(&:to_s)
|
41
|
+
else
|
42
|
+
attributes["name"].to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def symbol
|
47
|
+
sym = attributes["printSymbol"]
|
48
|
+
if sym.is_a?(Hash)
|
49
|
+
hash_to_markup(sym)
|
50
|
+
elsif sym
|
51
|
+
sym.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def primary_code
|
56
|
+
attributes["@Code"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def secondary_code
|
60
|
+
attributes["@CODE"]
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_hash
|
64
|
+
[:names, :symbol, :primary_code, :secondary_code].inject({}) do |h,a|
|
65
|
+
if v = self.send(a)
|
66
|
+
h[a] = v
|
67
|
+
end
|
68
|
+
h
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|