# -*- encoding: utf-8 -*- # -*- frozen_string_literal: true -*- # -*- warn_indent: true -*- require "set" module UnitMeasurements class Unit attr_reader :name, :value, :aliases, :unit_group def initialize(name, value:, aliases:, unit_group: nil) @name = name.to_s.freeze @value = value @aliases = Set.new(aliases.sort.map(&:to_s).map(&:freeze)).freeze @unit_group = unit_group end def with(name: nil, value: nil, aliases: nil, unit_group: nil) self.class.new( (name || self.name), value: (value || self.value), aliases: (aliases || self.aliases), unit_group: (unit_group || self.unit_group) ) end def names (aliases + [name]).sort.freeze end def to_s name end def inspect aliases = "(#{@aliases.join(", ")})" if @aliases.any? "#<#{self.class.name}: #{name} #{aliases}>" end def conversion_factor return value if value.is_a?(Numeric) measurement_value, measurement_unit = parse_value(value) conversion_factor = unit_group.unit_for!(measurement_unit).conversion_factor conversion_factor * measurement_value end private SI_PREFIXES = [ ["q", %w(quecto), 1e-30], ["r", %w(ronto), 1e-27], ["y", %w(yocto), 1e-24], ["z", %w(zepto), 1e-21], ["a", %w(atto), 1e-18], ["f", %w(femto), 1e-15], ["p", %w(pico), 1e-12], ["n", %w(nano), 1e-9], ["μ", %w(micro), 1e-6], ["m", %w(milli), 1e-3], ["c", %w(centi), 1e-2], ["d", %w(deci), 1e-1], ["da", %w(deca deka), 1e+1], ["h", %w(hecto), 1e+2], ["k", %w(kilo), 1e+3], ["M", %w(mega), 1e+6], ["G", %w(giga), 1e+9], ["T", %w(tera), 1e+12], ["P", %w(peta), 1e+15], ["E", %w(exa), 1e+18], ["Z", %w(zetta), 1e+21], ["Y", %w(yotta), 1e+24], ["R", %w(ronna), 1e+27], ["Q", %w(quetta), 1e+30] ].map(&:freeze).freeze def parse_value(tokens) case tokens when String tokens = Parser.parse(value) when Array raise BaseError, "Cannot parse [number, unit] formatted tokens from #{tokens}." unless tokens.size == 2 else raise BaseError, "Value of the unit must be defined as string or array, but received #{tokens}" end [tokens[0].to_r, tokens[1].freeze] end end end