#--
# Units
#
# Copyright (c) 2005 Peter Vanbroekhoven & Thomas Sawyer
#
# Ruby License
#
# This module is free software. You may use, modify, and/or redistribute this
# software under the same terms as Ruby.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.
#
# ==========================================================================
# Revision History ::
# --------------------------------------------------------------------------
# 2005.10.01 peter Finished
# 2005.08.01 trans Started
# ==========================================================================
#
#++
# :title: Units
#
# This is a very extensive SI units system.
#
# == Usage
#
# Here are some examples of usage.
#
# 1.bit/s + 8.bytes/s
# (1.bit/s).to(byte/s)
# 1.mile.to(feet)
# 1.acre.to(yd**2)
# 1.acre.to(sq_yd)
# 1.gallon.to(self.L)
# 1.lb.to(kg)
# 1.m.s.to(m.s)
# 1.sq_mi.to(km**2)
# 1.mile.to(km)
# 1.usd.to(twd)
#
# == Author(s)
#
# * Peter Vanbroekhoven
# * Thomas Sawyer
#
require 'rbconfig'
require 'soap/wsdlDriver'
require 'yaml'
# The namespace for all unit related classes. Mixing this in has the additional effect
# of making Units.with_unit_converter available without the Units.
prefix,
# as well as the shortcuts for creating Units (see Units#method_missing).
module Units
def method_missing(m, *args, &blk)
if args.length == 1
args[0] = Units::Converter.converter(args[0]) if not args[0].is_a? Units::Converter
return Units::Unit.new({m => 1}, args[0]) if args[0].registered?(m)
elsif Units::Converter.current.registered?(m)
raise ArgumentError, "Wrong number of arguments" if args.length != 0
return Units::Unit.new({m => 1}, Units::Converter.current)
end
super
end
# Executes the block with the current Converter changed to
# the given Converter. This allows to temporarily change the
# Converter used by default.
#
# Example:
#
# with_unit_converter(:uk) {
# puts 1.cwt.to(lb) # => 112.0 lb
# }
#
# with_unit_converter(:us) {
# puts 1.cwt.to(lb) # => 100.0 lb
# }
#
# See also Converter.current.
def with_unit_converter(name, &blk) # :yields:
Units::Converter.with_converter(name, &blk)
end
module_function :with_unit_converter
# This class represents a Unit. A Unit uses a given Converter with
# a number of registered Units in which it can be expressed.
# A Unit is the product of the powers of other Units. In principle, these
# need not be integer powers, but this may cause problems with rounding.
# The following code for example returns +false+:
# Unit.new(:m => 0.1) * Unit.new(:m => 0.2) == Unit.new(:m => 0.3)
#
# Units can be multiplied and divided, or raised to a given power.
# This can only be done when both units use the same Converter.
# As an extra, 1 can be divided by a Unit.
#
# Examples:
#
# Unit.new(:mi => 1, :s => -1) ** 2 # => mi**2/s**2
# Unit.new(:mi => 1, :s => -1) * Unit.new(:s => 1, :usd => -1) # => mi/usd
# Unit.new(:mi => 1, :s => -1, Converter.converter(:uk)) *
# Unit.new(:s => 1, :usd => -1, Converter.converter(:us)) # => TypeError
# 1 / Unit.new(:mi => 1, :s => -1) # => s/mi
class Unit
attr_reader :units, :converter
# Creates a new (composite) Unit.
# It is passed a hash of the form {:unit => exponent}
, and the
# Converter to use.
#
# Examples:
#
# Unit.new(:m => 1, :s => -1, Converter.converter(:uk)) # => m/s
# Unit.new(:mi => 1, :s => -2) # => mi/s**2
#
# See also Converter, Converter.converter
def initialize(units = {}, converter = Units::Converter.current)
@units, @converter = {}, converter
units.each_pair { |k, v| @units[k.to_sym] = v }
check_units
end
# Raises to the given power.
def **(p)
result = {}
@units.each_pair do |u, e|
result[u] = e * p
end
Units::Unit.new(result, @converter)
end
# Multiplies with the given Unit.
def *(other)
do_op(:*, :+, other)
end
# Divides by the given Unit.
def /(other)
do_op(:/, :-, other)
end
# Returns +true+ iff this Unit has all exponents equal to 0.
def unitless?
@units.empty?
end
def coerce(other) # :nodoc:
return [self, Units::Unit.new({}, converter)] if other == 1
raise TypeError, "incompatible converters" if converter != other.converter
converter.coerce_units(self, other)
end
# Returns +true+ iff the two Units are equals, i.e., iff they have
# the same exponent for all units, and they both use the same Converter.
def ==(other)
other.is_a?(Units::Unit) && other.units == units && other.converter == converter
end
# Returns +true+ iff this Unit is compatible with the given
# Unit. This is less strict than equality because for example hours are
# compatible with seconds ("we can add them"), but hours are not seconds.
def compatible_with?(other)
return false if converter != other.converter
conv1, conv2 = converter.coerce_units(self, other)
conv1.units == conv2.units
end
# Returns a human readable string representation.
def to_s
numerator = ""
denominator = ""
@units.each_pair do |u, e|
(e > 0 ? numerator : denominator) << " #{u.to_s}#{"**#{e.abs}" if e.abs != 1}"
end
"#{numerator.lstrip}#{"/" + denominator.lstrip if not denominator.empty?}"
end
alias inspect to_s
def method_missing(m, *args, &blk)
if converter.registered?(m)
raise ArgumentError, "Wrong number of arguments" if args.length != 0
return Units::Unit.new({m => 1}, converter) * self
end
super
end
private
def check_units
@units.keys.each do |unit|
raise ArgumentError, "unit #{unit.to_s.dump} not registered" if not @converter.registered? unit
@units.delete(unit) if @units[unit] == 0
end
end
def do_op(op, dual_op, other)
raise TypeError, "incompatible converters for #{op}" if converter != other.converter
result = @units.dup
other.units.each_pair do |u, e|
result[u] = 0 if not result[u]
result[u] = result[u].send(dual_op, e)
end
Units::Unit.new(result, @converter)
end
end
# This class represents a Value with a numeric value and a Unit.
# The numeric value can be any Numeric, though it is not recommended
# to use Values.
#
# A Value can be added to, subtracted from and multiplied with another value,
# though only when both Values are using the same Converter. While multiplication
# is always possible, adding or subtracting values with incompatible Units
# results in a TypeError. When two Units are compatible but not the same,
# the Value with the larger of the Units is converted to the smaller of the Units.
# For example adding 100 seconds and 1 minute, the latter is converted to 60 seconds
# because a second is smaller than a minute. The result is 160 seconds.
class Value < Numeric
# The numeric value of this Value.
attr_reader :value
# The Unit of this value.
attr_reader :unit
# Creates a new Value with the given numeric value and the given unit.
# Simply returns the given value if the given unit is valueless,
# i.e., when unit.unitless?
is true.
def self.new(value, unit)
return value if unit.unitless?
super
end
def initialize(value, unit) # :nodoc:
@value, @unit = value, unit
end
# Returns the Converter used by this Value's Unit.
# Equivalent to unit.converter
.
def converter
unit.converter
end
def *(other) # :nodoc:
do_multiplicative_op(:*, other)
end
def /(other) # :nodoc:
do_multiplicative_op(:/, other)
end
def +(other) # :nodoc:
do_additive_op(:+, other)
end
def -(other) # :nodoc:
do_additive_op(:-, other)
end
def ==(other) # :nodoc:
(self - other).value == 0.0
end
# Converts this Value to the given Unit.
# This only works if the Converters used by this Value's Unit
# and the given Unit are the same. It obviously fails if the
# Units are not compatible (can't add apples and oranges).
def to(to_unit)
conv1, conv2 = unit.coerce(to_unit)
raise TypeError, "incompatible units for operation" if conv1.units != conv2.units
mult = conv1.multiplier / conv2.multiplier
Value.new(value * mult, to_unit)
end
def coerce(other) # :nodoc:
[self, Value.new(other, Units::Unit.new({}, @unit.converter))]
end
# Returns a human readable string representation.
def to_s
"#{@value} #{@unit}"
end
alias inspect to_s
def method_missing(m, *args, &blk)
if self.converter.registered?(m)
raise ArgumentError, "Wrong number of arguments" if args.length != 0
return self * Value.new(1, Units::Unit.new({m => 1}, self.converter))
end
super
end
private
def do_additive_op(op, other)
if other.is_a? Value
conv1, conv2 = unit.coerce(other.unit)
raise TypeError, "incompatible units for #{op}" if conv1.units != conv2.units
mult = conv2.multiplier / conv1.multiplier
if mult > 1
Value.new(value.send(op, other.value * mult), unit)
else
Value.new(other.value.send(op, value * mult), other.unit)
end
else
raise TypeError, "incompatible operands for #{op}"
end
end
def do_multiplicative_op(op, other)
if other.is_a? Value
Value.new(value.send(op, other.value), unit.send(op, other.unit))
elsif other.is_a? Units::Unit
Value.new(value, unit.send(op, other))
else
Value.new(value.send(op, other), unit)
end
end
end
# This class handles all conversions between Units.
#
# There are two kinds of Units; those that are not expressed as a function
# of other Units --called base units-- and those that are expressed
# as a function of other Units --called derived units. The former kind is
# registered without specifying how it depends on other Units, while the latter
# kind is registered with doing so.
#
# This class also registers a list of Converters that are generally useable.
# The default Converter which is used when none is specified, can be retrieved
# with Converter.current. Converters can be registered with Converter.register.
#
# Converters can be loaded from YAML. This allows Converters to be specified in
# configuration files.
class Converter
# Encapsulates a service for retrieving exchange rates.
# This is used by Converter.register_currency.
# An instance of this class behaves like a Numeric when used
# in calculations. This class is used to look up exchange rates
# lazily.
#
# This class is not supposed to be instantiated by itself. Instead a
# subclass should be created that defines the method +get_rate+.
# The only subclass provided is currently XMethods.
#
# To be found automatically from YAML files, exchange services should
# be located under Units::Converter::ExchangeRate.
class ExchangeRate
def self.create_conversion(curr, converter) # :nodoc:
{:unit => Units::Unit.new({%s{--base-currency--} => 1}, converter), :multiplier => self.new(curr)}
end
def initialize(curr) # :nodoc:
@curr = curr
end
# This method should be overridden in subclasses to return the
# exchange rate represented by this object. The unit in question
# is available as a String in the instance variable @curr
.
# The rate should be calculated against an arbitrary but fixed base currency.
# The rate should be such that the following would be true
# 1 * curr = rate * base_curr
# This function should return +nil+ if the currency is not supported by this
# retrieval service. It should not raise an exception.
def get_rate
raise NoMethodError, "undefined method `get_rate' for #{to_s}:#{self.class}"
end
def value # :nodoc:
@value ||= get_rate or raise TypeError, "currency not supported by current service: #{@curr.to_s.dump}"
end
def coerce(other) # :nodoc:
[value, other]
end
def *(other) # :nodoc:
value * other
end
def /(other) # :nodoc:
value / other
end
def +(other) # :nodoc:
value + other
end
def -(other) # :nodoc:
value - other
end
def **(other) # :nodoc:
value ** other
end
# Exchange rate retrieval service that uses a service from http://www.xmethods.com.
# See http://rubyurl.com/7uq.
class XMethods < ExchangeRate
# This is the only method that a subclass of ExchangeRate
# needs to implement. This is a good example to follow.
def get_rate
driver.getRate(country_mapping[@curr], country_mapping[base])
rescue
nil
end
private
def data
@@data ||= YAML.load_file(File.join(Units::Config::DATADIR, 'xmethods', 'mapping.yaml'))
end
def country_mapping
@@country_mapping ||= data[:mapping]
end
def base
@@base ||= data[:base]
end
def driver
@@driver ||= SOAP::WSDLDriverFactory.new("http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl").create_rpc_driver #.createDriver
end
end
# Cached values for the XMethods exchange rate service.
class CachedXMethods < ExchangeRate
# This is the only method that a subclass of ExchangeRate
# needs to implement. This is a good example to follow.
def get_rate
data[@curr]
end
private
def data
@@data ||= YAML.load_file(File.join(Units::Config::DATADIR, 'xmethods', 'cached.yaml'))
end
end
end
Conversion = Struct.new(:units, :multiplier) # :nodoc:
class Conversion # :nodoc:
def **(p)
Conversion.new(units ** p, multiplier ** p)
end
def *(m)
Conversion.new(units * m.units, multiplier * m.multiplier)
end
def /(m)
Conversion.new(units / m.units, multiplier / m.multiplier)
end
end
# The prefixes used for SI units. See also Converter#register_si_unit.
SI_PREFIXES = {
'yotta' => {:abbrev => 'Y', :multiplier => 1e24},
'zetta' => {:abbrev => 'Z', :multiplier => 1e21},
'exa' => {:abbrev => 'E', :multiplier => 1e18},
'peta' => {:abbrev => 'P', :multiplier => 1e14},
'tera' => {:abbrev => 'T', :multiplier => 1e12},
'giga' => {:abbrev => 'G', :multiplier => 1e9},
'mega' => {:abbrev => 'M', :multiplier => 1e6},
'kilo' => {:abbrev => 'k', :multiplier => 1e3},
'hecto' => {:abbrev => 'h', :multiplier => 1e2},
'deca' => {:abbrev => 'da', :multiplier => 1e1},
'deci' => {:abbrev => 'd', :multiplier => 1e-1},
'centi' => {:abbrev => 'c', :multiplier => 1e-2},
'milli' => {:abbrev => 'm', :multiplier => 1e-3},
'micro' => {:abbrev => 'u', :multiplier => 1e-6},
'nano' => {:abbrev => 'n', :multiplier => 1e-9},
'pico' => {:abbrev => 'p', :multiplier => 1e-12},
'femto' => {:abbrev => 'f', :multiplier => 1e-15},
'atto' => {:abbrev => 'a', :multiplier => 1e-18},
'zepto' => {:abbrev => 'z', :multiplier => 1e-21},
'yocto' => {:abbrev => 'y', :multiplier => 1e-24}
}
# The prefixes used for binary units. See also Converter#register_binary_unit.
BINARY_PREFIXES = {
'yotta' => {:abbrev => 'Y', :multiplier => 1024.0 ** 8},
'zetta' => {:abbrev => 'Z', :multiplier => 1024.0 ** 7},
'exa' => {:abbrev => 'E', :multiplier => 1024.0 ** 6},
'peta' => {:abbrev => 'P', :multiplier => 1024.0 ** 5},
'tera' => {:abbrev => 'T', :multiplier => 1024.0 ** 4},
'giga' => {:abbrev => 'G', :multiplier => 1024.0 ** 3},
'mega' => {:abbrev => 'M', :multiplier => 1024.0 ** 2},
'kilo' => {:abbrev => 'k', :multiplier => 1024.0},
}
# The prefixes used for length units. See also Converter#register_length_unit.
LENGTH_PREFIXES = {
'square_' => {:abbrev => 'sq_', :power => 2},
'cubic_' => {:abbrev => 'cu_', :power => 3}
}
# Creates a new Converter, where the data is loaded from the
# file with the given file name.
def self.from_yaml(file)
Units::Converter.new {
load_yaml(file)
}
end
# Creates a new Converter. If a block is given,
# it is executed in the newly created Converter's context.
def initialize(&blk)
@loaded_yaml = []
@conversions = {}
instance_eval(&blk) if blk
end
# Checks whether the unit with the given name is registered.
# The name can be a symbol or a string.
def registered?(unit)
unit = unit.to_sym
conversions(unit) != nil
end
# Returns the list of registered unit names as symbols.
def registered_units
@conversions.keys
end
# Registers a new Unit with the given name. The +data+ parameter
# is a Hash with some extra parameters (can be Strings or Symbols):
# +alias+:: Specifies possible aliases for the Unit.
# +abbrev+:: Specifies possible abbreviations or symbols for the Unit.
# The differences with aliases is that prefixes work differently;
# see +register_si_unit+ and +register_binary_unit+.
# +equals+:: If present, specifies how the Unit depends on other Units.
# The value for this key can either be a Hash with +unit+ mapping to
# a Unit and +multiplier+ mapping to a numeric multiplier, or
# a String containing the multiplier, followed by a space, followed by
# a representation of the Unit as returned by Unit#to_s.
#
# Examples:
#
# converter.register_unit(:pint, :alias => :pints, :abbrev => [:pt, :pts]))
# converter.register_unit(:quart, 'alias' => :quarts, :abbrev => ['qt', :qts], :equals => '2.0 pt'))
# converter.register_unit(:gallon, :alias => :gallons, :abbrev => :gal, 'equals' => {:unit => Unit.new('qt' => 1, converter), 'multiplier' => 4.0))
#
# Note that Symbols and Strings are generally exchangeable within this
# library (internally they are converted to Symbols). The number one reason
# for this is that String look better in YAML.
#
# See also +register_si_unit+, +register_binary_unit+, +register_length_unit+
# and +register_currency+.
def register_unit(name, data = {})
unit, aliases, abbrevs = extract_data(name, data, :to_sym)
conversion = data[:equals]
conversion = decode_conversion(conversion) if conversion
conversion = convert_conversion(conversion[:unit].units, conversion[:multiplier]) if conversion
register_unit_internal(unit, conversion)
conversion = convert_conversion({unit => 1}, 1) if not conversion
(aliases + abbrevs).each do |u|
register_unit_internal(u, conversion)
end
end
# Registers a new SI unit. The Unit and its aliases are registered with
# all available SI prefixes (see SI_PREFIXES) as well, while the
# abbreviations are registered with the abbreviated version of the
# prefixes.
#
# For the syntax of the options, see +register_unit+.
def register_si_unit(unit, data = {})
register_prefixed_unit(unit, SI_PREFIXES, data)
end
# Registers a new binary unit. The Unit and its aliases are registered with
# all available binary prefixes (see BINARY_PREFIXES) as well, while the
# abbreviations are registered with the abbreviated version of the
# prefixes.
#
# For the syntax of the options, see +register_unit+.
def register_binary_unit(unit, data = {})
register_prefixed_unit(unit, BINARY_PREFIXES, data)
end
# Registers a new length unit. The Unit and its aliases are registered with
# all available length prefixes (see LENGTH_PREFIXES) as well, while the
# abbreviations are registered with the abbreviated version of the
# prefixes.
#
# For the syntax of the options, see +register_unit+.
def register_length_unit(unit, data = {})
register_prefixed_unit(unit, LENGTH_PREFIXES, data)
end
# Registers a given currency, with a given servive to find the exchange
# rate between the given currency and a given base currency. The service
# should be a subclass of ExchangeRate. This is not strictly necessary,
# but ExchangeRate handles all of the magic.
def register_currency(curr, service = nil)
service ||= Units::Config::DEFAULT_CURRENCY_SERVICE
register_unit(curr, :equals => service.create_conversion(curr, self))
end
def coerce_units(unit1, unit2) # :nodoc:
[convert_conversion(unit1.units), convert_conversion(unit2.units)]
end
def method_missing(m, *args, &blk)
if registered?(m)
raise ArgumentError, "Wrong number of arguments" if args.length != 0
return Units::Unit.new({m => 1}, self)
end
super
end
# Loads data from the YAML file with the given name.
# Returns +self+.
def load_yaml(file)
return if @loaded_yaml.include? file
data = YAML.load_file(file)
@loaded_yaml << file
old_service = Thread.current['current_currency_exchange_service']
data.each do |r|
rule = {}
r.each_pair do |k, v|
rule[k.to_sym] = v
end
case rule[:type]
when 'import'
load_yaml(File.join(Units::Config::DATADIR, rule[:file] + '.yaml'))
when 'si'
register_si_unit(rule[:name], rule)
when 'unit'
register_unit(rule[:name], rule)
when 'length'
register_length_unit(rule[:name], rule)
when 'binary'
register_binary_unit(rule[:name], rule)
when 'currency'
register_currency(rule[:name], Thread.current['current_currency_exchange_service'])
when 'service'
begin
Thread.current['current_currency_exchange_service'] = Units::Converter::ExchangeRate.const_get(rule[:name])
rescue NameError
raise NameError, "Exchange service not found: #{rule[:name].to_s.dump}"
end
else
raise "unknown rule type #{rule[:type].to_s.dump}"
end
end
Thread.current['current_currency_exchange_service'] = old_service
self
end
# Returns the current Converter in the current Thread.
# The default converter is the one returned by converter(:default)
.
# See also Units#with_converter and Converter.converter.
def self.current
Thread.current["current_unit_converter"] ||= converter(:default)
end
def self.with_converter(conv) # :nodoc:
old_conv = Thread.current["current_unit_converter"]
if conv.is_a? Units::Converter
Thread.current["current_unit_converter"] = conv
else
Thread.current["current_unit_converter"] = converter(conv)
end
yield
ensure
Thread.current["current_unit_converter"] = old_conv
end
# Returns the converter with the given name.
# This name can be a Symbol or a String.
def self.converter(name)
converters[name.to_sym] or raise ArgumentError, "No converter #{name.to_s.dump} found"
end
# Registers the given Converter under the given name.
# This name can be a Symbol or a String.
def self.register(name, converter)
converters[name.to_sym] = converter
end
# Returns the list of names of registered converters.
def self.registered_converters
converters.keys
end
private
def clean_eval(some_name_noone_uses)
eval some_name_noone_uses, binding, __FILE__, __LINE__
end
def register_unit_internal(unit, conversion)
raise "unit #{unit.to_s.dump} already registered with #{self}" if registered? unit
@conversions[unit] = conversion || :none
end
def register_prefixed_unit(unit, prefixes, data = {})
unit, aliases, abbrevs = extract_data(unit, data, :to_s)
register_unit(unit, :equals => data[:equals], :alias => aliases, :abbrev => abbrevs)
unit_sym = unit.to_sym
prefixes.each_pair do |pre,info|
abbrev = info[:abbrev]
multiplier = info[:multiplier] || 1
power = info[:power] || 1
register_unit(pre + unit, :equals => {:unit => Units::Unit.new({unit_sym => power}, self), :multiplier => multiplier})
aliases.each do |a|
register_unit(pre + a, :equals => {:unit => Units::Unit.new({unit_sym => power}, self), :multiplier => multiplier})
end
abbrevs.each do |a|
register_unit(abbrev + a, :equals => {:unit => Units::Unit.new({unit_sym => power}, self), :multiplier => multiplier})
end
end
end
def extract_data(unit, data, conv)
sarray = proc do |k|
list = data[k] || []
list = [list] if not list.is_a? Array
list.map { |a| a.send(conv) }
end
unit = unit.send(conv)
return unit, sarray[:alias], sarray[:abbrev].select { |a| a != unit }
end
def decode_conversion(data)
if not data.is_a? String
return {:unit => data[:unit] || data['unit'],
:multiplier => data[:multiplier] || data['multiplier']}
end
data.rstrip!
if m = /^(\d+(\.\d+(?:[eE][-+]?\d+)?)?(?:\s+|$))?(.+)?$/.match(data)
unit = m[3] ? clean_eval(m[3].gsub(/\b([A-Z]\w*|in)\b/, 'self.\1')) : Units::Unit.new({}, self)
if m[1]
multiplier = m[2] ? Float(m[1]) : Integer(m[1])
{:unit => unit, :multiplier => multiplier}
else
{:unit => unit}
end
else
raise ArgumentError, "Wrong unit string"
end
end
def convert_conversion(units, multiplier = nil)
multiplier ||= 1
base_units = {}
other_units = {}
units.each_pair do |u, e|
(conversions(u) != :none ? other_units : base_units)[u] = e
end
result = Conversion.new(Units::Unit.new(base_units, self), multiplier)
other_units.each_pair do |u, e|
result *= (conversions(u) ** e)
end
result
end
def conversions(unit)
@conversions[unit] || (unit == %s{--base-currency--} ? :none : nil)
end
def self.converters
@converters ||= {}
end
end
# Contains some configuration related constants
module Config
# The directory in which the data files are searched for
DATADIR = File.join( ::Config::CONFIG['datadir'], 'facets', 'units' )
# The standard service used for looking up currency exchange rates
DEFAULT_CURRENCY_SERVICE = Units::Converter::ExchangeRate::XMethods
end
#-- Initialization after everything is set up
class Converter
standard_converters = YAML.load_file(File.join(Units::Config::DATADIR, 'standard.yaml'))
standard_converters.each do |name|
register(name, Units::Converter.from_yaml(File.join(Units::Config::DATADIR, name + ".yaml")))
end
end
end
class Numeric
def method_missing(m, *args, &blk)
if args.length == 1
args[0] = Units::Converter.converter(args[0]) if not args[0].is_a? Units::Converter
return Value.new(self, Units::Unit.new({m => 1}, args[0])) if args[0].registered?(m)
elsif Units::Converter.current.registered?(m)
raise ArgumentError, "Wrong number of arguments" if args.length != 0
return Units::Value.new(self, Units::Unit.new({m => 1}, Units::Converter.current))
end
super
end
end
# _____ _
# |_ _|__ ___| |_
# | |/ _ \/ __| __|
# | | __/\__ \ |_
# |_|\___||___/\__|
#
=begin testing
require 'test/unit'
include Units
class TC01 < Test::Unit::TestCase
def test_01_001
assert_equal( "65.0 bit/s", (1.bit/s + 8.bytes/s).to_s )
end
def test_01_002
assert_equal( "0.125 byte/s", ((1.bit/s).to(byte/s)).to_s )
end
def test_01_003
assert_equal( "5280.0 feet", (1.mile.to(feet)).to_s )
end
def test_01_004
assert_equal( "4840.0 yd**2", (1.acre.to(yd**2)).to_s )
end
def test_01_005
assert_equal( "4840.0 sq_yd", (1.acre.to(sq_yd)).to_s )
end
def test_01_006
assert_equal( "3.785411784 L", (1.gallon.to(self.L)).to_s )
end
def test_01_007
assert_equal( "0.45359237 kg", (1.lb.to(kg)).to_s )
end
def test_01_008
assert_equal( "1 s m", (1.m.s.to(m.s)).to_s )
end
def test_01_009
assert_equal( "2.589988110336 km**2", (1.sq_mi.to(km**2)).to_s )
end
def test_01_010
assert_equal( "1.609344 km", (1.mile.to(km)).to_s )
end
# currency rates change frequently, temp commented
#def test_01_011
# assert_equal( "33.2750003803837 twd", (1.usd.to(twd)).to_s )
#end
def test_01_012
with_unit_converter(:uk) {
assert_equal( "112.0 lb", 1.cwt.to(lb).to_s )
}
end
def test_01_013
with_unit_converter(:us) {
assert_equal( "100.0 lb", 1.cwt.to(lb).to_s )
}
end
def test_01_014
assert_equal( "112.0 lb", (1.cwt(:uk).to(lb(:uk))).to_s )
assert_equal( "100.0 lb", (1.cwt(:us).to(lb(:us))).to_s )
end
def test_01_015
assert_equal( "lb", (Converter.current.lb).to_s )
end
def test_01_016
assert_equal( [:us, :cex, :default, :uk], Converter.registered_converters )
end
def test_01_017
assert_raises(TypeError) {
1.try.to(usd)
}
end
def test_01_016
assert_equal( "33.2190007563597 twd", (1.usd(:cex).to(twd(:cex))).to_s )
end
end
=end