-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]). -export_type([load_config_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 load_config_error() :: {config_load_error, oaspec@config:config_error()} | {mode_parse_error, oaspec@config:config_error()} | {output_validation_error, oaspec@config:config_error()}. -file("src/oaspec/cli.gleam", 397). -spec unique_id() -> binary(). unique_id() -> Integer_value = erlang:unique_integer(), case Integer_value < 0 of true -> <<"n"/utf8, (erlang:integer_to_binary(- Integer_value))/binary>>; false -> erlang:integer_to_binary(Integer_value) end. -file("src/oaspec/cli.gleam", 345). ?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 = <<<<<>/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(Entry@1) -> erlang:element(2, Entry@1) end ), Formatted_resolved = case oaspec@formatter:format_files(Temp_paths) of {ok, nil} -> gleam@list:map( Temp_entries, fun(Entry@2) -> {Original_path@1, Temp_path@1} = Entry@2, 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", 441). ?DOC(" Format a `LoadConfigError` into a user-facing message.\n"). -spec load_config_error_to_string(load_config_error()) -> binary(). load_config_error_to_string(Error) -> Detail = case Error of {config_load_error, Error@1} -> oaspec@config:error_to_string(Error@1); {mode_parse_error, Error@2} -> oaspec@config:error_to_string(Error@2); {output_validation_error, Error@3} -> oaspec@config:error_to_string(Error@3) end, <<"Error: "/utf8, Detail/binary>>. -file("src/oaspec/cli.gleam", 451). ?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, load_config_error()}. load_config(Config_path, Mode_opt, Output_opt) -> gleam@result:'try'( begin _pipe = oaspec@config:load(Config_path), gleam@result:map_error( _pipe, fun(Field@0) -> {config_load_error, Field@0} end ) 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(Parsed) -> oaspec@config:with_mode(Cfg, Parsed) end ), gleam@result:map_error( _pipe@2, fun(Field@0) -> {mode_parse_error, Field@0} end ) 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(Field@0) -> {output_validation_error, Field@0} end ) end) end ). -file("src/oaspec/cli.gleam", 493). ?DOC(" Format a GenerateError into a printable error message.\n"). -spec format_generate_error(oaspec@generate:generate_error()) -> binary(). format_generate_error(Err) -> {validation_errors, Errors} = Err, <<"Error: OpenAPI spec contains unsupported features:\n"/utf8, (gleam@string:join( gleam@list:map( Errors, fun(Validation_error) -> <<" - "/utf8, (oaspec@openapi@diagnostic:to_short_string( Validation_error ))/binary>> end ), <<"\n"/utf8>> ))/binary>>. -file("src/oaspec/cli.gleam", 505). ?DOC(" Print warnings if any exist.\n"). -spec print_warnings(list(oaspec@openapi@diagnostic:diagnostic())) -> nil. print_warnings(Warnings) -> case Warnings of [] -> nil; _ -> gleam_stdlib:println(<<"Warnings:"/utf8>>), gleam@list:each( Warnings, fun(W) -> gleam_stdlib:println( <<" - "/utf8, (oaspec@openapi@diagnostic:to_short_string(W))/binary>> ) end ) end. -file("src/oaspec/cli.gleam", 161). ?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 /types`, etc. package: api # Generation mode: server, client, or both (default: both). # mode: both # Enable guard validation in generated server/client code (default: false). # When enabled, generated routers validate request bodies against schema # constraints and return 422 on failure. Generated clients validate # request bodies before sending. # validate: false # 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, Write_err} -> gleam_stdlib:println( <<<<<<"Error: failed to write "/utf8, Path/binary>>/binary, ": "/utf8>>/binary, (simplifile:describe_error(Write_err))/binary>> ), erlang:halt(1) end end. -file("src/oaspec/cli.gleam", 143). ?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 = begin _pipe@2 = Output_path(Flags), gleam@result:unwrap( _pipe@2, <<"./oaspec.yaml"/utf8>> ) end, run_init(Path) end ) end ) end ) end. -file("src/oaspec/cli.gleam", 278). ?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, <>}; {ok, Existing} -> case gleam@string:compare(Existing, Content) of eq -> {error, nil}; _ -> {ok, <>} 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 = <<<>/binary, Name/binary>>, case gleam@list:contains( Expected_paths, Full_path ) of true -> {error, nil}; false -> {ok, <>} 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", 478). ?DOC( " Unwrap a Result or print the error message and exit.\n" " Designed for use with `use value <- require(result, to_message)`.\n" ). -spec require( {ok, WHP} | {error, WHQ}, fun((WHQ) -> binary()), fun((WHP) -> nil) ) -> nil. require(Result, To_message, Continue) -> case Result of {ok, Value} -> Continue(Value); {error, Err} -> gleam_stdlib:println(To_message(Err)), erlang:halt(1) end. -file("src/oaspec/cli.gleam", 258). ?DOC(" Write generated files to disk, format them, and print summary.\n"). -spec write_files( list(oaspec@codegen@context:generated_file()), oaspec@config:config() ) -> nil. write_files(Files, Cfg) -> gleam_stdlib:println(<<"Generating code..."/utf8>>), require( oaspec@codegen@writer:write_all( Files, Cfg, fun(Path) -> gleam_stdlib:println(<<" Generated: "/utf8, Path/binary>>) end ), fun(E) -> <<"Error: "/utf8, (oaspec@codegen@writer:error_to_string(E))/binary>> end, fun(Written) -> require( oaspec@formatter:format_files( oaspec@codegen@writer:output_dirs(Cfg) ), fun(E@1) -> <<"Error: "/utf8, (oaspec@formatter:error_to_string(E@1))/binary>> end, fun(_) -> gleam_stdlib:println(<<" Formatted generated code"/utf8>>), gleam_stdlib:println(<<""/utf8>>), gleam_stdlib:println( <<<<"Successfully generated "/utf8, (erlang:integer_to_binary( erlang:length(Written) ))/binary>>/binary, " files"/utf8>> ) end ) end ). -file("src/oaspec/cli.gleam", 214). ?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(), boolean() ) -> nil. run_generate( Config_path, Mode_opt, Output_opt, Check_mode, Fail_on_warnings, Validate_mode ) -> gleam_stdlib:println(<<"oaspec v"/utf8, (<<"0.17.0"/utf8>>)/binary>>), gleam_stdlib:println(<<"Loading config from: "/utf8, Config_path/binary>>), require( load_config(Config_path, Mode_opt, Output_opt), fun load_config_error_to_string/1, fun(Cfg) -> Cfg@1 = case Validate_mode of true -> oaspec@config:with_validate(Cfg, true); false -> Cfg end, gleam_stdlib:println( <<"Parsing OpenAPI spec: "/utf8, (oaspec@config:input(Cfg@1))/binary>> ), require( oaspec@openapi@parser:parse_file(oaspec@config:input(Cfg@1)), fun(E) -> <<"Error: "/utf8, (oaspec@openapi@parser:parse_error_to_string(E))/binary>> end, fun(Spec) -> require( oaspec@generate:generate(Spec, Cfg@1), fun format_generate_error/1, fun(Summary) -> gleam_stdlib:println( <<"Spec loaded: "/utf8, (erlang:element(3, Summary))/binary>> ), print_warnings(erlang:element(4, Summary)), case Fail_on_warnings andalso (erlang:element( 4, Summary ) /= []) 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@1); false -> write_files( erlang:element(2, Summary), Cfg@1 ) end end ) end ) end ). -file("src/oaspec/cli.gleam", 32). ?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:flag( begin _pipe@10 = glint:bool_flag( <<"validate"/utf8>> ), _pipe@11 = glint:flag_default( _pipe@10, false ), glint:flag_help( _pipe@11, <<"Enable guard validation in generated server/client code"/utf8>> ) end, fun(Validate) -> glint:command_help( <<"Generate Gleam code from an OpenAPI specification"/utf8>>, fun() -> glint:command( fun( _, _, Flags ) -> Config_path@1 = begin _pipe@12 = Config_path( Flags ), gleam@result:unwrap( _pipe@12, <<"./oaspec.yaml"/utf8>> ) end, Mode_opt = case begin _pipe@13 = Mode( Flags ), gleam@result:unwrap( _pipe@13, <<""/utf8>> ) end of <<""/utf8>> -> none; S -> {some, S} end, Output_opt = case begin _pipe@14 = Output( Flags ), gleam@result:unwrap( _pipe@14, <<""/utf8>> ) end of <<""/utf8>> -> none; S@1 -> {some, S@1} end, Check_mode = begin _pipe@15 = Check( Flags ), gleam@result:unwrap( _pipe@15, false ) end, Fail_on_warnings_mode = begin _pipe@16 = Fail_on_warnings( Flags ), gleam@result:unwrap( _pipe@16, false ) end, Validate_mode = begin _pipe@17 = Validate( Flags ), gleam@result:unwrap( _pipe@17, false ) end, run_generate( Config_path@1, Mode_opt, Output_opt, Check_mode, Fail_on_warnings_mode, Validate_mode ) end ) end ) end ) end ) end ) end ) end ) end ) end. -file("src/oaspec/cli.gleam", 406). ?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.17.0"/utf8>>)/binary>>), gleam_stdlib:println(<<"Loading config from: "/utf8, Config_path/binary>>), require( load_config(Config_path, Mode_opt, none), fun load_config_error_to_string/1, fun(Cfg) -> gleam_stdlib:println( <<"Parsing OpenAPI spec: "/utf8, (oaspec@config:input(Cfg))/binary>> ), require( oaspec@openapi@parser:parse_file(oaspec@config:input(Cfg)), fun(E) -> <<"Error: "/utf8, (oaspec@openapi@parser:parse_error_to_string(E))/binary>> end, fun(Spec) -> require( oaspec@generate:validate_only(Spec, Cfg), fun format_generate_error/1, fun(Summary) -> gleam_stdlib:println( <<"Spec loaded: "/utf8, (erlang:element(2, Summary))/binary>> ), print_warnings(erlang:element(3, Summary)), gleam_stdlib:println(<<""/utf8>>), gleam_stdlib:println(<<"Validation passed."/utf8>>) end ) end ) end ). -file("src/oaspec/cli.gleam", 109). ?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\n\nCommands:\n init Create a default oaspec.yaml config file\n generate Generate Gleam code from an OpenAPI spec\n validate Validate an OpenAPI spec without generating code\n\nRun 'oaspec --help' for more information."/utf8>> ), _pipe@3 = glint:pretty_help(_pipe@2, glint:default_pretty_help()), _pipe@4 = glint:add(_pipe@3, [<<"init"/utf8>>], init_command()), _pipe@5 = glint:add(_pipe@4, [<<"generate"/utf8>>], generate_command()), glint:add(_pipe@5, [<<"validate"/utf8>>], validate_command()).