Current section

Files

Jump to
libero src libero@codegen.erl
Raw

src/libero@codegen.erl

-module(libero@codegen).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/libero/codegen.gleam").
-export([module_to_underscored/1, to_pascal_case/1, module_to_mjs_path/2, endpoints_contain/2, variant_pattern/2, emit_client_msg_variants/2, collect_endpoint_type_modules/2, collect_endpoint_type_imports/3, is_dict/1, is_option/1, build_alias_resolver/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 codegen submodules.\n"
"\n"
" Naming helpers and small predicates over `field_type.FieldType` graphs.\n"
).
-file("src/libero/codegen.gleam", 16).
?DOC(
" Convert a Gleam module path like \"shared/discount\" to a flat\n"
" underscore-separated alias. 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", 21).
?DOC(" Convert a snake_case name to PascalCase.\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", 36).
?DOC(
" Convert a Gleam module path to its compiled .mjs bundle path.\n"
" The package name is the Gleam package that owns the module (determines\n"
" the top-level directory in the JS build output).\n"
).
-spec module_to_mjs_path(binary(), binary()) -> binary().
module_to_mjs_path(Module_path, Package) ->
<<<<<<Package/binary, "/"/utf8>>/binary, Module_path/binary>>/binary,
".mjs"/utf8>>.
-file("src/libero/codegen.gleam", 47).
?DOC(
" True if any endpoint's parameter or return type (transitively)\n"
" satisfies `predicate`.\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", 60).
?DOC(
" Emit a constructor / pattern shape like `Variant(label1:, label2:)` or\n"
" just `Variant` when there are no parameters.\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
),
<<<<<<Variant_name/binary, "("/utf8>>/binary,
(gleam@string:join(Labels, <<", "/utf8>>))/binary>>/binary,
")"/utf8>>
end.
-file("src/libero/codegen.gleam", 77).
?DOC(
" Emit the body lines of the generated `ClientMsg` type. Uses\n"
" `resolve_alias` to qualify user-defined types with the correct\n"
" import alias (needed when multiple modules share the same last\n"
" segment, e.g. two different `id_` modules).\n"
).
-spec emit_client_msg_variants(
list(libero@scanner:handler_endpoint()),
fun((binary()) -> binary())
) -> list(binary()).
emit_client_msg_variants(Endpoints, Resolve_alias) ->
gleam@list:map(
Endpoints,
fun(E) ->
Variant_name = to_pascal_case(
<<"server_"/utf8, (erlang:element(3, E))/binary>>
),
case erlang:element(6, E) of
[] ->
<<" "/utf8, Variant_name/binary>>;
Params ->
Fields = gleam@list:map(
Params,
fun(P) ->
{Label, Ft} = P,
<<<<Label/binary, ": "/utf8>>/binary,
(libero@field_type:to_gleam_source_with_alias(
Ft,
Resolve_alias
))/binary>>
end
),
<<<<<<<<" "/utf8, Variant_name/binary>>/binary, "("/utf8>>/binary,
(gleam@string:join(Fields, <<", "/utf8>>))/binary>>/binary,
")"/utf8>>
end
end
).
-file("src/libero/codegen.gleam", 121).
?DOC(
" Collect every module path referenced by endpoint parameter types and,\n"
" optionally, return types. Kept separate from import rendering so callers\n"
" can compare exact module paths before aliases and strings enter the mix.\n"
).
-spec collect_endpoint_type_modules(
list(libero@scanner:handler_endpoint()),
boolean()
) -> list(binary()).
collect_endpoint_type_modules(Endpoints, Include_return) ->
_pipe = Endpoints,
_pipe@3 = gleam@list:flat_map(
_pipe,
fun(E) ->
From_params = gleam@list:flat_map(
erlang:element(6, E),
fun(P) ->
libero@field_type:collect_user_types(erlang:element(2, P))
end
),
case Include_return of
true ->
_pipe@1 = From_params,
_pipe@2 = lists:append(
_pipe@1,
libero@field_type:collect_user_types(
erlang:element(4, E)
)
),
lists:append(
_pipe@2,
libero@field_type:collect_user_types(
erlang:element(5, E)
)
);
false ->
From_params
end
end
),
_pipe@4 = gleam@list:map(_pipe@3, fun(Ref) -> erlang:element(1, Ref) end),
_pipe@5 = gleam@list:unique(_pipe@4),
gleam@list:sort(_pipe@5, fun gleam@string:compare/2).
-file("src/libero/codegen.gleam", 103).
?DOC(
" Collect `import <module>` lines for every module path referenced\n"
" (transitively) by the endpoints' parameter types and, optionally,\n"
" return types. Uses aliases from `resolve_alias` so that modules\n"
" with the same last segment get distinct names.\n"
).
-spec collect_endpoint_type_imports(
list(libero@scanner:handler_endpoint()),
boolean(),
fun((binary()) -> binary())
) -> list(binary()).
collect_endpoint_type_imports(Endpoints, Include_return, Resolve_alias) ->
_pipe = collect_endpoint_type_modules(Endpoints, Include_return),
gleam@list:map(
_pipe,
fun(Mod) ->
Alias = Resolve_alias(Mod),
case Alias =:= libero@field_type:last_segment(Mod) of
true ->
<<"import "/utf8, Mod/binary>>;
false ->
<<<<<<"import "/utf8, Mod/binary>>/binary, " as "/utf8>>/binary,
Alias/binary>>
end
end
).
-file("src/libero/codegen.gleam", 142).
-spec is_dict(libero@field_type:field_type()) -> boolean().
is_dict(Ft) ->
case Ft of
{dict_of, _, _} ->
true;
_ ->
false
end.
-file("src/libero/codegen.gleam", 149).
-spec is_option(libero@field_type:field_type()) -> boolean().
is_option(Ft) ->
case Ft of
{option_of, _} ->
true;
_ ->
false
end.
-file("src/libero/codegen.gleam", 159).
?DOC(
" Build a resolver function that maps a module path to its import alias.\n"
" Uses the last segment when unique, or the full underscored path when\n"
" two or more modules share the same last segment.\n"
).
-spec build_alias_resolver(list(libero@scanner:handler_endpoint())) -> fun((binary()) -> binary()).
build_alias_resolver(Endpoints) ->
All_modules = begin
_pipe = Endpoints,
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(E) ->
From_params = gleam@list:flat_map(
erlang:element(6, E),
fun(P) ->
libero@field_type:collect_user_types(
erlang:element(2, P)
)
end
),
From_return = lists:append(
libero@field_type:collect_user_types(erlang:element(4, E)),
libero@field_type:collect_user_types(erlang:element(5, E))
),
lists:append(From_params, From_return)
end
),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Ref) -> erlang:element(1, Ref) end
),
gleam@list:unique(_pipe@2)
end,
Segment_counts = gleam@list:fold(
All_modules,
maps:new(),
fun(Acc, Mod) ->
Seg = libero@field_type:last_segment(Mod),
Count = case gleam_stdlib:map_get(Acc, Seg) of
{ok, N} ->
N + 1;
{error, nil} ->
1
end,
gleam@dict:insert(Acc, Seg, Count)
end
),
fun(Module_path) ->
Seg@1 = libero@field_type:last_segment(Module_path),
case gleam_stdlib:map_get(Segment_counts, Seg@1) of
{ok, N@1} when N@1 > 1 ->
module_to_underscored(Module_path);
_ ->
Seg@1
end
end.
-file("src/libero/codegen.gleam", 197).
?DOC(
" Emit `import_line` (with a leading newline) iff any endpoint type\n"
" transitively satisfies `predicate`; otherwise the empty string.\n"
).
-spec import_if(
list(libero@scanner:handler_endpoint()),
fun((libero@field_type:field_type()) -> boolean()),
binary()
) -> binary().
import_if(Endpoints, Predicate, Import_line) ->
gleam@bool:guard(
not endpoints_contain(Endpoints, Predicate),
<<""/utf8>>,
fun() -> <<"\n"/utf8, Import_line/binary>> end
).