auom 0.0.6 → 0.1.0

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 (46) hide show
  1. data/.circle.yml +4 -0
  2. data/.rubocop.yml +7 -0
  3. data/.travis.yml +2 -9
  4. data/Changelog.md +4 -0
  5. data/Gemfile +2 -2
  6. data/Gemfile.devtools +39 -32
  7. data/auom.gemspec +3 -4
  8. data/circle.yml +4 -0
  9. data/config/flay.yml +1 -1
  10. data/config/{site.reek → reek.yml} +12 -11
  11. data/config/rubocop.yml +89 -0
  12. data/lib/auom.rb +7 -325
  13. data/lib/auom/algebra.rb +13 -10
  14. data/lib/auom/equalization.rb +6 -2
  15. data/lib/auom/inspection.rb +22 -57
  16. data/lib/auom/relational.rb +4 -2
  17. data/lib/auom/unit.rb +324 -0
  18. data/spec/shared/incompatible_operation_behavior.rb +3 -1
  19. data/spec/shared/operation_behavior.rb +3 -2
  20. data/spec/shared/sunits_shared.rb +3 -1
  21. data/spec/spec_helper.rb +2 -0
  22. data/spec/unit/auom/algebra/add_spec.rb +8 -6
  23. data/spec/unit/auom/algebra/divide_spec.rb +26 -15
  24. data/spec/unit/auom/algebra/multiply_spec.rb +25 -15
  25. data/spec/unit/auom/algebra/substract_spec.rb +8 -6
  26. data/spec/unit/auom/equalization/equality_operator_spec.rb +5 -3
  27. data/spec/unit/auom/inspection/class_methods/prettify_unit_part_spec.rb +6 -4
  28. data/spec/unit/auom/inspection/inspect_spec.rb +15 -13
  29. data/spec/unit/auom/relational/greater_than_or_equal_to_predicate_spec.rb +3 -1
  30. data/spec/unit/auom/relational/greater_than_predicate_spec.rb +3 -1
  31. data/spec/unit/auom/relational/less_than_or_equal_to_predicate_spec.rb +3 -1
  32. data/spec/unit/auom/relational/less_than_predicate_spec.rb +3 -1
  33. data/spec/unit/auom/unit/assert_same_unit_spec.rb +5 -3
  34. data/spec/unit/auom/unit/class_methods/convert_spec.rb +8 -6
  35. data/spec/unit/auom/unit/class_methods/lookup_spec.rb +6 -4
  36. data/spec/unit/auom/unit/class_methods/new_spec.rb +22 -21
  37. data/spec/unit/auom/unit/class_methods/try_convert_spec.rb +6 -4
  38. data/spec/unit/auom/unit/class_methods/units_spec.rb +3 -1
  39. data/spec/unit/auom/unit/denominators_spec.rb +5 -3
  40. data/spec/unit/auom/unit/numerators_spec.rb +5 -3
  41. data/spec/unit/auom/unit/same_unit_predicate_spec.rb +5 -3
  42. data/spec/unit/auom/unit/scalar_spec.rb +4 -2
  43. data/spec/unit/auom/unit/unit +0 -0
  44. data/spec/unit/auom/unit/unit_spec.rb +5 -3
  45. data/spec/unit/auom/unit/unitless_predicate_spec.rb +9 -1
  46. metadata +13 -28
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module AUOM
2
4
  # The AUOM algebra
3
5
  module Algebra
@@ -8,7 +10,7 @@ module AUOM
8
10
  # @return [Unit]
9
11
  #
10
12
  # @example
11
- #
13
+ #
12
14
  # # unitless
13
15
  # Unit.new(1) + Unit.new(2) # => <Unit @scalar=3>
14
16
  #
@@ -24,7 +26,7 @@ module AUOM
24
26
  klass = self.class
25
27
  operand = klass.convert(operand)
26
28
  assert_same_unit(operand)
27
- klass.new(operand.scalar + scalar, numerators.dup, denominators.dup)
29
+ klass.new(operand.scalar + scalar, numerators, denominators)
28
30
  end
