unitwise-193 1.0.4

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