-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", 57). ?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", 73). ?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@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 = <<<>/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", 14). ?DOC(" Write pre-generated files to disk based on configuration.\n"). -spec write_all( list(oaspec@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), Written_files = [], gleam@result:'try'(case oaspec@config:mode(Cfg) 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 oaspec@config:mode(Cfg) of client -> gleam@result:'try'( ensure_directory(Client_path), fun(_) -> _pipe@1 = write_files( Shared_files, 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(_) -> _pipe@1 = write_files( Shared_files, 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", 112). ?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" ). -spec resolve_paths( list(oaspec@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), Server_entries = case oaspec@config:mode(Cfg) of server -> gleam@list:map( lists:append(Shared_files, Server_files), fun(F@4) -> {<<<>/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) -> {<<<>/binary, (erlang:element(2, F@4))/binary>>, erlang:element(3, F@4)} end ); client -> [] end, Client_entries = case oaspec@config:mode(Cfg) of client -> gleam@list:map( lists:append(Shared_files, Client_files), fun(F@5) -> {<<<>/binary, (erlang:element(2, F@5))/binary>>, erlang:element(3, F@5)} end ); both -> gleam@list:map( lists:append(Shared_files, Client_files), fun(F@5) -> {<<<>/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", 150). ?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" ). -spec expected_paths( list(oaspec@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), Server_entries = case oaspec@config:mode(Cfg) of server -> gleam@list:map( lists:append(Shared_files, Server_files), fun(F@3) -> <<<>/binary, (erlang:element(2, F@3))/binary>> end ); both -> gleam@list:map( lists:append(Shared_files, Server_files), fun(F@3) -> <<<>/binary, (erlang:element(2, F@3))/binary>> end ); client -> [] end, Client_entries = case oaspec@config:mode(Cfg) of client -> gleam@list:map( lists:append(Shared_files, Client_files), fun(F@4) -> <<<>/binary, (erlang:element(2, F@4))/binary>> end ); both -> gleam@list:map( lists:append(Shared_files, Client_files), fun(F@4) -> <<<>/binary, (erlang:element(2, F@4))/binary>> end ); server -> [] end, lists:append(Server_entries, Client_entries). -file("src/oaspec/codegen/writer.gleam", 184). ?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", 193). ?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.