29
31
 
30
32
  alias_method :+, :add
@@ -36,7 +38,7 @@ module AUOM
36
38
  # @return [Unit]
37
39
  #
38
40
  # @example
39
- #
41
+ #
40
42
  # # unitless
41
43
  # Unit.new(2) - Unit.new(1) # => <Unit @scalar=1>
42
44
  #
@@ -49,7 +51,7 @@ module AUOM
49
51
  # @api public
50
52
  #
51
53
  def substract(operand)
52
- add(self.class.convert(operand) * -1)
54
+ add(operand * -1)
53
55
  end
54
56
 
55
57
  alias_method :-, :substract
@@ -61,7 +63,7 @@ module AUOM
61
63
  # @return [Unit]
62
64
  #
63
65
  # @example
64
- #
66
+ #
65
67
  # # unitless
66
68
  # Unit.new(2) * Unit.new(1) # => <Unit @scalar=2>
67
69
  #
@@ -93,7 +95,7 @@ module AUOM
93
95
  # @return [Unit]
94
96
  #
95
97
  # @example
96
- #
98
+ #
97
99
  # # unitless
98
100
  # Unit.new(2) / Unit.new(1) # => <Unit @scalar=2>
99
101
  #
@@ -111,11 +113,12 @@ module AUOM
111
113
 
112
114
  self * klass.new(
113
115
  1 / operand.scalar,
114
- operand.denominators.dup,
115
- operand.numerators.dup
116
+ operand.denominators,
117
+ operand.numerators
116
118
  )
117
119
  end
118
120
 
119
121
  alias_method :/, :divide
120
- end
121
- end
122
+
123
+ end # Algebra
124
+ end # AUOM
@@ -1,6 +1,9 @@
1
+ # encoding: UTF-8
2
+
1
3
  module AUOM
2
4
  # Equalization for auom units
3
5
  module Equalization
6
+
4
7
  # Check for equivalent value and try to convert
5
8
  #
6
9
  # @param [Object] other
@@ -28,5 +31,6 @@ module AUOM
28
31
  def ==(other)
29
32
  eql?(self.class.try_convert(other))
30
33
  end
31
- end
32
- end
34
+
35
+ end # Equalization
36
+ end # AUOM
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module AUOM
2
4
  # Inspection module for auom units
3
5
  module Inspection
@@ -8,7 +10,7 @@ module AUOM
8
10
  # @api private
9
11
  #
10
12
  def inspect
11
- sprintf('<%s @scalar=%s%s>', self.class.name, pretty_scalar, pretty_unit)
13
+ sprintf('<%s @scalar=%s%s>', self.class, pretty_scalar, pretty_unit)
12
14
  end
13
15
 
14
16
  private
@@ -20,43 +22,26 @@ module AUOM
20
22
  # @api private
21
23
  #
22
24
  def pretty_scalar
23
- unless fractions?
24
- integer_value
25
+ if reminder?
26
+ sprintf('~%0.4f', scalar)
25
27
  else
26
- '~%0.4f' % float_value
28
+ scalar.to_i
27
29
  end
28
30
  end
29
31
 
30
- # Return float value
31
- #
32
- # @return [Float]
33
- #
34
- # @api private
35
- #
36
- def float_value
37
- scalar.to_f
38
- end
39
-
40
- # Return integer value
41
- #
42
- # @return [Fixnum]
43
- #
44
- # @api private
45
- #
46
- def integer_value
47
- scalar.to_i
48
- end
49
-
50
32
  # Return prettyfied unit part
51
33
  #
52
34
  # @return [String]
35
+ # if there is a prettifiable unit part
36
+ #
37
+ # @return [nil]
38
+ # otherwise
53
39
  #
54
40
  # @api private
55
41
  #
56
42
  def pretty_unit
57
- return '' if unitless?
43
+ return if unitless?
58
44
 
59
- klass = self.class
60
45
  numerator = Inspection.prettify_unit_part(@numerators)
61
46
  denominator = Inspection.prettify_unit_part(@denominators)
62
47
 
