Current section
Files
Jump to
Current section
Files
src/embeds@env.erl
-module(embeds@env).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([string/1, generate/1, get_variables/3, list/2, default_parse/2, typed_parse/1, typed_parse_fallback/2, int/1, float/1, bool/1, main/0]).
-export_type([options/0]).
-type options() :: {options,
binary(),
binary(),
fun((binary(), binary()) -> embeds@generate:code()),
binary(),
gleam@option:option(gleam@regex:regex())}.
-spec chop_prefix(binary(), binary()) -> {ok, binary()} | {error, nil}.
chop_prefix(String, Prefix) ->
case {gleam@string:pop_grapheme(String), gleam@string:pop_grapheme(Prefix)} of
{{ok, {Ch1, String@1}}, {ok, {Ch2, Prefix@1}}} when Ch1 =:= Ch2 ->
chop_prefix(String@1, Prefix@1);
{_, {error, nil}} ->
{ok, String};
{_, _} ->
{error, nil}
end.
-spec string(binary()) -> embeds@generate:code().
string(Value) ->
{constant, gleam@string:inspect(Value)}.
-spec generate(options()) -> {ok, nil} | {error, list(embeds@generate:error())}.
generate(Options) ->
embeds@generate:guard_gleam_project(
fun() ->
{options, Module, Header, Parse, Prefix, Regex} = Options,
Defs = get_variables(Module, Prefix, Regex),
Wrapped_parse = fun(Name, Code) -> case Code of
{text, Text} ->
Parse(Name, Text);
{binary, _} ->
skip
end end,
embeds@generate:generate(Defs, Header, Wrapped_parse, true)
end
).
-spec get_variables(
binary(),
binary(),
gleam@option:option(gleam@regex:regex())
) -> list(embeds@generate:definition()).
get_variables(Module, Prefix, Regex) ->
gleam@list:filter_map(
maps:to_list(envoy_ffi:all()),
fun(_use0) ->
{Key, Value} = _use0,
gleam@result:'try'(
chop_prefix(Key, Prefix),
fun(Var_name) -> gleam@result:'try'(case Regex of
{some, Regex@1} ->
case gleam@regex:scan(Regex@1, Var_name) of
[{match, _, [{some, Matched} | _]} | _] ->
{ok, Matched};
[_ | _] ->
{ok, Var_name};
[] ->
{error, nil}
end;
none ->
{ok, Var_name}
end, fun(Var_name@1) ->
Var_name@2 = embeds@generate:to_gleam_identifier(
Var_name@1
),
{ok,
{definition,
Key,
fun() -> {ok, {text, Value}} end,
0,
Module,
Var_name@2}}
end) end
)
end
).
-spec list(fun((binary()) -> embeds@generate:code()), binary()) -> fun((binary()) -> embeds@generate:code()).
list(Item_parser, Separator) ->
fun(Value) ->
List = gleam@string:split(Value, Separator),
Result = (gleam@list:try_fold(
List,
{[], true},
fun(_use0, Value@1) ->
{Parts, Is_const} = _use0,
case Item_parser(gleam@string:trim(Value@1)) of
skip ->
{ok, {Parts, Is_const}};
{generate_error, Error} ->
{error, Error};
{constant, Code} ->
{ok, {[Code | Parts], Is_const}};
{function_body, Code@1} ->
{ok, {[Code@1 | Parts], false}}
end
end
)),
case Result of
{ok, {Parts@1, Is_const@1}} ->
Code@2 = gleam@string:inspect(lists:reverse(Parts@1)),
case Is_const@1 of
true ->
{constant, Code@2};
false ->
{function_body, Code@2}
end;
{error, Err} ->
{generate_error, Err}
end
end.
-spec default_parse(binary(), binary()) -> embeds@generate:code().
default_parse(_, Value) ->
{constant, gleam@string:inspect(Value)}.
-spec typed_parse(list({binary(), fun((binary()) -> embeds@generate:code())})) -> fun((binary(), binary()) -> embeds@generate:code()).
typed_parse(Definitions) ->
fun(Key, Value) -> case gleam@list:key_find(Definitions, Key) of
{ok, Parser} ->
Parser(gleam@string:trim(Value));
{error, nil} ->
skip
end end.
-spec typed_parse_fallback(
list({binary(), fun((binary()) -> embeds@generate:code())}),
fun((binary(), binary()) -> embeds@generate:code())
) -> fun((binary(), binary()) -> embeds@generate:code()).
typed_parse_fallback(Definitions, Fallback) ->
fun(Key, Value) -> case gleam@list:key_find(Definitions, Key) of
{ok, Parser} ->
Parser(gleam@string:trim(Value));
{error, nil} ->
Fallback(Key, Value)
end end.
-spec int(binary()) -> embeds@generate:code().
int(Value) ->
embeds@generate:result_to_const(
(gleam@result:map(
begin
_pipe = gleam@int:parse(Value),
gleam@result:replace_error(
_pipe,
<<"Invalid integer: "/utf8, Value/binary>>
)
end,
fun(N) -> gleam@string:inspect(N) end
))
).
-spec float(binary()) -> embeds@generate:code().
float(Value) ->
embeds@generate:result_to_const(
(gleam@result:map(
begin
_pipe = gleam@float:parse(Value),
gleam@result:replace_error(
_pipe,
<<"Invalid number: "/utf8, Value/binary>>
)
end,
fun(N) -> gleam@string:inspect(N) end
))
).
-spec bool(binary()) -> embeds@generate:code().
bool(Value) ->
Sanitized = gleam@string:trim(gleam@string:lowercase(Value)),
Bool = case Sanitized of
<<"true"/utf8>> ->
true;
<<"yes"/utf8>> ->
true;
<<"on"/utf8>> ->
true;
<<"1"/utf8>> ->
true;
_ ->
false
end,
{constant, gleam@string:inspect(Bool)}.
-spec parse_definitions(list(binary())) -> {ok,
list({binary(), fun((binary()) -> embeds@generate:code())})} |
{error, list(embeds@generate:error())}.
parse_definitions(Args) ->
embeds@internal:traverse_map(
Args,
fun(Arg) -> case gleam@string:split(Arg, <<":"/utf8>>) of
[Name, Type_name] ->
Name@1 = gleam@string:trim(Name),
case gleam@string:trim(gleam@string:lowercase(Type_name)) of
<<"bool"/utf8>> ->
{ok, {Name@1, fun bool/1}};
<<"int"/utf8>> ->
{ok, {Name@1, fun int/1}};
<<"float"/utf8>> ->
{ok, {Name@1, fun float/1}};
<<"string"/utf8>> ->
{ok, {Name@1, fun string/1}};
_ ->
{error,
[{invalid_argument,
<<""/utf8>>,
Arg,
<<"Invalid type name: "/utf8,
Type_name/binary>>}]}
end;
_ ->
{error,
[{invalid_argument,
<<""/utf8>>,
Arg,
<<"Invalid type definition"/utf8>>}]}
end end
).
-spec generate_cmd() -> glint:command(nil).
generate_cmd() ->
glint:command_help(
<<<<<<<<"Generate Gleam modules containing String constants of whitelisted environment variables.\n\n\n"/utf8,
"By default, environment variables starting with `BUILD_' will be included, and a module called build_env will be generated.\n\n"/utf8>>/binary,
"The generted constants have this prefix stripped.\n\n\n"/utf8>>/binary,
"You can optionally provide a list of NAME:TYPE pairs to generate constants of other types instead.\n\n"/utf8>>/binary,
"Valid types names are: int, float, bool, string"/utf8>>,
fun() ->
glint:flag(
begin
_pipe = glint:string_flag(<<"module"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<"build_env"/utf8>>),
glint:flag_help(
_pipe@1,
<<"Name of the generated Gleam module (default: build_env)"/utf8>>
)
end,
fun(Module_name) ->
glint:flag(
begin
_pipe@2 = glint:string_flag(<<"prefix"/utf8>>),
_pipe@3 = glint:flag_default(
_pipe@2,
<<"BUILD_"/utf8>>
),
glint:flag_help(
_pipe@3,
<<"Only environment variables matching this prefix will be included, and the prefix will be stripped from their name. (default: BUILD_)"/utf8>>
)
end,
fun(Prefix) ->
glint:flag(
begin
_pipe@4 = glint:string_flag(
<<"filter"/utf8>>
),
glint:flag_help(
_pipe@4,
<<"Use a regular expression to match environment variables. If a capture group exists, it will be used as the variable name. If both prefix and regex are given, the prefix will be stripped first."/utf8>>
)
end,
fun(Filter) ->
glint:command(
fun(_, Args, Flags) ->
_assert_subject = Module_name(Flags),
{ok, Module} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"embeds/env"/utf8>>,
function => <<"generate_cmd"/utf8>>,
line => 154}
)
end,
_assert_subject@1 = Prefix(Flags),
{ok, Prefix@1} = case _assert_subject@1 of
{ok, _} -> _assert_subject@1;
_assert_fail@1 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail@1,
module => <<"embeds/env"/utf8>>,
function => <<"generate_cmd"/utf8>>,
line => 155}
)
end,
Filter@1 = begin
_pipe@5 = Filter(Flags),
gleam@option:from_result(
_pipe@5
)
end,
Result = (gleam@result:'try'(
case Filter@1 of
{some, Filter@2} ->
case gleam@regex:from_string(
Filter@2
) of
{ok, Regex} ->
{ok,
{some,
Regex}};
{error,
{compile_error,
Error,
_}} ->
{error,
[{invalid_argument,
<<"filter"/utf8>>,
Filter@2,
Error}]}
end;
none ->
{ok, none}
end,
fun(Regex@1) ->
gleam@result:'try'(
parse_definitions(Args),
fun(Definitions) ->
Options = {options,
embeds@generate:to_gleam_module_name(
Module
),
<<"//// Generated by `embeds`, manual edits are futile"/utf8>>,
typed_parse_fallback(
Definitions,
fun default_parse/2
),
Prefix@1,
Regex@1},
generate(Options)
end
)
end
)),
case Result of
{ok, nil} ->
nil;
{error, Errs} ->
gleam@io:println_error(
embeds@generate:describe_errors(
Errs
)
)
end,
nil
end
)
end
)
end
)
end
)
end
).
-spec main() -> nil.
main() ->
_pipe = glint:new(),
_pipe@1 = glint:pretty_help(_pipe, glint:default_pretty_help()),
_pipe@2 = glint:add(_pipe@1, [], generate_cmd()),
glint:run(_pipe@2, erlang:element(4, argv:load())).