Current section
Files
Jump to
Current section
Files
src/ghtml.erl
-module(ghtml).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ghtml.gleam").
-export([parse_options/1, generate_all/2, main/0]).
-export_type([generation_stats/0, cli_options/0]).
-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(
" ghtml - Gleam HTML Template Generator.\n"
"\n"
" A preprocessor that converts `.ghtml` template files into Gleam modules\n"
" with Lustre `Element(msg)` render functions.\n"
"\n"
" ## Usage\n"
"\n"
" ```sh\n"
" gleam run -m ghtml # Generate all\n"
" gleam run -m ghtml -- force # Force regenerate\n"
" gleam run -m ghtml -- watch # Watch mode\n"
" gleam run -m ghtml -- clean # Remove orphans\n"
" ```\n"
).
-type generation_stats() :: {generation_stats, integer(), integer(), integer()}.
-type cli_options() :: {cli_options, boolean(), boolean(), boolean(), binary()}.
-file("src/ghtml.gleam", 39).
?DOC(" Parse command-line arguments into options\n").
-spec parse_options(list(binary())) -> cli_options().
parse_options(Args) ->
Root = begin
_pipe = Args,
_pipe@1 = gleam@list:find(
_pipe,
fun(Arg) ->
not gleam@list:contains(
[<<"force"/utf8>>, <<"clean"/utf8>>, <<"watch"/utf8>>],
Arg
)
end
),
gleam@result:unwrap(_pipe@1, <<"."/utf8>>)
end,
{cli_options,
gleam@list:contains(Args, <<"force"/utf8>>),
gleam@list:contains(Args, <<"clean"/utf8>>),
gleam@list:contains(Args, <<"watch"/utf8>>),
Root}.
-file("src/ghtml.gleam", 65).
?DOC(" Run cleanup mode - remove orphaned generated files\n").
-spec run_clean(binary()) -> nil.
run_clean(Root) ->
Count = ghtml@scanner:cleanup_orphans(Root),
gleam_stdlib:println(
<<<<"Cleaned up "/utf8, (erlang:integer_to_binary(Count))/binary>>/binary,
" orphaned files"/utf8>>
).
-file("src/ghtml.gleam", 127).
?DOC(" Process a single template file\n").
-spec process_file(binary(), binary()) -> {ok, nil} | {error, binary()}.
process_file(Source_path, Output_path) ->
case simplifile:read(Source_path) of
{ok, Content} ->
Hash = ghtml@cache:hash_content(Content),
case ghtml@parser:parse(Content) of
{ok, Template} ->
Gleam_code = ghtml@codegen:generate(
Template,
Source_path,
Hash
),
case simplifile:write(Output_path, Gleam_code) of
{ok, _} ->
gleam_stdlib:println(
<<<<<<"✓ "/utf8, Source_path/binary>>/binary,
" → "/utf8>>/binary,
Output_path/binary>>
),
{ok, nil};
{error, _} ->
gleam_stdlib:println(
<<"✗ Error writing "/utf8, Output_path/binary>>
),
{error, <<"Write error"/utf8>>}
end;
{error, Errors} ->
gleam_stdlib:println(
<<<<"✗ Parse errors in "/utf8, Source_path/binary>>/binary,
":"/utf8>>
),
gleam_stdlib:println(
ghtml@parser:format_errors(Errors, Content)
),
{error, <<"Parse error"/utf8>>}
end;
{error, _} ->
gleam_stdlib:println(
<<"✗ Error reading "/utf8, Source_path/binary>>
),
{error, <<"Read error"/utf8>>}
end.
-file("src/ghtml.gleam", 106).
?DOC(" Generate all templates in the given directory\n").
-spec generate_all(binary(), boolean()) -> generation_stats().
generate_all(Root, Force) ->
_pipe = ghtml@scanner:find_ghtml_files(Root),
gleam@list:fold(
_pipe,
{generation_stats, 0, 0, 0},
fun(Stats, Source_path) ->
Output_path = ghtml@scanner:to_output_path(Source_path),
case Force orelse ghtml@cache:needs_regeneration(
Source_path,
Output_path
) of
true ->
case process_file(Source_path, Output_path) of
{ok, _} ->
{generation_stats,
erlang:element(2, Stats) + 1,
erlang:element(3, Stats),
erlang:element(4, Stats)};
{error, _} ->
{generation_stats,
erlang:element(2, Stats),
erlang:element(3, Stats),
erlang:element(4, Stats) + 1}
end;
false ->
gleam_stdlib:println(
<<<<"· "/utf8, Source_path/binary>>/binary,
" (unchanged)"/utf8>>
),
{generation_stats,
erlang:element(2, Stats),
erlang:element(3, Stats) + 1,
erlang:element(4, Stats)}
end
end
).
-file("src/ghtml.gleam", 71).
?DOC(" Run generation mode - scan and generate templates\n").
-spec run_generate(binary(), cli_options()) -> nil.
run_generate(Root, Options) ->
gleam_stdlib:println(<<"ghtml v0.1.0"/utf8>>),
gleam_stdlib:println(<<""/utf8>>),
Stats = generate_all(Root, erlang:element(2, Options)),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<"Generated: "/utf8,
(erlang:integer_to_binary(erlang:element(2, Stats)))/binary>>
),
gleam_stdlib:println(
<<"Skipped (unchanged): "/utf8,
(erlang:integer_to_binary(erlang:element(3, Stats)))/binary>>
),
case erlang:element(4, Stats) > 0 of
true ->
gleam_stdlib:println(
<<"Errors: "/utf8,
(erlang:integer_to_binary(erlang:element(4, Stats)))/binary>>
);
false ->
nil
end,
Orphans = ghtml@scanner:cleanup_orphans(Root),
case Orphans > 0 of
true ->
gleam_stdlib:println(
<<"Removed orphans: "/utf8,
(erlang:integer_to_binary(Orphans))/binary>>
);
false ->
nil
end,
case erlang:element(4, Options) of
true ->
gleam_stdlib:println(<<""/utf8>>),
_ = ghtml@watcher:start_watching(Root),
gleam_erlang_ffi:sleep_forever();
false ->
nil
end.
-file("src/ghtml.gleam", 55).
?DOC(" Main entry point for the CLI\n").
-spec main() -> nil.
main() ->
Options = parse_options(erlang:element(4, argv:load())),
case erlang:element(3, Options) of
true ->
run_clean(erlang:element(5, Options));
false ->
run_generate(erlang:element(5, Options), Options)
end.