module UnitConversion UNIT_PARSING_REGEX = /^(?(?:-?[0-9]+)?(?:\.?[0-9]*)?)\s*(?[a-z\/.\-()0-9]+)$/ UNIT_ALIASES = { cup: %w(cups c), tablespoon: %w(tbsp tbs tablespoons), teaspoon: %w(tsp teaspoons), ounce: %w(oz ounces), pound: %w(pounds lb lbs), pint: %w(pints), quart: %w(quarts), gallon: %w(gallons ga), gram: %w(grams g), kilogram: %w(kilograms kg) } UNIT_ALIAS_MAP = Hash[UNIT_ALIASES.map { |unit, values| values.map { |v| [v, unit] } }.flatten(1)] class UnparseableUnitError < StandardError end class UnknownUnitError < UnparseableUnitError end class << self def parse(unit_string) match = UNIT_PARSING_REGEX.match(unit_string.to_s.strip) if match && match[:value].present? && match[:unit].present? begin Unitwise(match[:value].to_f, match[:unit]) rescue Unitwise::ExpressionError => err raise UnknownUnitError, err.message end else raise UnparseableUnitError, "'#{unit_string}' does not appear to be a valid measurement of the form (ie '5 cup' or '223 gram/cup')" end end def density?(unit) unit.compatible_with? Unitwise(1, 'g/ml') end def normalize_unit_names(unit_description) result = unit_description.dup result = result.downcase.gsub(/[a-z]+/) do |match| UNIT_ALIAS_MAP[match] || match end result end end end