# -*- encoding: utf-8 -*- # -*- frozen_string_literal: true -*- # -*- warn_indent: true -*- module UnitMeasurements # The +UnitMeasurements::UnitGroup+ class provides a collection of units with # methods to retrieve units by name, check if a unit is defined, and much more. # # It serves as a container for organizing and working with units within the # unit group. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 class UnitGroup # The primitive unit of the unit group. # # @example # UnitMeasurements::Length.primitive # => #<UnitMeasurements::Unit: m (meter, meters, metre, metres)> # # @return [Unit] The primitive unit of the unit group. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 attr_reader :primitive # An array of units within the unit group. # # @example # UnitMeasurements::Length.units # => [#<UnitMeasurements::Unit: m (meter, meters, metre, metres)>, ...] # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 attr_reader :units # Initializes a new +UnitGroup+ instance. # # @param [String|Symbol, optional] primitive The name of the primitive unit. # @param [Array<Unit>] units An array of +Unit+ instances. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def initialize(primitive, units) @units = units.map { |unit| unit.with(unit_group: self) } @primitive = unit_for!(primitive) if primitive end # Returns the unit instance for a given unit name. It returns +nil+ if unit # is not defined within the unit group. # # @example # UnitMeasurements::Length.unit_for("m") # => #<UnitMeasurements::Unit: m (meter, meters, metre, metres)> # # UnitMeasurements::Length.unit_for("z") # => nil # # @param [String|Symbol] name The name of the unit. # # @return [Unit|NilClass] A +Unit+ instance or +nil+ if the unit is not found. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_for(name) unit_name_to_unit(name) end # This method works same as +unit_for+ but it raises +UnitError+ if the # unit is not defined within the unit group. # # @example # UnitMeasurements::Length.unit_for!("m") # => #<UnitMeasurements::Unit: m (meter, meters, metre, metres)> # # UnitMeasurements::Length.unit_for!("z") # => Invalid unit: 'z'. (UnitMeasurements::UnitError) # # @param [String|Symbol] name The name of the unit to look for. # # @return [Unit] A +Unit+ instance. # # @raise [UnitError] If the unit is not found. # # @see #unit_for # @see UnitError # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_for!(name) unit = unit_for(name) raise UnitError, name unless unit unit end alias_method :[], :unit_for! # Returns an array of names of all the units defined within the unit group, # sorted alphabetically. # # @example # UnitMeasurements::Length.unit_names # => ["ft", "in", "m", "mi", "yd"] # # @return [Array<String>] An array of unit names. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_names units.map(&:name).sort end # Returns an array of names and aliases of all the units defined within the # unit group, sorted alphabetically. # # @example # UnitMeasurements::Length.unit_names_with_aliases # => ["\"", "'", "feet", "foot", "ft", "in", "inch", "inches", "m", "meter", "meters", "metre", "metres", "mi", "mile", "miles", "yard", "yards", "yd"] # # @return [Array<String>] An array of unit names and aliases. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_names_with_aliases units.flat_map(&:names).sort end # Checks if a unit with a given name is defined within the unit group. # # @example # UnitMeasurements::Length.defined?("m") # => true # # UnitMeasurements::Length.defined?("metre") # => false # # @param [String|Symbol] name The name of the unit to look for. # # @return [TrueClass|FalseClass] +true+ # if the unit is defined, +false+ otherwise. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def defined?(name) unit = unit_for(name) unit ? unit.name == name.to_s : false end # Checks if a given name corresponds to a defined unit or an alias of any # defined unit. # # @example # UnitMeasurements::Length.unit_or_alias?("m") # => true # # UnitMeasurements::Length.unit_or_alias?("metre") # => true # # @param [String|Symbol] name The name or alias of the unit to look for. # # @return [TrueClass|FalseClass] +true+ # if the name corresponds to a unit or alias, +false+ otherwise. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_or_alias?(name) !!unit_for(name) end private # @private # Returns a hash where keys are unit names (including aliases) and values # are corresponding +Unit+ instances. # # @example # UnitMeasurements::Length.unit_with_name_and_aliases # => {"m"=>#<UnitMeasurements::Unit: m (meter, meters, metre, metres)>, ...} # # @return [Hash] A hash containing unit +names+ as keys and +Unit+ objects as values. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_with_name_and_aliases units.each_with_object({}) do |unit, hash| unit.names.each { |name| hash[name.to_s] = unit } end end # @private # Returns the +Unit+ instance for a given unit name. # # @param [String|Symbol] name The name of the unit. # # @return [Unit|NilClass] A +Unit+ instance or +nil+ if the unit is not found. # # @author {Harshal V. Ladhe}[https://shivam091.github.io/] # @since 1.0.0 def unit_name_to_unit(name) unit_with_name_and_aliases[name.to_s] end end end