@@ -65,42 +50,21 @@ module AUOM
65
50
  return " #{numerator}"
66
51
  end
67
52
 
68
-
69
53
  sprintf(' %s/%s', numerator, denominator)
70
54
  end
71
55
 
72
- # Check if scalar has fractions in decimal representation
56
+ # Test if scalar has and reminder in decimal representation
73
57
  #
74
58
  # @return [true]
75
- # if scalar has fractions in decimal representation
59
+ # if there is a reminder
76
60
  #
77
61
  # @return [false]
78
- # if scalar NOT has fractions in decimal representation
79
- #
80
- # @api private
81
- #
82
- def fractions?
83
- !(scalar_numerator % scalar_denominator).zero?
84
- end
85
-
86
- # Return scalar numerator
87
- #
88
- # @return [Fixnum]
62
+ # otherwise
89
63
  #
90
64
  # @api private
91
65
  #
92
- def scalar_numerator
93
- scalar.numerator
94
- end
95
-
96
- # Return scalar denominator
97
- #
98
- # @return [Fixnum]
99
- #
100
- # @api private
101
- #
102
- def scalar_denominator
103
- scalar.denominator
66
+ def reminder?
67
+ !(scalar % scalar.denominator).zero?
104
68
  end
105
69
 
106
70
  # Return prettified units
@@ -112,7 +76,7 @@ module AUOM
112
76
  # @api private
113
77
  #
114
78
  def self.prettify_unit_part(base)
115
- counts(base).map { |unit,length| length > 1 ? "#{unit}^#{length}" : unit }.join('*')
79
+ counts(base).map { |unit, length| length > 1 ? "#{unit}^#{length}" : unit }.join('*')
116
80
  end
117
81
 
118
82
  # Return unit counts
@@ -124,8 +88,8 @@ module AUOM
124
88
  # @api private
125
89
  #
126
90
  def self.counts(base)
127
- counts = base.each_with_object(Hash.new(0)) { |unit,hash| hash[unit] += 1 }
128
- counts.sort do |left,right|
91
+ counts = base.each_with_object(Hash.new(0)) { |unit, hash| hash[unit] += 1 }
92
+ counts.sort do |left, right|
129
93
  result = right.last <=> left.last
130
94
  if result == 0
131
95
  left.first <=> right.first
@@ -136,5 +100,6 @@ module AUOM
136
100
  end
137
101
 
138
102
  private_class_method :counts
139
- end
140
- end
103
+
104
+ end # Inspection
105
+ end # AUOM
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  module AUOM
2
4
  # Mixin to add relational operators
3
5
  module Relational
@@ -86,5 +88,5 @@ module AUOM
86
88
  scalar.public_send(operation, other.scalar)
87
89
  end
88
90
 
