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.
- data/CHANGES +7 -0
- data/COPYING +344 -0
- data/README +110 -0
- data/lib/stick/constants.rb +3 -0
- data/lib/stick/constants/cgs.rb +151 -0
- data/lib/stick/constants/mks.rb +158 -0
- data/lib/stick/constants/number.rb +33 -0
- data/lib/stick/constants/typeless_cgs.rb +141 -0
- data/lib/stick/constants/typeless_mks.rb +142 -0
- data/lib/stick/currency.rb +8 -0
- data/lib/stick/mapcar.rb +61 -0
- data/lib/stick/matrix.rb +1022 -0
- data/lib/stick/quaternion.rb +562 -0
- data/lib/stick/times.rb +441 -0
- data/lib/stick/units.rb +112 -0
- data/lib/stick/units/base.rb +980 -0
- data/lib/stick/units/currency.rb +159 -0
- data/lib/stick/units/data/binary/base.rb +4 -0
- data/lib/stick/units/data/cex.rb +5 -0
- data/lib/stick/units/data/currency-default.rb +5 -0
- data/lib/stick/units/data/currency-standard.rb +2 -0
- data/lib/stick/units/data/currency/base.rb +89 -0
- data/lib/stick/units/data/iec.rb +5 -0
- data/lib/stick/units/data/iec_binary/base.rb +6 -0
- data/lib/stick/units/data/si.rb +7 -0
- data/lib/stick/units/data/si/base.rb +9 -0
- data/lib/stick/units/data/si/derived.rb +26 -0
- data/lib/stick/units/data/si/extra.rb +22 -0
- data/lib/stick/units/data/uk.rb +10 -0
- data/lib/stick/units/data/uk/base.rb +22 -0
- data/lib/stick/units/data/units-default.rb +11 -0
- data/lib/stick/units/data/units-standard.rb +5 -0
- data/lib/stick/units/data/us.rb +10 -0
- data/lib/stick/units/data/us/base.rb +23 -0
- data/lib/stick/units/data/xmethods.rb +5 -0
- data/lib/stick/units/data/xmethods/cached.rb +84 -0
- data/lib/stick/units/data/xmethods/mapping.rb +87 -0
- data/lib/stick/units/loaders.rb +98 -0
- data/lib/stick/units/units.rb +109 -0
- data/meta/MANIFEST +76 -0
- data/meta/ROLLRC +2 -0
- data/meta/icli.yaml +16 -0
- data/meta/project.yaml +18 -0
- data/task/clobber/package +10 -0
- data/task/publish +57 -0
- data/task/release +10 -0
- data/task/setup +1616 -0
- data/task/test +25 -0
- data/test/spec_matrix.rb +342 -0
- data/test/test_currency.rb +26 -0
- data/test/test_matrix.rb +359 -0
- data/test/test_units.rb +205 -0
- data/work/TODO +20 -0
- data/work/bytes.rb +231 -0
- data/work/multipliers.rb +195 -0
- 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
|