unitwise 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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