stick 1.2.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 (56) hide show
  1. data/CHANGES +7 -0
  2. data/COPYING +344 -0
  3. data/README +110 -0
  4. data/lib/stick/constants.rb +3 -0
  5. data/lib/stick/constants/cgs.rb +151 -0
  6. data/lib/stick/constants/mks.rb +158 -0
  7. data/lib/stick/constants/number.rb +33 -0
  8. data/lib/stick/constants/typeless_cgs.rb +141 -0
  9. data/lib/stick/constants/typeless_mks.rb +142 -0
  10. data/lib/stick/currency.rb +8 -0
  11. data/lib/stick/mapcar.rb +61 -0
  12. data/lib/stick/matrix.rb +1022 -0
  13. data/lib/stick/quaternion.rb +562 -0
  14. data/lib/stick/times.rb +441 -0
  15. data/lib/stick/units.rb +112 -0
  16. data/lib/stick/units/base.rb +980 -0
  17. data/lib/stick/units/currency.rb +159 -0
  18. data/lib/stick/units/data/binary/base.rb +4 -0
  19. data/lib/stick/units/data/cex.rb +5 -0
  20. data/lib/stick/units/data/currency-default.rb +5 -0
  21. data/lib/stick/units/data/currency-standard.rb +2 -0
  22. data/lib/stick/units/data/currency/base.rb +89 -0
  23. data/lib/stick/units/data/iec.rb +5 -0
  24. data/lib/stick/units/data/iec_binary/base.rb +6 -0
  25. data/lib/stick/units/data/si.rb +7 -0
  26. data/lib/stick/units/data/si/base.rb +9 -0
  27. data/lib/stick/units/data/si/derived.rb +26 -0
  28. data/lib/stick/units/data/si/extra.rb +22 -0
  29. data/lib/stick/units/data/uk.rb +10 -0
  30. data/lib/stick/units/data/uk/base.rb +22 -0
  31. data/lib/stick/units/data/units-default.rb +11 -0
  32. data/lib/stick/units/data/units-standard.rb +5 -0
  33. data/lib/stick/units/data/us.rb +10 -0
  34. data/lib/stick/units/data/us/base.rb +23 -0
  35. data/lib/stick/units/data/xmethods.rb +5 -0
  36. data/lib/stick/units/data/xmethods/cached.rb +84 -0
  37. data/lib/stick/units/data/xmethods/mapping.rb +87 -0
  38. data/lib/stick/units/loaders.rb +98 -0
  39. data/lib/stick/units/units.rb +109 -0
  40. data/meta/MANIFEST +76 -0
  41. data/meta/ROLLRC +2 -0
  42. data/meta/icli.yaml +16 -0
  43. data/meta/project.yaml +18 -0
  44. data/task/clobber/package +10 -0
  45. data/task/publish +57 -0
  46. data/task/release +10 -0
  47. data/task/setup +1616 -0
  48. data/task/test +25 -0
  49. data/test/spec_matrix.rb +342 -0
  50. data/test/test_currency.rb +26 -0
  51. data/test/test_matrix.rb +359 -0
  52. data/test/test_units.rb +205 -0
  53. data/work/TODO +20 -0
  54. data/work/bytes.rb +231 -0
  55. data/work/multipliers.rb +195 -0
  56. metadata +138 -0
