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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +297 -0
- data/Rakefile +21 -0
- data/data/base_unit.yaml +43 -0
- data/data/derived_unit.yaml +3542 -0
- data/data/prefix.yaml +121 -0
- data/lib/unitwise.rb +70 -0
- data/lib/unitwise/atom.rb +121 -0
- data/lib/unitwise/base.rb +58 -0
- data/lib/unitwise/compatible.rb +60 -0
- data/lib/unitwise/errors.rb +7 -0
- data/lib/unitwise/expression.rb +35 -0
- data/lib/unitwise/expression/composer.rb +41 -0
- data/lib/unitwise/expression/decomposer.rb +68 -0
- data/lib/unitwise/expression/matcher.rb +47 -0
- data/lib/unitwise/expression/parser.rb +58 -0
- data/lib/unitwise/expression/transformer.rb +37 -0
- data/lib/unitwise/ext.rb +2 -0
- data/lib/unitwise/ext/numeric.rb +45 -0
- data/lib/unitwise/functional.rb +117 -0
- data/lib/unitwise/measurement.rb +198 -0
- data/lib/unitwise/prefix.rb +24 -0
- data/lib/unitwise/scale.rb +139 -0
- data/lib/unitwise/search.rb +46 -0
- data/lib/unitwise/standard.rb +29 -0
- data/lib/unitwise/standard/base.rb +73 -0
- data/lib/unitwise/standard/base_unit.rb +21 -0
- data/lib/unitwise/standard/derived_unit.rb +49 -0
- data/lib/unitwise/standard/extras.rb +17 -0
- data/lib/unitwise/standard/function.rb +35 -0
- data/lib/unitwise/standard/prefix.rb +17 -0
- data/lib/unitwise/standard/scale.rb +25 -0
- data/lib/unitwise/term.rb +142 -0
- data/lib/unitwise/unit.rb +181 -0
- data/lib/unitwise/version.rb +3 -0
- data/test/support/scale_tests.rb +117 -0
- data/test/test_helper.rb +19 -0
- data/test/unitwise/atom_test.rb +129 -0
- data/test/unitwise/base_test.rb +6 -0
- data/test/unitwise/expression/decomposer_test.rb +45 -0
- data/test/unitwise/expression/matcher_test.rb +42 -0
- data/test/unitwise/expression/parser_test.rb +109 -0
- data/test/unitwise/ext/numeric_test.rb +54 -0
- data/test/unitwise/functional_test.rb +17 -0
- data/test/unitwise/measurement_test.rb +233 -0
- data/test/unitwise/prefix_test.rb +25 -0
- data/test/unitwise/scale_test.rb +7 -0
- data/test/unitwise/search_test.rb +18 -0
- data/test/unitwise/term_test.rb +55 -0
- data/test/unitwise/unit_test.rb +87 -0
- data/test/unitwise_test.rb +35 -0
- data/unitwise.gemspec +33 -0
- 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
|