Current section
Files
Jump to
Current section
Files
lib/toml_elixir.ex
defmodule TomlElixir do
@moduledoc """
# TomlElixir
[TOML](https://github.com/toml-lang/toml) parser for elixir.
## Installation
The package can be installed by adding `toml_elixir` to your list of
dependencies in `mix.exs`:
```elixir
def deps do
[{:toml_elixir, "~> 1.0.0"}]
end
```
## Usage
TomlElixir is used by calling parse functions
* `TomlElixir.parse/2`
* `TomlElixir.parse!/2`
"""
@type toml_value :: {:string, binary} |
{:datetime, tuple} |
{:number, number} |
{:boolean, boolean}
@type toml_ident :: {:identifier, binary}
@type toml_key_val :: {toml_ident, toml_value}
@type toml_group :: {:group, [toml_ident], [toml_key_val]}
@type toml_multi :: {:multi, [toml_ident], [toml_key_val]}
@type toml_return :: [toml_key_val | toml_multi | toml_group]
@type return :: toml_return | Map.t
@type options :: [no_parse: boolean] | Keyword.t
@doc """
Parse toml string to map or return raw list. Return ok/error tuple
## Possible options
- no_parse :: boolean
"""
@spec parse(binary, options) :: {:ok, return} | {:error, String.t}
def parse(str, opts \\ []) when is_binary(str) do
with {:ok, tokens} <- lexer(str),
{:ok, list} <- parser(tokens)
do
if Keyword.get(opts, :no_parse) == true do
{:ok, list}
else
{:ok, to_map(list)}
end
end
end
@doc """
Parse toml string to map or return raw list. Raises error on failure
## Possible options
- no_parse :: boolean
"""
@spec parse!(binary, options) :: return
def parse!(str, opts \\ []) when is_binary(str) do
case parse(str, opts) do
{:ok, map} -> map
{:error, err} -> raise err
end
end
@spec lexer(binary) :: {:ok, List.t} | {:error, String.t}
defp lexer(str) when is_binary(str) do
str
|> to_charlist()
|> :toml_lexer.string()
|> erl_result_parse()
end
@spec parser(List.t) :: {:ok, List.t} | {:error, String.t}
defp parser(tokens) when is_list(tokens) do
tokens
|> :toml_parser.parse()
|> erl_result_parse()
end
@spec erl_result_parse({:ok, [term], term} | {:ok, [term]} |
{:error, {number, term, binary}} |
{:error, {number, term, {atom, binary}, term}}) ::
{:ok, [term]} | {:error, String.t}
defp erl_result_parse({:ok, tokens, _}),
do: {:ok, tokens}
defp erl_result_parse({:ok, list}),
do: {:ok, list}
defp erl_result_parse({:error, {line, _, err}}),
do: {:error, "Error on line #{line}: #{err}"}
defp erl_result_parse({:error, {line, _, {err, msg}}, _}),
do: {:error, "Error on line #{line}: #{err} #{msg}"}
@spec to_map(toml_return) :: Map.t
defp to_map(val),
do: to_map(val, %{})
@spec to_map(toml_return, List.t | Map.t) :: List.t | Map.t
defp to_map(val, []),
do: [to_map(val, %{})]
defp to_map(val, list) when is_list(list),
do: List.update_at(list, -1, &(to_map(val, &1)))
defp to_map([{:group, idents, values} | tail], map),
do: to_map(tail, group(idents, values, map))
defp to_map([{:multi, idents, values} | tail], map),
do: to_map(tail, multi(idents, values, map))
defp to_map([{{:identifier, key}, values} | tail], map) when is_list(values),
do: to_map(tail, put(map, key, value(values)))
defp to_map([{{:identifier, key}, val} | tail], map),
do: to_map(tail, put(map, key, value(val)))
defp to_map([], map),
do: map
@spec group([toml_ident], [toml_key_val], Map.t | List.t) :: Map.t | List.t
defp group(idents, values, []),
do: [group(idents, values, %{})]
defp group(idents, values, list) when is_list(list),
do: List.update_at(list, -1, &group(idents, values, &1))
defp group([{:identifier, key} | tail], values, map),
do: put(map, key, group(tail, values, get(map, key, %{})))
defp group([], values, map),
do: to_map(values, map)
@spec multi([toml_ident], [toml_key_val], Map.t) :: Map.t
defp multi([{:identifier, key} | []], values, map),
do: put(map, key, to_map(values, insert_end(map, key, %{})))
defp multi([{:identifier, key} | tail], values, map),
do: put(map, key, multi(tail, values, get(map, key, %{})))
@spec value(toml_value | [toml_value]) :: term
defp value([head | tail]),
do: [value(head) | value(tail)]
defp value([]),
do: []
defp value({:string, val}),
do: "#{val}"
defp value({:datetime, val}),
do: val
defp value({:number, val}),
do: val
defp value({:boolean, val}),
do: val
@spec insert_end(Map.t, binary, term) :: List.t
defp insert_end(map, key, value),
do: List.insert_at(get(map, key, []), -1, value)
@spec get(Map.t, binary, term) :: Map.t
defp get(map, key, default),
do: Map.get(map, "#{key}", default)
@spec put(Map.t, binary, term) :: Map.t
defp put(map, key, value),
do: Map.put(map, "#{key}", value)
end