module UnitConversion class ValueUnit def self.for(value_string, unit_string = nil, formatter = nil) raise UnparseableUnitError, "value is empty" if value_string.blank? if ValueUnit === value_string return value_string end if String === value_string && unit_string.nil? value_string, unit_string = parse_single_string(value_string) end if value_string.is_a?(ParsedNumber) value = value_string else value = ParsedNumber.new(value_string) end unit = case unit_string when nil nil when ->(u) { u.blank? } nil when ParsedUnit unit_string else ParsedUnit.new(unit_string) end if unit.nil? ValueNoUnit.new(value, formatter) else ValueUnit.new(value, unit, formatter) end end def self.parse_single_string(value_unit_string) match = UNIT_PARSING_REGEX.match(value_unit_string.to_s.strip) if match && match[:value].present? return match[:value], match[:unit] else raise UnparseableUnitError, "'#{value_unit_string}' does not appear to be a valid measurement of the form (ie '5 cup' or '223 gram/cup')" end end attr_reader :formatter def initialize(value, unit, formatter = nil) @value = value @unit = unit @formatter = formatter || NumberFormatter.for(value, unit) end def to_s "#{pretty_value} #{unit}" end def unit @unit end def value @value end def raw_value @value.value end def unitwise unit.unitwise(value.value) end # Returns a new ValueUnit scaled by the given factor def scale(factor) if factor.present? parsed_factor = ParsedNumber.new(factor) ScaleConversion.new(parsed_factor).convert(self) else self end end # Returns a new ValueUnit with the given new_parsed_unit. If converting between mass and volume, # also requires a density UnitValue def convert(new_unit, density = nil) new_parsed_unit = ParsedUnit.new(new_unit) parsed_density = density ? ValueUnit.for(density) : nil if new_parsed_unit.unit != self.unit.unit ConvertConversion.new(new_parsed_unit, parsed_density).convert(self) else self end end def auto_unit AutoUnitConversion.new.convert(self) end def to_metric MetricUnitConversion.new.convert(self) end def to_standard StandardUnitConversion.new.convert(self) end def to_mass(density) parsed_density = ValueUnit.for(density) MassUnitConversion.new(parsed_density).convert(self) end def to_volume(density) parsed_density = ValueUnit.for(density) VolumeUnitConversion.new(parsed_density).convert(self) end def density? unit.density? end def volume? unit.volume? end def mass? unit.mass? end def pretty_value formatter.format(value.value) end end class ValueNoUnit < ValueUnit def initialize(value, formatter = nil) @value = value @formatter = formatter || NumberFormatter.for(value, unit) end def to_s "#{pretty_value}" end def unit nil end def unitwise raise UnknownUnitError, "No unit value provided" end def convert(new_parsed_unit, density_unit_value = nil) raise UnknownUnitError, "No unit value provided" end def density? false end def volume? false end def mass? false end end end