defmodule HXL do
@moduledoc File.read!(Path.join([__DIR__, "..", "README.md"]))
alias __MODULE__.{Parser, Eval}
@type opt ::
{:variables, map()}
| {:functions, map()}
| {:keys, :atoms | :string | (binary -> term())}
| {:evaluator, HXL.Evaluator.t()}
@type opts :: [opt()]
@doc """
Reads a `HCL` document from file.
Uses same options as `decode/2`
## Examples
iex> HXL.decode_file("/path/to/file.hcl")
{:ok, %{"a" => "b"}}
"""
@spec decode_file(Path.t(), opts()) :: {:ok, map()} | {:error, term()}
def decode_file(path, opts \\ []) do
with {:ok, bin} <- File.read(path),
{:ok, _body} = return <- decode(bin, opts) do
return
else
# File error
{:error, reason} when is_atom(reason) ->
msg =
reason
|> :file.format_error()
|> List.to_string()
{:error, msg}
# Lex/parse error
{:error, _reason} = err ->
err
end
end
@doc """
Reads a `HCL` document from file, returns the document directly or raises `HXL.Error`.
See `decode_file/1`
"""
@spec decode_file!(Path.t(), opts) :: map() | no_return()
def decode_file!(path, opts \\ []) do
case decode_file(path, opts) do
{:ok, body} -> body
{:error, reason} -> raise HXL.Error, reason
end
end
@doc """
Decode a binary to a `HCL` document.
`decode/2` parses and evaluates the AST before returning the `HCL` docuement.
If the document is using functions in it's definition, these needs to be passed in the `opts` part of this functions.
See bellow for an example
## Options
The following options can be passed to configure evaluation of the document:
* `:evaluator` - A `HXL.Evaluator` module to interpret the AST during evaluation. See `HXL.Evaluator` for more information.
* `:functions` - A map of `(<function_name> -> <function>)` to make available in document evaluation.
* `:variables` - A map of Top level variables that should be injected into the context when evaluating the document.
* `:keys` - controls how keys in the parsed AST are evaluated. Possible values are:
* `:strings` (default) - evaluates keys as strings
* `:atoms` - converts keys to atoms with `String.to_atom/1`
* `:atoms!` - converts keys to atoms with `String.to_existing_atom/1`
* `(key -> term)` - converts keys using the provided function
## Examples
Using functions:
iex> hcl = "a = upper(trim(\" a \"))"
"a = upper(trim(\" a \"))"
iex> HXL.decode(hcl, functions: %{"upper" => &String.capitalize/1, "trim" => &String.trim/1})
{:ok, %{"a" => "A"}}
Using variables:
iex> hcl = "a = b"
"a = b"
iex> HXL.decode(hcl, variables: %{"b" => "B"})
{:ok, %{"a" => "B"}}
"""
@spec decode(binary(), opts()) :: {:ok, map()} | {:error, term()}
def decode(binary, opts \\ []) do
with {:ok, body} <- Parser.parse(binary),
%Eval{document: doc} <- Eval.eval(body, opts) do
{:ok, doc}
end
end
@doc """
Reads a `HCL` document from a binary. Returns the document or raises `HXL.Error`.
See `from_binary/1`
"""
@spec decode!(binary(), opts) :: map() | no_return()
def decode!(bin, opts \\ []) do
case decode(bin, opts) do
{:ok, doc} -> doc
{:error, reason} -> raise HXL.Error, reason
end
end
@doc """
Decode a binary to a `HXL` document AST.
## Examples
iex> HXL.decode_as_ast("a = 1")
{:ok, %HXL.Ast.Body{
statements: [
%HXL.Ast.Attr{
expr: %HXL.Ast.Literal{value: {:int, 1}}, name: "a"}
]
}}
"""
@spec decode_as_ast(binary()) :: {:ok, HXL.Ast.t()} | {:error, term()}
defdelegate decode_as_ast(binary), to: __MODULE__.Parser, as: :parse
end