Class: Measure

Inherits:
Object
  • Object
show all
Defined in:
lib/measure.rb,
lib/measure/length.rb,
lib/measure/weight.rb,
lib/measure/version.rb

Overview

Author

Kenta Murata

Copyright

Copyright © 2008 Kenta Murata

License

LGPL version 3.0

Defined Under Namespace

Modules: VERSION Classes: CompatibilityError, InvalidUnitError, UnitRedefinitionError

Constant Summary collapse

@@units =
[]
@@dimension_map =
{}
@@conversion_map =
{}
@@alias_map =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, unit) ⇒ Measure

class << self



199
200
201
202
# File 'lib/measure.rb', line 199

def initialize(value, unit)
  @value, @unit = value, unit
  return nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



395
396
397
398
399
400
401
# File 'lib/measure.rb', line 395

def method_missing(name, *args)
  if /^as_(\w+)/.match(name.to_s)
    unit = $1.to_sym
    return convert(unit)
  end
  return saved_method_missing(name, *args)
end

Instance Attribute Details

#unitObject (readonly)

Returns the value of attribute unit.



204
205
206
# File 'lib/measure.rb', line 204

def unit
  @unit
end

#valueObject (readonly)

Returns the value of attribute value.



204
205
206
# File 'lib/measure.rb', line 204

def value
  @value
end

Class Method Details

.clear_unitsObject

Clear all defined units.



67
68
69
70
71
72
73
# File 'lib/measure.rb', line 67

def clear_units
  @@units.clear
  @@dimension_map.clear
  @@conversion_map.clear
  @@alias_map.clear
  return nil
end

.conversion_mapObject



18
19
20
# File 'lib/measure.rb', line 18

def conversion_map
  @@conversion_map
end

.define_alias(unit, base) ⇒ Object Also known as: def_alias

Defines an alias. Measure::UnitRedefinitionError is raised when the alias is redefined. Measure::InvalidUnitError is raised when the base unit is not defined.



114
115
116
117
118
119
# File 'lib/measure.rb', line 114

def define_alias(unit, base)
  if self.has_unit?(unit)
    raise UnitRedefinitionError, "unit [#{unit}] is already defined"
  end
  @@alias_map[unit] = resolve_alias base
end

.define_conversion(origin, conversion) ⇒ Object Also known as: def_conversion

Defines conversions.



127
128
129
130
131
132
133
134
135
# File 'lib/measure.rb', line 127

def define_conversion(origin, conversion)
  origin = resolve_alias origin
  @@conversion_map[origin] ||= {}
  conversion.each {|target, conv|
    target = resolve_alias target
    @@conversion_map[origin][target] = conv
  }
  return nil
end

.define_unit(unit, dimension = 1) ⇒ Object Also known as: def_unit

Defines a unit. The default dimension is 1. Measure::UnitRedefinitionError is raised when the unit is redefined.



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/measure.rb', line 95

def define_unit(unit, dimension=1)
  if @@units.include?(unit)
    if self.dimension(unit) != dimension
      raise UnitRedefinitionError, "unit [#{unit}] is already defined"
    end
  else
    @@units << unit
    @@dimension_map[unit] = dimension
    return self
  end
end

.dimension(unit) ⇒ Object Also known as: dim



155
156
157
# File 'lib/measure.rb', line 155

def dimension(unit)
  return @@dimension_map[resolve_alias(unit)]
end

.direct_compatible?(u1, u2) ⇒ Boolean

Test direct compatibility between two units, u1 and u2.

Returns:

  • (Boolean)


51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/measure.rb', line 51

def direct_compatible?(u1, u2)
  u1 = resolve_alias u1
  u2 = resolve_alias u2
  return true if u1 == u2
  if @@conversion_map.has_key? u1 and @@conversion_map[u1].has_key? u2
    return true
  end
  if @@conversion_map.has_key? u2 and @@conversion_map[u2].has_key? u1
    return true unless Proc === @@conversion_map[u2][u1]
  end
  return false
end

.disable_short_formObject



47
48
49
# File 'lib/measure/support.rb', line 47

def disable_short_form
  MeasureSupport.disable
end

.enable_short_formObject



