Current section
Files
Jump to
Current section
Files
src/ghtml@codegen.erl
-module(ghtml@codegen).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ghtml/codegen.gleam").
-export([generate/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(
" Gleam code generator from parsed templates.\n"
"\n"
" Transforms the parsed AST into valid Gleam source code that uses the\n"
" Lustre library to render HTML elements. Generated code is formatted\n"
" to be compliant with `gleam format`.\n"
).
-file("src/ghtml/codegen.gleam", 69).
?DOC(" Extract the filename from a full path\n").
-spec extract_filename(binary()) -> binary().
extract_filename(Path) ->
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
_pipe@2 = gleam@list:last(_pipe@1),
gleam@result:unwrap(_pipe@2, <<"unknown.ghtml"/utf8>>).
-file("src/ghtml/codegen.gleam", 167).
?DOC(" Check if a node or its children need `none`\n").
-spec node_needs_none(ghtml@types:node_()) -> boolean().
node_needs_none(Node) ->
case Node of
{if_node, _, Then_branch, Else_branch, _} ->
case Else_branch of
[] ->
true;
_ ->
gleam@list:any(Then_branch, fun node_needs_none/1) orelse gleam@list:any(
Else_branch,
fun node_needs_none/1
)
end;
{each_node, _, _, _, Body, _} ->
gleam@list:any(Body, fun node_needs_none/1);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(erlang:element(3, B), fun node_needs_none/1)
end
);
{element, _, _, Children, _} ->
gleam@list:any(Children, fun node_needs_none/1);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_needs_none/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 162).
?DOC(" Check if template needs `none` import (if without else)\n").
-spec template_needs_none(list(ghtml@types:node_())) -> boolean().
template_needs_none(Nodes) ->
gleam@list:any(Nodes, fun node_needs_none/1).
-file("src/ghtml/codegen.gleam", 192).
?DOC(" Check if a node or its children need `fragment`\n").
-spec node_needs_fragment(ghtml@types:node_()) -> boolean().
node_needs_fragment(Node) ->
case Node of
{if_node, _, Then_branch, Else_branch, _} ->
Then_needs = erlang:length(Then_branch) > 1,
Else_needs = erlang:length(Else_branch) > 1,
((Then_needs orelse Else_needs) orelse gleam@list:any(
Then_branch,
fun node_needs_fragment/1
))
orelse gleam@list:any(Else_branch, fun node_needs_fragment/1);
{each_node, _, _, _, Body, _} ->
(erlang:length(Body) > 1) orelse gleam@list:any(
Body,
fun node_needs_fragment/1
);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
(erlang:length(erlang:element(3, B)) > 1) orelse gleam@list:any(
erlang:element(3, B),
fun node_needs_fragment/1
)
end
);
{element, _, _, Children, _} ->
gleam@list:any(Children, fun node_needs_fragment/1);
{fragment, _, _} ->
true;
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 186).
?DOC(" Check if template needs `fragment` import (multiple roots or multiple children in branches)\n").
-spec template_needs_fragment(list(ghtml@types:node_())) -> boolean().
template_needs_fragment(Nodes) ->
(erlang:length(Nodes) > 1) orelse gleam@list:any(
Nodes,
fun node_needs_fragment/1
).
-file("src/ghtml/codegen.gleam", 220).
?DOC(" Check if user has imported a specific module\n").
-spec has_user_import(list(binary()), binary()) -> boolean().
has_user_import(Imports, Module) ->
gleam@list:any(
Imports,
fun(Imp) ->
(gleam_stdlib:string_starts_with(Imp, <<Module/binary, "."/utf8>>)
orelse gleam_stdlib:string_starts_with(
Imp,
<<Module/binary, "{"/utf8>>
))
orelse (Imp =:= Module)
end
).
-file("src/ghtml/codegen.gleam", 234).
?DOC(" Check if a node or its children need `text`\n").
-spec node_needs_text(ghtml@types:node_()) -> boolean().
node_needs_text(Node) ->
case Node of
{text_node, _, _} ->
true;
{expr_node, _, _} ->
true;
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_needs_text/1) orelse gleam@list:any(
Else_branch,
fun node_needs_text/1
);
{each_node, _, _, _, Body, _} ->
gleam@list:any(Body, fun node_needs_text/1);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(erlang:element(3, B), fun node_needs_text/1)
end
);
{element, _, _, Children, _} ->
gleam@list:any(Children, fun node_needs_text/1);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_needs_text/1)
end.
-file("src/ghtml/codegen.gleam", 229).
?DOC(" Check if template needs `text` import\n").
-spec template_needs_text(list(ghtml@types:node_())) -> boolean().
template_needs_text(Nodes) ->
gleam@list:any(Nodes, fun node_needs_text/1).
-file("src/ghtml/codegen.gleam", 255).
?DOC(" Check if a node or its children have each nodes\n").
-spec node_has_each(ghtml@types:node_()) -> boolean().
node_has_each(Node) ->
case Node of
{each_node, _, _, _, Body, _} ->
true orelse gleam@list:any(Body, fun node_has_each/1);
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_has_each/1) orelse gleam@list:any(
Else_branch,
fun node_has_each/1
);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(erlang:element(3, B), fun node_has_each/1)
end
);
{element, _, _, Children, _} ->
gleam@list:any(Children, fun node_has_each/1);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_has_each/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 250).
?DOC(" Check if any nodes have each nodes\n").
-spec template_has_each(list(ghtml@types:node_())) -> boolean().
template_has_each(Nodes) ->
gleam@list:any(Nodes, fun node_has_each/1).
-file("src/ghtml/codegen.gleam", 275).
?DOC(" Check if a node or its children have each nodes with index\n").
-spec node_has_each_with_index(ghtml@types:node_()) -> boolean().
node_has_each_with_index(Node) ->
case Node of
{each_node, _, _, {some, _}, Body, _} ->
true orelse gleam@list:any(Body, fun node_has_each_with_index/1);
{each_node, _, _, none, Body@1, _} ->
gleam@list:any(Body@1, fun node_has_each_with_index/1);
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_has_each_with_index/1) orelse gleam@list:any(
Else_branch,
fun node_has_each_with_index/1
);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(
erlang:element(3, B),
fun node_has_each_with_index/1
)
end
);
{element, _, _, Children, _} ->
gleam@list:any(Children, fun node_has_each_with_index/1);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_has_each_with_index/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 270).
?DOC(" Check if any nodes have each nodes with index\n").
-spec template_has_each_with_index(list(ghtml@types:node_())) -> boolean().
template_has_each_with_index(Nodes) ->
gleam@list:any(Nodes, fun node_has_each_with_index/1).
-file("src/ghtml/codegen.gleam", 315).
?DOC(" Check if attrs list contains non-event attributes\n").
-spec has_non_event_attrs(list(ghtml@types:attr())) -> boolean().
has_non_event_attrs(Attrs) ->
gleam@list:any(Attrs, fun(Attr) -> case Attr of
{static_attr, _, _} ->
true;
{dynamic_attr, _, _} ->
true;
{boolean_attr, _} ->
true;
{event_attr, _, _} ->
false
end end).
-file("src/ghtml/codegen.gleam", 299).
?DOC(" Check if a node or its children have attributes\n").
-spec node_has_attrs(ghtml@types:node_()) -> boolean().
node_has_attrs(Node) ->
case Node of
{element, _, Attrs, Children, _} ->
has_non_event_attrs(Attrs) orelse gleam@list:any(
Children,
fun node_has_attrs/1
);
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_has_attrs/1) orelse gleam@list:any(
Else_branch,
fun node_has_attrs/1
);
{each_node, _, _, _, Body, _} ->
gleam@list:any(Body, fun node_has_attrs/1);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(erlang:element(3, B), fun node_has_attrs/1)
end
);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_has_attrs/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 294).
?DOC(" Check if any nodes have attributes\n").
-spec template_has_attrs(list(ghtml@types:node_())) -> boolean().
template_has_attrs(Nodes) ->
gleam@list:any(Nodes, fun node_has_attrs/1).
-file("src/ghtml/codegen.gleam", 348).
?DOC(" Check if attrs list contains event attributes\n").
-spec has_event_attrs(list(ghtml@types:attr())) -> boolean().
has_event_attrs(Attrs) ->
gleam@list:any(Attrs, fun(Attr) -> case Attr of
{event_attr, _, _} ->
true;
_ ->
false
end end).
-file("src/ghtml/codegen.gleam", 332).
?DOC(" Check if a node or its children have events\n").
-spec node_has_events(ghtml@types:node_()) -> boolean().
node_has_events(Node) ->
case Node of
{element, _, Attrs, Children, _} ->
has_event_attrs(Attrs) orelse gleam@list:any(
Children,
fun node_has_events/1
);
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_has_events/1) orelse gleam@list:any(
Else_branch,
fun node_has_events/1
);
{each_node, _, _, _, Body, _} ->
gleam@list:any(Body, fun node_has_events/1);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(erlang:element(3, B), fun node_has_events/1)
end
);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_has_events/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 327).
?DOC(" Check if any nodes have event attributes\n").
-spec template_has_events(list(ghtml@types:node_())) -> boolean().
template_has_events(Nodes) ->
gleam@list:any(Nodes, fun node_has_events/1).
-file("src/ghtml/codegen.gleam", 358).
?DOC(" Check if a tag is a custom element (contains a hyphen)\n").
-spec is_custom_element(binary()) -> boolean().
is_custom_element(Tag) ->
gleam_stdlib:contains_string(Tag, <<"-"/utf8>>).
-file("src/ghtml/codegen.gleam", 368).
?DOC(" Check if a node or its children have custom elements\n").
-spec node_has_custom_elements(ghtml@types:node_()) -> boolean().
node_has_custom_elements(Node) ->
case Node of
{element, Tag, _, Children, _} ->
is_custom_element(Tag) orelse gleam@list:any(
Children,
fun node_has_custom_elements/1
);
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_has_custom_elements/1) orelse gleam@list:any(
Else_branch,
fun node_has_custom_elements/1
);
{each_node, _, _, _, Body, _} ->
gleam@list:any(Body, fun node_has_custom_elements/1);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(
erlang:element(3, B),
fun node_has_custom_elements/1
)
end
);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_has_custom_elements/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 363).
?DOC(" Check if any nodes have custom elements\n").
-spec template_has_custom_elements(list(ghtml@types:node_())) -> boolean().
template_has_custom_elements(Nodes) ->
gleam@list:any(Nodes, fun node_has_custom_elements/1).
-file("src/ghtml/codegen.gleam", 391).
?DOC(" Check if a node or its children have standard HTML elements\n").
-spec node_has_html_elements(ghtml@types:node_()) -> boolean().
node_has_html_elements(Node) ->
case Node of
{element, Tag, _, Children, _} ->
not is_custom_element(Tag) orelse gleam@list:any(
Children,
fun node_has_html_elements/1
);
{if_node, _, Then_branch, Else_branch, _} ->
gleam@list:any(Then_branch, fun node_has_html_elements/1) orelse gleam@list:any(
Else_branch,
fun node_has_html_elements/1
);
{each_node, _, _, _, Body, _} ->
gleam@list:any(Body, fun node_has_html_elements/1);
{case_node, _, Branches, _} ->
gleam@list:any(
Branches,
fun(B) ->
gleam@list:any(
erlang:element(3, B),
fun node_has_html_elements/1
)
end
);
{fragment, Children@1, _} ->
gleam@list:any(Children@1, fun node_has_html_elements/1);
_ ->
false
end.
-file("src/ghtml/codegen.gleam", 386).
?DOC(" Check if any nodes have standard HTML elements (non-custom)\n").
-spec template_has_html_elements(list(ghtml@types:node_())) -> boolean().
template_has_html_elements(Nodes) ->
gleam@list:any(Nodes, fun node_has_html_elements/1).
-file("src/ghtml/codegen.gleam", 77).
?DOC(" Generate import statements for the generated module\n").
-spec generate_imports(ghtml@types:template()) -> binary().
generate_imports(Template) ->
Needs_attrs = template_has_attrs(erlang:element(4, Template)),
Needs_events = template_has_events(erlang:element(4, Template)),
Needs_none = template_needs_none(erlang:element(4, Template)),
Needs_fragment = template_needs_fragment(erlang:element(4, Template)),
Needs_each = template_has_each(erlang:element(4, Template)),
Needs_each_index = template_has_each_with_index(erlang:element(4, Template)),
Needs_element = template_has_custom_elements(erlang:element(4, Template)),
Needs_html = template_has_html_elements(erlang:element(4, Template)),
Needs_text = template_needs_text(erlang:element(4, Template)),
User_imports = erlang:element(2, Template),
Element_items = [<<"type Element"/utf8>>],
Element_items@1 = case Needs_text of
true ->
lists:append(Element_items, [<<"text"/utf8>>]);
false ->
Element_items
end,
Element_items@2 = case Needs_element of
true ->
lists:append(Element_items@1, [<<"element"/utf8>>]);
false ->
Element_items@1
end,
Element_items@3 = case Needs_none of
true ->
lists:append(Element_items@2, [<<"none"/utf8>>]);
false ->
Element_items@2
end,
Element_items@4 = case Needs_fragment of
true ->
lists:append(Element_items@3, [<<"fragment"/utf8>>]);
false ->
Element_items@3
end,
Element_import = <<<<"import lustre/element.{"/utf8,
(gleam@string:join(Element_items@4, <<", "/utf8>>))/binary>>/binary,
"}"/utf8>>,
Base_imports = case Needs_html of
true ->
<<Element_import/binary, "\nimport lustre/element/html"/utf8>>;
false ->
Element_import
end,
Base_imports@1 = case Needs_each of
true ->
<<Base_imports/binary, "\nimport lustre/element/keyed"/utf8>>;
false ->
Base_imports
end,
Imports = case {Needs_attrs, Needs_events} of
{true, true} ->
<<Base_imports@1/binary,
"\nimport lustre/attribute\nimport lustre/event"/utf8>>;
{true, false} ->
<<Base_imports@1/binary, "\nimport lustre/attribute"/utf8>>;
{false, true} ->
<<Base_imports@1/binary, "\nimport lustre/event"/utf8>>;
{false, false} ->
Base_imports@1
end,
Imports@1 = case Needs_each andalso not has_user_import(
User_imports,
<<"gleam/list"/utf8>>
) of
true ->
<<Imports/binary, "\nimport gleam/list"/utf8>>;
false ->
Imports
end,
Imports@2 = case Needs_each_index andalso not has_user_import(
User_imports,
<<"gleam/int"/utf8>>
) of
true ->
<<Imports@1/binary, "\nimport gleam/int"/utf8>>;
false ->
Imports@1
end,
Imports@3 = case User_imports of
[] ->
Imports@2;
_ ->
User_import_lines = begin
_pipe = gleam@list:map(
User_imports,
fun(Imp) -> <<"import "/utf8, Imp/binary>> end
),
gleam@string:join(_pipe, <<"\n"/utf8>>)
end,
<<<<Imports@2/binary, "\n"/utf8>>/binary, User_import_lines/binary>>
end,
Imports@3.
-file("src/ghtml/codegen.gleam", 452).
?DOC(" Generate function parameters from template params (single line, no trailing comma)\n").
-spec generate_params(list({binary(), binary()})) -> binary().
generate_params(Params) ->
_pipe = Params,
_pipe@1 = gleam@list:map(
_pipe,
fun(P) ->
<<<<(erlang:element(1, P))/binary, ": "/utf8>>/binary,
(erlang:element(2, P))/binary>>
end
),
gleam@string:join(_pipe@1, <<", "/utf8>>).
-file("src/ghtml/codegen.gleam", 572).
?DOC(" Generate code for an event handler attribute\n").
-spec generate_event_attr(binary(), binary()) -> binary().
generate_event_attr(Event, Handler) ->
case Event of
<<"click"/utf8>> ->
<<<<"event.on_click("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"input"/utf8>> ->
<<<<"event.on_input("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"change"/utf8>> ->
<<<<"event.on_change("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"submit"/utf8>> ->
<<<<"event.on_submit("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"blur"/utf8>> ->
<<<<"event.on_blur("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"focus"/utf8>> ->
<<<<"event.on_focus("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"keydown"/utf8>> ->
<<<<"event.on_keydown("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"keyup"/utf8>> ->
<<<<"event.on_keyup("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"keypress"/utf8>> ->
<<<<"event.on_keypress("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"mouseenter"/utf8>> ->
<<<<"event.on_mouse_enter("/utf8, Handler/binary>>/binary,
")"/utf8>>;
<<"mouseleave"/utf8>> ->
<<<<"event.on_mouse_leave("/utf8, Handler/binary>>/binary,
")"/utf8>>;
<<"mouseover"/utf8>> ->
<<<<"event.on_mouse_over("/utf8, Handler/binary>>/binary, ")"/utf8>>;
<<"mouseout"/utf8>> ->
<<<<"event.on_mouse_out("/utf8, Handler/binary>>/binary, ")"/utf8>>;
_ ->
Event_name = case gleam_stdlib:string_starts_with(
Event,
<<"on:"/utf8>>
) of
true ->
gleam@string:drop_start(Event, 3);
false ->
Event
end,
<<<<<<<<"event.on(\""/utf8, Event_name/binary>>/binary,
"\", "/utf8>>/binary,
Handler/binary>>/binary,
")"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 631).
?DOC(" Escape special characters in a string for Gleam code\n").
-spec escape_string(binary()) -> binary().
escape_string(S) ->
_pipe = S,
_pipe@1 = gleam@string:replace(_pipe, <<"\\"/utf8>>, <<"\\\\"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"\\\""/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<"\n"/utf8>>, <<"\\n"/utf8>>),
_pipe@4 = gleam@string:replace(_pipe@3, <<"\r"/utf8>>, <<"\\r"/utf8>>),
gleam@string:replace(_pipe@4, <<"\t"/utf8>>, <<"\\t"/utf8>>).
-file("src/ghtml/codegen.gleam", 651).
?DOC(" Helper function to collapse consecutive spaces/tabs (not newlines)\n").
-spec collapse_spaces(list(binary()), boolean(), list(binary())) -> list(binary()).
collapse_spaces(Chars, Saw_space, Acc) ->
case Chars of
[] ->
Acc;
[C | Rest] ->
case C of
<<" "/utf8>> ->
case Saw_space of
true ->
collapse_spaces(Rest, true, Acc);
false ->
collapse_spaces(Rest, true, [<<" "/utf8>> | Acc])
end;
<<"\t"/utf8>> ->
case Saw_space of
true ->
collapse_spaces(Rest, true, Acc);
false ->
collapse_spaces(Rest, true, [<<" "/utf8>> | Acc])
end;
<<"\n"/utf8>> ->
collapse_spaces(Rest, false, [C | Acc]);
<<"\r"/utf8>> ->
collapse_spaces(Rest, false, [C | Acc]);
_ ->
collapse_spaces(Rest, false, [C | Acc])
end
end.
-file("src/ghtml/codegen.gleam", 642).
?DOC(
" Normalize whitespace by collapsing multiple spaces/tabs to single spaces\n"
" Newlines are preserved (they will be escaped later)\n"
).
-spec normalize_whitespace(binary()) -> binary().
normalize_whitespace(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:to_graphemes(_pipe),
_pipe@2 = collapse_spaces(_pipe@1, false, []),
_pipe@3 = lists:reverse(_pipe@2),
erlang:list_to_binary(_pipe@3).
-file("src/ghtml/codegen.gleam", 676).
?DOC(" Check if a string is blank (empty or only whitespace)\n").
-spec is_blank(binary()) -> boolean().
is_blank(Text) ->
gleam@string:trim(Text) =:= <<""/utf8>>.
-file("src/ghtml/codegen.gleam", 609).
?DOC(" Generate code for a text node with whitespace normalization (inline)\n").
-spec generate_text_inline(binary()) -> binary().
generate_text_inline(Content) ->
Normalized = normalize_whitespace(Content),
case is_blank(Normalized) of
true ->
<<""/utf8>>;
false ->
<<<<"text(\""/utf8, (escape_string(Normalized))/binary>>/binary,
"\")"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 409).
?DOC(" Check if a tag is a void element (no children allowed)\n").
-spec is_void_element(binary()) -> boolean().
is_void_element(Tag) ->
gleam@list:contains(
[<<"area"/utf8>>,
<<"base"/utf8>>,
<<"br"/utf8>>,
<<"col"/utf8>>,
<<"embed"/utf8>>,
<<"hr"/utf8>>,
<<"img"/utf8>>,
<<"input"/utf8>>,
<<"link"/utf8>>,
<<"meta"/utf8>>,
<<"param"/utf8>>,
<<"source"/utf8>>,
<<"track"/utf8>>,
<<"wbr"/utf8>>],
Tag
).
-file("src/ghtml/codegen.gleam", 599).
?DOC(" Find the Lustre function for a known attribute\n").
-spec find_attr_function(binary()) -> {ok, binary()} | {error, nil}.
find_attr_function(Name) ->
gleam@list:find_map(
[{<<"class"/utf8>>, <<"attribute.class"/utf8>>},
{<<"id"/utf8>>, <<"attribute.id"/utf8>>},
{<<"href"/utf8>>, <<"attribute.href"/utf8>>},
{<<"src"/utf8>>, <<"attribute.src"/utf8>>},
{<<"alt"/utf8>>, <<"attribute.alt"/utf8>>},
{<<"type"/utf8>>, <<"attribute.type_"/utf8>>},
{<<"value"/utf8>>, <<"attribute.value"/utf8>>},
{<<"name"/utf8>>, <<"attribute.name"/utf8>>},
{<<"placeholder"/utf8>>, <<"attribute.placeholder"/utf8>>},
{<<"disabled"/utf8>>, <<"attribute.disabled"/utf8>>},
{<<"readonly"/utf8>>, <<"attribute.readonly"/utf8>>},
{<<"checked"/utf8>>, <<"attribute.checked"/utf8>>},
{<<"selected"/utf8>>, <<"attribute.selected"/utf8>>},
{<<"autofocus"/utf8>>, <<"attribute.autofocus"/utf8>>},
{<<"for"/utf8>>, <<"attribute.for"/utf8>>},
{<<"role"/utf8>>, <<"attribute.role"/utf8>>},
{<<"style"/utf8>>, <<"attribute.style"/utf8>>},
{<<"width"/utf8>>, <<"attribute.width"/utf8>>},
{<<"height"/utf8>>, <<"attribute.height"/utf8>>},
{<<"title"/utf8>>, <<"attribute.title"/utf8>>},
{<<"target"/utf8>>, <<"attribute.target"/utf8>>},
{<<"rel"/utf8>>, <<"attribute.rel"/utf8>>},
{<<"action"/utf8>>, <<"attribute.action"/utf8>>},
{<<"method"/utf8>>, <<"attribute.method"/utf8>>},
{<<"required"/utf8>>, <<"attribute.required"/utf8>>}],
fun(Pair) -> case erlang:element(1, Pair) =:= Name of
true ->
{ok, erlang:element(2, Pair)};
false ->
{error, nil}
end end
).
-file("src/ghtml/codegen.gleam", 533).
?DOC(" Generate code for a static attribute\n").
-spec generate_static_attr(binary(), binary()) -> binary().
generate_static_attr(Name, Value) ->
case find_attr_function(Name) of
{ok, Func} ->
<<<<<<Func/binary, "(\""/utf8>>/binary,
(escape_string(Value))/binary>>/binary,
"\")"/utf8>>;
{error, _} ->
<<<<<<<<"attribute.attribute(\""/utf8, Name/binary>>/binary,
"\", \""/utf8>>/binary,
(escape_string(Value))/binary>>/binary,
"\")"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 546).
?DOC(" Generate code for a dynamic attribute\n").
-spec generate_dynamic_attr(binary(), binary()) -> binary().
generate_dynamic_attr(Name, Expr) ->
case find_attr_function(Name) of
{ok, Func} ->
<<<<<<Func/binary, "("/utf8>>/binary, Expr/binary>>/binary,
")"/utf8>>;
{error, _} ->
<<<<<<<<"attribute.attribute(\""/utf8, Name/binary>>/binary,
"\", "/utf8>>/binary,
Expr/binary>>/binary,
")"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 554).
?DOC(" Generate code for a boolean attribute\n").
-spec generate_boolean_attr(binary(), boolean()) -> binary().
generate_boolean_attr(Name, Is_custom) ->
case Is_custom of
true ->
<<<<"attribute.attribute(\""/utf8, Name/binary>>/binary,
"\", \"\")"/utf8>>;
false ->
case gleam@list:contains(
[<<"disabled"/utf8>>,
<<"readonly"/utf8>>,
<<"checked"/utf8>>,
<<"selected"/utf8>>,
<<"autofocus"/utf8>>,
<<"required"/utf8>>],
Name
) of
true ->
case find_attr_function(Name) of
{ok, Func} ->
<<Func/binary, "(True)"/utf8>>;
{error, _} ->
<<<<"attribute.attribute(\""/utf8, Name/binary>>/binary,
"\", \"\")"/utf8>>
end;
false ->
<<<<"attribute.attribute(\""/utf8, Name/binary>>/binary,
"\", \"\")"/utf8>>
end
end.
-file("src/ghtml/codegen.gleam", 523).
?DOC(" Generate code for a single attribute\n").
-spec generate_attr(ghtml@types:attr(), boolean()) -> binary().
generate_attr(Attr, Is_custom) ->
case Attr of
{static_attr, Name, Value} ->
generate_static_attr(Name, Value);
{dynamic_attr, Name@1, Expr} ->
generate_dynamic_attr(Name@1, Expr);
{event_attr, Event, Handler} ->
generate_event_attr(Event, Handler);
{boolean_attr, Name@2} ->
generate_boolean_attr(Name@2, Is_custom)
end.
-file("src/ghtml/codegen.gleam", 509).
?DOC(" Generate code for a list of attributes\n").
-spec generate_attrs(list(ghtml@types:attr()), boolean()) -> binary().
generate_attrs(Attrs, Is_custom) ->
case gleam@list:is_empty(Attrs) of
true ->
<<"[]"/utf8>>;
false ->
Attr_strings = begin
_pipe = Attrs,
_pipe@1 = gleam@list:map(
_pipe,
fun(Attr) -> generate_attr(Attr, Is_custom) end
),
gleam@string:join(_pipe@1, <<", "/utf8>>)
end,
<<<<"["/utf8, Attr_strings/binary>>/binary, "]"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 618).
?DOC(" Generate code for a list of children nodes (inline, comma-separated)\n").
-spec generate_children_inline(list(ghtml@types:node_())) -> binary().
generate_children_inline(Children) ->
_pipe = Children,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Child) ->
Code = generate_node_inline(Child),
case gleam@string:trim(Code) of
<<""/utf8>> ->
{error, nil};
_ ->
{ok, Code}
end
end
),
gleam@string:join(_pipe@1, <<", "/utf8>>).
-file("src/ghtml/codegen.gleam", 459).
?DOC(" Generate code for a single AST node (inline, no indentation)\n").
-spec generate_node_inline(ghtml@types:node_()) -> binary().
generate_node_inline(Node) ->
case Node of
{element, Tag, Attrs, Children, _} ->
generate_element_inline(Tag, Attrs, Children);
{text_node, Content, _} ->
generate_text_inline(Content);
{expr_node, Expr, _} ->
<<<<"text("/utf8, Expr/binary>>/binary, ")"/utf8>>;
{if_node, Condition, Then_branch, Else_branch, _} ->
generate_if_node_inline(Condition, Then_branch, Else_branch);
{each_node, Collection, Item, Index, Body, _} ->
generate_each_node_inline(Collection, Item, Index, Body);
{case_node, Expr@1, Branches, _} ->
generate_case_node_inline(Expr@1, Branches);
{fragment, Children@1, _} ->
generate_fragment_inline(Children@1)
end.
-file("src/ghtml/codegen.gleam", 753).
?DOC(" Generate code for a case node\n").
-spec generate_case_node_inline(binary(), list(ghtml@types:case_branch())) -> binary().
generate_case_node_inline(Expr, Branches) ->
Branches_code = begin
_pipe = Branches,
_pipe@1 = gleam@list:map(
_pipe,
fun(Branch) ->
Body_code = generate_branch_content(erlang:element(3, Branch)),
<<<<(erlang:element(2, Branch))/binary, " -> "/utf8>>/binary,
Body_code/binary>>
end
),
gleam@string:join(_pipe@1, <<" "/utf8>>)
end,
<<<<<<<<"case "/utf8, Expr/binary>>/binary, " { "/utf8>>/binary,
Branches_code/binary>>/binary,
" }"/utf8>>.
-file("src/ghtml/codegen.gleam", 702).
?DOC(" Generate code for branch content (single node or fragment for multiple)\n").
-spec generate_branch_content(list(ghtml@types:node_())) -> binary().
generate_branch_content(Nodes) ->
case Nodes of
[] ->
<<"none()"/utf8>>;
[Single] ->
generate_node_inline(Single);
Multiple ->
Children = generate_children_inline(Multiple),
<<<<"fragment(["/utf8, Children/binary>>/binary, "])"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 414).
?DOC(" Generate the main render function\n").
-spec generate_function(ghtml@types:template()) -> binary().
generate_function(Template) ->
Params = generate_params(erlang:element(3, Template)),
case erlang:length(erlang:element(4, Template)) of
0 ->
<<<<"pub fn render("/utf8, Params/binary>>/binary,
") -> Element(msg) {\n fragment([])\n}"/utf8>>;
1 ->
Body = generate_node_inline(
begin
_pipe = gleam@list:first(erlang:element(4, Template)),
gleam@result:unwrap(
_pipe,
{text_node,
<<""/utf8>>,
ghtml@types:point_span(ghtml@types:start_position())}
)
end
),
<<<<<<<<"pub fn render("/utf8, Params/binary>>/binary,
") -> Element(msg) {\n "/utf8>>/binary,
Body/binary>>/binary,
"\n}"/utf8>>;
_ ->
Children = begin
_pipe@1 = erlang:element(4, Template),
_pipe@2 = gleam@list:filter_map(
_pipe@1,
fun(Node) ->
Code = generate_node_inline(Node),
case gleam@string:trim(Code) of
<<""/utf8>> ->
{error, nil};
_ ->
{ok, <<" "/utf8, Code/binary>>}
end
end
),
gleam@string:join(_pipe@2, <<",\n"/utf8>>)
end,
<<<<<<<<"pub fn render("/utf8, Params/binary>>/binary,
") -> Element(msg) {\n fragment([\n"/utf8>>/binary,
Children/binary>>/binary,
",\n ])\n}"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 59).
?DOC(" Generate Gleam code from a parsed template\n").
-spec generate(ghtml@types:template(), binary(), binary()) -> binary().
generate(Template, Source_path, Hash) ->
Filename = extract_filename(Source_path),
Header = ghtml@cache:generate_header(Filename, Hash),
Imports = generate_imports(Template),
Body = generate_function(Template),
<<<<<<<<<<Header/binary, "\n"/utf8>>/binary, Imports/binary>>/binary,
"\n\n"/utf8>>/binary,
Body/binary>>/binary,
"\n"/utf8>>.
-file("src/ghtml/codegen.gleam", 475).
?DOC(" Generate code for an HTML element (inline format)\n").
-spec generate_element_inline(
binary(),
list(ghtml@types:attr()),
list(ghtml@types:node_())
) -> binary().
generate_element_inline(Tag, Attrs, Children) ->
Is_custom = is_custom_element(Tag),
Is_void = is_void_element(Tag),
Attrs_code = generate_attrs(Attrs, Is_custom),
case Is_custom of
true ->
Children_code = generate_children_inline(Children),
<<<<<<<<<<<<"element(\""/utf8, Tag/binary>>/binary, "\", "/utf8>>/binary,
Attrs_code/binary>>/binary,
", ["/utf8>>/binary,
Children_code/binary>>/binary,
"])"/utf8>>;
false ->
case Is_void of
true ->
<<<<<<<<"html."/utf8, Tag/binary>>/binary, "("/utf8>>/binary,
Attrs_code/binary>>/binary,
")"/utf8>>;
false ->
Children_code@1 = generate_children_inline(Children),
<<<<<<<<<<<<"html."/utf8, Tag/binary>>/binary, "("/utf8>>/binary,
Attrs_code/binary>>/binary,
", ["/utf8>>/binary,
Children_code@1/binary>>/binary,
"])"/utf8>>
end
end.
-file("src/ghtml/codegen.gleam", 681).
?DOC(" Generate code for an if node (case expression with True/False branches)\n").
-spec generate_if_node_inline(
binary(),
list(ghtml@types:node_()),
list(ghtml@types:node_())
) -> binary().
generate_if_node_inline(Condition, Then_branch, Else_branch) ->
Then_code = generate_branch_content(Then_branch),
Else_code = case Else_branch of
[] ->
<<"none()"/utf8>>;
_ ->
generate_branch_content(Else_branch)
end,
<<<<<<<<<<<<"case "/utf8, Condition/binary>>/binary, " { True -> "/utf8>>/binary,
Then_code/binary>>/binary,
" False -> "/utf8>>/binary,
Else_code/binary>>/binary,
" }"/utf8>>.
-file("src/ghtml/codegen.gleam", 714).
?DOC(" Generate code for an each node (keyed with list.map or list.index_map)\n").
-spec generate_each_node_inline(
binary(),
binary(),
gleam@option:option(binary()),
list(ghtml@types:node_())
) -> binary().
generate_each_node_inline(Collection, Item, Index, Body) ->
Body_code = generate_branch_content(Body),
case Index of
none ->
<<<<<<<<<<<<<<<<"keyed.fragment(list.map("/utf8, Collection/binary>>/binary,
", fn("/utf8>>/binary,
Item/binary>>/binary,
") { #("/utf8>>/binary,
Item/binary>>/binary,
".id, "/utf8>>/binary,
Body_code/binary>>/binary,
") }))"/utf8>>;
{some, Idx} ->
<<<<<<<<<<<<<<<<<<<<"keyed.fragment(list.index_map("/utf8,
Collection/binary>>/binary,
", fn("/utf8>>/binary,
Item/binary>>/binary,
", "/utf8>>/binary,
Idx/binary>>/binary,
") { #(int.to_string("/utf8>>/binary,
Idx/binary>>/binary,
"), "/utf8>>/binary,
Body_code/binary>>/binary,
") }))"/utf8>>
end.
-file("src/ghtml/codegen.gleam", 766).
?DOC(" Generate code for a fragment node\n").
-spec generate_fragment_inline(list(ghtml@types:node_())) -> binary().
generate_fragment_inline(Children) ->
Children_code = generate_children_inline(Children),
<<<<"fragment(["/utf8, Children_code/binary>>/binary, "])"/utf8>>.