# `Nvir.Parser`
[🔗](https://github.com/lud/nvir/blob/v0.16.3/lib/nvir/parser.ex#L1)

A behaviour for environment variables sources parser.

The default implementation used in Nvir is `Nvir.Parser.DefaultParser`. It can
only parse classic dotenv files.

Parsing other sources such as YAML files or encrypted files requires to
provide your own parser.

See the documentation of the `c:parse_file/1` callback for implementation
guidelines.

# `key`

```elixir
@type key() :: String.t()
```

# `template`

```elixir
@type template() :: [binary() | {:var, binary()}]
```

# `template_resolver`

```elixir
@type template_resolver() :: (String.t() -&gt; nil | String.t())
```

# `variable`

```elixir
@type variable() :: {key(), binary() | template()}
```

# `parse_file`

```elixir
@callback parse_file(path :: String.t()) :: {:ok, [variable()]} | {:error, Exception.t()}
```

This callback must return a `{:ok, variables}` or `{:error, reason}` tuple.

The `variables` is a list of `{key, value}` tuples where:

* `key` is a string
* `value` is either a string or a `template`
* A `template` is a list of `chunks`
* A `chunk` is either a string or a `{:var, varname}` tuple.

Templates are used for interpolation. When a variable uses interpolation, the
parser must not attempt to read the interpolated variables from environment,
but rather return a template instead of a binary value.

Variables used in interpolation within other variables values do not have to
exist in the file. Nvir uses a resolver to provide those values to execute the
templates.

In this example, the `INTRO` variable is defined in the same file, but the
`WHO` variable is not. This makes not difference, as the parser must not
require those values.

    iex> file_contents = "INTRO=hello\nGREETING=$INTRO $WHO!"
    iex> Nvir.Parser.DefaultParser.parse_string(file_contents)
    {:ok, [{"INTRO", "hello"}, {"GREETING", [{:var, "INTRO"}, " ", {:var, "WHO"}, "!"]}]}

You can test and debug your parser by using `Nvir.Parser.interpolate_var/2`
and a simple resolver.

    iex> file_contents = "GREETING=$INTRO $WHO!"
    iex> {:ok, [{"GREETING", template}]} = Nvir.Parser.DefaultParser.parse_string(file_contents)
    iex> resolver = fn
    ...>   "INTRO" -> "Hello"
    ...>   "WHO" -> "World"
    ...> end
    iex> Nvir.Parser.interpolate_var(template, resolver)
    "Hello World!"

### Expected results examples

Given this file content:

```bash
WHO=World
GREETING=Hello $WHO!
```

The `c:parse_file/1` callback should return the following:

```elixir
{:ok,
  [
    {"WHO", "World"},
    {"GREETING", ["Hello ", {:var, "WHO"}, "!"]}
  ]}
```

With this one using accumulative interpolation:

```bash
PATH=/usr/local/bin
PATH=$PATH:/usr/bin
PATH=/home/me/bin:$PATH
```

The parser should produce the following:

```elixir
{:ok,
  [
    {"PATH", "/usr/local/bin"},
    {"PATH", [{:var, "PATH"}, ":/usr/bin"]},
    {"PATH", ["/home/me/bin:", {:var, "PATH"}]}
  ]}
```

# `interpolate_var`

```elixir
@spec interpolate_var(String.t(), template_resolver()) :: String.t()
```

Takes a parsed value returned by the parser implementation, and a resolver
for the interpolated variables.

A resolver is a function that takes a variable name and returns a string or
`nil`.

### Example

    iex> envfile = """
    iex> GREETING=Hello $NAME!
    iex> """
    iex> {:ok, [{"GREETING", variable}]} = Nvir.Parser.DefaultParser.parse_string(envfile)
    iex> resolver = fn "NAME" -> "World" end
    iex> Nvir.Parser.interpolate_var(variable, resolver)
    "Hello World!"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