43
44
45
# File 'lib/measure/support.rb', line 43

def enable_short_form
  MeasureSupport.enable
end

.find_conversion_route(u1, u2) ⇒ Object Also known as: find_multi_hop_conversion



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/measure.rb', line 160

def find_conversion_route(u1, u2)
  visited = []
  queue = [[u1]]
  while route = queue.shift
    next if visited.include? route.last
    visited.push route.last
    return route if route.last == u2
    neighbors(route.last).each{|u|
      queue.push(route + [u]) unless visited.include? u }
  end
  return nil
end

.has_unit?(unit) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
25
26
27
28
29
# File 'lib/measure.rb', line 22

def has_unit?(unit)
  begin
    unit = resolve_alias unit
    return @@units.include?(unit)
  rescue InvalidUnitError
    return false
  end
end

.num_unitsObject

The number of defined units.



87
88
89
# File 'lib/measure.rb', line 87

def num_units
  return @@units.length + @@alias_map.length
end

.resolve_alias(unit) ⇒ Object

Resolves an alias and returns the entity. The returned unit is NOT invalid always. InvalidUnitError is raised if a given unit is undefined.



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/measure.rb', line 36

def resolve_alias(unit)
  # NOTE: mustn't use has_unit? method to avoid infinite recursion
  return unit if @@units.include? unit
  unless @@alias_map.has_key? unit
    raise InvalidUnitError, "unit `#{unit}' is undefined"
  end
  while @@alias_map.has_key? unit
    unit = @@alias_map[unit]
  end
  return unit
end

.short_formObject



34
35
36
37
38
39
40
41
# File 'lib/measure/support.rb', line 34

def short_form
  begin
    MeasureSupport.enable
    return yield
  ensure
    MeasureSupport.disable
  end
end

.short_form_available?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/measure/support.rb', line 51

def short_form_available?
  MeasureSupport.enable?
end

.undefine_unit(unit) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/measure.rb', line 139

def undefine_unit(unit)
  if @@units.include? unit
    @@conversion_map.delete unit
    @@conversion_map.each {|k, v| v.delete unit }
    @@units.delete unit
    return true
  elsif @@alias_map.has_key? unit
    @@alias_map.delete unit
    return true
  end
  return false
end

.units(dimension = nil) ⇒ Object

Returns defined units. If dimension is specified, returning units are of only the dimension.



79
80
81
82
# File 'lib/measure.rb', line 79

def units(dimension=nil)
  return @@units.dup if dimension.nil?
  @@dimension_map.select {|k, v| v == dimension }.collect{|k, v| k }
end

Instance Method Details

#*(other) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/measure.rb', line 287

def *(other)
  case other
  when Measure
    return other * self.value if self.unit == 1
    return Measure(self.value * other.value, self.unit) if other.unit == 1
    # TODO: dimension
    raise NotImplementedError, "this feature has not implemented yet"
#       if self.unit == other.unit
#         return Measure(self.value * other.value, self.unit)
#       elsif Measure.dim(self.unit) == Measure.dim(other.unit)
#         return Measure(self.value - other.convert(self.unit).value, self.unit)
#       else
#         return Measure(self.value * other.convert(self.unit).value, self.unit)
#       end
  when Numeric
    return Measure(self.value * other, self.unit)
  else
    check_coercable other
    a, b = other.coerce self
    return a * b
  end
end

#+(other) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/measure.rb', line 247

def +(other)
  case other
  when Measure
    if self.unit == other.unit
      return Measure(self.value + other.value, self.unit)
    elsif Measure.dim(self.unit) == Measure.dim(other.unit)
      return Measure(self.value + other.convert(self.unit).value, self.unit)
    else
      raise TypeError, "incompatible dimensions: " +
        "#{Measure.dim(self.unit)} and #{Measure.dim(other.unit)}"
    end
  when Numeric
    return Measure(self.value + other, self.unit)
  else
    check_coercable other
    a, b = other.coerce self
    return a + b
  end
end

#-(other) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/measure.rb', line 267

