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