-module(libero@codegen). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/libero/codegen.gleam"). -export([write_file/2, extract_dir/1, ensure_parent_dir/1, last_split/2, module_to_mjs_path/1, module_to_underscored/1, to_pascal_case/1, endpoints_contain/2, variant_pattern/2, emit_client_msg_variants/1, collect_endpoint_type_imports/2, is_dict/1, is_option/1, import_if/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( " Cross-cutting helpers used by every codegen submodule.\n" "\n" " File I/O, path utilities, naming helpers, and small predicates over\n" " `field_type.FieldType` graphs. The actual generators live in the\n" " per-domain modules (codegen_dispatch, codegen_stubs, codegen_decoders,\n" " codegen_server).\n" ). -file("src/libero/codegen.gleam", 33). ?DOC( " Write content to a file, logging the path on success.\n" " Gleam files are run through `gleam format` before writing.\n" ). -spec write_file(binary(), binary()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_file(Path, Content) -> Formatted = case gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) of true -> libero@format:format_gleam(Content); false -> Content end, case simplifile:write(Path, Formatted) of {ok, _} -> gleam_stdlib:println(<<" wrote "/utf8, Path/binary>>), {ok, nil}; {error, Cause} -> {error, {cannot_write_file, Path, Cause}} end. -file("src/libero/codegen.gleam", 61). ?DOC( " Strip the final path segment, returning the directory portion of `path`.\n" " Returns \".\" when there's no parent (single-segment input).\n" ). -spec extract_dir(binary()) -> binary(). extract_dir(Path) -> case begin _pipe = gleam@string:split(Path, <<"/"/utf8>>), lists:reverse(_pipe) end of [_ | Rest_rev] -> case Rest_rev of [] -> <<"."/utf8>>; _ -> gleam@string:join(lists:reverse(Rest_rev), <<"/"/utf8>>) end; [] -> <<"."/utf8>> end. -file("src/libero/codegen.gleam", 54). ?DOC( " Create the parent directory for the given file path, ignoring any\n" " error. create_directory_all is idempotent (no error if the dir\n" " already exists) and any real write failure surfaces on the\n" " subsequent simplifile.write call.\n" ). -spec ensure_parent_dir(binary()) -> nil. ensure_parent_dir(Path) -> _ = simplifile:create_directory_all(extract_dir(Path)), nil. -file("src/libero/codegen.gleam", 77). ?DOC( " Take the substring after the LAST occurrence of `separator` in `input`.\n" " Falls back to `input` if the separator is absent. Splitting on the last\n" " occurrence (not the first) matters for nested paths like\n" " `vendor/x/src/inner/src/types`, where the leftmost match would yield the\n" " wrong segment.\n" ). -spec last_split(binary(), binary()) -> binary(). last_split(Input, Separator) -> case gleam@string:split(Input, Separator) of [_] -> Input; Parts -> _pipe = gleam@list:last(Parts), gleam@result:unwrap(_pipe, Input) end. -file("src/libero/codegen.gleam", 88). ?DOC( " Convert a Gleam module path like \"shared/discount\" to its compiled\n" " .mjs bundle path \"shared/shared/discount.mjs\". The first segment\n" " is the package name (Gleam convention) and is repeated because\n" " the bundle layout is `/.mjs`.\n" ). -spec module_to_mjs_path(binary()) -> binary(). module_to_mjs_path(Module_path) -> case gleam@string:split_once(Module_path, <<"/"/utf8>>) of {error, nil} -> <<<<<>/binary, Module_path/binary>>/binary, ".mjs"/utf8>>; {ok, {Package, _}} -> <<<<<>/binary, Module_path/binary>>/binary, ".mjs"/utf8>> end. -file("src/libero/codegen.gleam", 104). ?DOC( " Convert a Gleam module path like \"shared/discount\" to a flat\n" " underscore-separated alias suitable for use as a Gleam identifier\n" " or import alias suffix. e.g. \"shared/discount\" -> \"shared_discount\".\n" ). -spec module_to_underscored(binary()) -> binary(). module_to_underscored(Module_path) -> gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>). -file("src/libero/codegen.gleam", 110). ?DOC( " Convert a snake_case name to PascalCase.\n" " e.g. \"get_items\" -> \"GetItems\", \"create_item\" -> \"CreateItem\"\n" ). -spec to_pascal_case(binary()) -> binary(). to_pascal_case(Name) -> _pipe = Name, _pipe@1 = gleam@string:split(_pipe, <<"_"/utf8>>), _pipe@2 = gleam@list:map( _pipe@1, fun(Word) -> case gleam_stdlib:string_pop_grapheme(Word) of {ok, {First, Rest}} -> <<(string:uppercase(First))/binary, Rest/binary>>; {error, nil} -> Word end end ), gleam@string:join(_pipe@2, <<""/utf8>>). -file("src/libero/codegen.gleam", 127). ?DOC( " True if any endpoint's parameter or return type (transitively)\n" " satisfies `predicate`. Used for stdlib import detection\n" " (Option, Dict).\n" ). -spec endpoints_contain( list(libero@scanner:handler_endpoint()), fun((libero@field_type:field_type()) -> boolean()) ) -> boolean(). endpoints_contain(Endpoints, Predicate) -> gleam@list:any( Endpoints, fun(E) -> (libero@field_type:contains(erlang:element(4, E), Predicate) orelse libero@field_type:contains( erlang:element(5, E), Predicate )) orelse gleam@list:any( erlang:element(6, E), fun(P) -> libero@field_type:contains(erlang:element(2, P), Predicate) end ) end ). -file("src/libero/codegen.gleam", 141). ?DOC( " Emit a constructor / pattern shape like `Variant(label1:, label2:)` or\n" " just `Variant` when there are no parameters. Used by both the dispatch\n" " match arms (destructure) and client stubs (constructor call).\n" ). -spec variant_pattern( binary(), list({binary(), libero@field_type:field_type()}) ) -> binary(). variant_pattern(Variant_name, Params) -> case Params of [] -> Variant_name; _ -> Labels = gleam@list:map( Params, fun(P) -> <<(erlang:element(1, P))/binary, ":"/utf8>> end ), <<<<<>/binary, (gleam@string:join(Labels, <<", "/utf8>>))/binary>>/binary, ")"/utf8>> end. -file("src/libero/codegen.gleam", 159). ?DOC( " Emit the body lines of the generated `ClientMsg` type — one variant\n" " per endpoint, indented by two spaces. Both the server dispatch and\n" " the client stubs need this exact shape; routing both through this\n" " helper guarantees they stay in sync (mismatched variants would\n" " silently break the wire contract).\n" ). -spec emit_client_msg_variants(list(libero@scanner:handler_endpoint())) -> list(binary()). emit_client_msg_variants(Endpoints) -> gleam@list:map( Endpoints, fun(E) -> Variant_name = to_pascal_case(erlang:element(3, E)), case erlang:element(6, E) of [] -> <<" "/utf8, Variant_name/binary>>; Params -> Fields = gleam@list:map( Params, fun(P) -> {Label, Ft} = P, <<<