Packages

The library, implements modules and structs for work with `The International System of Units`.

Current section

Files

Jump to
si lib unit.ex
Raw

lib/unit.ex

defmodule SI.Unit do
@moduledoc false
alias SI.Multiplier.{Exa,
Peta,
Tera,
Giga,
Mega,
Kilo,
Hecto,
Deca,
Deci,
Centi,
Milli,
Micro,
Nano,
Pico,
Femto,
Atto}
@callback create(number()) :: term()
@multipliers [
{Exa.symbol(), Exa},
{Peta.symbol(), Peta},
{Tera.symbol(), Tera},
{Giga.symbol(), Giga},
{Mega.symbol(), Mega},
{Kilo.symbol(), Kilo},
{Hecto.symbol(), Hecto},
{Deca.symbol(), Deca},
{Deci.symbol(), Deci},
{Centi.symbol(), Centi},
{Milli.symbol(), Milli},
{Micro.symbol(), Micro},
{Nano.symbol(), Nano},
{Pico.symbol(), Pico},
{Femto.symbol(), Femto},
{Atto.symbol(), Atto},
]
defmacro __using__(opts) do
name = opts[:name] || raise("the `name` is required option")
symbol = opts[:symbol] || raise("the `symbol` is required option")
quote do
module = __MODULE__
Module.put_attribute(__MODULE__, :name, unquote(name))
Module.put_attribute(__MODULE__, :symbol, unquote(symbol))
@behaviour SI.Unit
@enforce_keys [:value]
defstruct [value: nil]
defprotocol Converter, do: Protocol.def(create(term))
defimpl Converter, for: Float do
@module module
def create(term), do: %{__struct__: @module, value: term}
end
defimpl Converter, for: Integer do
@module module
def create(term), do: %{__struct__: @module, value: term/1}
end
@spec name() :: binary()
def name, do: @name
@spec symbol() :: atom()
def symbol, do: @symbol
@spec create(term()) :: term()
def create(from), do: __MODULE__.Converter.create(from)
end
end
defmacro compile_protocol_impl(module, for, multiplier) do
quote do
defimpl :"#{unquote(module)}.Converter", for: unquote(for) do
@multiplier unquote(multiplier)
@module unquote(module)
def create(%_{value: value}), do: %{__struct__: @module, value: value * @multiplier}
end
end
end
defmacro compile_derivative_units(generating_units) do
quote do
Enum.map(
unquote(generating_units),
fn {module_name, opts} ->
defmodule module_name do
alias SI.Unit
use Unit,
name: opts[:name],
symbol: opts[:symbol]
end
end
)
end
end
@spec generate_derivative_list(atom()) :: [{atom(), keyword()}]
def generate_derivative_list(basic_module) do
Enum.map(@multipliers, fn {_multiplier_symbol, multiplier_module} ->
{
unit_module_name(basic_module, multiplier_module),
symbol: derivative_symbol(basic_module, multiplier_module),
name: derivative_name(basic_module, multiplier_module),
multiplier: multiplier_module,
for: basic_module
}
end)
end
@spec derivative_symbol(atom(), atom()) :: atom()
def derivative_symbol(original_module, multiplier_module), do: :"#{multiplier_module.symbol()}#{original_module.symbol()}"
@spec derivative_name(atom(), atom()) :: binary()
def derivative_name(original_module, multiplier_module), do: "#{multiplier_module.name()}#{original_module.name()}"
@spec unit_module_name(atom(), atom()) :: atom()
defp unit_module_name(original_module, multiplier_module)
when is_atom(original_module) and is_atom(multiplier_module) do
# Example for main module `Measurement.SI.Unit.Gram` and multiplier `Measurement.SI.Multiplier.Kilo`
Module.split(original_module) # => ["Elixir", "Measurement", "SI", "Unit", "Gram"]
|> Enum.reverse() # => ["Gram", "Unit", "SI", "Measurement", "Elixir"]
|> (
fn [name | tail] -> # => ["Gram", ["Unit", "SI", "Measurement", "Elixir"]]
[
Module.split(multiplier_module) # => ["Elixir", "Measurement", "SI", "Multiplier", "Kilo"]
|> List.last() # => "Kilo"
|> Kernel.<>(String.downcase("#{name}")) # => "Kilogram"
] ++ tail # => ["Kilogram", "Unit", "SI", "Measurement", "Elixir"]
end).()
|> Enum.reverse() # => ["Elixir", "Measurement", "SI", "Unit", "Kilogram"]
|> Module.concat()
end
def unit_module_name(module_name) when is_atom(module_name), do: module_name
end