-module(rally@dependency_resolver).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/dependency_resolver.gleam").
-export([resolve/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Client dependency resolver.\n"
"\n"
" Starting from the tree-shaken page modules and layout files, follows\n"
" their import chains through the server's src/ tree and copies any\n"
" shared modules the client package needs. Catches @external(erlang)\n"
" imports that would fail to compile for JavaScript and reports the\n"
" import chain so the developer can find the problem.\n"
).
-file("src/rally/dependency_resolver.gleam", 106).
-spec extract_imports(binary()) -> list(binary()).
extract_imports(Source) ->
case glance:module(Source) of
{ok, Ast} ->
gleam@list:map(
erlang:element(2, Ast),
fun(Def) -> erlang:element(3, erlang:element(3, Def)) end
);
_ ->
[]
end.
-file("src/rally/dependency_resolver.gleam", 113).
-spec collect_ffi_files(binary(), binary(), binary()) -> list(rally@generator@client:generated_file()).
collect_ffi_files(Src_root, Client_root, Module_path) ->
Ffi_path = <<<<<<Src_root/binary, "/"/utf8>>/binary, Module_path/binary>>/binary,
"_ffi.mjs"/utf8>>,
case simplifile:read(Ffi_path) of
{ok, Content} ->
Dest = <<<<<<Client_root/binary, "/src/"/utf8>>/binary,
Module_path/binary>>/binary,
"_ffi.mjs"/utf8>>,
[{generated_file, Dest, Content}];
_ ->
[]
end.
-file("src/rally/dependency_resolver.gleam", 170).
-spec do_find_line(list(binary()), binary(), integer()) -> integer().
do_find_line(Lines, Needle, N) ->
case Lines of
[] ->
N;
[Line | Rest] ->
case gleam_stdlib:contains_string(Line, Needle) of
true ->
N;
_ ->
do_find_line(Rest, Needle, N + 1)
end
end.
-file("src/rally/dependency_resolver.gleam", 164).
-spec find_line_number(binary(), binary()) -> integer().
find_line_number(Content, Needle) ->
_pipe = Content,
_pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
do_find_line(_pipe@1, Needle, 1).
-file("src/rally/dependency_resolver.gleam", 133).
-spec check_erlang_external(binary(), binary(), list(binary())) -> {ok, nil} |
{error, binary()}.
check_erlang_external(Content, Module_path, Chain) ->
Has_erlang = gleam_stdlib:contains_string(
Content,
<<"@external(erlang,"/utf8>>
),
Has_javascript = gleam_stdlib:contains_string(
Content,
<<"@external(javascript,"/utf8>>
),
Error_msg = begin
Line = find_line_number(Content, <<"@external(erlang,"/utf8>>),
Chain_str = gleam@string:join(
gleam@list:map(
lists:append(Chain, [Module_path]),
fun(C) -> <<C/binary, ".gleam"/utf8>> end
),
<<" -> "/utf8>>
),
<<<<<<<<<<<<<<<<Module_path/binary, ".gleam (line "/utf8>>/binary,
(erlang:integer_to_binary(Line))/binary>>/binary,
") contains @external(erlang, ...) which can't compile for JavaScript.\n\n"/utf8>>/binary,
" Import chain: "/utf8>>/binary,
Chain_str/binary>>/binary,
"\n\n"/utf8>>/binary,
" Server-only code belongs in page modules as server_* functions (which rally\n"/utf8>>/binary,
" strips from the client), or in separate modules that client code doesn't import."/utf8>>
end,
gleam@bool:guard(
Has_erlang andalso not Has_javascript,
{error, Error_msg},
fun() -> {ok, nil} end
).
-file("src/rally/dependency_resolver.gleam", 128).
-spec should_skip(binary()) -> boolean().
should_skip(Module_path) ->
gleam_stdlib:string_starts_with(Module_path, <<"generated/"/utf8>>) orelse (Module_path
=:= <<"server_context"/utf8>>).
-file("src/rally/dependency_resolver.gleam", 41).
-spec resolve_loop(
list({binary(), list(binary())}),
gleam@set:set(binary()),
binary(),
binary(),
list(rally@generator@client:generated_file())
) -> {ok, list(rally@generator@client:generated_file())} | {error, binary()}.
resolve_loop(Frontier, Visited, Src_root, Client_root, Acc) ->
case Frontier of
[] ->
{ok, Acc};
[{Module_path, Chain} | Rest] ->
case gleam@set:contains(Visited, Module_path) orelse should_skip(
Module_path
) of
true ->
resolve_loop(Rest, Visited, Src_root, Client_root, Acc);
false ->
File_path = <<<<<<Src_root/binary, "/"/utf8>>/binary,
Module_path/binary>>/binary,
".gleam"/utf8>>,
Visited@1 = gleam@set:insert(Visited, Module_path),
case simplifile:read(File_path) of
{ok, Content} ->
case check_erlang_external(
Content,
Module_path,
Chain
) of
{ok, _} ->
Dest = <<<<<<Client_root/binary,
"/src/"/utf8>>/binary,
Module_path/binary>>/binary,
".gleam"/utf8>>,
File = {generated_file, Dest, Content},
Ffi_files = collect_ffi_files(
Src_root,
Client_root,
Module_path
),
New_chain = lists:append(
Chain,
[Module_path]
),
New_imports = gleam@list:map(
extract_imports(Content),
fun(Imp) -> {Imp, New_chain} end
),
resolve_loop(
lists:append(Rest, New_imports),
Visited@1,
Src_root,
Client_root,
lists:append([File | Ffi_files], Acc)
);
{error, Msg} ->
{error, Msg}
end;
_ ->
resolve_loop(
Rest,
Visited@1,
Src_root,
Client_root,
Acc
)
end
end
end.
-file("src/rally/dependency_resolver.gleam", 18).
-spec resolve(list({binary(), binary()}), binary(), binary()) -> {ok,
list(rally@generator@client:generated_file())} |
{error, binary()}.
resolve(Seed_sources, Src_root, Client_root) ->
Seed_modules = begin
_pipe = gleam@list:map(
Seed_sources,
fun(Pair) -> erlang:element(1, Pair) end
),
gleam@set:from_list(_pipe)
end,
Seed_imports = gleam@list:flat_map(
Seed_sources,
fun(Pair@1) ->
gleam@list:map(
extract_imports(erlang:element(2, Pair@1)),
fun(Imp) -> {Imp, [erlang:element(1, Pair@1)]} end
)
end
),
resolve_loop(Seed_imports, Seed_modules, Src_root, Client_root, []).