unitwise-193 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +21 -0
  5. data/CHANGELOG.md +44 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +297 -0
  9. data/Rakefile +21 -0
  10. data/data/base_unit.yaml +43 -0
  11. data/data/derived_unit.yaml +3542 -0
  12. data/data/prefix.yaml +121 -0
  13. data/lib/unitwise.rb +70 -0
  14. data/lib/unitwise/atom.rb +121 -0
  15. data/lib/unitwise/base.rb +58 -0
  16. data/lib/unitwise/compatible.rb +60 -0
  17. data/lib/unitwise/errors.rb +7 -0
  18. data/lib/unitwise/expression.rb +35 -0
  19. data/lib/unitwise/expression/composer.rb +41 -0
  20. data/lib/unitwise/expression/decomposer.rb +68 -0
  21. data/lib/unitwise/expression/matcher.rb +47 -0
  22. data/lib/unitwise/expression/parser.rb +58 -0
  23. data/lib/unitwise/expression/transformer.rb +37 -0
  24. data/lib/unitwise/ext.rb +2 -0
  25. data/lib/unitwise/ext/numeric.rb +45 -0
  26. data/lib/unitwise/functional.rb +117 -0
  27. data/lib/unitwise/measurement.rb +198 -0
  28. data/lib/unitwise/prefix.rb +24 -0
  29. data/lib/unitwise/scale.rb +139 -0
  30. data/lib/unitwise/search.rb +46 -0
  31. data/lib/unitwise/standard.rb +29 -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 +142 -0
  40. data/lib/unitwise/unit.rb +181 -0
  41. data/lib/unitwise/version.rb +3 -0
  42. data/test/support/scale_tests.rb +117 -0
  43. data/test/test_helper.rb +19 -0
  44. data/test/unitwise/atom_test.rb +129 -0
  45. data/test/unitwise/base_test.rb +6 -0
  46. data/test/unitwise/expression/decomposer_test.rb +45 -0
  47. data/test/unitwise/expression/matcher_test.rb +42 -0
  48. data/test/unitwise/expression/parser_test.rb +109 -0
  49. data/test/unitwise/ext/numeric_test.rb +54 -0
  50. data/test/unitwise/functional_test.rb +17 -0
  51. data/test/unitwise/measurement_test.rb +233 -0
  52. data/test/unitwise/prefix_test.rb +25 -0
  53. data/test/unitwise/scale_test.rb +7 -0
  54. data/test/unitwise/search_test.rb +18 -0
  55. data/test/unitwise/term_test.rb +55 -0
  56. data/test/unitwise/unit_test.rb +87 -0
  57. data/test/unitwise_test.rb +35 -0
  58. data/unitwise.gemspec +33 -0
  59. metadata +246 -0
