Current section

Files

Jump to
oaspec src oaspec@codegen@writer.erl
Raw

src/oaspec@codegen@writer.erl

-module(oaspec@codegen@writer).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/codegen/writer.gleam").
-export([write_all/3, resolve_paths/2, expected_paths/2, output_dirs/1, error_to_string/1]).
-export_type([write_error/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.
-type write_error() :: {directory_create_error, binary(), binary()} |
{file_write_error, binary(), binary()}.
-file("src/oaspec/codegen/writer.gleam", 88).
-spec normalise_dir(binary()) -> binary().
normalise_dir(Path) ->
gleam@bool:guard(
not gleam_stdlib:string_ends_with(Path, <<"/"/utf8>>),
Path,
fun() -> gleam@string:drop_end(Path, 1) end
).
-file("src/oaspec/codegen/writer.gleam", 94).
?DOC(" Ensure a directory exists, creating it if necessary.\n").
-spec ensure_directory(binary()) -> {ok, nil} | {error, write_error()}.
ensure_directory(Path) ->
_pipe = simplifile:create_directory_all(Path),
gleam@result:map_error(
_pipe,
fun(Err) ->
{directory_create_error,
Path,
<<"Failed to create directory: "/utf8,
(simplifile:describe_error(Err))/binary>>}
end
).
-file("src/oaspec/codegen/writer.gleam", 110).
?DOC(
" Write a list of generated files to a directory.\n"
"\n"
" Issue #247: files marked `SkipIfExists` (currently `handlers.gleam`)\n"
" are written only on first generation. If the file already exists on\n"
" disk, it is left alone — the user owns the contents and the\n"
" generator must not clobber their implementation.\n"
).
-spec write_files(
list(oaspec@internal@codegen@context:generated_file()),
binary(),
list(binary()),
fun((binary()) -> nil)
) -> {ok, list(binary())} | {error, write_error()}.
write_files(Files, Base_path, Written, On_write) ->
gleam@list:try_fold(
Files,
Written,
fun(Acc, File) ->
Full_path = <<<<Base_path/binary, "/"/utf8>>/binary,
(erlang:element(2, File))/binary>>,
case {erlang:element(5, File), simplifile_erl:is_file(Full_path)} of
{skip_if_exists, {ok, true}} ->
{ok, Acc};
{_, _} ->
gleam@result:'try'(
begin
_pipe = simplifile:write(
Full_path,
erlang:element(3, File)
),
gleam@result:map_error(
_pipe,
fun(Err) ->
{file_write_error,
Full_path,
<<"Failed to write file: "/utf8,
(simplifile:describe_error(Err))/binary>>}
end
)
end,
fun(_) ->
On_write(Full_path),
{ok, [Full_path | Acc]}
end
)
end
end
).
-file("src/oaspec/codegen/writer.gleam", 25).
?DOC(
" Write pre-generated files to disk based on configuration.\n"
"\n"
" **Path overlap.** When `mode = Both` and `output_server` resolves\n"
" to the same directory as `output_client` (e.g. both set to\n"
" `\"out\"`, or to `\"out\"` and `\"out/\"`, which differ as strings but\n"
" name the same directory), shared files are written **once**, not\n"
" twice. Each unique destination path triggers `on_write` exactly\n"
" once and appears at most once in the returned list. Distinct\n"
" server / client paths preserve the existing behaviour: shared\n"
" files land in both directories.\n"
).
-spec write_all(
list(oaspec@internal@codegen@context:generated_file()),
oaspec@config:config(),
fun((binary()) -> nil)
) -> {ok, list(binary())} | {error, write_error()}.
write_all(Files, Cfg, On_write) ->
Shared_files = gleam@list:filter(
Files,
fun(F) -> erlang:element(4, F) =:= shared_target end
),
Server_files = gleam@list:filter(
Files,
fun(F@1) -> erlang:element(4, F@1) =:= server_target end
),
Client_files = gleam@list:filter(
Files,
fun(F@2) -> erlang:element(4, F@2) =:= client_target end
),
Server_path = oaspec@config:output_server(Cfg),
Client_path = oaspec@config:output_client(Cfg),
Mode = oaspec@config:mode(Cfg),
Server_and_client_overlap = (Mode =:= both) andalso (normalise_dir(
Server_path
)
=:= normalise_dir(Client_path)),
Written_files = [],
gleam@result:'try'(case Mode of
server ->
gleam@result:'try'(
ensure_directory(Server_path),
fun(_) ->
_pipe = write_files(
Shared_files,
Server_path,
Written_files,
On_write
),
gleam@result:'try'(
_pipe,
fun(W) ->
write_files(
Server_files,
Server_path,
W,
On_write
)
end
)
end
);
both ->
gleam@result:'try'(
ensure_directory(Server_path),
fun(_) ->
_pipe = write_files(
Shared_files,
Server_path,
Written_files,
On_write
),
gleam@result:'try'(
_pipe,
fun(W) ->
write_files(
Server_files,
Server_path,
W,
On_write
)
end
)
end
);
client ->
{ok, Written_files}
end, fun(Written_files@1) -> gleam@result:'try'(case Mode of
client ->
gleam@result:'try'(
ensure_directory(Client_path),
fun(_) ->
Shared_for_client = case Server_and_client_overlap of
true ->
[];
false ->
Shared_files
end,
_pipe@1 = write_files(
Shared_for_client,
Client_path,
Written_files@1,
On_write
),
gleam@result:'try'(
_pipe@1,
fun(W@1) ->
write_files(
Client_files,
Client_path,
W@1,
On_write
)
end
)
end
);
both ->
gleam@result:'try'(
ensure_directory(Client_path),
fun(_) ->
Shared_for_client = case Server_and_client_overlap of
true ->
[];
false ->
Shared_files
end,
_pipe@1 = write_files(
Shared_for_client,
Client_path,
Written_files@1,
On_write
),
gleam@result:'try'(
_pipe@1,
fun(W@1) ->
write_files(
Client_files,
Client_path,
W@1,
On_write
)
end
)
end
);
server ->
{ok, Written_files@1}
end, fun(Written_files@2) -> {ok, Written_files@2} end) end).
-file("src/oaspec/codegen/writer.gleam", 154).
?DOC(
" Resolve generated files to their full output paths and content.\n"
" Used by --check to compare against existing files without writing.\n"
"\n"
" Issue #247: `SkipIfExists` files (e.g. user-owned `handlers.gleam`)\n"
" are dropped from the result. `--check` is meant to flag drift in\n"
" generator-owned files; user-edited files are expected to differ\n"
" from the bootstrap stub and should not be reported as out-of-date.\n"
"\n"
" Issue #548: when `mode = Both` and `output_server` resolves to the\n"
" same directory as `output_client`, shared files appear once in\n"
" the result, not twice — `--check` would otherwise compare the\n"
" same file twice and double-count any drift.\n"
).
-spec resolve_paths(
list(oaspec@internal@codegen@context:generated_file()),
oaspec@config:config()
) -> list({binary(), binary()}).
resolve_paths(Files, Cfg) ->
Files@1 = gleam@list:filter(
Files,
fun(F) -> erlang:element(5, F) /= skip_if_exists end
),
Shared_files = gleam@list:filter(
Files@1,
fun(F@1) -> erlang:element(4, F@1) =:= shared_target end
),
Server_files = gleam@list:filter(
Files@1,
fun(F@2) -> erlang:element(4, F@2) =:= server_target end
),
Client_files = gleam@list:filter(
Files@1,
fun(F@3) -> erlang:element(4, F@3) =:= client_target end
),
Server_path = oaspec@config:output_server(Cfg),
Client_path = oaspec@config:output_client(Cfg),
Mode = oaspec@config:mode(Cfg),
Server_and_client_overlap = (Mode =:= both) andalso (normalise_dir(
Server_path
)
=:= normalise_dir(Client_path)),
Server_entries = case Mode of
server ->
gleam@list:map(
lists:append(Shared_files, Server_files),
fun(F@4) ->
{<<<<Server_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@4))/binary>>,
erlang:element(3, F@4)}
end
);
both ->
gleam@list:map(
lists:append(Shared_files, Server_files),
fun(F@4) ->
{<<<<Server_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@4))/binary>>,
erlang:element(3, F@4)}
end
);
client ->
[]
end,
Shared_for_client = case Server_and_client_overlap of
true ->
[];
false ->
Shared_files
end,
Client_entries = case Mode of
client ->
gleam@list:map(
lists:append(Shared_for_client, Client_files),
fun(F@5) ->
{<<<<Client_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@5))/binary>>,
erlang:element(3, F@5)}
end
);
both ->
gleam@list:map(
lists:append(Shared_for_client, Client_files),
fun(F@5) ->
{<<<<Client_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@5))/binary>>,
erlang:element(3, F@5)}
end
);
server ->
[]
end,
lists:append(Server_entries, Client_entries).
-file("src/oaspec/codegen/writer.gleam", 202).
?DOC(
" Every file path the generator would write for the given config — including\n"
" `SkipIfExists` files. Used by `--check` to keep user-owned `handlers.gleam`\n"
" off the orphan list while still excluding it from byte-comparison drift\n"
" reports (`resolve_paths` drops `SkipIfExists` entries).\n"
"\n"
" Same path-overlap dedup as `resolve_paths` (#548): shared files\n"
" appear once when the two output directories coincide.\n"
).
-spec expected_paths(
list(oaspec@internal@codegen@context:generated_file()),
oaspec@config:config()
) -> list(binary()).
expected_paths(Files, Cfg) ->
Shared_files = gleam@list:filter(
Files,
fun(F) -> erlang:element(4, F) =:= shared_target end
),
Server_files = gleam@list:filter(
Files,
fun(F@1) -> erlang:element(4, F@1) =:= server_target end
),
Client_files = gleam@list:filter(
Files,
fun(F@2) -> erlang:element(4, F@2) =:= client_target end
),
Server_path = oaspec@config:output_server(Cfg),
Client_path = oaspec@config:output_client(Cfg),
Mode = oaspec@config:mode(Cfg),
Server_and_client_overlap = (Mode =:= both) andalso (normalise_dir(
Server_path
)
=:= normalise_dir(Client_path)),
Server_entries = case Mode of
server ->
gleam@list:map(
lists:append(Shared_files, Server_files),
fun(F@3) ->
<<<<Server_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@3))/binary>>
end
);
both ->
gleam@list:map(
lists:append(Shared_files, Server_files),
fun(F@3) ->
<<<<Server_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@3))/binary>>
end
);
client ->
[]
end,
Shared_for_client = case Server_and_client_overlap of
true ->
[];
false ->
Shared_files
end,
Client_entries = case Mode of
client ->
gleam@list:map(
lists:append(Shared_for_client, Client_files),
fun(F@4) ->
<<<<Client_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@4))/binary>>
end
);
both ->
gleam@list:map(
lists:append(Shared_for_client, Client_files),
fun(F@4) ->
<<<<Client_path/binary, "/"/utf8>>/binary,
(erlang:element(2, F@4))/binary>>
end
);
server ->
[]
end,
lists:append(Server_entries, Client_entries).
-file("src/oaspec/codegen/writer.gleam", 243).
?DOC(" Return the output directories that would be written to for the given config.\n").
-spec output_dirs(oaspec@config:config()) -> list(binary()).
output_dirs(Cfg) ->
case oaspec@config:mode(Cfg) of
server ->
[oaspec@config:output_server(Cfg)];
client ->
[oaspec@config:output_client(Cfg)];
both ->
[oaspec@config:output_server(Cfg), oaspec@config:output_client(Cfg)]
end.
-file("src/oaspec/codegen/writer.gleam", 252).
?DOC(" Convert a write error to a human-readable string.\n").
-spec error_to_string(write_error()) -> binary().
error_to_string(Error) ->
case Error of
{directory_create_error, Path, Detail} ->
<<<<<<"Failed to create directory "/utf8, Path/binary>>/binary,
": "/utf8>>/binary,
Detail/binary>>;
{file_write_error, Path@1, Detail@1} ->
<<<<<<"Failed to write file "/utf8, Path@1/binary>>/binary,
": "/utf8>>/binary,
Detail@1/binary>>
end.