def -(other)
  case other
  when Measure
    if self.unit == other.unit
      return Measure(self.value - other.value, self.unit)
    elsif Measure.dim(self.unit) == Measure.dim(other.unit)
      return Measure(self.value - other.convert(self.unit).value, self.unit)
    else
      raise TypeError, "incompatible dimensions: " +
        "#{Measure.dim(self.unit)} and #{Measure.dim(other.unit)}"
    end
  when Numeric
    return Measure(self.value - other, self.unit)
  else
    check_coerecable other
    a, b = other.coerce self
    return a - b
  end
end

#/(other) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/measure.rb', line 310

def /(other)
  case other
  when Measure
    # TODO: dimension
    raise NotImplementedError, "this feature has not implemented yet"
#       if self.unit == other.unit
#         return Measure(self.value / other.value, self.unit)
#       else
#         return Measure(self.value / other.convert(self.unit).value, self.unit)
#       end
  when Numeric
    return Measure(self.value / other, self.unit)
  else
    check_coercable other
    a, b = other.coerce self
    return a / b
  end
end

#<(other) ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/measure.rb', line 206

def <(other)
  case other
  when Measure
    if self.unit == other.unit
      return self.value < other.value
    else
      return self < other.convert(self.value)
    end
  when Numeric
    return self.value < other
  else
    raise ArgumentError, 'unable to compare with #{other.inspect}'
  end
end

#==(other) ⇒ Object



236
237
238
239
240
241
242
243
244
245
# File 'lib/measure.rb', line 236

def ==(other)
  return self.value == other.value if self.unit == other.unit
  if Measure.direct_compatible? self.unit, other.unit
    return self == other.convert(self.unit)
  elsif Measure.direct_compatible? other.unit, self.unit
    return self.convert(other.unit) == other
  else
    return false
  end
end

#>(other) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/measure.rb', line 221

def >(other)
  case other
  when Measure
    if self.unit == other.unit
      return self.value > other.value
    else
      return self > other.convert(self.value)
    end
  when Numeric
    return self.value > other
  else
    raise ArgumentError, 'unable to compare with #{other.inspect}'
  end
end

#absObject



338
339
340
# File 'lib/measure.rb', line 338

def abs
  return Measure(self.value.abs, self.unit)
end

#coerce(other) ⇒ Object



329
330
331
332
333
334
335
336
# File 'lib/measure.rb', line 329

def coerce(other)
  case other
  when Numeric
    return [Measure(other, 1), self]
  else
    raise TypeError, "#{other.class} can't convert into #{self.class}"
  end
end

#convert(unit) ⇒ Object

Raises:



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/measure.rb', line 350

def convert(unit)
  return self if unit == self.unit
  to_unit = Measure.resolve_alias unit
  raise InvalidUnitError, "unknown unit: #{unit}" unless Measure.has_unit? unit
  from_unit = Measure.resolve_alias self.unit
  if Measure.direct_compatible? from_unit, to_unit
    # direct conversion
    if @@conversion_map.has_key? from_unit and @@conversion_map[from_unit].has_key? to_unit
      conv = @@conversion_map[from_unit][to_unit]
      case conv
      when Proc
        value = conv[self.value]
      else
        value = self.value * conv
      end
    else
      value = self.value / @@conversion_map[to_unit][from_unit].to_f
    end
  elsif route = Measure.find_multi_hop_conversion(from_unit, to_unit)
    u1 = route.shift
    value = self.value
    while u2 = route.shift
      if @@conversion_map.has_key? u1 and @@conversion_map[u1].has_key? u2
        conv = @@conversion_map[u1][u2]
        case conv
        when Proc
          value = conv[vaule]
        else
          value *= conv
        end
      else
        value /= @@conversion_map[u2][u1].to_f
      end
      u1 = u2
    end
  else
    raise CompatibilityError, "units not compatible: #{self.unit} and #{unit}"
  end
  # Second 
  return Measure.new(value, unit)
end

#saved_method_missingObject



392
# File 'lib/measure.rb', line 392

alias saved_method_missing method_missing

#to_aObject



346
347
348
# File 'lib/measure.rb', line 346

def to_a
  return [self.value, self.unit]
end

#to_sObject



342
343
344
# File 'lib/measure.rb', line 342

def to_s
  return "#{self.value} [#{self.unit}]"
end