-module(libero@source). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/libero/source.gleam"). -export([walk_directory/1, derive_module_path/1, parse_module/1, build_type_import_map/1, build_type_alias_originals/1, build_alias_resolution_map/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. ?MODULEDOC( " Source-file helpers for seed-driven type discovery.\n" "\n" " Frameworks such as Rally decide which types cross the boundary and pass\n" " those seeds to Libero.\n" ). -file("src/libero/source.gleam", 59). -spec visit_file(list(binary()), binary(), binary()) -> list(binary()). visit_file(Acc, Entry, Child) -> gleam@bool:guard( not gleam_stdlib:string_ends_with(Entry, <<".gleam"/utf8>>), Acc, fun() -> [Child | Acc] end ). -file("src/libero/source.gleam", 49). -spec visit_subdirectory(list(binary()), binary(), binary()) -> {ok, list(binary())} | {error, libero@gen_error:gen_error()}. visit_subdirectory(Acc, Entry, Child) -> gleam@bool:guard( Entry =:= <<"generated"/utf8>>, {ok, Acc}, fun() -> gleam@result:'try'( walk_directory(Child), fun(Nested) -> {ok, lists:append(Nested, Acc)} end ) end ). -file("src/libero/source.gleam", 34). -spec visit_entry(list(binary()), binary(), binary()) -> {ok, list(binary())} | {error, libero@gen_error:gen_error()}. visit_entry(Acc, Parent, Entry) -> Child = <<<>/binary, Entry/binary>>, Is_symlink = begin _pipe = simplifile_erl:is_symlink(Child), gleam@result:unwrap(_pipe, false) end, Is_dir = gleam@result:unwrap(simplifile_erl:is_directory(Child), false), gleam@bool:guard( Is_symlink andalso Is_dir, {ok, Acc}, fun() -> case Is_dir of true -> visit_subdirectory(Acc, Entry, Child); false -> {ok, visit_file(Acc, Entry, Child)} end end ). -file("src/libero/source.gleam", 21). ?DOC( " Recursively walk a directory, returning every `.gleam` file found.\n" " Skips any subdirectory named `generated`.\n" ). -spec walk_directory(binary()) -> {ok, list(binary())} | {error, libero@gen_error:gen_error()}. walk_directory(Path) -> gleam@result:'try'( begin _pipe = simplifile_erl:read_directory(Path), gleam@result:map_error( _pipe, fun(Cause) -> {cannot_read_dir, Path, Cause} end ) end, fun(Entries) -> gleam@result:map( gleam@list:try_fold( Entries, [], fun(Acc, Entry) -> visit_entry(Acc, Path, Entry) end ), fun(Files) -> gleam@list:sort(Files, fun gleam@string:compare/2) end ) end ). -file("src/libero/source.gleam", 71). ?DOC( " Derive the Gleam module path from a file path by finding the last\n" " occurrence of `/src/` and taking everything after it, then stripping\n" " the `.gleam` extension.\n" ). -spec derive_module_path(binary()) -> binary(). derive_module_path(File_path) -> Without_extension = case gleam_stdlib:string_ends_with( File_path, <<".gleam"/utf8>> ) of true -> gleam@string:slice( File_path, 0, string:length(File_path) - string:length(<<".gleam"/utf8>>) ); false -> File_path end, case gleam@string:split(Without_extension, <<"/src/"/utf8>>) of [_] -> Without_extension; Parts -> _pipe = gleam@list:last(Parts), gleam@result:unwrap(_pipe, Without_extension) end. -file("src/libero/source.gleam", 89). ?DOC( " Read a `.gleam` file and parse it via `glance`, surfacing both I/O and\n" " parser failures as `GenError` variants tagged with the file path.\n" ). -spec parse_module(binary()) -> {ok, glance:module_()} | {error, libero@gen_error:gen_error()}. parse_module(File_path) -> gleam@result:'try'( begin _pipe = simplifile:read(File_path), gleam@result:map_error( _pipe, fun(Cause) -> {cannot_read_file, File_path, Cause} end ) end, fun(Content) -> _pipe@1 = glance:module(Content), gleam@result:map_error( _pipe@1, fun(Cause@1) -> {parse_failed, File_path, Cause@1} end ) end ). -file("src/libero/source.gleam", 102). ?DOC( " Build a map from unqualified type names to the full module path of their\n" " import.\n" ). -spec build_type_import_map(list(glance:definition(glance:import()))) -> gleam@dict:dict(binary(), binary()). build_type_import_map(Imports) -> gleam@list:fold( Imports, maps:new(), fun(Acc, Def) -> {definition, _, Imp} = Def, gleam@list:fold( erlang:element(5, Imp), Acc, fun(Inner_acc, Uq) -> Key = case erlang:element(3, Uq) of {some, Alias} -> Alias; none -> erlang:element(2, Uq) end, gleam@dict:insert(Inner_acc, Key, erlang:element(3, Imp)) end ) end ). -file("src/libero/source.gleam", 119). ?DOC( " Map locally-bound type names back to their original names from the source\n" " module. Only populated when an import uses `type X as Y`.\n" ). -spec build_type_alias_originals(list(glance:definition(glance:import()))) -> gleam@dict:dict(binary(), binary()). build_type_alias_originals(Imports) -> gleam@list:fold( Imports, maps:new(), fun(Acc, Def) -> {definition, _, Imp} = Def, gleam@list:fold( erlang:element(5, Imp), Acc, fun(Inner_acc, Uq) -> case erlang:element(3, Uq) of {some, Alias} -> gleam@dict:insert( Inner_acc, Alias, erlang:element(2, Uq) ); none -> Inner_acc end end ) end ). -file("src/libero/source.gleam", 135). ?DOC( " Build a map from import aliases and bare module names to the full module\n" " path.\n" ). -spec build_alias_resolution_map(list(glance:definition(glance:import()))) -> gleam@dict:dict(binary(), binary()). build_alias_resolution_map(Imports) -> gleam@list:fold( Imports, maps:new(), fun(Acc, Def) -> {definition, _, Imp} = Def, Last_seg = libero@field_type:last_segment(erlang:element(3, Imp)), Alias = case erlang:element(4, Imp) of {some, {named, Name}} -> Name; _ -> Last_seg end, gleam@dict:insert(Acc, Alias, erlang:element(3, Imp)) end ).