Current section
Files
Jump to
Current section
Files
lib/units.ex
defmodule ExUc.Units do
@moduledoc """
# Units and conversion accessor
Allow efficient access to units and conversions defined in config.
"""
# ETS table to store map of aliases by unit
@table :ex_uc_units_map
@doc """
Gets a map with every kind of unit defined in config.
The result has a very traversable structure as:
```
%{
kind_of_unit: [
alias_0: :main,
alias_N: :main,
],
...
}
```
Returns Map
"""
def map do
stored_map = cond do
:ets.info(@table) == :undefined ->
:ets.new(@table, [:named_table])
:"$end_of_table"
true -> :ets.first(@table)
end
get_map(stored_map)
end
defp get_map(:"$end_of_table") do
parsed_map = Application.get_all_env(:ex_uc)
|> Enum.filter(fn {kind, _opts} -> Atom.to_string(kind) |> String.ends_with?("_units") end)
|> Enum.map(fn {kind, units} ->
units_map = units
|> Enum.flat_map(fn {main, aliases} ->
cond do
is_list(aliases) -> [{main, main} | for(alias <- aliases, do: {String.to_atom(alias), main})]
is_binary(aliases) -> [{main, main}, {String.to_atom(aliases), main}]
true -> [{main, main}, {aliases, main}]
end
end)
{kind, units_map}
end)
|> Enum.into(%{})
:ets.insert(@table, {parsed_map})
parsed_map
end
defp get_map(parsed_map), do: parsed_map
@doc """
Gets the kind of unit for ther given unit.
## Parameters
- unit: Atom representing the unit to find the kind.
## Examples
```
iex>ExUc.Units.get_kind(:kg)
"mass"
iex>ExUc.Units.get_kind(:meter)
"length"
```
"""
def get_kind(unit) do
kind_kw = map()
|> Enum.find(fn {_kind, units} -> units |> Keyword.has_key?(unit) end)
case kind_kw do
{kind_name, _units} -> kind_name
|> Atom.to_string
|> String.replace_suffix("_units", "")
_ -> nil
end
end
@doc """
Gets the unit among its aliases that can be used as a key in conversions
## Parameters
- alias: Atom with a unit aliases.
- kind: Atom or String for the kind where the alias is.
## Examples
```
iex>ExUc.Units.get_key_alias(:meter, "length")
:m
iex>ExUc.Units.get_key_alias(:pounds, :mass)
:lb
iex>ExUc.Units.get_key_alias(:r4R3, :mass)
nil
```
"""
def get_key_alias(alias, kind) do
kind_token = "#{kind}_units" |> String.to_atom
with aliases <- Map.get(map(), kind_token),
main <- Keyword.get_values(aliases, alias) |> List.first,
do: main
end
@doc """
Gets the conversion factor for the units
If can find inverse relation when the conversion is a factor.
Returns Atom.t, Integer.t, Float.t
## Parameters
- from: Atom representing the unit to convert from
- to: Atom representing the unit to convert to
## Examples
```
iex>ExUc.Units.get_conversion(:g, :mg)
{:ok, 1000}
iex>ExUc.Units.get_conversion(:g, :zz)
{:error, "undetermined conversion"}
# This relation has not been defined but
# the inverse is based on a factor, so is valid.
iex>ExUc.Units.get_conversion(:km, :m)
{:ok, 1.0e3}
```
"""
def get_conversion(from_alias, to_alias) do
kind = get_kind(from_alias)
conversion_key = "#{kind}_conversions" |> String.to_atom
{from, to} = {get_key_alias(from_alias, kind), get_key_alias(to_alias, kind)}
conversions = Application.get_env(:ex_uc, conversion_key) |> Enum.into(%{})
regular_key = "#{from}_to_#{to}" |> String.to_atom
inverted_key = "#{to}_to_#{from}" |> String.to_atom
cond do
Map.has_key?(conversions, regular_key) ->
{:ok, Map.get(conversions, regular_key)}
Map.has_key?(conversions, inverted_key) && is_number(Map.get(conversions, inverted_key)) ->
{:ok, 1 / Map.get(conversions, inverted_key)}
true -> {:error, "undetermined conversion"}
end
end
end