stick 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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