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,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,70 @@
1
+ require 'liner'
2
+ require 'memoizable'
3
+ require 'parslet'
4
+ require 'signed_multiset'
5
+ require 'yaml'
6
+ require 'bigdecimal'
7
+
8
+ require 'unitwise/version'
9
+ require 'unitwise/base'
10
+ require 'unitwise/compatible'
11
+ require 'unitwise/expression'
12
+ require 'unitwise/scale'
13
+ require 'unitwise/functional'
14
+ require 'unitwise/measurement'
15
+ require 'unitwise/atom'
16
+ require 'unitwise/prefix'
17
+ require 'unitwise/term'
18
+ require 'unitwise/unit'
19
+ require 'unitwise/search'
20
+ require 'unitwise/errors'
21
+
22
+ # Unitwise is a library for performing mathematical operations and conversions
23
+ # on all units defined by the [Unified Code for Units of Measure(UCUM).
24
+ module Unitwise
25
+
26
+ # Search for available compounds. This is just a helper method for
27
+ # convenience
28
+ # @param term [String, Regexp]
29
+ # @return [Array]
30
+ # @api public
31
+ def self.search(term)
32
+ Search.search(term)
33
+ end
34
+
35
+ # Determine if a given string is a valid unit expression
36
+ # @param expression [String]
37
+ # @return [true, false]
38
+ # @api public
39
+ def self.valid?(expression)
40
+ begin
41
+ !!Unitwise::Expression.decompose(expression)
42
+ rescue ExpressionError
43
+ false
44
+ end
45
+ end
46
+
47
+ # The system path for the installed gem
48
+ # @api private
49
+ def self.path
50
+ @path ||= File.dirname(File.dirname(__FILE__))
51
+ end
52
+
53
+ # A helper to get the location of a yaml data file
54
+ # @api private
55
+ def self.data_file(key)
56
+ File.join path, 'data', "#{key}.yaml"
57
+ end
58
+ end
59
+
60
+ # Measurement initializer shorthand. Use this to instantiate new measurements.
61
+ # @param first [Numeric, String] Either a numeric value or a unit expression
62
+ # @param last [String, Nil] Either a unit expression, or nil
63
+ # @return [Unitwise::Measurement]
64
+ # @example
65
+ # Unitwise(20, 'mile') # => #<Unitwise::Measurement 20 mile>
66
+ # Unitwise('km') # => #<Unitwise::Measurement 1 km>
67
+ # @api public
68
+ def Unitwise(*args)
69
+ Unitwise::Measurement.new(*args)
70
+ end
@@ -0,0 +1,121 @@
1
+ module Unitwise
2
+ # Atoms are the most basic elements in Unitwise. They are named coded and
3
+ # scaled units without prefixes, multipliers, exponents, etc. Examples are
4
+ # 'meter', 'hour', 'pound force'.
5
+ class Atom < Base
6
+ liner :classification, :property, :metric, :special, :arbitrary, :dim
7
+ include Compatible
8
+
9
+ class << self
10
+ # Array of hashes representing atom properties.
11
+ # @api private
12
+ def data
13
+ @data ||= data_files.map { |file| YAML.load(File.open file) }.flatten
14
+ end
15
+
16
+ # Data files containing atom data
17
+ # @api private
18
+ def data_files
19
+ %w(base_unit derived_unit).map { |type| Unitwise.data_file type }
20
+ end
21
+ end
22
+
23
+ # Determine if an atom is base level. All atoms that are not base are
24
+ # defined directly or indirectly in reference to a base atom.
25
+ # @return [true, false]
26
+ # @api public
27
+ def base?
28
+ !!(@dim && !scale)
29
+ end
30
+
31
+ # Determine if an atom is derived. Derived atoms are defined with respect
32
+ # to other atoms.
33
+ # @return [true, false]
34
+ # @api public
35
+ def derived?
36
+ !base?
37
+ end
38
+
39
+ # Determine if an atom is metric. Metric atoms can be combined with metric
40
+ # prefixes.
41
+ # @return [true, false]
42
+ # @api public
43
+ def metric
44
+ base? ? true : !!@metric
45
+ end
46
+ alias_method :metric?, :metric
47
+
48
+ # Determine if a unit is special. Special atoms are not defined on a
49
+ # traditional ratio scale.
50
+ # @return [true, false]
51
+ # @api public
52
+ def special
53
+ !!@special
54
+ end
55
+ alias_method :special?, :special
56
+
57
+ # Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
58
+ # dimension and have no general meaning, therefore cannot be compared with
59
+ # any other unit.
60
+ # @return [true, false]
61
+ # @api public
62
+ def arbitrary
63
+ !!@arbitrary
64
+ end
65
+ alias_method :arbitrary?, :arbitrary
66
+
67
+ # Determine how far away a unit is from a base unit.
68
+ # @return [Integer]
69
+ # @api public
70
+ def depth
71
+ base? ? 0 : scale.depth + 1
72
+ end
73
+ memoize :depth
74
+
75
+ # Determine if this is the last atom in the scale chain
76
+ # @return [true, false]
77
+ # @api public
78
+ def terminal?
79
+ depth <= 3
80
+ end
81
+
82
+ # A representation of an atoms composition. Used to determine if two
83
+ # different atoms are compatible.
84
+ # @return [String]
85
+ # @api public
86
+ def dim
87
+ terminal? ? @dim || property : composition_string
88
+ end
89
+
90
+ # Set the atom's scale. It can be set as a Scale or a Functional
91
+ # @return [Unitwise::Functional, Unitwise::Scale]
92
+ # @api public
93
+ def scale=(attrs)
94
+ @scale = if attrs[:function_code]
95
+ Functional.new(attrs[:value], attrs[:unit_code], attrs[:function_code])
96
+ else
97
+ Scale.new(attrs[:value], attrs[:unit_code])
98
+ end
99
+ end
100
+
101
+ # Get a numeric value that can be used to with other atoms to compare with
102
+ # or operate on. Base units have a scalar of 1.
103
+ # @return [Numeric]
104
+ # @api public
105
+ def scalar(magnitude = 1)
106
+ base? ? BigDecimal(magnitude.to_s) : scale.scalar(magnitude)
107
+ end
108
+
109
+ def magnitude(scalar = scalar())
110
+ special? ? scale.magnitude(scalar) : BigDecimal('1')
111
+ end
112
+
113
+ # An atom may have a complex scale with several base atoms at various
114
+ # depths. This method returns all of this atoms base level terms.
115
+ # @return [Array] An array containing base Unitwise::Term
116
+ def root_terms
117
+ base? ? [Term.new(:atom_code => primary_code)] : scale.root_terms
118
+ end
119
+ memoize :root_terms
120
+ end
121
+ end
@@ -0,0 +1,58 @@
1
+ module Unitwise
2
+ # The base class that Atom and Prefix are extended from. This class provides
3
+ # shared functionality for said classes.
4
+ class Base
5
+ liner :names, :primary_code, :secondary_code, :symbol, :scale
6
+ include Memoizable
7
+
8
+ # The list of tracked items.
9
+ # @return [Array] An array of memoized instances.
10
+ # @api public
11
+ def self.all
12
+ @all ||= data.map { |d| new d }
13
+ end
14
+
15
+ # Find a matching instance by a specified attribute.
16
+ # @param string [String] The search term
17
+ # @param method [Symbol] The attribute to search by
18
+ # @return The first matching instance
19
+ # @example
20
+ # Unitwise::Atom.find('m')
21
+ # @api public
22
+ def self.find(string, method = :primary_code)
23
+ all.find do |i|
24
+ key = i.send(method)
25
+ if key.is_a? Array
26
+ key.include?(string)
27
+ else
28
+ key == string
29
+ end
30
+ end
31
+ end
32
+
33
+ # Setter for the names attribute. Will always set as an array.
34
+ # @api semipublic
35
+ def names=(names)
36
+ @names = Array(names)
37
+ end
38
+
39
+ # A set of method friendly names.
40
+ # @return [Array] An array of strings
41
+ # @api semipublic
42
+ def slugs
43
+ names.map do |n|
44
+ n.downcase.strip.gsub(/\s/, '_').gsub(/\W/, '')
45
+ end
46
+ end
47
+ memoize :slugs
48
+
49
+ # String representation for the instance.
50
+ # @param mode [symbol] The attribute to for stringification
51
+ # @return [String]
52
+ # @api public
53
+ def to_s(mode = :primary_code)
54
+ res = send(mode)
55
+ res.respond_to?(:each) ? res.first.to_s : res.to_s
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ module Unitwise
2
+ # Compatible is used to establish compatibility between units, terms, or
3
+ # measurements. This is done by determining the objects atomic composition
4
+ # represented as a Signed Multiset.
5
+ module Compatible
6
+ # @api private
7
+ def self.included(base)
8
+ base.send :include, Comparable
9
+ base.send :include, Memoizable unless base < Memoizable
10
+ base.send :memoize, :composition, :composition_string
11
+ end
12
+
13
+ def initialize(*args)
14
+ super(*args)
15
+ freeze
16
+ end
17
+
18
+ # A representation of a unit based on the atoms it's derived from.
19
+ # @return [SignedMultiset]
20
+ # @api public
21
+ def composition
22
+ root_terms.reduce(SignedMultiset.new) do |s, t|
23
+ s.increment(t.atom.dim, t.exponent) if t.atom
24
+ s
25
+ end
26
+ end
27
+
28
+ # Define a default #dim for included classes.
29
+ # @return [String]
30
+ # @api public
31
+ def dim
32
+ composition_string
33
+ end
34
+
35
+ # A string representation of a unit based on the atoms it's derived from
36
+ # @return [String]
37
+ # @api public
38
+ def composition_string
39
+ composition.sort.map do |k, v|
40
+ v == 1 ? k.to_s : "#{k}#{v}"
41
+ end.join('.')
42
+ end
43
+
44
+ # Determine if this instance is similar to or compatible with other
45
+ # @return [true false]
46
+ # @api public
47
+ def compatible_with?(other)
48
+ composition == other.composition
49
+ end
50
+
51
+ # Compare whether the instance is greater, less than or equal to other.
52
+ # @return [-1 0 1]
53
+ # @api public
54
+ def <=>(other)
55
+ if other.respond_to?(:composition) && compatible_with?(other)
56
+ scalar <=> other.scalar
57
+ end
58
+ end
59
+ end
60
+ 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,35 @@
1
+ require 'unitwise/expression/matcher'
2
+ require 'unitwise/expression/parser'
3
+ require 'unitwise/expression/transformer'
4
+ require 'unitwise/expression/composer'
5
+ require 'unitwise/expression/decomposer'
6
+
7
+ module Unitwise
8
+ # The Expression module encompases all functions around encoding and decoding
9
+ # strings into Measurement::Units and vice-versa.
10
+ module Expression
11
+ class << self
12
+ # Build a string representation of a collection of terms
13
+ # @param terms [Array]
14
+ # @return [String]
15
+ # @example
16
+ # Unitwise::Expression.compose(terms) # => "m2/s2"
17
+ # @api public
18
+ def compose(terms, method = :primary_code)
19
+ Composer.new(terms, method).expression
20
+ end
21
+
22
+ # Convert a string representation of a unit into an array of terms
23
+ # @param expression [String] The string you wish to convert
24
+ # @return [Array]
25
+ # @example
26
+ # Unitwise::Expression.decompose("m2/s2")
27
+ # # => [<Unitwise::Term m2>, <Unitwise::Term s-2>]
28
+ # @api public
29
+ def decompose(expression)
30
+ Decomposer.parse(expression)
31
+ end
32
+
33
+ end
34
+ end
35
+ end