Current section

Files

Jump to
oaspec src oaspec@cli.erl
Raw

src/oaspec@cli.erl

-module(oaspec@cli).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/cli.gleam").
-export([app/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.
-file("src/oaspec/cli.gleam", 402).
-spec unique_id() -> binary().
unique_id() ->
N = erlang:unique_integer(),
case N < 0 of
true ->
<<"n"/utf8, (erlang:integer_to_binary(- N))/binary>>;
false ->
erlang:integer_to_binary(N)
end.
-file("src/oaspec/cli.gleam", 354).
?DOC(
" Format resolved content by writing to a temp directory, running gleam format,\n"
" and reading back the formatted content.\n"
).
-spec format_resolved_content(list({binary(), binary()})) -> list({binary(),
binary()}).
format_resolved_content(Resolved) ->
Temp_dir = <<"/tmp/oaspec_check_"/utf8, (unique_id())/binary>>,
case simplifile:create_directory_all(Temp_dir) of
{error, _} ->
Resolved;
{ok, nil} ->
Temp_entries = gleam@list:index_map(
Resolved,
fun(Entry, Idx) ->
{Original_path, Content} = Entry,
Temp_path = <<<<<<Temp_dir/binary, "/"/utf8>>/binary,
(erlang:integer_to_binary(Idx))/binary>>/binary,
".gleam"/utf8>>,
_ = simplifile:write(Temp_path, Content),
{Original_path, Temp_path}
end
),
Temp_paths = gleam@list:map(
Temp_entries,
fun(E) -> erlang:element(2, E) end
),
Formatted_resolved = case oaspec@formatter:format_files(Temp_paths) of
{ok, nil} ->
gleam@list:map(
Temp_entries,
fun(Entry@1) ->
{Original_path@1, Temp_path@1} = Entry@1,
case simplifile:read(Temp_path@1) of
{ok, Formatted} ->
{Original_path@1, Formatted};
{error, _} ->
Content@1 = begin
_pipe = gleam@list:find(
Resolved,
fun(R) ->
erlang:element(1, R) =:= Original_path@1
end
),
_pipe@1 = gleam@result:map(
_pipe,
fun(R@1) ->
erlang:element(2, R@1)
end
),
gleam@result:unwrap(
_pipe@1,
<<""/utf8>>
)
end,
{Original_path@1, Content@1}
end
end
);
{error, _} ->
Resolved
end,
_ = simplifile_erl:delete(Temp_dir),
Formatted_resolved
end.
-file("src/oaspec/cli.gleam", 458).
?DOC(" Pure config loading and validation pipeline.\n").
-spec load_config(
binary(),
gleam@option:option(binary()),
gleam@option:option(binary())
) -> {ok, oaspec@config:config()} | {error, binary()}.
load_config(Config_path, Mode_opt, Output_opt) ->
gleam@result:'try'(
begin
_pipe = oaspec@config:load(Config_path),
gleam@result:map_error(_pipe, fun oaspec@config:error_to_string/1)
end,
fun(Cfg) -> gleam@result:'try'(case Mode_opt of
none ->
{ok, Cfg};
{some, Mode_str} ->
_pipe@1 = oaspec@config:parse_mode(Mode_str),
_pipe@2 = gleam@result:map(
_pipe@1,
fun(M) -> oaspec@config:with_mode(Cfg, M) end
),
gleam@result:map_error(
_pipe@2,
fun oaspec@config:error_to_string/1
)
end, fun(Cfg@1) ->
Cfg@2 = case Output_opt of
none ->
Cfg@1;
{some, Path} ->
oaspec@config:with_output(Cfg@1, {some, Path})
end,
_pipe@3 = oaspec@config:validate_output_package_match(Cfg@2),
_pipe@4 = gleam@result:map(_pipe@3, fun(_) -> Cfg@2 end),
gleam@result:map_error(
_pipe@4,
fun oaspec@config:error_to_string/1
)
end) end
).
-file("src/oaspec/cli.gleam", 152).
?DOC(" Create a config file template.\n").
-spec run_init(binary()) -> nil.
run_init(Path) ->
Template = <<"# oaspec configuration file
# See https://github.com/nao1215/oaspec for documentation.
# Path to your OpenAPI 3.x specification (YAML or JSON).
input: openapi.yaml
# Gleam module namespace for generated code.
# Generated imports will be `import <package>/types`, etc.
package: api
# Generation mode: server, client, or both (default: both).
# mode: both
# Output settings (optional).
# output:
# dir: ./gen # Base output directory (default: ./gen)
# server: ./gen/api # Override server output path
# client: ./gen_client/api # Override client output path
"/utf8>>,
case simplifile_erl:is_file(Path) of
{ok, true} ->
gleam_stdlib:println(
<<<<"Error: "/utf8, Path/binary>>/binary,
" already exists"/utf8>>
),
erlang:halt(1);
_ ->
case simplifile:write(Path, Template) of
{ok, _} ->
gleam_stdlib:println(<<"Created "/utf8, Path/binary>>);
{error, _} ->
gleam_stdlib:println(
<<"Error: failed to write "/utf8, Path/binary>>
),
erlang:halt(1)
end
end.
-file("src/oaspec/cli.gleam", 131).
?DOC(" The init command definition.\n").
-spec init_command() -> glint:command(nil).
init_command() ->
begin
glint:flag(
begin
_pipe = glint:string_flag(<<"output"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<"./oaspec.yaml"/utf8>>),
glint:flag_help(
_pipe@1,
<<"Output path for the config file"/utf8>>
)
end,
fun(Output_path) ->
glint:command_help(
<<"Create a oaspec.yaml config file"/utf8>>,
fun() ->
glint:command(
fun(_, _, Flags) ->
Path = case Output_path(Flags) of
{ok, P} ->
P;
{error, _} ->
<<"./oaspec.yaml"/utf8>>
end,
run_init(Path)
end
)
end
)
end
)
end.
-file("src/oaspec/cli.gleam", 289).
?DOC(
" Check that generated code matches existing files on disk.\n"
" Exits 0 if all files match, exits 1 if any differ, missing, or orphaned.\n"
).
-spec run_check(
list(oaspec@codegen@context:generated_file()),
oaspec@config:config()
) -> nil.
run_check(Files, Cfg) ->
gleam_stdlib:println(
<<"Checking generated code against existing files..."/utf8>>
),
Resolved = oaspec@codegen@writer:resolve_paths(Files, Cfg),
Expected_paths = gleam@list:map(
Resolved,
fun(Entry) -> erlang:element(1, Entry) end
),
Resolved@1 = format_resolved_content(Resolved),
Diffs = gleam@list:filter_map(
Resolved@1,
fun(Entry@1) ->
{Path, Content} = Entry@1,
case simplifile:read(Path) of
{error, _} ->
{ok, <<Path/binary, " (missing)"/utf8>>};
{ok, Existing} ->
case gleam@string:compare(Existing, Content) of
eq ->
{error, nil};
_ ->
{ok, <<Path/binary, " (differs)"/utf8>>}
end
end
end
),
Output_dirs = oaspec@codegen@writer:output_dirs(Cfg),
Orphans = gleam@list:flat_map(
Output_dirs,
fun(Dir) -> case simplifile_erl:read_directory(Dir) of
{error, _} ->
[];
{ok, Entries} ->
gleam@list:filter_map(
Entries,
fun(Name) ->
case gleam_stdlib:string_ends_with(
Name,
<<".gleam"/utf8>>
) of
false ->
{error, nil};
true ->
Full_path = <<<<Dir/binary, "/"/utf8>>/binary,
Name/binary>>,
case gleam@list:contains(
Expected_paths,
Full_path
) of
true ->
{error, nil};
false ->
{ok,
<<Full_path/binary,
" (orphaned)"/utf8>>}
end
end
end
)
end end
),
All_issues = lists:append(Diffs, Orphans),
case All_issues of
[] ->
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<"All files up to date, check passed."/utf8>>);
_ ->
gleam_stdlib:println(<<""/utf8>>),
gleam@list:each(
All_issues,
fun(D) -> gleam_stdlib:println(<<" "/utf8, D/binary>>) end
),
gleam_stdlib:println(
<<<<"\n"/utf8,
(erlang:integer_to_binary(erlang:length(All_issues)))/binary>>/binary,
" file(s) out of date. Run 'oaspec generate' to update."/utf8>>
),
erlang:halt(1)
end.
-file("src/oaspec/cli.gleam", 194).
?DOC(
" Execute the generation pipeline.\n"
" Handles IO (printing, exit codes) while delegating pure logic to\n"
" generate.generate and load_config.\n"
).
-spec run_generate(
binary(),
gleam@option:option(binary()),
gleam@option:option(binary()),
boolean(),
boolean()
) -> nil.
run_generate(Config_path, Mode_opt, Output_opt, Check_mode, Fail_on_warnings) ->
gleam_stdlib:println(<<"oaspec v"/utf8, (<<"0.10.0"/utf8>>)/binary>>),
gleam_stdlib:println(<<"Loading config from: "/utf8, Config_path/binary>>),
case load_config(Config_path, Mode_opt, Output_opt) of
{error, Msg} ->
gleam_stdlib:println(<<"Error: "/utf8, Msg/binary>>),
erlang:halt(1);
{ok, Cfg} ->
gleam_stdlib:println(
<<"Parsing OpenAPI spec: "/utf8,
(erlang:element(2, Cfg))/binary>>
),
case oaspec@openapi@parser:parse_file(erlang:element(2, Cfg)) of
{error, E} ->
gleam_stdlib:println(
<<"Error: "/utf8,
(oaspec@openapi@parser:parse_error_to_string(E))/binary>>
),
erlang:halt(1);
{ok, Spec} ->
case oaspec@generate:generate(Spec, Cfg) of
{error, {validation_errors, Errors}} ->
gleam_stdlib:println(
<<"Error: OpenAPI spec contains unsupported features:"/utf8>>
),
gleam@list:each(
Errors,
fun(E@1) ->
gleam_stdlib:println(
<<" - "/utf8,
(oaspec@openapi@diagnostic:to_string(
E@1
))/binary>>
)
end
),
erlang:halt(1);
{ok, Summary} ->
gleam_stdlib:println(
<<"Spec loaded: "/utf8,
(erlang:element(3, Summary))/binary>>
),
Has_warnings = erlang:element(4, Summary) /= [],
case erlang:element(4, Summary) of
[] ->
nil;
Warnings ->
gleam_stdlib:println(<<"Warnings:"/utf8>>),
gleam@list:each(
Warnings,
fun(W) ->
gleam_stdlib:println(
<<" - "/utf8,
(oaspec@openapi@diagnostic:to_string(
W
))/binary>>
)
end
)
end,
case Fail_on_warnings andalso Has_warnings of
true ->
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<"Error: warnings present and --fail-on-warnings is set."/utf8>>
),
erlang:halt(1);
false ->
nil
end,
case Check_mode of
true ->
run_check(erlang:element(2, Summary), Cfg);
false ->
gleam_stdlib:println(
<<"Generating code..."/utf8>>
),
case oaspec@codegen@writer:write_all(
erlang:element(2, Summary),
Cfg,
fun(Path) ->
gleam_stdlib:println(
<<" Generated: "/utf8,
Path/binary>>
)
end
) of
{ok, Written} ->
Output_dirs = oaspec@codegen@writer:output_dirs(
Cfg
),
case oaspec@formatter:format_files(
Output_dirs
) of
{ok, nil} ->
gleam_stdlib:println(
<<" Formatted generated code"/utf8>>
);
{error, E@2} ->
gleam_stdlib:println(
<<"Error: "/utf8,
(oaspec@formatter:error_to_string(
E@2
))/binary>>
),
erlang:halt(1)
end,
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<<<"Successfully generated "/utf8,
(erlang:integer_to_binary(
erlang:length(
Written
)
))/binary>>/binary,
" files"/utf8>>
);
{error, E@3} ->
gleam_stdlib:println(
<<"Error: "/utf8,
(oaspec@codegen@writer:error_to_string(
E@3
))/binary>>
),
erlang:halt(1)
end
end
end
end
end.
-file("src/oaspec/cli.gleam", 30).
?DOC(" The generate command definition.\n").
-spec generate_command() -> glint:command(nil).
generate_command() ->
begin
glint:flag(
begin
_pipe = glint:string_flag(<<"config"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<"./oaspec.yaml"/utf8>>),
glint:flag_help(_pipe@1, <<"Path to config file"/utf8>>)
end,
fun(Config_path) ->
glint:flag(
begin
_pipe@2 = glint:string_flag(<<"mode"/utf8>>),
_pipe@3 = glint:flag_default(_pipe@2, <<""/utf8>>),
glint:flag_help(
_pipe@3,
<<"Generation mode: server, client, or both (overrides config)"/utf8>>
)
end,
fun(Mode) ->
glint:flag(
begin
_pipe@4 = glint:string_flag(<<"output"/utf8>>),
_pipe@5 = glint:flag_default(
_pipe@4,
<<""/utf8>>
),
glint:flag_help(
_pipe@5,
<<"Output directory override"/utf8>>
)
end,
fun(Output) ->
glint:flag(
begin
_pipe@6 = glint:bool_flag(
<<"check"/utf8>>
),
_pipe@7 = glint:flag_default(
_pipe@6,
false
),
glint:flag_help(
_pipe@7,
<<"Check that generated code matches existing files without writing"/utf8>>
)
end,
fun(Check) ->
glint:flag(
begin
_pipe@8 = glint:bool_flag(
<<"fail-on-warnings"/utf8>>
),
_pipe@9 = glint:flag_default(
_pipe@8,
false
),
glint:flag_help(
_pipe@9,
<<"Treat warnings as errors"/utf8>>
)
end,
fun(Fail_on_warnings) ->
glint:command_help(
<<"Generate Gleam code from an OpenAPI specification"/utf8>>,
fun() ->
glint:command(
fun(_, _, Flags) ->
Config_path@1 = begin
_pipe@10 = Config_path(
Flags
),
gleam@result:unwrap(
_pipe@10,
<<"./oaspec.yaml"/utf8>>
)
end,
Mode_opt = case begin
_pipe@11 = Mode(
Flags
),
gleam@result:unwrap(
_pipe@11,
<<""/utf8>>
)
end of
<<""/utf8>> ->
none;
S ->
{some,
S}
end,
Output_opt = case begin
_pipe@12 = Output(
Flags
),
gleam@result:unwrap(
_pipe@12,
<<""/utf8>>
)
end of
<<""/utf8>> ->
none;
S@1 ->
{some,
S@1}
end,
Check_mode = begin
_pipe@13 = Check(
Flags
),
gleam@result:unwrap(
_pipe@13,
false
)
end,
Fail_on_warnings_mode = begin
_pipe@14 = Fail_on_warnings(
Flags
),
gleam@result:unwrap(
_pipe@14,
false
)
end,
run_generate(
Config_path@1,
Mode_opt,
Output_opt,
Check_mode,
Fail_on_warnings_mode
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end.
-file("src/oaspec/cli.gleam", 411).
?DOC(" Execute the validation-only pipeline.\n").
-spec run_validate(binary(), gleam@option:option(binary())) -> nil.
run_validate(Config_path, Mode_opt) ->
gleam_stdlib:println(<<"oaspec v"/utf8, (<<"0.10.0"/utf8>>)/binary>>),
gleam_stdlib:println(<<"Loading config from: "/utf8, Config_path/binary>>),
case load_config(Config_path, Mode_opt, none) of
{error, Msg} ->
gleam_stdlib:println(<<"Error: "/utf8, Msg/binary>>),
erlang:halt(1);
{ok, Cfg} ->
gleam_stdlib:println(
<<"Parsing OpenAPI spec: "/utf8,
(erlang:element(2, Cfg))/binary>>
),
case oaspec@openapi@parser:parse_file(erlang:element(2, Cfg)) of
{error, E} ->
gleam_stdlib:println(
<<"Error: "/utf8,
(oaspec@openapi@parser:parse_error_to_string(E))/binary>>
),
erlang:halt(1);
{ok, Spec} ->
case oaspec@generate:validate_only(Spec, Cfg) of
{error, {validation_errors, Errors}} ->
gleam_stdlib:println(
<<"Error: OpenAPI spec contains unsupported features:"/utf8>>
),
gleam@list:each(
Errors,
fun(E@1) ->
gleam_stdlib:println(
<<" - "/utf8,
(oaspec@openapi@diagnostic:to_string(
E@1
))/binary>>
)
end
),
erlang:halt(1);
{ok, Summary} ->
gleam_stdlib:println(
<<"Spec loaded: "/utf8,
(erlang:element(2, Summary))/binary>>
),
case erlang:element(3, Summary) of
[] ->
nil;
Warnings ->
gleam_stdlib:println(<<"Warnings:"/utf8>>),
gleam@list:each(
Warnings,
fun(W) ->
gleam_stdlib:println(
<<" - "/utf8,
(oaspec@openapi@diagnostic:to_string(
W
))/binary>>
)
end
)
end,
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<"Validation passed."/utf8>>)
end
end
end.
-file("src/oaspec/cli.gleam", 97).
?DOC(" The validate command definition.\n").
-spec validate_command() -> glint:command(nil).
validate_command() ->
begin
glint:flag(
begin
_pipe = glint:string_flag(<<"config"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<"./oaspec.yaml"/utf8>>),
glint:flag_help(_pipe@1, <<"Path to config file"/utf8>>)
end,
fun(Config_path) ->
glint:flag(
begin
_pipe@2 = glint:string_flag(<<"mode"/utf8>>),
_pipe@3 = glint:flag_default(_pipe@2, <<""/utf8>>),
glint:flag_help(
_pipe@3,
<<"Generation mode: server, client, or both (overrides config)"/utf8>>
)
end,
fun(Mode) ->
glint:command_help(
<<"Validate an OpenAPI specification without generating code"/utf8>>,
fun() ->
glint:command(
fun(_, _, Flags) ->
Config_path@1 = begin
_pipe@4 = Config_path(Flags),
gleam@result:unwrap(
_pipe@4,
<<"./oaspec.yaml"/utf8>>
)
end,
Mode_opt = case begin
_pipe@5 = Mode(Flags),
gleam@result:unwrap(
_pipe@5,
<<""/utf8>>
)
end of
<<""/utf8>> ->
none;
S ->
{some, S}
end,
run_validate(Config_path@1, Mode_opt)
end
)
end
)
end
)
end
)
end.
-file("src/oaspec/cli.gleam", 19).
?DOC(" Set up the CLI application.\n").
-spec app() -> glint:glint(nil).
app() ->
_pipe = glint:new(),
_pipe@1 = glint:with_name(_pipe, <<"oaspec"/utf8>>),
_pipe@2 = glint:global_help(
_pipe@1,
<<"Generate Gleam code from OpenAPI 3.x specifications"/utf8>>
),
_pipe@3 = glint:pretty_help(_pipe@2, glint:default_pretty_help()),
_pipe@4 = glint:add(_pipe@3, [<<"generate"/utf8>>], generate_command()),
_pipe@5 = glint:add(_pipe@4, [<<"validate"/utf8>>], validate_command()),
glint:add(_pipe@5, [<<"init"/utf8>>], init_command()).