Current section
Files
Jump to
Current section
Files
src/envie@parse.erl
-module(envie@parse).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/envie/parse.gleam").
-export([parse_dotenv/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/envie/parse.gleam", 149).
?DOC(
" Remove an inline comment from an unquoted value.\n"
"\n"
" `8080 # default port` → `8080`\n"
).
-spec strip_inline_comment(binary()) -> binary().
strip_inline_comment(S) ->
case gleam@string:split_once(S, <<" #"/utf8>>) of
{ok, {Before, _}} ->
gleam@string:trim_end(Before);
{error, _} ->
S
end.
-file("src/envie/parse.gleam", 134).
-spec do_process_escapes(list(binary()), binary()) -> binary().
do_process_escapes(Chars, Acc) ->
case Chars of
[] ->
Acc;
[<<"\\"/utf8>>, <<"n"/utf8>> | Rest] ->
do_process_escapes(Rest, <<Acc/binary, "\n"/utf8>>);
[<<"\\"/utf8>>, <<"t"/utf8>> | Rest@1] ->
do_process_escapes(Rest@1, <<Acc/binary, "\t"/utf8>>);
[<<"\\"/utf8>>, <<"\\"/utf8>> | Rest@2] ->
do_process_escapes(Rest@2, <<Acc/binary, "\\"/utf8>>);
[<<"\\"/utf8>>, <<"\""/utf8>> | Rest@3] ->
do_process_escapes(Rest@3, <<Acc/binary, "\""/utf8>>);
[<<"\\"/utf8>>, C | Rest@4] ->
do_process_escapes(
Rest@4,
<<<<Acc/binary, "\\"/utf8>>/binary, C/binary>>
);
[C@1 | Rest@5] ->
do_process_escapes(Rest@5, <<Acc/binary, C@1/binary>>)
end.
-file("src/envie/parse.gleam", 130).
?DOC(
" Process escape sequences inside double-quoted values.\n"
"\n"
" Supported: `\\\\n` → newline, `\\\\t` → tab, `\\\\\\\\` → backslash,\n"
" `\\\\\"` → double-quote. Unknown sequences are kept as-is.\n"
).
-spec process_escapes(binary()) -> binary().
process_escapes(S) ->
do_process_escapes(gleam@string:to_graphemes(S), <<""/utf8>>).
-file("src/envie/parse.gleam", 104).
-spec parse_quoted(list(binary()), binary(), binary(), boolean()) -> binary().
parse_quoted(Chars, Quote, Acc, Process) ->
case Chars of
[] ->
Acc;
[Q | _] when Q =:= Quote ->
case Process of
true ->
process_escapes(Acc);
false ->
Acc
end;
[<<"\\"/utf8>>, C | Rest] when Process ->
parse_quoted(
Rest,
Quote,
<<<<Acc/binary, "\\"/utf8>>/binary, C/binary>>,
Process
);
[C@1 | Rest@1] ->
parse_quoted(Rest@1, Quote, <<Acc/binary, C@1/binary>>, Process)
end.
-file("src/envie/parse.gleam", 96).
-spec unquote(binary()) -> binary().
unquote(S) ->
case gleam@string:to_graphemes(S) of
[<<"\""/utf8>> | Rest] ->
parse_quoted(Rest, <<"\""/utf8>>, <<""/utf8>>, true);
[<<"'"/utf8>> | Rest@1] ->
parse_quoted(Rest@1, <<"'"/utf8>>, <<""/utf8>>, false);
_ ->
strip_inline_comment(S)
end.
-file("src/envie/parse.gleam", 61).
-spec parse_assignment(integer(), binary()) -> {ok,
gleam@option:option({binary(), binary()})} |
{error, envie@error:parse_error()}.
parse_assignment(Line_num, Line) ->
Effective = case gleam_stdlib:string_starts_with(Line, <<"export "/utf8>>) of
true ->
gleam@string:slice(Line, 7, string:length(Line) - 7);
false ->
Line
end,
case gleam@string:split_once(Effective, <<"="/utf8>>) of
{ok, {Raw_key, Raw_value}} ->
Key = gleam@string:trim(Raw_key),
case Key of
<<""/utf8>> ->
{error,
{invalid_syntax,
Line_num,
Line,
<<"Empty variable name"/utf8>>}};
_ ->
Value = unquote(gleam@string:trim(Raw_value)),
{ok, {some, {Key, Value}}}
end;
{error, _} ->
{error,
{invalid_syntax,
Line_num,
Line,
<<"Expected KEY=value format"/utf8>>}}
end.
-file("src/envie/parse.gleam", 44).
-spec parse_line(integer(), binary()) -> {ok,
gleam@option:option({binary(), binary()})} |
{error, envie@error:parse_error()}.
parse_line(Line_num, Line) ->
Trimmed = gleam@string:trim(Line),
case Trimmed of
<<""/utf8>> ->
{ok, none};
_ ->
case gleam_stdlib:string_starts_with(Trimmed, <<"#"/utf8>>) of
true ->
{ok, none};
false ->
parse_assignment(Line_num, Trimmed)
end
end.
-file("src/envie/parse.gleam", 22).
-spec parse_lines(
list({integer(), binary()}),
list({binary(), binary()}),
list(envie@error:parse_error())
) -> {ok, list({binary(), binary()})} | {error, list(envie@error:parse_error())}.
parse_lines(Lines, Vars, Errors) ->
case Lines of
[] ->
case Errors of
[] ->
{ok, lists:reverse(Vars)};
_ ->
{error, lists:reverse(Errors)}
end;
[{Line_num, Line} | Rest] ->
case parse_line(Line_num, Line) of
{ok, none} ->
parse_lines(Rest, Vars, Errors);
{ok, {some, Pair}} ->
parse_lines(Rest, [Pair | Vars], Errors);
{error, Err} ->
parse_lines(Rest, Vars, [Err | Errors])
end
end.
-file("src/envie/parse.gleam", 13).
?DOC(
" Parse a string in .env format into a list of `#(key, value)` pairs.\n"
"\n"
" Lines that are empty or start with `#` are silently skipped.\n"
" Lines that do not contain an `=` produce a `ParseError`.\n"
).
-spec parse_dotenv(binary()) -> {ok, list({binary(), binary()})} |
{error, list(envie@error:parse_error())}.
parse_dotenv(Content) ->
_pipe = Content,
_pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
_pipe@2 = gleam@list:index_map(
_pipe@1,
fun(Line, Idx) -> {Idx + 1, Line} end
),
parse_lines(_pipe@2, [], []).