Current section
Files
Jump to
Current section
Files
src/embeds@generate.erl
-module(embeds@generate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([default_print/2, describe_error/1, describe_errors/1, guard_gleam_project/1, bits_to_data/1, data_to_bits/1, result_to_const/1, skip_or_const/1, result_to_fn/1, skip_or_fn/1, to_gleam_identifier/1, to_gleam_module_name/1, generate_module_file/3, generate_modules/6, generate/4]).
-export_type([error/0, data/0, code/0, definition/0]).
-type error() :: not_inside_gleam_project |
{invalid_argument, binary(), binary(), binary()} |
{could_not_list_files, binary(), simplifile:file_error()} |
{could_not_read, binary(), binary()} |
{duplicate_var_name, binary(), binary()} |
{printer_error, binary(), binary()} |
{write_error, binary(), simplifile:file_error()}.
-type data() :: {text, binary()} | {binary, bitstring()}.
-type code() :: skip |
{generate_error, binary()} |
{constant, binary()} |
{function_body, binary()}.
-type definition() :: {definition,
binary(),
fun(() -> {ok, data()} | {error, binary()}),
integer(),
binary(),
binary()}.
-spec default_print(binary(), data()) -> code().
default_print(_, Data) ->
Code = case Data of
{text, Text} ->
gleam@string:inspect(Text);
{binary, Bits} ->
gleam@bit_array:inspect(Bits)
end,
{constant, Code}.
-spec describe_error(error()) -> binary().
describe_error(Err) ->
case Err of
not_inside_gleam_project ->
<<"generate needs to be called inside the root directory of your Gleam project!"/utf8>>;
{invalid_argument, Name, Value, Reason} ->
<<<<<<<<<<"Invalid argument "/utf8, Name/binary>>/binary,
" ("/utf8>>/binary,
Value/binary>>/binary,
"): "/utf8>>/binary,
Reason/binary>>;
{could_not_list_files, Path, E} ->
<<<<<<"Could not list files from "/utf8, Path/binary>>/binary,
": "/utf8>>/binary,
(simplifile:describe_error(E))/binary>>;
{could_not_read, Path@1, E@1} ->
<<<<<<"Could not read "/utf8, Path@1/binary>>/binary, ": "/utf8>>/binary,
E@1/binary>>;
{duplicate_var_name, A, B} ->
<<<<<<<<"I have found 2 source definitions "/utf8, A/binary>>/binary,
" and "/utf8>>/binary,
B/binary>>/binary,
" - both would produce the same constant in the same module!"/utf8>>;
{printer_error, Path@2, E@2} ->
<<<<<<"The printer for "/utf8, Path@2/binary>>/binary,
" returned an error: "/utf8>>/binary,
E@2/binary>>;
{write_error, Path@3, E@3} ->
<<<<<<"Error writing module "/utf8, Path@3/binary>>/binary,
": "/utf8>>/binary,
(simplifile:describe_error(E@3))/binary>>
end.
-spec describe_errors(list(error())) -> binary().
describe_errors(Errs) ->
gleam@string:join(gleam@list:map(Errs, fun describe_error/1), <<"\n"/utf8>>).
-spec guard_gleam_project(fun(() -> {ok, IPS} | {error, list(error())})) -> {ok,
IPS} |
{error, list(error())}.
guard_gleam_project(F) ->
case simplifile_erl:is_file(<<"gleam.toml"/utf8>>) of
{ok, true} ->
F();
_ ->
{error, [not_inside_gleam_project]}
end.
-spec bits_to_data(bitstring()) -> data().
bits_to_data(Bits) ->
case gleam@bit_array:to_string(Bits) of
{ok, Text} ->
{text, Text};
{error, _} ->
{binary, Bits}
end.
-spec data_to_bits(data()) -> bitstring().
data_to_bits(Data) ->
case Data of
{text, Text} ->
gleam_stdlib:identity(Text);
{binary, Bits} ->
Bits
end.
-spec result_to_const({ok, binary()} | {error, binary()}) -> code().
result_to_const(Result) ->
case Result of
{ok, Code} ->
{constant, Code};
{error, Err} ->
{generate_error, Err}
end.
-spec skip_or_const({ok, binary()} | {error, nil}) -> code().
skip_or_const(Result) ->
case Result of
{ok, Code} ->
{constant, Code};
{error, nil} ->
skip
end.
-spec result_to_fn({ok, binary()} | {error, binary()}) -> code().
result_to_fn(Result) ->
case Result of
{ok, Code} ->
{function_body, Code};
{error, Err} ->
{generate_error, Err}
end.
-spec skip_or_fn({ok, binary()} | {error, nil}) -> code().
skip_or_fn(Result) ->
case Result of
{ok, Code} ->
{function_body, Code};
{error, nil} ->
skip
end.
-spec to_gleam_identifier(binary()) -> binary().
to_gleam_identifier(Str) ->
_assert_subject = gleam@regex:from_string(<<"[^a-zA-Z0-9]+"/utf8>>),
{ok, Re} = 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/generate"/utf8>>,
function => <<"to_gleam_identifier"/utf8>>,
line => 242})
end,
Var_name = begin
_pipe = Str,
_pipe@1 = gleam_stdlib:regex_replace(Re, _pipe, <<" "/utf8>>),
_pipe@2 = gleam@string:trim(_pipe@1),
_pipe@3 = gleam@string:replace(_pipe@2, <<" "/utf8>>, <<"_"/utf8>>),
gleam@string:lowercase(_pipe@3)
end,
case Var_name of
<<"0"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"1"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"2"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"3"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"4"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"5"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"6"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"7"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"8"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<"9"/utf8, _/binary>> ->
<<"n"/utf8, Var_name/binary>>;
<<""/utf8>> ->
<<"empty"/utf8>>;
<<"as"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"assert"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"auto"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"case"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"const"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"delegate"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"derive"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"echo"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"else"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"fn"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"if"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"implement"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"import"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"let"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"macro"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"opaque"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"panic"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"pub"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"test"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"todo"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"type"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
<<"use"/utf8>> ->
<<Var_name/binary, "_"/utf8>>;
_ ->
Var_name
end.
-spec to_gleam_module_name(binary()) -> binary().
to_gleam_module_name(Path) ->
Module = gleam@string:join(
(gleam@list:filter_map(
begin
_pipe = Path,
_pipe@1 = gleam@string:replace(
_pipe,
<<"\\"/utf8>>,
<<"/"/utf8>>
),
gleam@string:split(_pipe@1, <<"/"/utf8>>)
end,
fun(Part) -> case Part of
<<"."/utf8>> ->
{error, nil};
<<".."/utf8>> ->
{error, nil};
<<""/utf8>> ->
{error, nil};
_ ->
{ok, to_gleam_identifier(Part)}
end end
)),
<<"/"/utf8>>
),
case Module of
<<""/utf8>> ->
<<"assets"/utf8>>;
_ ->
Module
end.
-spec module_file(binary()) -> binary().
module_file(Module) ->
filepath:join(<<"src"/utf8>>, <<Module/binary, ".gleam"/utf8>>).
-spec generate_module_file(
list(definition()),
binary(),
fun((binary(), data()) -> code())
) -> {ok, binary()} | {error, list(error())}.
generate_module_file(Definitions, Header, Print) ->
gleam@result:'try'(
(embeds@internal:traverse_map(
Definitions,
fun(Definition) ->
embeds@internal:'try'(
begin
_pipe = (erlang:element(3, Definition))(),
gleam@result:map_error(
_pipe,
fun(E) ->
{could_not_read,
erlang:element(2, Definition),
E}
end
)
end,
fun(Data) ->
Code = Print(erlang:element(2, Definition), Data),
case Code of
skip ->
{error, []};
{generate_error, Err} ->
{error,
[{printer_error,
erlang:element(2, Definition),
Err}]};
{constant, Code@1} ->
{ok,
<<<<<<<<<<<<"/// "/utf8,
(erlang:element(
2,
Definition
))/binary>>/binary,
"\n"/utf8>>/binary,
"pub const "/utf8>>/binary,
(erlang:element(6, Definition))/binary>>/binary,
" = "/utf8>>/binary,
Code@1/binary>>};
{function_body, Code@2} ->
{ok,
<<<<<<<<<<<<<<"/// "/utf8,
(erlang:element(
2,
Definition
))/binary>>/binary,
"\n"/utf8>>/binary,
"pub fn "/utf8>>/binary,
(erlang:element(
6,
Definition
))/binary>>/binary,
"() {\n"/utf8>>/binary,
Code@2/binary>>/binary,
"\n}"/utf8>>}
end
end
)
end
)),
fun(Definitions@1) -> case {Definitions@1, Header} of
{[], _} ->
{ok, <<""/utf8>>};
{_, <<""/utf8>>} ->
{ok, gleam@string:join(Definitions@1, <<"\n\n"/utf8>>)};
{_, _} ->
{ok,
<<<<Header/binary, "\n\n\n"/utf8>>/binary,
(gleam@string:join(Definitions@1, <<"\n\n"/utf8>>))/binary>>}
end end
).
-spec get_mtime(binary()) -> {ok, integer()} | {error, simplifile:file_error()}.
get_mtime(Path) ->
gleam@result:map(
simplifile_erl:file_info(Path),
fun(Info) -> erlang:element(10, Info) end
).
-spec write(binary(), binary()) -> {ok, nil} | {error, simplifile:file_error()}.
write(Path, Contents) ->
gleam@result:'try'(
begin
_pipe = Path,
_pipe@1 = filepath:directory_name(_pipe),
simplifile:create_directory_all(_pipe@1)
end,
fun(_) -> simplifile:write(Path, Contents) end
).
-spec do_gather_by(list(IRD), fun((IRD) -> IRF), list({IRF, list(IRD)})) -> list({IRF,
list(IRD)}).
do_gather_by(List, To_key, Acc) ->
case List of
[] ->
lists:reverse(Acc);
[Head | Tail] ->
Key = To_key(Head),
{Matching, Remaining} = gleam@list:partition(
Tail,
fun(Other) -> To_key(Other) =:= Key end
),
do_gather_by(Remaining, To_key, [{Key, [Head | Matching]} | Acc])
end.
-spec gather_by(list(IQY), fun((IQY) -> IRA)) -> list({IRA, list(IQY)}).
gather_by(List, To_key) ->
do_gather_by(List, To_key, []).
-spec enforce_unique_by(list(IRK), fun((IRK) -> any())) -> {ok, list(IRK)} |
{error, {IRK, IRK}}.
enforce_unique_by(List, To_key) ->
gleam@list:try_fold(
List,
[],
fun(Acc, Elem) ->
Key = To_key(Elem),
case gleam@list:find(
Acc,
fun(Existing) -> To_key(Existing) =:= Key end
) of
{ok, Existing@1} ->
{error, {Existing@1, Elem}};
{error, nil} ->
{ok, [Elem | Acc]}
end
end
).
-spec generate_modules(
list(definition()),
binary(),
fun((binary(), data()) -> code()),
boolean(),
fun((binary()) -> {ok, integer()} | {error, simplifile:file_error()}),
fun((binary(), binary()) -> {ok, nil} | {error, simplifile:file_error()})
) -> {ok, nil} | {error, list(error())}.
generate_modules(Defs, Header, Print, Force, Get_mtime, Write) ->
gleam@result:map(
(gleam@result:'try'(
begin
_pipe = Defs,
_pipe@1 = gather_by(
_pipe,
fun(File) -> erlang:element(5, File) end
),
embeds@internal:traverse_map(
_pipe@1,
fun(Pair) ->
case enforce_unique_by(
erlang:element(2, Pair),
fun(File@1) -> erlang:element(6, File@1) end
) of
{ok, Files} ->
Files@1 = begin
_pipe@2 = Files,
gleam@list:sort(
_pipe@2,
fun(A, B) ->
gleam@string:compare(
erlang:element(6, A),
erlang:element(6, B)
)
end
)
end,
{ok, {erlang:element(1, Pair), Files@1}};
{error, Conflict} ->
{error,
[{duplicate_var_name,
erlang:element(
2,
(erlang:element(1, Conflict))
),
erlang:element(
2,
(erlang:element(2, Conflict))
)}]}
end
end
)
end,
fun(Module_specs) ->
embeds@internal:traverse_map(
Module_specs,
fun(_use0) ->
{Module_name, Defs@1} = _use0,
Module_file = module_file(Module_name),
Needs_update = case Get_mtime(Module_file) of
{ok, Module_ts} ->
gleam@list:any(
Defs@1,
fun(_use0@1) ->
{definition, _, _, File_ts, _, _} = _use0@1,
(File_ts =< 0) orelse (File_ts >= Module_ts)
end
);
{error, _} ->
true
end,
gleam@bool:guard(
not Force andalso not Needs_update,
{error, []},
fun() ->
gleam@result:'try'(
generate_module_file(Defs@1, Header, Print),
fun(Contents) ->
embeds@internal:'try'(
begin
_pipe@3 = Write(
Module_file,
Contents
),
gleam@result:map_error(
_pipe@3,
fun(E) ->
{write_error,
Module_file,
E}
end
)
end,
fun(_) -> {ok, nil} end
)
end
)
end
)
end
)
end
)),
fun(_) -> nil end
).
-spec generate(
list(definition()),
binary(),
fun((binary(), data()) -> code()),
boolean()
) -> {ok, nil} | {error, list(error())}.
generate(Defs, Header, Print, Force) ->
guard_gleam_project(
fun() ->
generate_modules(
Defs,
Header,
Print,
Force,
fun get_mtime/1,
fun write/2
)
end
).