@@ -0,0 +1,24 @@
1
+ module Unitwise
2
+ # A prefix can be used with metric atoms to modify their scale.
3
+ class Prefix < Base
4
+ liner :scalar
5
+
6
+ # The data loaded from the UCUM spec files
7
+ # @api semipublic
8
+ def self.data
9
+ @data ||= YAML.load File.open(data_file)
10
+ end
11
+
12
+ # The location of the UCUM spec prefix data file
13
+ # @api semipublic
14
+ def self.data_file
15
+ Unitwise.data_file 'prefix'
16
+ end
17
+
18
+ # Set the scalar value for the prefix, always as a BigDecimal
19
+ # @api semipublic
20
+ def scalar=(value)
21
+ @scalar = BigDecimal(value.to_s)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,139 @@
1
+ module Unitwise
2
+ # A Unitwise::Scale represents a value and a unit, sort of like a vector, it
3
+ # has two components. In this case, it's a value and unit rather than a
4
+ # magnitude and direction. This class should be considered mostly privateish.
5
+ class Scale
6
+ liner :value, :unit
7
+ include Unitwise::Compatible
8
+
9
+ def initialize(value, unit)
10
+ self.value = if value.is_a? self.class
11
+ value.convert_to(unit).value
12
+ else
13
+ value
14
+ end
15
+ self.unit = unit
16
+ freeze
17
+ end
18
+
19
+ # Set the unit vector.
20
+ # @param value [String, Unitwise::Unit]
21
+ # @api public
22
+ def unit=(value)
23
+ @unit = value.is_a?(Unit) ? value : Unit.new(value)
24
+ end
25
+
26
+ # List the atoms associated with this scale's unit.
27
+ # @return [Array]
28
+ # @api public
29
+ def atoms
30
+ unit.atoms
31
+ end
32
+
33
+ def value=(value)
34
+ @value = BigDecimal(value.to_s)
35
+ end
36
+
37
+ # List the terms associated with this scale's unit.
38
+ # @return [Array]
39
+ # @api public
40
+ def terms
41
+ unit.terms
42
+ end
43
+
44
+ # Is this scale's unit special?
45
+ # @return [true, false]
46
+ # @api public
47
+ def special?
48
+ unit.special?
49
+ end
50
+
51
+ # Get a scalar value for this scale.
52
+ # @param magnitude [Numeric] An optional magnitude on this scale.
53
+ # @return [Numeric] A scalar value on a linear scale
54
+ # @api public
55
+ def scalar(magnitude = value)
56
+ if special?
57
+ unit.scalar(magnitude)
58
+ else
59
+ value * unit.scalar
60
+ end
61
+ end
62
+
63
+ # Get a magnitude based on a linear scale value. Only used by scales with
64
+ # special atoms in it's hierarchy.
65
+ # @param scalar [Numeric] A linear scalar value
66
+ # @return [Numeric] The equivalent magnitude on this scale
67
+ # @api public
68
+ def magnitude(scalar = scalar())
69
+ if special?
70
+ unit.magnitude(scalar)
71
+ else
72
+ value * unit.magnitude
73
+ end
74
+ end
75
+
76
+ # The base terms this scale's unit is derived from
77
+ # @return [Array] An array of Unitwise::Term
78
+ # @api public
79
+ def root_terms
80
+ unit.root_terms
81
+ end
82
+ memoize :root_terms
83
+
84
+ # How far away is this instances unit from the deepest level atom.
85
+ # @return [Integer]
86
+ # @api public
87
+ def depth
88
+ unit.depth + 1
89
+ end
90
+ memoize :depth
91
+
92
+ # Attempts to coerce the value to the simplest Numeric that fully expresses
93
+ # it's value. For instance a value of 1.0 would return 1, a value of
94
+ # #<BigDecimal:7f9558d559b8,'0.45E1',18(18)> would return 4.5.
95
+ # @return [Numeric]
96
+ # @api public
97
+ def simplified_value
98
+ if value.is_a?(Integer)
99
+ value
100
+ elsif (i = Integer(value)) == value
101
+ i
102
+ elsif value.is_a?(Float) || value.is_a?(Rational)
103
+ value
104
+ elsif (f = Float(value)) == value
105
+ f
106
+ else
107
+ value
108
+ end
109
+ end
110
+ memoize :simplified_value
111
+
112
+ def expression
113
+ unit.expression
114
+ end
115
+
116
+ # Convert to a simple string representing the scale.
117
+ # @api public
118
+ def to_s(mode = nil)
119
+ "#{simplified_value} #{unit.to_s(mode)}"
120
+ end
121
+
122
+ def inspect
123
+ "#<#{self.class} value=#{simplified_value} unit=#{unit}>"
124
+ end
125
+
126
+ # Redefine hash for apropriate hash/key lookup
127
+ # @api semipublic
128
+ def hash
129
+ [value, unit.to_s, self.class].hash
130
+ end
131
+ memoize :hash
132
+
133
+ # Redefine hash equality to match the hashes
134
+ # @api semipublic
135
+ def eql?(other)
136
+ hash == other.hash
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,46 @@
1
+ module Unitwise
2
+ # The search module provides a simple search mechanism around known basic
3
+ # units. The full list of avaliable units infinite, so this search creates
4
+ # a small subset of atoms and prefixes to help users find what they are
5
+ # looking for. Thus, there is a multitude of valid units that may be
6
+ # constructed that this module will not be aware of.
7
+ module Search
8
+ class << self
9
+ # An abbreviated list of possible units. These are known combinations
10
+ # of atoms and prefixes.
11
+ # @return [Array] A list of known units
12
+ # @api public
13
+ def all
14
+ @all ||= begin
15
+ units = []
16
+ Atom.all.each do |a|
17
+ units << build(a)
18
+ Unitwise::Prefix.all.each { |p| units << build(a, p) } if a.metric?
19
+ end
20
+ units
21
+ end
22
+ end
23
+
24
+ # Search the list of known units for a match.
25
+ # @param term [String, Regexp] The term to search for
26
+ # @return [Array] A list of matching units.
27
+ # @api public
28
+ def search(term)
29
+ all.select do |unit|
30
+ unit.aliases.any? { |str| Regexp.new(term).match(str) }
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Helper method for building a new unit by a known atom and prefix.
37
+ # @param atom [Unitwise::Atom]
38
+ # @param prefix [Unitwise::Prefix, nil]
39
+ # @return [Unitwise::Unit]
40
+ # @api private
41
+ def build(atom, prefix = nil)
42
+ Unit.new([Term.new(:atom => atom, :prefix => prefix)])
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
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
+ # The Standard module is responsible for fetching the UCUM specification unit
12
+ # standards and translating them into yaml files. This code is only used for
13
+ # by the rake task `rake unitwise:update_standard` and as such is not
14
+ # normally loaded.
15
+ module Standard
16
+ HOST = "unitsofmeasure.org"
17
+ PATH = "/ucum-essence.xml"
18
+
19
+ class << self
20
+ def body
21
+ @body ||= Net::HTTP.get HOST, PATH
22
+ end
23
+
24
+ def hash
25
+ Nori.new.parse(body)["root"]
26
+ end
27
+ end
28
+ end
29
+ 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
@@ -0,0 +1,21 @@
1
+ module Unitwise::Standard
2
+ class BaseUnit < Base
3
+
4
+ def self.remote_key
5
+ "base_unit"
6
+ end
7
+
8
+ def property
9
+ attributes["property"].to_s
10
+ end
11
+
12
+ def dim
13
+ attributes["@dim"]
14
+ end
15
+
16
+ def to_hash
17
+ super.merge :property => property, :dim => dim
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ module Unitwise::Standard
2
+ class DerivedUnit < Base
3
+
4
+ def self.remote_key
5
+ "unit"
6
+ end
7
+
8
+ def self.local_key
9
+ "derived_unit"
10
+ end
11
+
12
+ def property
13
+ attributes["property"].to_s
14
+ end
15
+
16
+ def scale
17
+ Scale.new(attributes["value"]) unless special?
18
+ end
19
+
20
+ def function
21
+ Function.new(attributes["value"]) if special?
22
+ end
23
+
24
+ def classification
25
+ attributes["@class"]
26
+ end
27
+
28
+ def metric?
29
+ attributes["@isMetric"] == 'yes'
30
+ end
31
+
32
+ def special?
33
+ attributes["@isSpecial"] == 'yes'
34
+ end
35
+
36
+ def arbitrary?
37
+ attributes["@isArbitrary"] == 'yes'
38
+ end
39
+
40
+ def to_hash
41
+ hash = super()
42
+ hash[:scale] = (special? ? function.to_hash : scale.to_hash)
43
+ hash.merge({:classification => classification,
44
+ :property => property, :metric => metric?,
45
+ :special => special?, :arbitrary => arbitrary?})
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ module Unitwise::Standard
2
+ module Extras
3
+ def hash_to_markup(hash)
4
+ hash.map do |k,v|
5
+ if v.respond_to?(:to_xml)
6
+ "<#{k}>#{v.to_xml}</#{k}>"
7
+ elsif v.respond_to?(:map)
8
+ v.map do |i|
9
+ "<#{k}>#{i}</#{k}>"
10
+ end.join('')
11
+ else
12
+ "<#{k}>#{v}</#{k}>"
13
+ end
14
+ end.join('')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module Unitwise::Standard
2
+ class Function
3
+
4
+ attr_accessor :attributes
5
+
6
+ def initialize(attributes)
7
+ @attributes = attributes
8
+ end
9
+
10
+ def name
11
+ attributes["function"]["@name"]
12
+ end
13
+
14
+ def value
15
+ attributes["function"]["@value"].to_f
16
+ end
17
+
18
+ def unit
19
+ attributes["function"]["@Unit"]
20
+ end
21
+
22
+ def primary
23
+ attributes["@Unit"].gsub(/\(.*\)/, '')
24
+ end
25
+
26
+ def secondary
27
+ attributes["@UNIT"]
28
+ end
29
+
30
+ def to_hash
31
+ {:function_code => primary, :value => value, :unit_code => unit}
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module Unitwise::Standard
2
+ class Prefix < Base
3
+
4
+ def self.remote_key
5
+ "prefix"
6
+ end
7
+
8
+ def scale
9
+ attributes["value"].attributes["value"]
10
+ end
11
+
12
+ def to_hash
13
+ super().merge(:scalar => scale)
14
+ end
15
+
16
+ end
17
+ end