module UnitConversion class Conversion end class ScaleConversion < Conversion def initialize(parsed_factor) @factor = parsed_factor end def convert(value_unit) value = @factor.value * value_unit.value.value ValueUnit.for(value, value_unit.unit, value_unit.formatter) end end class ConvertConversion < Conversion def initialize(parsed_unit, density_unit_value = nil) @target_unit = parsed_unit raise UnknownUnitError, "#{density_unit_value} is not a density" if density_unit_value && !density_unit_value.density? @density = density_unit_value end def convert(value_unit) input = value_unit.unitwise if value_unit.volume? && @target_unit.mass? raise MissingDensityError, "Cannot convert #{value_unit.unit} to #{@target_unit} without density" unless @density input = input * @density.unitwise elsif value_unit.mass? && @target_unit.volume? raise MissingDensityError, "Cannot convert #{value_unit.unit} to #{@target_unit} without density" unless @density input = input / @density.unitwise end input = input.convert_to @target_unit.unit formatter = @target_unit.metric? ? DecimalFormatter.new : value_unit.formatter ValueUnit.for(input.value, @target_unit, formatter) end end class AutoUnitConversion < Conversion def convert(value_unit) unless known_auto_unit?(value_unit.unit) return value_unit end value = value_unit.raw_value unit = value_unit.unit.unit new_unit = unit unit_orders = UNIT_ORDERS.values.detect { |orders| orders.include?(unit.to_sym) } unit_range = UNIT_RANGES[unit.to_sym] if value < unit_range.first unit_orders = unit_orders.reverse end idx = unit_orders.index(new_unit.to_sym) while !unit_range.include?(value) && idx < (unit_orders.length - 1) idx += 1 next_unit = unit_orders[idx] value = Unitwise(value, new_unit).convert_to(next_unit).value new_unit = next_unit.to_s unit_range = UNIT_RANGES[new_unit.to_sym] end ValueUnit.for(value, new_unit, value_unit.formatter) end def known_auto_unit?(unit) unit && UNIT_ORDERS.values.flatten.include?(unit.unit.to_sym) end end end