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
data/data/prefix.yaml
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
---
|
2
|
+
- :names: yotta
|
3
|
+
:symbol: Y
|
4
|
+
:primary_code: Y
|
5
|
+
:secondary_code: YA
|
6
|
+
:scalar: 1e24
|
7
|
+
- :names: zetta
|
8
|
+
:symbol: Z
|
9
|
+
:primary_code: Z
|
10
|
+
:secondary_code: ZA
|
11
|
+
:scalar: 1e21
|
12
|
+
- :names: exa
|
13
|
+
:symbol: E
|
14
|
+
:primary_code: E
|
15
|
+
:secondary_code: EX
|
16
|
+
:scalar: 1e18
|
17
|
+
- :names: peta
|
18
|
+
:symbol: P
|
19
|
+
:primary_code: P
|
20
|
+
:secondary_code: PT
|
21
|
+
:scalar: 1e15
|
22
|
+
- :names: tera
|
23
|
+
:symbol: T
|
24
|
+
:primary_code: T
|
25
|
+
:secondary_code: TR
|
26
|
+
:scalar: 1e12
|
27
|
+
- :names: giga
|
28
|
+
:symbol: G
|
29
|
+
:primary_code: G
|
30
|
+
:secondary_code: GA
|
31
|
+
:scalar: 1e9
|
32
|
+
- :names: mega
|
33
|
+
:symbol: M
|
34
|
+
:primary_code: M
|
35
|
+
:secondary_code: MA
|
36
|
+
:scalar: 1e6
|
37
|
+
- :names: kilo
|
38
|
+
:symbol: k
|
39
|
+
:primary_code: k
|
40
|
+
:secondary_code: K
|
41
|
+
:scalar: 1e3
|
42
|
+
- :names: hecto
|
43
|
+
:symbol: h
|
44
|
+
:primary_code: h
|
45
|
+
:secondary_code: H
|
46
|
+
:scalar: 1e2
|
47
|
+
- :names: deka
|
48
|
+
:symbol: da
|
49
|
+
:primary_code: da
|
50
|
+
:secondary_code: DA
|
51
|
+
:scalar: 1e1
|
52
|
+
- :names: deci
|
53
|
+
:symbol: d
|
54
|
+
:primary_code: d
|
55
|
+
:secondary_code: D
|
56
|
+
:scalar: 1e-1
|
57
|
+
- :names: centi
|
58
|
+
:symbol: c
|
59
|
+
:primary_code: c
|
60
|
+
:secondary_code: C
|
61
|
+
:scalar: 1e-2
|
62
|
+
- :names: milli
|
63
|
+
:symbol: m
|
64
|
+
:primary_code: m
|
65
|
+
:secondary_code: M
|
66
|
+
:scalar: 1e-3
|
67
|
+
- :names: micro
|
68
|
+
:symbol: μ
|
69
|
+
:primary_code: u
|
70
|
+
:secondary_code: U
|
71
|
+
:scalar: 1e-6
|
72
|
+
- :names: nano
|
73
|
+
:symbol: n
|
74
|
+
:primary_code: n
|
75
|
+
:secondary_code: N
|
76
|
+
:scalar: 1e-9
|
77
|
+
- :names: pico
|
78
|
+
:symbol: p
|
79
|
+
:primary_code: p
|
80
|
+
:secondary_code: P
|
81
|
+
:scalar: 1e-12
|
82
|
+
- :names: femto
|
83
|
+
:symbol: f
|
84
|
+
:primary_code: f
|
85
|
+
:secondary_code: F
|
86
|
+
:scalar: 1e-15
|
87
|
+
- :names: atto
|
88
|
+
:symbol: a
|
89
|
+
:primary_code: a
|
90
|
+
:secondary_code: A
|
91
|
+
:scalar: 1e-18
|
92
|
+
- :names: zepto
|
93
|
+
:symbol: z
|
94
|
+
:primary_code: z
|
95
|
+
:secondary_code: ZO
|
96
|
+
:scalar: 1e-21
|
97
|
+
- :names: yocto
|
98
|
+
:symbol: y
|
99
|
+
:primary_code: y
|
100
|
+
:secondary_code: YO
|
101
|
+
:scalar: 1e-24
|
102
|
+
- :names: kibi
|
103
|
+
:symbol: Ki
|
104
|
+
:primary_code: Ki
|
105
|
+
:secondary_code: KIB
|
106
|
+
:scalar: '1024'
|
107
|
+
- :names: mebi
|
108
|
+
:symbol: Mi
|
109
|
+
:primary_code: Mi
|
110
|
+
:secondary_code: MIB
|
111
|
+
:scalar: '1048576'
|
112
|
+
- :names: gibi
|
113
|
+
:symbol: Gi
|
114
|
+
:primary_code: Gi
|
115
|
+
:secondary_code: GIB
|
116
|
+
:scalar: '1073741824'
|
117
|
+
- :names: tebi
|
118
|
+
:symbol: Ti
|
119
|
+
:primary_code: Ti
|
120
|
+
:secondary_code: TIB
|
121
|
+
:scalar: '1099511627776'
|
data/lib/unitwise.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "unitwise/version"
|
2
|
+
require 'unitwise/base'
|
3
|
+
require 'unitwise/expression'
|
4
|
+
require 'unitwise/composable'
|
5
|
+
require 'unitwise/scale'
|
6
|
+
require 'unitwise/functional'
|
7
|
+
require 'unitwise/measurement'
|
8
|
+
require 'unitwise/atom'
|
9
|
+
require 'unitwise/prefix'
|
10
|
+
require 'unitwise/term'
|
11
|
+
require 'unitwise/unit'
|
12
|
+
require 'unitwise/function'
|
13
|
+
require 'unitwise/errors'
|
14
|
+
require 'unitwise/ext'
|
15
|
+
|
16
|
+
module Unitwise
|
17
|
+
def self.path
|
18
|
+
@path ||= File.dirname(File.dirname(__FILE__))
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.data_file(key)
|
22
|
+
File.join path, "data", "#{key}.yaml"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Unitwise
|
2
|
+
class Atom < Base
|
3
|
+
attr_accessor :classification, :property, :metric, :special
|
4
|
+
attr_accessor :arbitrary, :dim
|
5
|
+
|
6
|
+
include Unitwise::Composable
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def data
|
10
|
+
@data ||= data_files.reduce([]){|m,f| m += YAML::load File.open(f)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def data_files
|
14
|
+
%w(base_unit derived_unit).map{|type| Unitwise.data_file type}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def base?
|
19
|
+
scale.nil? && !dim.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def derived?
|
23
|
+
!base?
|
24
|
+
end
|
25
|
+
|
26
|
+
def metric?
|
27
|
+
base? ? true : !!metric
|
28
|
+
end
|
29
|
+
|
30
|
+
def nonmetric?
|
31
|
+
!metric?
|
32
|
+
end
|
33
|
+
|
34
|
+
def special?
|
35
|
+
!!special
|
36
|
+
end
|
37
|
+
|
38
|
+
def arbitrary?
|
39
|
+
!!arbitrary
|
40
|
+
end
|
41
|
+
|
42
|
+
def depth
|
43
|
+
base? ? 0 : scale.depth + 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def terminal?
|
47
|
+
depth <= 3
|
48
|
+
end
|
49
|
+
|
50
|
+
def key
|
51
|
+
base? ? dim : property
|
52
|
+
end
|
53
|
+
|
54
|
+
def scale=(attrs)
|
55
|
+
@scale = if attrs[:function_code]
|
56
|
+
Functional.new(attrs[:value], attrs[:unit_code], attrs[:function_code])
|
57
|
+
else
|
58
|
+
Scale.new(attrs[:value], attrs[:unit_code])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def scalar
|
63
|
+
base? ? 1 : scale.scalar
|
64
|
+
end
|
65
|
+
|
66
|
+
def functional(x, direction=1)
|
67
|
+
scale.functional(x, direction)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def root_terms
|
72
|
+
base? ? [Term.new(atom: self)] : scale.root_terms
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_s
|
76
|
+
"#{primary_code} (#{names.join('|')})"
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect
|
80
|
+
"<#{self.class} #{to_s}>"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module Unitwise
|
3
|
+
class Base
|
4
|
+
attr_accessor :primary_code, :secondary_code, :symbol
|
5
|
+
attr_reader :names, :scale
|
6
|
+
|
7
|
+
def self.all
|
8
|
+
@all ||= data.map{|d| self.new d }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.find(string)
|
12
|
+
[:primary_code, :secondary_code, :names, :slugs, :symbol].reduce(nil) do |m, method|
|
13
|
+
if found = find_by(method, string)
|
14
|
+
return found
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find_by(method, string)
|
20
|
+
self.all.find do |i|
|
21
|
+
key = i.send(method)
|
22
|
+
if key.respond_to?(:each)
|
23
|
+
key.include?(string)
|
24
|
+
else
|
25
|
+
key == string
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(attrs)
|
31
|
+
attrs.each do |k, v|
|
32
|
+
public_send :"#{k}=", v
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def names=(names)
|
37
|
+
@names = Array(names)
|
38
|
+
end
|
39
|
+
|
40
|
+
def slugs
|
41
|
+
names.map(&:to_slug)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Unitwise
|
2
|
+
module Composable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, Comparable
|
6
|
+
end
|
7
|
+
|
8
|
+
def composition
|
9
|
+
root_terms.reduce(SignedMultiset.new) do |s, t|
|
10
|
+
s.increment(t.atom.key, t.exponent) if t.atom; s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def similar_to?(other)
|
15
|
+
self.composition == other.composition
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=>(other)
|
19
|
+
if other.respond_to?(:composition) && similar_to?(other)
|
20
|
+
scalar <=> other.scalar
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
require 'unitwise/expression/matcher'
|
4
|
+
require 'unitwise/expression/parser'
|
5
|
+
require 'unitwise/expression/transformer'
|
6
|
+
require 'unitwise/expression/composer'
|
7
|
+
require 'unitwise/expression/decomposer'
|
8
|
+
|
9
|
+
module Unitwise
|
10
|
+
module Expression
|
11
|
+
class << self
|
12
|
+
def compose(terms)
|
13
|
+
Composer.new(terms).expression
|
14
|
+
end
|
15
|
+
|
16
|
+
def decompose(expression)
|
17
|
+
begin
|
18
|
+
Decomposer.new(expression).terms
|
19
|
+
rescue ExpressionError
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Unitwise
|
2
|
+
module Expression
|
3
|
+
class Composer
|
4
|
+
attr_reader :terms
|
5
|
+
def initialize(input)
|
6
|
+
if input.respond_to?(:terms)
|
7
|
+
@terms = input.terms
|
8
|
+
elsif input.respond_to?(:each)
|
9
|
+
@terms = input
|
10
|
+
else
|
11
|
+
@terms = Expression.decompose(input.to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def set
|
16
|
+
@set ||= terms.reduce(SignedMultiset.new) do |s, t|
|
17
|
+
s.increment({f: t.factor, p: t.prefix_code, a: t.atom_code}, t.exponent); s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def numerator
|
22
|
+
@numerator ||= set.select{|k,v| v > 0}.map do |k,v|
|
23
|
+
"#{k[:f] if k[:f] != 1}#{k[:p]}#{k[:a]}#{v if v != 1}"
|
24
|
+
end.select{|t| !t.empty?}.join('.')
|
25
|
+
end
|
26
|
+
|
27
|
+
def denominator
|
28
|
+
@denominator ||= set.select{|k,v| v < 0}.map do |k,v|
|
29
|
+
"#{k[:f] if k[:f] != 1}#{k[:p]}#{k[:a]}#{-v if v != -1}"
|
30
|
+
end.select{|t| !t.empty?}.join('.')
|
31
|
+
end
|
32
|
+
|
33
|
+
def expression
|
34
|
+
@expression = []
|
35
|
+
@expression << (numerator.empty? ? '1' : numerator)
|
36
|
+
(@expression << denominator) unless denominator.empty?
|
37
|
+
@expression.join('/')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Unitwise
|
2
|
+
module Expression
|
3
|
+
class Decomposer
|
4
|
+
|
5
|
+
PARSERS = [:primary_code, :secondary_code, :names, :slugs, :symbol].map do |t|
|
6
|
+
Parser.new(t)
|
7
|
+
end
|
8
|
+
|
9
|
+
TRANSFORMER = Transformer.new
|
10
|
+
|
11
|
+
attr_reader :expression
|
12
|
+
|
13
|
+
def initialize(expression)
|
14
|
+
@expression = expression.to_s
|
15
|
+
if terms.nil? || terms.empty?
|
16
|
+
raise ExpressionError, "Could not evaluate '#{@expression}'."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse
|
21
|
+
PARSERS.reduce(nil) do |m, p|
|
22
|
+
if prs = p.parse(expression) rescue next
|
23
|
+
return prs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def transform
|
29
|
+
@transform ||= TRANSFORMER.apply(parse)
|
30
|
+
end
|
31
|
+
|
32
|
+
def terms
|
33
|
+
if transform.respond_to?(:terms)
|
34
|
+
transform.terms
|
35
|
+
else
|
36
|
+
Array(transform)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Unitwise
|
2
|
+
module Expression
|
3
|
+
class Matcher
|
4
|
+
class << self
|
5
|
+
def atom(method)
|
6
|
+
new(Atom.all, method).alternative
|
7
|
+
end
|
8
|
+
|
9
|
+
def metric_atom(method)
|
10
|
+
new(Atom.all.select(&:metric?), method).alternative
|
11
|
+
end
|
12
|
+
|
13
|
+
def prefix(method)
|
14
|
+
new(Prefix.all, method).alternative
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :collection, :method
|
19
|
+
|
20
|
+
def initialize(collection, method=:primary_code)
|
21
|
+
@collection = collection
|
22
|
+
@method = method
|
23
|
+
end
|
24
|
+
|
25
|
+
def strings
|
26
|
+
@stings ||= collection.map(&method).flatten.compact.sort do |x,y|
|
27
|
+
y.length <=> x.length
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def matchers
|
32
|
+
@matchers ||= strings.map {|s| Parslet::Atoms::Str.new(s) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def alternative
|
36
|
+
@alternative ||= Parslet::Atoms::Alternative.new(*matchers)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|