89
- end
90
- end
91
+ end # Relational
92
+ end # AUOM
@@ -0,0 +1,324 @@
1
+ # encoding: UTF-8
2
+
3
+ module AUOM
4
+ # A scalar with units
5
+ class Unit
6
+ include Equalizer.new(:scalar, :numerators, :denominators)
7
+ include Algebra
8
+ include Equalization
9
+ include Inspection
10
+ include Relational
11
+
12
+ # Return scalar
13
+ #
14
+ # @example
15
+ #
16
+ # include AUOM
17
+ # m = Unit.new(1, :meter)
18
+ # m.scalar # => Rational(1, 1)
19
+ #
20
+ # @return [Rational]
21
+ #
22
+ # @api public
23
+ #
24
+ attr_reader :scalar
25
+
26
+ # Return numerators
27
+ #
28
+ # @example
29
+ #
30
+ # include AUOM
31
+ # m = Unit.new(1, :meter)
32
+ # m.numerators # => [:meter]
33
+ #
34
+ # @return [Rational]
35
+ #
36
+ # @api public
37
+ #
38
+ attr_reader :numerators
39
+
40
+ # Return denominators
41
+ #
42
+ # @example
43
+ #
44
+ # include AUOM
45
+ # m = Unit.new(1, :meter)
46
+ # m.denoninators # => []
47
+ #
48
+ # @return [Rational]
49
+ #
50
+ # @api public
51
+ #
52
+ attr_reader :denominators
53
+
54
+ # Return unit descriptor
55
+ #
56
+ # @return [Array]
57
+ #
58
+ # @api public
59
+ #
60
+ # @example
61
+ #
62
+ # u = Unit.new(1, [:meter, :meter], :euro)
63
+ # u.unit # => [[:meter, :meter], [:euro]]
64
+ #
65
+ attr_reader :unit
66
+
67
+ # These constants can easily be changed
68
+ # by an application specific subclass that overrides
69
+ # AUOM::Unit.units with an own hash!
70
+ UNITS = {
71
+ item: [1, :item],
72
+ liter: [1, :liter],
73
+ pack: [1, :pack],
74
+ can: [1, :can],
75
+ kilogramm: [1, :kilogramm],
76
+ euro: [1, :euro],
77
+ meter: [1, :meter],
78
+ kilometer: [1000, :meter]
79
+ }.freeze
80
+
81
+ # Return buildin units symbols
82
+ #
83
+ # @return [Hash]
84
+ #
85
+ # @api private
86
+ #
87
+ def self.units
88
+ UNITS
89
+ end
90
+
91
+ # Check for unitless unit
92
+ #
93
+ # @return [true]
94
+ # return true if unit is unitless
95
+ #
96
+ # @return [false]
97
+ # return false if unit is NOT unitless
98
+ #
99
+ # @example
100
+ #
101
+ # Unit.new(1).unitless? # => true
102
+ # Unit.new(1, :meter).unitless ? # => false
103
+ #
104
+ # @api public
105
+ #
106
+ def unitless?
107
+ numerators.empty? and denominators.empty?
108
+ end
109
+
110
+ # Test if units are the same
111
+ #
112
+ # @param [Unit] other
113
+ #
114
+ # @return [true]
115
+ # if units are the same
116
+ #
117
+ # @return [false]
118
+ # otehrwise
119
+ #
120
+ # @example
121
+ #
122
+ # a = Unit.new(1)
123
+ # b = Unit.new(1, :euro)
124
+ # c = Unit.new(2, :euro)
125
+ #
126
+ # a.same_unit?(b) # => false
127
+ # b.same_unit?(c) # => true
128
+ #
129
+ # @api public
130
+ #
131
+ def same_unit?(other)
132
+ other.unit.eql?(unit)
133
+ end
134
+
135
+ # Instancitate a new unit
136
+ #
137
+ # @param [Rational] scalar
138
+ # @param [Enumerable] numerators
139
+ # @param [Enumerable] denominators
140
+ #
141
+ # @return [Unit]
142
+ #
143
+ # @example
144
+ #
145
+ # # A unitless unit
146
+ # u = Unit.new(1)
147
+ # u.unitless? # => true
148
+ # u.scalar # => Rational(1, 1)
149
+ #
150
+ # # A unitless unit from string
151
+ # u = Unit.new('1.5')
152
+ # u.unitless? # => true
153
+ # u.scalar # => Rational(3, 2)
154
+ #
155
+ # # A simple unit
156
+ # u = Unit.new(1, :meter)
157
+ # u.unitless? # => false
158
+ # u.numerators # => [:meter]
159
+ # u.scalar # => Rational(1, 1)
160
+ #
161
+ # # A complex unit
162
+ # u = Unit.new(Rational(1, 3), :euro, :meter)
163
+ # u.fractions? # => true
164
+ # u.sclar # => Rational(1, 3)
165
+ # u.inspect # => <AUOM::Unit @scalar=~0.3333 euro/meter>
166
+ # u.unit # => [[:euro], [:meter]]
167
+ #
168
+ # @api public
169
+ #
170
+ # TODO: Move defaults coercions etc to .build method
171
+ #
172
+ def self.new(scalar, numerators = nil, denominators = nil)
173
+ scalar = rational(scalar)
174
+
175
+ scalar, numerators = resolve([*numerators], scalar, :*)
176
+ scalar, denominators = resolve([*denominators], scalar, :/)
177
+
178
+ # sorting on #to_s as Symbol#<=> is not present on 1.8.7
179
+ super(scalar, *[numerators, denominators].map { |base| base.sort_by(&:to_s) }).freeze
180
+ end
181
+
182
+ # Assert units are the same
183
+ #
184
+ # @param [Unit] other
185
+ #
186
+ # @return [self]
187
+ #
188
+ # @api private
189
+ #
190
+ def assert_same_unit(other)
191
+ unless same_unit?(other)
192
+ raise ArgumentError, 'Incompatible units'
193
+ end
194
+
195
+ self
196
+ end
197
+
198
+ # Return converted operand or raise error
199
+ #
200
+ # @param [Object] operand
201
+ #
202
+ # @return [Unit]
203
+ #
204
+ # @raise [ArgumentError]
205
+ # raises argument error in case operand cannot be converted
206
+ #
207
+ # @api private
208
+ #
209
+ def self.convert(operand)
210
+ converted = try_convert(operand)
211
+ unless converted
212
+ raise ArgumentError, "Cannot convert #{operand.inspect} to #{self}"
213
+ end
214
+ converted
215
+ end
216
+
217
+ # Return converted operand or nil
218
+ #
219
+ # @param [Object] operand
220
+ #
221
+ # @return [Unit]
222
+ # return unit in case operand can be converted
223
+ #
224
+ # @return [nil]
225
+ # return nil in case operand can NOT be converted
226
+ #
227
+ # @api private
228
+ #
229
+ def self.try_convert(operand)
230
+ case operand
231
+ when self
232
+ operand
233
+ when Fixnum, Rational
234
+ new(operand)
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ # Initialize unit
241
+ #
242
+ # @param [Rational] scalar
243
+ # @param [Enumerable] numerators
244
+ # @param [Enumerable] denominators
245
+ #
246
+ # @api private
247
+ #
248
+ def initialize(scalar, numerators, denominators)
249
+ @scalar = scalar
250
+
251
+ [numerators, denominators].permutation do |left, right|
252
+ left.delete_if { |item| right.delete_at(right.index(item) || right.length) }
253
+ end
254
+
255
+ @numerators = numerators.freeze
256
+ @denominators = denominators.freeze
257
+
258
+ @unit = [@numerators, @denominators].freeze
259
+ @scalar.freeze
260
+ end
261
+
262
+ # Return rational converted from value
263
+ #
264
+ # @param [Object] value
265
+ #
266
+ # @return [Rationa]
267
+ #
268
+ # @raise [ArgumentError]
269
+ # raises argument error when cannot be converted to a rational
270
+ #
271
+ # @api private
272
+ #
273
+ def self.rational(value)
274
+ case value
275
+ when Rational
276
+ value
277
+ when Fixnum
278
+ Rational(value)
279
+ else
280
+ raise ArgumentError, "#{value.inspect} cannot be converted to rational"
281
+ end
282
+ end
283
+
284
+ private_class_method :rational
285
+
286
+ # Resolve unit component
287
+ #
288
+ # @param [Enumerable] components
289
+ # @param [Symbol] operation
290
+ #
291
+ # @return [Array]
292
+ #
293
+ # @api private
294
+ #
295
+ def self.resolve(components, scalar, operation)
296
+ resolved = components.map do |component|
297
+ scale, component = lookup(component)
298
+ scalar = scalar.public_send(operation, scale)
299
+ component
300
+ end
301
+ [scalar, resolved]
302
+ end
303
+
304
+ private_class_method :resolve
305
+
306
+ # Return unit information
307
+ #
308
+ # @param [Symbol] value
309
+ # the unit to search for
310
+ #
311
+ # @return [Array]
312
+ #
313
+ # @api private
314
+ #
315
+ def self.lookup(value)
316
+ units.fetch(value) do
317
+ raise ArgumentError, "Unknown unit #{value.inspect}"
318
+ end
319
+ end
320
+
321
+ private_class_method :lookup
322
+
323
+ end # Unit
324
+ end # AUOM