@@ -0,0 +1,980 @@
1
+ # Title:
2
+ #
3
+ # Units
4
+ #
5
+ # Summary:
6
+ #
7
+ # SI Units system, integrated into Ruby's method call system.
8
+ #
9
+ # Copyright:
10
+ #
11
+ # Copyright (c) 2005 Peter Vanbroekhoven, Thomas Sawyer
12
+ #
13
+ # License:
14
+ #
15
+ # Ruby License
16
+ #
17
+ # This module is free software. You may use, modify, and/or redistribute this
18
+ # software under the same terms as Ruby.
19
+ #
20
+ # This program is distributed in the hope that it will be useful, but WITHOUT
21
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22
+ # FOR A PARTICULAR PURPOSE.
23
+ #
24
+ # Created:
25
+ #
26
+ # 2005.08.01
27
+ #
28
+ # Authors:
29
+ #
30
+ # - Peter Vanbroekhoven
31
+ # - Thomas Sawyer
32
+
33
+ require 'rbconfig'
34
+ require 'stick/units/loaders'
35
+
36
+ class Exception
37
+
38
+ def clean_backtrace(regex)
39
+ regex = /^#{::Regexp.escape(__FILE__)}:\d+:in `#{::Regexp.escape(regex)}'$/ if regex.is_a? ::String
40
+ set_backtrace(backtrace.reject { |a| regex =~ a })
41
+ self
42
+ end
43
+
44
+ def self.with_clean_backtrace(regex)
45
+ begin
46
+ yield
47
+ rescue ::Exception
48
+ $!.clean_backtrace(regex).clean_backtrace("with_clean_backtrace") if not $DEBUG
49
+ raise
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ # The namespace for all unit related classes. Mixing this in has the additional effect
56
+ # of making Units.with_unit_converter available without the <code>Units.</code> prefix,
57
+ # as well as the shortcuts for creating Units (see Units#method_missing).
58
+ #
59
+ #--
60
+ # http://en.wikipedia.org/wiki/Celsius_scale
61
+ #
62
+ # degrees_Celsius vs Celsius_degrees
63
+ # Kelvin
64
+ #
65
+ # Kelvin Celsius Fahrenheit Rankine Delisle Newton Réaumur Rømer
66
+ #
67
+ # K C F R D N R R
68
+ #
69
+ # http://en.wikipedia.org/wiki/Conversion_of_units
70
+ #++
71
+
72
+ module Stick
73
+
74
+ def method_missing(m, *args, &blk)
75
+ if args.length == 1
76
+ args[0] = (::Stick::Converter.converter(args[0]) rescue nil) if not args[0].is_a? ::Stick::Converter
77
+ return ::Stick::Unit.new({m => 1}, args[0]) if args[0] && args[0].registered?(m)
78
+ elsif (::Stick::Converter.current.registered?(m) rescue false)
79
+ raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
80
+ return ::Stick::Unit.new({m => 1}, ::Stick::Converter.current)
81
+ end
82
+ ::Exception.with_clean_backtrace("method_missing") {
83
+ super
84
+ }
85
+ end
86
+
87
+ def const_missing(c)
88
+ if (::Stick::Converter.current.registered?(c) rescue false)
89
+ return ::Stick::Unit.new({c => 1}, ::Stick::Converter.current)
90
+ else
91
+ ::Exception.with_clean_backtrace("const_missing") {
92
+ super
93
+ }
94
+ end
95
+ end
96
+
97
+ def self.append_features(m)
98
+ m.send(:extend, ::Stick)
99
+ super
100
+ end
101
+
102
+ # Executes the block with the current Converter changed to
103
+ # the given Converter. This allows to temporarily change the
104
+ # Converter used by default. A Converter object can be given,
105
+ # or the name of a registered Converter.
106
+ #
107
+ # Example:
108
+ #
109
+ # with_unit_converter(:uk) {
110
+ # puts 1.cwt.to(lb) # => 112.0 lb
111
+ # }
112
+ #
113
+ # with_unit_converter(:us) {
114
+ # puts 1.cwt.to(lb) # => 100.0 lb
115
+ # }
116
+ #
117
+ # See also Converter.current.
118
+ def with_unit_converter(name, &blk) # :yields:
119
+ ::Stick::Converter.with_converter(name, &blk)
120
+ end
121
+
122
+ module_function :with_unit_converter
123
+
124
+ class Regexps
125
+
126
+ NUMBER_REGEXP = /(\-?\d+((?:\.\d+)?(?:[eE][-+]?\d+)?))/
127
+ SINGLE_UNIT_REGEXP = /([a-zA-Z_]+)(?::([a-zA-Z_]+))?(?:\s*\*\*\s*([+-]?\d+))?/
128
+ SINGLE_UNIT_NOC_REGEXP = /[a-zA-Z_]+(?::[a-zA-Z_]+)?(?:\s*\*\*\s*[+-]?\d+)?/ # Violates DRY principle
129
+ MULTIPLE_UNIT_REGEXP = /#{SINGLE_UNIT_NOC_REGEXP}(?:(?:\s+|\s*\*\s*)#{SINGLE_UNIT_NOC_REGEXP})*/
130
+ TOTAL_UNIT_REGEXP = /^\s*(?:1|(#{MULTIPLE_UNIT_REGEXP}))\s*(?:\/\s*(#{MULTIPLE_UNIT_REGEXP})\s*)?$/
131
+ TOTAL_UNIT_NOC_REGEXP = /\s*(?:1|(?:#{MULTIPLE_UNIT_REGEXP}))\s*(?:\/\s*(?:#{MULTIPLE_UNIT_REGEXP})\s*)?/ # Violates DRY principle
132
+ VALUE_REGEXP = /^\s*#{NUMBER_REGEXP}\s*\*?\s*(#{TOTAL_UNIT_NOC_REGEXP})$/
133
+
134
+ end
135
+
136
+ class BaseUnit
137
+
138
+ attr_reader :name, :converter
139
+
140
+ def initialize(name, converter = ::Stick::Converter.current)
141
+ name = name.to_sym
142
+ raise ::ArgumentError, "unit #{name.to_s.dump} not registered with #{converter}" if not converter.registered? name
143
+ @name = name
144
+ @converter = converter
145
+ end
146
+
147
+ def conversion
148
+ @converter.send(:conversions, @name)
149
+ end
150
+
151
+ def ==(other)
152
+ other.is_a?(::Stick::BaseUnit) && other.name == @name && other.converter == @converter
153
+ end
154
+
155
+ alias eql? ==
156
+
157
+ def hash
158
+ @name.hash ^ @converter.hash
159
+ end
160
+
161
+ def to_s
162
+ if ::Stick::Converter.current.includes?(converter)
163
+ @name.to_s
164
+ else
165
+ "#{@converter}:#{@name}"
166
+ end
167
+ end
168
+
169
+ alias inspect to_s
170
+
171
+ end
172
+
173
+ # This class represents a Unit. A Unit uses a given Converter with
174
+ # a number of registered Stick in which it can be expressed.
175
+ # A Unit is the product of the powers of other Stick. In principle, these
176
+ # need not be integer powers, but this may cause problems with rounding.
177
+ # The following code for example returns +false+:
178
+ # Unit.new(:m => 0.1) * Unit.new(:m => 0.2) == Unit.new(:m => 0.3)
179
+ #
180
+ # Stick can be multiplied, divided, and raised to a given power.
181
+ # As an extra, 1 can be divided by a Unit.
182
+ #
183
+ # Examples:
184
+ #
185
+ # Unit.new(:mi => 1, :s => -1) ** 2 # => mi**2/s**2
186
+ # Unit.new(:mi => 1, :s => -1) * Unit.new(:s => 1, :usd => -1) # => mi/usd
187
+ # Unit.new(:mi => 1, :s => -1, Converter.converter(:uk)) *
188
+ # Unit.new(:s => 1, :usd => -1, Converter.converter(:us)) # => TypeError
189
+ # 1 / Unit.new(:mi => 1, :s => -1) # => s/mi
190
+ class Unit
191
+
192
+ attr_reader :units
193
+
194
+ # Creates a new (composite) Unit.
195
+ # It is passed a hash of the form <code>{:unit => exponent}</code>, and the
196
+ # Converter to use.
197
+ #
198
+ # Examples:
199
+ #
200
+ # Unit.new(:m => 1, :s => -1, Converter.converter(:uk)) # => m/s
201
+ # Unit.new(:mi => 1, :s => -2) # => mi/s**2
202
+ #
203
+ # See also Converter, Converter.converter
204
+ def initialize(units = {}, converter = nil)
205
+ conv = proc { converter ||= ::Stick::Converter.current }
206
+ if units.is_a? ::String
207
+ @units = decode_string(units, conv)
208
+ else
209
+ @units = {}
210
+ units.each_pair do |k, v|
211
+ k = conv[].base_unit(k.to_sym) if not k.is_a? ::Stick::BaseUnit
212
+ @units[k] = v
213
+ end
214
+ end
215
+ normalize_units
216
+ end
217
+
218
+ # Raises to the given power.
219
+ def **(p)
220
+ result = {}
221
+ @units.each_pair do |u, e|
222
+ result[u] = e * p
223
+ end
224
+ ::Stick::Unit.new(result)
225
+ end
226
+
227
+ # Multiplies with the given Unit.
228
+ def *(other)
229
+ do_op(:*, :+, other)
230
+ end
231
+
232
+ # Divides by the given Unit.
233
+ def /(other)
234
+ do_op(:/, :-, other)
235
+ end
236
+
237
+ # Returns +true+ iff this Unit has all exponents equal to 0.
238
+ def unitless?
239
+ @units.empty?
240
+ end
241
+
242
+ def coerce(other) # :nodoc:
243
+ return [::Stick::Unit.new({}), self] if other == 1
244
+ ::Stick::Converter.coerce_units(self, other)
245
+ end
246
+
247
+ def simplify
248
+ ::Stick::Converter.simplify_unit(self)
249
+ end
250
+
251
+ # Returns +true+ iff the two Stick are equals, <i>i.e.,</i> iff they have
252
+ # the same exponent for all units, and they both use the same Converter.
253
+ def ==(other)
254
+ other.is_a?(::Stick::Unit) && other.units == units
255
+ end
256
+
257
+ alias eql? ==
258
+
259
+ def hash
260
+ @units.to_a.map { |e| e.hash }.hash
261
+ end
262
+
263
+ # Returns +true+ iff this Unit is compatible with the given
264
+ # Unit. This is less strict than equality because for example hours are
265
+ # compatible with seconds ("we can add them"), but hours are not seconds.
266
+ def compatible_with?(other)
267
+ conv1, conv2 = ::Stick::Converter.coerce_units(self, other)
268
+ conv1.units == conv2.units
269
+ end
270
+
271
+ # Returns a human readable string representation.
272
+ def to_s
273
+ numerator = ""
274
+ denominator = ""
275
+ @units.each_pair do |u, e|
276
+ e_abs = e > 0 ? e : -e # This works with Complex too
277
+ (e > 0 ? numerator : denominator) << " #{u.to_s}#{"**#{e_abs}" if e_abs != 1}"
278
+ end
279
+ "#{numerator.lstrip}#{"/" + denominator.lstrip if not denominator.empty?}"
280
+ end
281
+
282
+ # Returns +self+.
283
+ def to_unit(converter = nil)
284
+ self
285
+ end
286
+
287
+ alias inspect to_s
288
+
289
+ def method_missing(m, *args, &blk)
290
+ if args.length == 1
291
+ args[0] = (::Stick::Converter.converter(args[0]) rescue nil) if not args[0].is_a? ::Stick::Converter
292
+ return self * ::Stick::Unit.new({m => 1}, args[0]) if args[0] && args[0].registered?(m)
293
+ elsif (::Stick::Converter.current.registered?(m) rescue false)
294
+ raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
295
+ return self * ::Stick::Unit.new({m => 1}, ::Stick::Converter.current)
296
+ end
297
+ ::Exception.with_clean_backtrace("method_missing") {
298
+ super
299
+ }
300
+ end
301
+
302
+ private
303
+
304
+ def decode_string(s, converter)
305
+ if ::Stick::Regexps::TOTAL_UNIT_REGEXP =~ s
306
+ numerator, denominator = $1, $2
307
+ units = {}
308
+ decode_multiplicative_string(numerator, 1, converter, units) if numerator
309
+ decode_multiplicative_string(denominator, -1, converter, units) if denominator
310
+ units
311
+ elsif /^\s*$/ =~ s
312
+ {}
313
+ else
314
+ raise ::ArgumentError, "Illegal unit string #{s.dump}"
315
+ end
316
+ end
317
+
318
+ def decode_multiplicative_string(s, multiplier, converter, result)
319
+ s.scan(::Stick::Regexps::SINGLE_UNIT_REGEXP) do |conv, unit, exp|
320
+ if unit
321
+ conv = ::Stick::Converter.converter(conv)
322
+ else
323
+ conv, unit = converter[], conv
324
+ end
325
+ exp ||= '1'
326
+ unit = conv.base_unit(unit)
327
+ result[unit] = (result[unit] || 0) + Integer(exp) * multiplier
328
+ end
329
+ end
330
+
331
+ def normalize_units
332
+ @units.keys.each do |unit|
333
+ @units.delete(unit) if @units[unit] == 0
334
+ end
335
+ end
336
+
337
+ def do_op(op, dual_op, other)
338
+ other = other.to_unit
339
+ raise TypeError, "cannot convert to Unit" unless ::Stick::Unit === other
340
+ result = @units.dup
341
+ other.units.each_pair do |u, e|
342
+ result[u] = 0 if not result[u]
343
+ result[u] = result[u].send(dual_op, e)
344
+ end
345
+ ::Stick::Unit.new(result)
346
+ end
347
+
348
+ end
349
+
350
+ # This class represents a Value with a numeric value and a Unit.
351
+ # The numeric value can be any Numeric, though it is not recommended
352
+ # to use Values.
353
+ #
354
+ # A Value can be added to, subtracted from and multiplied with another value,
355
+ # though only when both Values are using the same Converter. While multiplication
356
+ # is always possible, adding or subtracting values with incompatible Stick
357
+ # results in a TypeError. When two Stick are compatible but not the same,
358
+ # the Value with the larger of the Stick is converted to the smaller of the Stick.
359
+ # For example adding 100 seconds and 1 minute, the latter is converted to 60 seconds
360
+ # because a second is smaller than a minute. The result is 160 seconds.
361
+ class Value < Numeric
362
+
363
+ # The numeric value of this Value.
364
+ attr_reader :value
365
+ # The Unit of this value.
366
+ attr_reader :unit
367
+
368
+ include Comparable
369
+
370
+ class << self
371
+
372
+ alias old_new new
373
+
374
+ private :old_new
375
+
376
+ # Creates a new Value with the given numeric value and the given unit.
377
+ # Simply returns the given value if the given unit is unitless,
378
+ # <i>i.e.,</i> when <code>unit.unitless?</code> is true.
379
+ def new(value, *args)
380
+ res = new!(value, *args)
381
+ return res.value if res.unit.unitless?
382
+ res
383
+ end
384
+
385
+ def new!(value, *args)
386
+ if ::String === value
387
+ str = *args
388
+ converter = case args.length
389
+ when 0
390
+ when 1
391
+ conv = args[0]
392
+ else
393
+ raise ArgumentError, "wrong number of arguments"
394
+ end
395
+ value, unit = decode_string(value, converter)
396
+ else
397
+ if args.length == 1
398
+ unit = args[0]
399
+ else
400
+ raise ArgumentError, "wrong number of arguments"
401
+ end
402
+ end
403
+ unit = Unit.new(unit) unless unit.is_a?(Unit)
404
+ old_new(value, unit)
405
+ end
406
+
407
+ end
408
+
409
+ def initialize(value, unit) # :nodoc:
410
+ @value, @unit = value, unit
411
+ end
412
+
413
+ %w{ * / }.each do |op|
414
+ eval %{
415
+ def #{op}(other)
416
+ ::Stick::Value.new(*do_multiplicative_op(:#{op}, other))
417
+ end
418
+ }
419
+ end
420
+
421
+ %w{ + - modulo remainder % }.each do |op|
422
+ eval %{
423
+ def #{op}(other)
424
+ ::Stick::Value.new(*do_additive_op(:#{op}, other))
425
+ end
426
+ }
427
+ end
428
+
429
+ def divmod(other)
430
+ (q, r), unit = *do_additive_op(:divmod, other)
431
+ [q, ::Stick::Value.new(r, unit)]
432
+ end
433
+
434
+ def div(other)
435
+ do_additive_op(:div, other)[0]
436
+ end
437
+
438
+ def -@ # :nodoc:
439
+ Value.new(-@value, @unit)
440
+ end
441
+
442
+ def +@ # :nodoc:
443
+ self
444
+ end
445
+
446
+ def **(other) # :nodoc:
447
+ ::Stick::Value.new(@value ** other, @unit ** other)
448
+ end
449
+
450
+ def <=>(other) # :nodoc:
451
+ if other == 0
452
+ @value <=> 0
453
+ else
454
+ (self - other).value <=> 0
455
+ end
456
+ rescue TypeError
457
+ nil
458
+ end
459
+
460
+ alias eql? ==
461
+
462
+ def hash
463
+ @value.hash ^ @unit.hash
464
+ end
465
+
466
+ %w{ abs ceil floor next round succ truncate }.each do |op|
467
+ eval %{
468
+ def #{op}
469
+ ::Stick::Value.new(@value.#{op}, @unit)
470
+ end
471
+ }
472
+ end
473
+
474
+ %w{ finite? infinite? integer? nan? nonzero? zero? }.each do |op|
475
+ eval %{
476
+ def #{op}
477
+ @value.#{op}
478
+ end
479
+ }
480
+ end
481
+
482
+ # Converts this Value to the given Unit.
483
+ # This only works if the Converters used by this Value's Unit
484
+ # and the given Unit are the same. It obviously fails if the
485
+ # Stick are not compatible (can't add apples and oranges).
486
+ def to(to_unit, converter = nil)
487
+ raise ArgumentError, "Wrong number of arguments" if converter && !(::String === to_unit)
488
+ to_unit = to_unit.to_unit(converter)
489
+ raise TypeError, "cannot convert to Unit" unless ::Stick::Unit === to_unit
490
+ conv1, conv2 = unit.coerce(to_unit)
491
+ raise TypeError, "incompatible units for operation" if conv1.units != conv2.units
492
+ mult = conv1.multiplier / conv2.multiplier
493
+ ::Stick::Value.new(value * mult, to_unit)
494
+ end
495
+
496
+ def coerce(other) # :nodoc:
497
+ if ::Numeric === other
498
+ [::Stick::Value.new!(other, ::Stick::Unit.new), self]
499
+ else
500
+ super
501
+ end
502
+ end
503
+
504
+ # Returns a human readable string representation.
505
+ def to_s
506
+ "#{@value} #{@unit}"
507
+ end
508
+
509
+ # Returns a float if this Value is unitless, and raises an
510
+ # exception otherwise.
511
+ def to_f
512
+ val = simplify
513
+ if ::Stick::Value === val
514
+ raise TypeError, "Cannot convert to float"
515
+ else
516
+ val.to_f
517
+ end
518
+ end
519
+
520
+ # Returns an int if this Value is unitless, and raises an
521
+ # exception otherwise.
522
+ def to_i
523
+ val = simplify
524
+ if ::Stick::Value === val
525
+ raise TypeError, "Cannot convert to integer"
526
+ else
527
+ val.to_i
528
+ end
529
+ end
530
+
531
+ # Returns an int if this Value is unitless, and raises an
532
+ # exception otherwise.
533
+ def to_int
534
+ val = simplify
535
+ if ::Stick::Value === val
536
+ raise TypeError, "Cannot convert to integer"
537
+ else
538
+ val.to_int
539
+ end
540
+ end
541
+
542
+ # Forces simplification of the Unit part of this Value. Returns
543
+ # a new Value or a Float.
544
+ def simplify
545
+ mul, new_unit = *@unit.simplify
546
+ if new_unit.unitless?
547
+ @value * mul
548
+ else
549
+ ::Stick::Value.new(@value * mul, new_unit)
550
+ end
551
+ end
552
+
553
+ # Returns +self+.
554
+ def to_value(converter = nil)
555
+ self
556
+ end
557
+
558
+ alias inspect to_s
559
+
560
+ def method_missing(m, *args, &blk)
561
+ if args.length == 1
562
+ args[0] = (::Stick::Converter.converter(args[0]) rescue nil) if not args[0].is_a? ::Stick::Converter
563
+ return self * ::Stick::Value.new(1, ::Stick::Unit.new({m => 1}, args[0])) if args[0] && args[0].registered?(m)
564
+ elsif (::Stick::Converter.current.registered?(m) rescue false)
565
+ raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
566
+ return self * ::Stick::Value.new(1, ::Stick::Unit.new({m => 1}, ::Stick::Converter.current))
567
+ end
568
+ ::Exception.with_clean_backtrace("method_missing") {
569
+ super
570
+ }
571
+ end
572
+
573
+ private
574
+
575
+ def self.decode_string(s, converter)
576
+ if m = ::Stick::Regexps::VALUE_REGEXP.match(s)
577
+ value = m[2].empty? ? Integer(m[1]) : Float(m[1])
578
+ unit = ::Stick::Unit.new(m[3], converter)
579
+ [value, unit]
580
+ else
581
+ raise ::ArgumentError, "Illegal value string #{s.dump}"
582
+ end
583
+ end
584
+
585
+ def do_additive_op(op, other)
586
+ other = other.to_value
587
+ raise TypeError, "cannot convert to Value" unless ::Stick::Value === other
588
+ if other.is_a? ::Stick::Value
589
+ conv1, conv2 = unit.coerce(other.unit)
590
+ raise TypeError, "incompatible units for #{op}" if conv1.units != conv2.units
591
+ mult = conv2.multiplier / conv1.multiplier
592
+ if mult > 1
593
+ [value.send(op, other.value * mult), unit]
594
+ else
595
+ mult = conv1.multiplier / conv2.multiplier
596
+ [(value * mult).send(op, other.value), other.unit]
597
+ end
598
+ else
599
+ raise TypeError, "incompatible operands for #{op}"
600
+ end
601
+ end
602
+
603
+ def do_multiplicative_op(op, other)
604
+ case other
605
+ when ::Stick::Value
606
+ [value.send(op, other.value), unit.send(op, other.unit)]
607
+ when ::Stick::Unit
608
+ [value, unit.send(op, other)]
609
+ when ::Numeric
610
+ [value.send(op, other), unit]
611
+ else
612
+ # TODO: How to make this work for Stick as well?
613
+ # Problem : how check whether to_value failed without
614
+ # masking all exceptions?
615
+ other = other.to_value
616
+ raise TypeError, "cannot convert to Value" unless ::Stick::Value === other
617
+ do_multiplicative_op(op, other)
618
+ end
619
+ end
620
+
621
+ end
622
+
623
+ # This class handles all conversions between Stick.
624
+ #
625
+ # There are two kinds of Stick; those that are not expressed as a function
626
+ # of other Stick --called base units-- and those that are expressed
627
+ # as a function of other Stick --called derived units. The latter kind is
628
+ # registered specifying how it depends on other Stick, while the former
629
+ # kind is not.
630
+ #
631
+ # This class also registers a list of Converters that are generally useable.
632
+ # The default Converter which is used when none is specified, can be retrieved
633
+ # with Converter.current. Converters can be registered with Converter.register.
634
+ #
635
+ # Converters can be loaded from YAML. This allows Converters to be specified in
636
+ # configuration files.
637
+ class Converter
638
+
639
+ Conversion = Struct.new(:units, :multiplier) # :nodoc:
640
+
641
+ class Conversion # :nodoc:
642
+
643
+ def **(p)
644
+ Conversion.new(units ** p, multiplier ** p)
645
+ end
646
+
647
+ def *(m)
648
+ Conversion.new(units * m.units, multiplier * m.multiplier)
649
+ end
650
+
651
+ def /(m)
652
+ Conversion.new(units / m.units, multiplier / m.multiplier)
653
+ end
654
+
655
+ end
656
+
657
+ ShiftedConversion = Struct.new(:delta_type, :offset)
658
+
659
+ # Returns the name of this Converter, or <tt>nil</tt> if the
660
+ # Converter is not registered.
661
+ attr_accessor :name
662
+ private :name=
663
+
664
+ # Creates a new Converter. If a block is given,
665
+ # it is executed in the newly created Converter's context.
666
+ def initialize(name)
667
+ @conversions = {}
668
+ @included = []
669
+ @name = name
670
+ end
671
+
672
+ # Included the given converter in the receiver, unless it
673
+ # was already included.
674
+ def include(conv)
675
+ conv = ::Stick::Converter.converter(conv) if not conv.is_a?(::Stick::Converter)
676
+ raise "Circular include" if conv.includes?(self)
677
+ @included << conv if not includes? conv
678
+ self
679
+ end
680
+
681
+ # Returns whether the given converter was included in the
682
+ # receiver.
683
+ def includes?(conv)
684
+ conv = ::Stick::Converter.converter(conv) if not conv.is_a?(::Stick::Converter)
685
+ return true if conv == self
686
+ @included.each do |c|
687
+ return true if conv == c || c.includes?(conv)
688
+ end
689
+ false
690
+ end
691
+
692
+ # Returns the list of all included converters. This list may
693
+ # contain duplicates in some cases.
694
+ def included_converters(result = [])
695
+ result << self
696
+ @included.reverse_each { |c| c.included_converters(result) }
697
+ result
698
+ end
699
+
700
+ # Checks whether the unit with the given name is registered.
701
+ # The name can be a symbol or a string.
702
+ def registered?(unit)
703
+ unit = unit.to_sym
704
+ return self if registered_here?(unit)
705
+ @included.reverse_each do |c|
706
+ if res = c.registered?(unit)
707
+ return res
708
+ end
709
+ end
710
+ nil
711
+ end
712
+
713
+ # Returns the base unit with this name
714
+ def base_unit(name)
715
+ if conv = registered?(name)
716
+ return ::Stick::BaseUnit.new(name, conv)
717
+ end
718
+ raise "unit #{name.to_s.dump} not registered with #{self}"
719
+ end
720
+
721
+ # Returns the list of registered unit names as symbols.
722
+ def registered_units
723
+ @conversions.keys
724
+ end
725
+
726
+ #
727
+ def method_missing(m, *args, &blk)
728
+ if registered?(m)
729
+ raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
730
+ return ::Stick::Unit.new({m => 1}, self)
731
+ end
732
+ ::Exception.with_clean_backtrace("method_missing") {
733
+ super
734
+ }
735
+ end
736
+
737
+ # Returns a human readable string representation of this Converter.
738
+ def to_s
739
+ (@name.to_s if @name) || "#<Converter:#{object_id.to_s(16)}>"
740
+ end
741
+
742
+ alias inspect to_s
743
+
744
+ private
745
+
746
+ # Registers a new Unit with the given name. The +data+ parameter
747
+ # is a Hash with some extra parameters (can be Strings or Symbols):
748
+ # +alias+:: Specifies possible aliases for the Unit.
749
+ # +abbrev+:: Specifies possible abbreviations or symbols for the Unit.
750
+ # The differences with aliases is that prefixes work differently;
751
+ # see +register_si_unit+, +register_binary_unit+ and
752
+ # +register_binary_iec_unit+.
753
+ # +equals+:: If present, specifies how the Unit depends on other Stick.
754
+ # The value for this key can either be a Hash with +unit+ mapping to
755
+ # a Unit and +multiplier+ mapping to a numeric multiplier, or
756
+ # a String containing the multiplier, followed by a space, followed by
757
+ # a representation of the Unit as returned by Unit#to_s.
758
+ #
759
+ # Examples:
760
+ #
761
+ # converter.register_unit(:pint, :alias => :pints, :abbrev => [:pt, :pts]))
762
+ # converter.register_unit(:quart, 'alias' => :quarts, :abbrev => ['qt', :qts], :equals => '2.0 pt'))
763
+ # converter.register_unit(:gallon, :alias => :gallons, :abbrev => :gal, 'equals' => {:unit => Unit.new('qt' => 1, converter), 'multiplier' => 4.0))
764
+ #
765
+ # Note that Symbols and Strings are generally exchangeable within this
766
+ # library (internally they are converted to Symbols). The number one reason
767
+ # for this is that String look better in YAML.
768
+ #
769
+ # See also +register_si_unit+, +register_binary_unit+, +register_binary_iec_unit+,
770
+ # +register_length_unit+, and +register_currency+ in currency.rb.
771
+ def register_unit(name, data = {})
772
+ unit, aliases, abbrevs = extract_data(name, data, :to_sym)
773
+ conversion = data[:equals]
774
+ conversion = decode_conversion(conversion) if conversion
775
+ conversion = self.class.convert_conversion(conversion[:unit].units, conversion[:multiplier]) if conversion
776
+ register_unit_internal(unit, conversion)
777
+ conversion = self.class.convert_conversion({base_unit(unit) => 1}, 1) if not conversion
778
+ (aliases + abbrevs).each do |u|
779
+ register_unit_internal(u, conversion)
780
+ end
781
+ end
782
+
783
+ def register_unit_internal(unit, conversion)
784
+ raise "unit #{unit.to_s.dump} already registered with #{self}" if registered_here? unit
785
+ @conversions[unit] = conversion || :none
786
+ end
787
+
788
+ def register_prefixed_unit(unit, prefixes, data = {})
789
+ unit, aliases, abbrevs = extract_data(unit, data, :to_s)
790
+ register_unit(unit, :equals => data[:equals], :alias => aliases, :abbrev => abbrevs)
791
+ unit_sym = unit.to_sym
792
+ prefixes.each_pair do |pre,info|
793
+ abbrev = info[:abbrev]
794
+ multiplier = info[:multiplier] || 1
795
+ power = info[:power] || 1
796
+ register_unit(pre + unit, :equals => {:unit => ::Stick::Unit.new({unit_sym => power}, self), :multiplier => multiplier})
797
+ aliases.each do |a|
798
+ register_unit(pre + a, :equals => {:unit => ::Stick::Unit.new({unit_sym => power}, self), :multiplier => multiplier})
799
+ end
800
+ abbrevs.each do |a|
801
+ register_unit(abbrev + a, :equals => {:unit => ::Stick::Unit.new({unit_sym => power}, self), :multiplier => multiplier})
802
+ end
803
+ end
804
+ end
805
+
806
+ def extract_data(unit, data, conv)
807
+ sarray = proc do |k|
808
+ list = data[k] || []
809
+ list = [list] if not list.is_a? Array
810
+ list.map { |a| a.send(conv) }
811
+ end
812
+ unit = unit.send(conv)
813
+ return unit, sarray[:alias], sarray[:abbrev].select { |a| a != unit }
814
+ end
815
+
816
+ def decode_conversion(data)
817
+ if not data.is_a? ::String
818
+ return {:unit => data[:unit] || data['unit'],
819
+ :multiplier => data[:multiplier] || data['multiplier']}
820
+ end
821
+ if /^\s*1\s*\// =~ data
822
+ {:unit => ::Stick::Unit.new(data, self)}
823
+ elsif m = /^\s*#{::Stick::Regexps::NUMBER_REGEXP}(?:\s+(\S.*)$|\s*$)/.match(data)
824
+ unit = m[3] ? ::Stick::Unit.new(m[3], self) : ::Stick::Unit.new({}, self)
825
+ if m[1]
826
+ multiplier = m[2].empty? ? Integer(m[1]) : Float(m[1])
827
+ {:unit => unit, :multiplier => multiplier}
828
+ else
829
+ {:unit => unit}
830
+ end
831
+ else
832
+ {:unit => ::Stick::Unit.new(data, self)}
833
+ end
834
+ end
835
+
836
+ # Checks whether the unit with the given name is registered.
837
+ # The name can be a symbol or a string.
838
+ def registered_here?(unit)
839
+ unit = unit.to_sym
840
+ conversions(unit) != nil
841
+ end
842
+
843
+ def conversions(unit)
844
+ @conversions[unit] #|| (unit == :'--base-currency--' ? :none : nil)
845
+ end
846
+
847
+ class << self
848
+
849
+ private :new
850
+
851
+ # Returns the current Converter in the current Thread.
852
+ # The default converter is the one returned by <code>converter(:default)</code>.
853
+ # See also Stick#with_converter and Converter.converter.
854
+ def current
855
+ Thread.current[:'Stick::converter'] ||= converter(:default)
856
+ end
857
+
858
+ def with_converter(conv) # :nodoc:
859
+ conv = converter(conv) if not conv.is_a? ::Stick::Converter
860
+ raise ::ArgumentError, "Converter expected" if not conv.is_a? ::Stick::Converter
861
+ begin
862
+ old_conv = Thread.current[:'Stick::converter']
863
+ if old_conv
864
+ new_conv = Converter.send(:new, nil)
865
+ new_conv.include(old_conv)
866
+ new_conv.include(conv)
867
+ else
868
+ new_conv = conv
869
+ end
870
+ Thread.current[:'Stick::converter'] = new_conv
871
+ yield
872
+ ensure
873
+ Thread.current[:'Stick::converter'] = old_conv
874
+ end
875
+ end
876
+
877
+ # Returns the converter with the given name.
878
+ # This name can be a Symbol or a String.
879
+ def converter(name, &blk)
880
+ if blk
881
+ (converters[name.to_sym] ||= new(name.to_sym)).instance_eval(&blk)
882
+ else
883
+ converters[name.to_sym] or raise ::ArgumentError, "No converter #{name.to_s.dump} found"
884
+ end
885
+ end
886
+
887
+ # # Registers the given Converter under the given name.
888
+ # # This name can be a Symbol or a String. A Converter
889
+ # # can be registered under one name only, and only one
890
+ # # Converter can be registered under a given name.
891
+ # def register(name, converter)
892
+ # name = name.to_sym
893
+ # return if converters[name] == converter
894
+ # raise ArgumentError, "This converter is already registered under anoher name" if converter.name
895
+ # raise ArgumentError, "A converter with this name already exists" if converters[name]
896
+ # converters[name] = converter
897
+ # converter.name ||= name
898
+ # end
899
+
900
+ # Returns the list of names of registered converters.
901
+ def registered_converters
902
+ converters.keys
903
+ end
904
+
905
+ def coerce_units(unit1, unit2) # :nodoc:
906
+ [convert_conversion(unit1.units), convert_conversion(unit2.units)]
907
+ end
908
+
909
+ def simplify_unit(unit) # :nodoc:
910
+ conv = convert_conversion(unit.units)
911
+ [conv[:multiplier], conv[:units]]
912
+ end
913
+
914
+ def convert_conversion(units, multiplier = nil)
915
+ multiplier ||= 1
916
+ base_units = {}
917
+ other_units = {}
918
+ units.each_pair do |u, e|
919
+ (u.conversion != :none ? other_units : base_units)[u] = e
920
+ end
921
+ result = Conversion.new(::Stick::Unit.new(base_units, self), multiplier)
922
+ other_units.each_pair do |u, e|
923
+ result *= (u.conversion ** e)
924
+ end
925
+ result
926
+ end
927
+
928
+ def converters
929
+ @converters ||= {}
930
+ end
931
+
932
+ end
933
+
934
+ end
935
+
936
+ # Contains some configuration related constants
937
+ module Config
938
+ # The directory in which the data files are searched for
939
+ #DATADIR = 'data/stick/units' #DATADIR = File.join(::Config::CONFIG['DATADIR'], 'stick', 'units')
940
+ #CONFIGDIR = 'lib/stick/data' #DATADIR = File.join(::Config::CONFIG['DATADIR'], 'stick', 'units')
941
+ SYSTEMDIR = File.dirname(__FILE__)
942
+ CONFIGDIR = File.join(SYSTEMDIR, 'data')
943
+ end
944
+
945
+ end
946
+
947
+
948
+ class Numeric
949
+ #
950
+ def method_missing(m, *args, &blk)
951
+ if args.length == 1
952
+ args[0] = (::Stick::Converter.converter(args[0]) rescue nil) if not args[0].is_a? ::Stick::Converter
953
+ return ::Stick::Value.new(self, ::Stick::Unit.new({m => 1}, args[0])) if args[0] && args[0].registered?(m)
954
+ elsif ::Stick::Converter.current.registered?(m)
955
+ raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
956
+ return ::Stick::Value.new(self, ::Stick::Unit.new({m => 1}, ::Stick::Converter.current))
957
+ end
958
+ ::Exception.with_clean_backtrace("method_missing") {
959
+ super
960
+ }
961
+ end
962
+
963
+ #
964
+ def to_value(unit)
965
+ ::Stick::Value.new(self, unit)
966
+ end
967
+ end
968
+
969
+
970
+ class String
971
+ #
972
+ def to_unit(converter = nil)
973
+ ::Stick::Unit.new(self, converter)
974
+ end
975
+
976
+ #
977
+ def to_value(converter = nil)
978
+ ::Stick::Value.new(self, converter)
979
+ end
980
+ end