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,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
@@ -0,0 +1,2 @@
1
+ require 'unitwise/ext/numeric'
2
+ require 'unitwise/ext/string'
@@ -0,0 +1,13 @@
1
+ class Numeric
2
+ def convert(unit)
3
+ Unitwise::Measurement.new(self, unit)
4
+ end
5
+
6
+ def method_missing(meth, *args, &block)
7
+ if Unitwise::Expression.decompose(meth)
8
+ self.convert(meth)
9
+ else
10
+ super(meth, *args, &block)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_slug
3
+ self.downcase.strip.gsub(/\s/, '_').gsub(/\W/, '')
4
+ end
5
+ end
@@ -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