Elex.Units.Catalog (Elex v0.3.0)

View Source

Caller-registered unit categories.

Elex does not ship units. A catalog is what the caller registers with add_category/3 and add_unit/3 (optional conversion and aliases:). Omitting conversion registers "value". Optional aliases: are extra input spellings of a canonical unit. They must be symbol-pattern names, unique catalog-wide, and are not valid as default::

Catalog.add_unit(catalog, :area, "m^2", aliases: ["m2", "sqm"])

Derived add_category may take identity: (a unit formula of the base-hub product) so a named hub such as "N" can name that formula. A matching identity unit must still be registered. identity: is rejected on base categories.

add_unit/4 takes an explicit conversion string; aliases: defaults to []. The conversion must be a string (value / 100), not a charlist. A formula-shaped unit name whose components are already registered must use a matching scale (cm at value / 100 means "cm^2" is value / 10000). identity: names the base-hub formula; a matching unit must still be registered (N plus "kg * m | s^2", ha plus "m^2"). add_category!/3 and add_unit!/3 raise ArgumentError instead of returning {:error, reason}.

Summary

Functions

Registers a base or derived category.

Same as add_category/3, but returns the catalog or raises ArgumentError.

Registers a unit with an invertible conversion to the category's conversion-default.

Same as add_unit/3 (optional conversion and aliases:), but returns the catalog or raises ArgumentError.

Resolves a registered unit name or alias to the canonical unit name.

Returns a map of registered category names to their conversion-default unit symbols.

Looks up the category whose dimension vector matches dim.

Looks up the category atom for a registered unit symbol or alias.

Looks up a category's formula as a dimension.

Looks up a registered unit entry by canonical name or alias.

Returns whether name is a base or derived category.

Returns an empty catalog.

Parses a unit formula against this catalog.

Returns :ok when every category default: hub is registered on that category and every derived category has a base-hub identity (default: name parses to the identity monomial, or a matching unit name). identity: does not substitute for that unit.

Types

category()

@type category() :: %{
  :default => String.t(),
  :additive => boolean(),
  :units => %{optional(String.t()) => unit()},
  optional(:formula) => String.t(),
  optional(:dim) => %{optional(atom()) => integer()},
  optional(:identity) => String.t()
}

t()

@type t() :: %Elex.Units.Catalog{categories: %{optional(atom()) => category()}}

unit()

@type unit() :: %{
  :to_default => String.t(),
  :to_default_ast => term(),
  :from_default_ast => term(),
  optional(:aliases) => [String.t()]
}

Functions

add_category(catalog, name, opts)

@spec add_category(t(), atom(), keyword()) :: {:ok, t()} | {:error, String.t()}

Registers a base or derived category.

Base and derived categories take default: — the conversion-hub unit (must be registered on that category before put_units/2). Derived categories also take formula: over base category names, and optional identity: — a unit formula of the base-hub product. A matching identity unit must still be registered before put_units/2. identity: is rejected on base categories. default_result_unit: is rejected; use default:. additive: defaults to true; offset conversions require additive: false.

Returns

  • {:ok, catalog} - Updated catalog
  • {:error, String.t()} - Registration failed

add_category!(catalog, name, opts)

@spec add_category!(t(), atom(), keyword()) :: t()

Same as add_category/3, but returns the catalog or raises ArgumentError.

add_unit(catalog, category, name, to_default \\ "value", opts \\ [])

@spec add_unit(t(), atom(), String.t(), String.t(), keyword()) ::
  {:ok, t()} | {:error, String.t()}

Registers a unit with an invertible conversion to the category's conversion-default.

The conversion is an Elex expression in value (a string, not a charlist). Omitting it (or passing only aliases:) registers "value". The inverse is derived with Elex.Inverter. A formula unit may not place the same category in both the numerator and the denominator (N * s | s, N * s | hour). A formula-shaped name whose components are already registered must match their combined scale.

aliases: are optional input-only symbol names for this canonical unit. They must match ^[A-Za-z][A-Za-z0-9_]*$ and be unique catalog-wide against unit names and other aliases. default: must be a canonical unit name, not an alias.

add_unit!(catalog, category, name, to_default \\ "value", opts \\ [])

@spec add_unit!(t(), atom(), String.t(), String.t(), keyword()) :: t()

Same as add_unit/3 (optional conversion and aliases:), but returns the catalog or raises ArgumentError.

canonical_name(catalog, name)

@spec canonical_name(t(), String.t()) :: {:ok, String.t()} | :error

Resolves a registered unit name or alias to the canonical unit name.

categories(catalog)

@spec categories(t()) :: %{optional(atom()) => String.t()}

Returns a map of registered category names to their conversion-default unit symbols.

category_for_dim(catalog, dim)

@spec category_for_dim(t(), %{optional(atom()) => integer()}) ::
  {:ok, atom()} | :error

Looks up the category whose dimension vector matches dim.

Base categories match %{category => 1}. Derived categories match the stored :dim from their formula.

category_for_unit(catalog, name)

@spec category_for_unit(t(), String.t()) :: {:ok, atom()} | :error

Looks up the category atom for a registered unit symbol or alias.

Returns

  • {:ok, category} - The category the unit belongs to
  • :error - The symbol is not registered

dimension(catalog, category)

@spec dimension(t(), atom()) :: {:ok, Elex.Dimension.t()} | :error

Looks up a category's formula as a dimension.

Base categories are %{category => 1}. Derived categories use the stored :dim from their formula.

fetch_unit(catalog, name)

@spec fetch_unit(t(), String.t()) :: {:ok, unit()} | :error

Looks up a registered unit entry by canonical name or alias.

Returns

  • {:ok, unit} - The unit with conversion ASTs
  • :error - The name is not a registered unit or alias

kind(catalog, name)

@spec kind(t(), atom()) :: {:ok, :base} | {:ok, :derived} | :error

Returns whether name is a base or derived category.

Returns

  • {:ok, :base} - The category has no formula:
  • {:ok, :derived} - The category was registered with formula:
  • :error - The name is not a registered category

new()

@spec new() :: t()

Returns an empty catalog.

parse_formula(catalog, source)

@spec parse_formula(t(), String.t()) ::
  {:ok, Elex.Units.Formula.monomial()} | {:error, String.t()}

Parses a unit formula against this catalog.

Rejects an invalid formula, unknown symbols, a formula that places the same category in both the numerator and the denominator (after expanding derived units), and a formula that cancels to an empty unit.

validate(catalog)

@spec validate(t()) :: :ok | {:error, String.t()}

Returns :ok when every category default: hub is registered on that category and every derived category has a base-hub identity (default: name parses to the identity monomial, or a matching unit name). identity: does not substitute for that unit.