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