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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +110 -0
  8. data/Rakefile +21 -0
  9. data/data/base_unit.yaml +43 -0
  10. data/data/derived_unit.yaml +3394 -0
  11. data/data/prefix.yaml +121 -0
  12. data/lib/unitwise.rb +25 -0
  13. data/lib/unitwise/atom.rb +84 -0
  14. data/lib/unitwise/base.rb +45 -0
  15. data/lib/unitwise/composable.rb +25 -0
  16. data/lib/unitwise/errors.rb +7 -0
  17. data/lib/unitwise/expression.rb +25 -0
  18. data/lib/unitwise/expression/composer.rb +41 -0
  19. data/lib/unitwise/expression/decomposer.rb +42 -0
  20. data/lib/unitwise/expression/matcher.rb +41 -0
  21. data/lib/unitwise/expression/parser.rb +53 -0
  22. data/lib/unitwise/expression/transformer.rb +22 -0
  23. data/lib/unitwise/ext.rb +2 -0
  24. data/lib/unitwise/ext/numeric.rb +13 -0
  25. data/lib/unitwise/ext/string.rb +5 -0
  26. data/lib/unitwise/function.rb +51 -0
  27. data/lib/unitwise/functional.rb +21 -0
  28. data/lib/unitwise/measurement.rb +89 -0
  29. data/lib/unitwise/prefix.rb +17 -0
  30. data/lib/unitwise/scale.rb +61 -0
  31. data/lib/unitwise/standard.rb +26 -0
  32. data/lib/unitwise/standard/base.rb +73 -0
  33. data/lib/unitwise/standard/base_unit.rb +21 -0
  34. data/lib/unitwise/standard/derived_unit.rb +49 -0
  35. data/lib/unitwise/standard/extras.rb +17 -0
  36. data/lib/unitwise/standard/function.rb +35 -0
  37. data/lib/unitwise/standard/prefix.rb +17 -0
  38. data/lib/unitwise/standard/scale.rb +25 -0
  39. data/lib/unitwise/term.rb +106 -0
  40. data/lib/unitwise/unit.rb +89 -0
  41. data/lib/unitwise/version.rb +3 -0
  42. data/test/test_helper.rb +7 -0
  43. data/test/unitwise/atom_test.rb +122 -0
  44. data/test/unitwise/base_test.rb +6 -0
  45. data/test/unitwise/expression/decomposer_test.rb +36 -0
  46. data/test/unitwise/expression/matcher_test.rb +42 -0
  47. data/test/unitwise/expression/parser_test.rb +91 -0
  48. data/test/unitwise/ext/numeric_test.rb +46 -0
  49. data/test/unitwise/ext/string_test.rb +13 -0
  50. data/test/unitwise/function_test.rb +42 -0
  51. data/test/unitwise/measurement_test.rb +168 -0
  52. data/test/unitwise/prefix_test.rb +25 -0
  53. data/test/unitwise/term_test.rb +44 -0
  54. data/test/unitwise/unit_test.rb +57 -0
  55. data/test/unitwise_test.rb +7 -0
  56. data/unitwise.gemspec +28 -0
  57. metadata +213 -0
@@ -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'
@@ -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,7 @@
1
+ module Unitwise
2
+ class ExpressionError < Exception
3
+ end
4
+
5
+ class ConversionError < Exception
6
+ end
7
+ 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