Packages

OpenAPI code generation for Gleam — parse specs, generate types, routes, clients, and React Query/SWR hooks

Current section

Files

Jump to
nori src nori@cli.erl
Raw

src/nori@cli.erl

-module(nori@cli).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/nori/cli.gleam").
-export([main/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(
" CLI module for the OpenAPI library.\n"
"\n"
" Provides `generate`, `bundle`, and `validate` commands.\n"
" Run with: `gleam run -m nori/cli`\n"
).
-file("src/nori/cli.gleam", 254).
-spec apply_output_override(nori@config:target_config(), binary()) -> nori@config:target_config().
apply_output_override(Tc, Output_override) ->
case Output_override of
<<""/utf8>> ->
Tc;
Dir ->
{target_config,
erlang:element(2, Tc),
Dir,
erlang:element(4, Tc),
erlang:element(5, Tc)}
end.
-file("src/nori/cli.gleam", 264).
-spec suffix(nori@config:target_config(), binary(), binary()) -> binary().
suffix(Tc, Base, Ext) ->
case erlang:element(4, Tc) of
true ->
<<<<Base/binary, ".generated"/utf8>>/binary, Ext/binary>>;
false ->
<<Base/binary, Ext/binary>>
end.
-file("src/nori/cli.gleam", 271).
-spec ts_config_from_options(nori@config:target_config()) -> nori@codegen@typescript@types:config().
ts_config_from_options(Tc) ->
Use_exports = case gleam_stdlib:map_get(
erlang:element(5, Tc),
<<"use_exports"/utf8>>
) of
{ok, <<"true"/utf8>>} ->
true;
_ ->
true
end,
Use_interfaces = case gleam_stdlib:map_get(
erlang:element(5, Tc),
<<"use_interfaces"/utf8>>
) of
{ok, <<"true"/utf8>>} ->
true;
{ok, <<"false"/utf8>>} ->
false;
_ ->
false
end,
Readonly_properties = case gleam_stdlib:map_get(
erlang:element(5, Tc),
<<"readonly_properties"/utf8>>
) of
{ok, <<"true"/utf8>>} ->
true;
_ ->
false
end,
{config, Use_exports, Use_interfaces, Readonly_properties}.
-file("src/nori/cli.gleam", 356).
-spec drop_prefix(binary(), binary()) -> binary().
drop_prefix(S, Prefix) ->
case gleam_stdlib:string_starts_with(S, Prefix) of
true ->
gleam@string:drop_start(S, string:length(Prefix));
false ->
S
end.
-file("src/nori/cli.gleam", 363).
-spec drop_suffix(binary(), binary()) -> binary().
drop_suffix(S, Suffix) ->
case gleam_stdlib:string_ends_with(S, Suffix) of
true ->
gleam@string:drop_end(S, string:length(Suffix));
false ->
S
end.
-file("src/nori/cli.gleam", 331).
?DOC(
" Derive a Gleam module prefix from the configured output directory.\n"
"\n"
" Looks for a `src/` segment anywhere in the path and takes everything after\n"
" it (Gleam module paths are rooted at `src/`). Falls back to stripping a\n"
" leading `./` if no `src/` is found.\n"
"\n"
" Examples:\n"
" \"./src/generated\" -> \"generated\"\n"
" \"src/generated\" -> \"generated\"\n"
" \"/tmp/proj/src/api/gen\" -> \"api/gen\"\n"
" \"./generated\" -> \"generated\" (no src/ — strip leading ./)\n"
" \"src\" or \"./src\" -> \"\" (top-level — no prefix)\n"
"\n"
" Returning \"\" disables real-import emission and falls back to a comment hint.\n"
).
-spec derive_module_prefix(binary()) -> binary().
derive_module_prefix(Dir) ->
Trimmed = begin
_pipe = Dir,
_pipe@1 = gleam@string:trim(_pipe),
drop_suffix(_pipe@1, <<"/"/utf8>>)
end,
Parts = gleam@string:split(Trimmed, <<"/src/"/utf8>>),
case Parts of
[_] ->
case (Trimmed =:= <<"src"/utf8>>) orelse (Trimmed =:= <<"./src"/utf8>>) of
true ->
<<""/utf8>>;
false ->
_pipe@2 = Trimmed,
_pipe@3 = drop_prefix(_pipe@2, <<"./"/utf8>>),
drop_prefix(_pipe@3, <<"/"/utf8>>)
end;
_ ->
_pipe@4 = Parts,
_pipe@5 = gleam@list:last(_pipe@4),
gleam@result:unwrap(_pipe@5, <<""/utf8>>)
end.
-file("src/nori/cli.gleam", 292).
-spec generate_gleam(nori@codegen@ir:codegen_i_r(), nori@config:target_config()) -> list(nori@codegen@plugin:generated_file()).
generate_gleam(Codegen_ir, Tc) ->
Module_prefix = derive_module_prefix(erlang:element(3, Tc)),
[{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"types"/utf8>>, <<".gleam"/utf8>>))/binary>>,
nori@codegen@gleam_types:generate(Codegen_ir)},
{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"client"/utf8>>, <<".gleam"/utf8>>))/binary>>,
nori@codegen@gleam_client:generate(Codegen_ir, Module_prefix)},
{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"routes"/utf8>>, <<".gleam"/utf8>>))/binary>>,
nori@codegen@gleam_routes:generate(Codegen_ir, Module_prefix)},
{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"middleware"/utf8>>, <<".gleam"/utf8>>))/binary>>,
nori@codegen@gleam_middleware:generate(Codegen_ir, Module_prefix)}].
-file("src/nori/cli.gleam", 370).
-spec generate_typescript(
nori@codegen@ir:codegen_i_r(),
nori@config:target_config()
) -> list(nori@codegen@plugin:generated_file()).
generate_typescript(Codegen_ir, Tc) ->
_ = ts_config_from_options(Tc),
[{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"types"/utf8>>, <<".ts"/utf8>>))/binary>>,
nori@codegen@typescript@types:generate(Codegen_ir)},
{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"client"/utf8>>, <<".ts"/utf8>>))/binary>>,
nori@codegen@typescript@fetch_client:generate(Codegen_ir)}].
-file("src/nori/cli.gleam", 387).
-spec generate_react_query(
nori@codegen@ir:codegen_i_r(),
nori@config:target_config(),
nori@config:target_config()
) -> list(nori@codegen@plugin:generated_file()).
generate_react_query(Codegen_ir, Tc, Ts_tc) ->
lists:append(
[generate_typescript(Codegen_ir, Ts_tc),
[{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"hooks"/utf8>>, <<".ts"/utf8>>))/binary>>,
nori@codegen@typescript@react_query:generate(Codegen_ir)}]]
).
-file("src/nori/cli.gleam", 403).
-spec generate_swr(
nori@codegen@ir:codegen_i_r(),
nori@config:target_config(),
nori@config:target_config()
) -> list(nori@codegen@plugin:generated_file()).
generate_swr(Codegen_ir, Tc, Ts_tc) ->
lists:append(
[generate_typescript(Codegen_ir, Ts_tc),
[{generated_file,
<<<<(erlang:element(3, Tc))/binary, "/"/utf8>>/binary,
(suffix(Tc, <<"swr-hooks"/utf8>>, <<".ts"/utf8>>))/binary>>,
nori@codegen@typescript@swr:generate(Codegen_ir)}]]
).
-file("src/nori/cli.gleam", 419).
-spec generate_fetch(
nori@codegen@ir:codegen_i_r(),
nori@config:target_config(),
nori@config:target_config()
) -> list(nori@codegen@plugin:generated_file()).
generate_fetch(Codegen_ir, _, Ts_tc) ->
generate_typescript(Codegen_ir, Ts_tc).
-file("src/nori/cli.gleam", 218).
-spec generate_all_enabled(
nori@codegen@ir:codegen_i_r(),
nori@config:config(),
binary()
) -> list(nori@codegen@plugin:generated_file()).
generate_all_enabled(Codegen_ir, Cfg, Output_override) ->
Out = erlang:element(3, Cfg),
Gleam_tc = apply_output_override(erlang:element(2, Out), Output_override),
Ts_tc = apply_output_override(erlang:element(3, Out), Output_override),
Rq_tc = apply_output_override(erlang:element(4, Out), Output_override),
Swr_tc = apply_output_override(erlang:element(5, Out), Output_override),
Fetch_tc = apply_output_override(erlang:element(6, Out), Output_override),
lists:append([case erlang:element(2, Gleam_tc) of
true ->
generate_gleam(Codegen_ir, Gleam_tc);
false ->
[]
end, case erlang:element(2, Ts_tc) of
true ->
generate_typescript(Codegen_ir, Ts_tc);
false ->
[]
end, case erlang:element(2, Rq_tc) of
true ->
generate_react_query(Codegen_ir, Rq_tc, Ts_tc);
false ->
[]
end, case erlang:element(2, Swr_tc) of
true ->
generate_swr(Codegen_ir, Swr_tc, Ts_tc);
false ->
[]
end, case erlang:element(2, Fetch_tc) of
true ->
generate_fetch(Codegen_ir, Fetch_tc, Ts_tc);
false ->
[]
end]).
-file("src/nori/cli.gleam", 175).
-spec generate_files_from_config(
nori@codegen@ir:codegen_i_r(),
nori@config:config(),
binary(),
binary()
) -> list(nori@codegen@plugin:generated_file()).
generate_files_from_config(Codegen_ir, Cfg, Target_override, Output_override) ->
case Target_override of
<<""/utf8>> ->
generate_all_enabled(Codegen_ir, Cfg, Output_override);
<<"all"/utf8>> ->
generate_all_enabled(Codegen_ir, Cfg, Output_override);
<<"gleam"/utf8>> ->
generate_gleam(
Codegen_ir,
apply_output_override(
erlang:element(2, erlang:element(3, Cfg)),
Output_override
)
);
<<"typescript"/utf8>> ->
generate_typescript(
Codegen_ir,
apply_output_override(
erlang:element(3, erlang:element(3, Cfg)),
Output_override
)
);
<<"react-query"/utf8>> ->
generate_react_query(
Codegen_ir,
apply_output_override(
erlang:element(4, erlang:element(3, Cfg)),
Output_override
),
apply_output_override(
erlang:element(3, erlang:element(3, Cfg)),
Output_override
)
);
<<"swr"/utf8>> ->
generate_swr(
Codegen_ir,
apply_output_override(
erlang:element(5, erlang:element(3, Cfg)),
Output_override
),
apply_output_override(
erlang:element(3, erlang:element(3, Cfg)),
Output_override
)
);
<<"fetch"/utf8>> ->
generate_fetch(
Codegen_ir,
apply_output_override(
erlang:element(6, erlang:element(3, Cfg)),
Output_override
),
apply_output_override(
erlang:element(3, erlang:element(3, Cfg)),
Output_override
)
);
_ ->
gleam_stdlib:println(
<<"Unknown target: "/utf8, Target_override/binary>>
),
[]
end.
-file("src/nori/cli.gleam", 445).
-spec get_directory(binary()) -> binary().
get_directory(Path) ->
Parts = gleam@string:split(Path, <<"/"/utf8>>),
case erlang:length(Parts) of
N when N > 1 ->
_pipe = Parts,
_pipe@1 = gleam@list:take(_pipe, N - 1),
gleam@string:join(_pipe@1, <<"/"/utf8>>);
_ ->
<<"."/utf8>>
end.
-file("src/nori/cli.gleam", 427).
-spec write_generated_files(list(nori@codegen@plugin:generated_file())) -> nil.
write_generated_files(Files) ->
gleam@list:each(
Files,
fun(File) ->
Full_path = erlang:element(2, File),
Dir = get_directory(Full_path),
case simplifile:create_directory_all(Dir) of
{ok, _} ->
nil;
{error, _} ->
nil
end,
case simplifile:write(Full_path, erlang:element(3, File)) of
{ok, _} ->
gleam_stdlib:println(<<" Wrote: "/utf8, Full_path/binary>>);
{error, _} ->
gleam_stdlib:println(
<<" Error writing: "/utf8, Full_path/binary>>
)
end
end
).
-file("src/nori/cli.gleam", 49).
-spec generate_command() -> glint:command(nil).
generate_command() ->
glint:command_help(
<<"Generate code from an OpenAPI spec.\n\n"/utf8,
"Targets: gleam, typescript, react-query, swr, fetch, all"/utf8>>,
fun() ->
glint:flag(
begin
_pipe = glint:string_flag(<<"target"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<""/utf8>>),
glint:flag_help(
_pipe@1,
<<"Target: gleam, typescript, react-query, swr, fetch, all"/utf8>>
)
end,
fun(Target) ->
glint:flag(
begin
_pipe@2 = glint:string_flag(<<"output"/utf8>>),
_pipe@3 = glint:flag_default(_pipe@2, <<""/utf8>>),
glint:flag_help(
_pipe@3,
<<"Output directory for generated files"/utf8>>
)
end,
fun(Output) ->
glint:flag(
begin
_pipe@4 = glint:string_flag(
<<"config"/utf8>>
),
_pipe@5 = glint:flag_default(
_pipe@4,
<<"nori.config.yaml"/utf8>>
),
glint:flag_help(
_pipe@5,
<<"Path to config file"/utf8>>
)
end,
fun(Config_path) ->
glint:flag(
begin
_pipe@6 = glint:string_flag(
<<"spec"/utf8>>
),
_pipe@7 = glint:flag_default(
_pipe@6,
<<""/utf8>>
),
glint:flag_help(
_pipe@7,
<<"Path to OpenAPI spec file (overrides config)"/utf8>>
)
end,
fun(Spec_arg) ->
glint:flag(
begin
_pipe@8 = glint:bool_flag(
<<"allow-unsupported"/utf8>>
),
_pipe@9 = glint:flag_default(
_pipe@8,
false
),
glint:flag_help(
_pipe@9,
<<"Generate even if the spec uses capabilities nori does not support yet (output may be incomplete)"/utf8>>
)
end,
fun(Allow_unsupported) ->
glint:command(
fun(_, _, Flags) ->
Target_val@1 = case Target(
Flags
) of
{ok, Target_val} -> Target_val;
_assert_fail ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"nori/cli"/utf8>>,
function => <<"generate_command"/utf8>>,
line => 85,
value => _assert_fail,
start => 2635,
'end' => 2676,
pattern_start => 2646,
pattern_end => 2660}
)
end,
Output_dir@1 = case Output(
Flags
) of
{ok, Output_dir} -> Output_dir;
_assert_fail@1 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"nori/cli"/utf8>>,
function => <<"generate_command"/utf8>>,
line => 86,
value => _assert_fail@1,
start => 2679,
'end' => 2720,
pattern_start => 2690,
pattern_end => 2704}
)
end,
Config_file@1 = case Config_path(
Flags
) of
{ok,
Config_file} -> Config_file;
_assert_fail@2 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"nori/cli"/utf8>>,
function => <<"generate_command"/utf8>>,
line => 87,
value => _assert_fail@2,
start => 2723,
'end' => 2770,
pattern_start => 2734,
pattern_end => 2749}
)
end,
Spec_override@1 = case Spec_arg(
Flags
) of
{ok,
Spec_override} -> Spec_override;
_assert_fail@3 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"nori/cli"/utf8>>,
function => <<"generate_command"/utf8>>,
line => 88,
value => _assert_fail@3,
start => 2773,
'end' => 2819,
pattern_start => 2784,
pattern_end => 2801}
)
end,
Allow_unsupported_val@1 = case Allow_unsupported(
Flags
) of
{ok,
Allow_unsupported_val} -> Allow_unsupported_val;
_assert_fail@4 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"nori/cli"/utf8>>,
function => <<"generate_command"/utf8>>,
line => 89,
value => _assert_fail@4,
start => 2822,
'end' => 2885,
pattern_start => 2833,
pattern_end => 2858}
)
end,
Cfg = case nori@config:load(
Config_file@1
) of
{ok, C} ->
C;
{error, _} ->
nori@config:default(
)
end,
Spec = case Spec_override@1 of
<<""/utf8>> ->
erlang:element(
2,
Cfg
);
S ->
S
end,
gleam_stdlib:println(
<<"Parsing spec: "/utf8,
Spec/binary>>
),
case nori@yaml:parse_file(
Spec
) of
{error, Err} ->
gleam_stdlib:println(
<<"Error: Failed to parse spec file"/utf8>>
),
case Err of
{file_error,
Path,
Msg} ->
gleam_stdlib:println(
<<<<<<" File error ("/utf8,
Path/binary>>/binary,
"): "/utf8>>/binary,
Msg/binary>>
);
{yaml_syntax_error,
Msg@1} ->
gleam_stdlib:println(
<<" YAML syntax error: "/utf8,
Msg@1/binary>>
);
{yaml_decode_error,
_} ->
gleam_stdlib:println(
<<" Failed to decode OpenAPI document"/utf8>>
)
end;
{ok, Doc} ->
Proceed = case nori@capability:check(
Doc
) of
{ok, _} ->
true;
{error,
Issues} ->
gleam_stdlib:println(
<<""/utf8>>
),
gleam_stdlib:println(
<<<<<<"Spec uses "/utf8,
(erlang:integer_to_binary(
erlang:length(
Issues
)
))/binary>>/binary,
" unsupported capabilit"/utf8>>/binary,
(case erlang:length(
Issues
) of
1 ->
<<"y:"/utf8>>;
_ ->
<<"ies:"/utf8>>
end)/binary>>
),
gleam@list:each(
Issues,
fun(
Issue
) ->
gleam_stdlib:println(
nori@capability:issue_to_string(
Issue
)
)
end
),
gleam_stdlib:println(
<<""/utf8>>
),
case Allow_unsupported_val@1 of
true ->
gleam_stdlib:println(
<<"Continuing anyway (--allow-unsupported); generated code may be incomplete."/utf8>>
),
true;
false ->
gleam_stdlib:println(
<<"Aborting. Pass --allow-unsupported to generate with degraded output."/utf8>>
),
false
end
end,
case Proceed of
false ->
nil;
true ->
gleam_stdlib:println(
<<"Building IR..."/utf8>>
),
Codegen_ir = nori@codegen@ir_builder:build(
Doc
),
Files = generate_files_from_config(
Codegen_ir,
Cfg,
Target_val@1,
Output_dir@1
),
case Files of
[] ->
gleam_stdlib:println(
<<"No files generated. Check your target or config."/utf8>>
);
_ ->
write_generated_files(
Files
),
gleam_stdlib:println(
<<<<"Generated "/utf8,
(erlang:integer_to_binary(
erlang:length(
Files
)
))/binary>>/binary,
" file(s)"/utf8>>
)
end
end
end
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/nori/cli.gleam", 512).
-spec write_init_file(binary(), binary()) -> nil.
write_init_file(Path, Content) ->
case simplifile_erl:is_file(Path) of
{ok, true} ->
gleam_stdlib:println(<<" Exists: "/utf8, Path/binary>>);
_ ->
case simplifile:write(Path, Content) of
{ok, _} ->
gleam_stdlib:println(<<" Created: "/utf8, Path/binary>>);
{error, _} ->
gleam_stdlib:println(
<<" Error creating "/utf8, Path/binary>>
)
end
end.
-file("src/nori/cli.gleam", 524).
-spec init_config() -> binary().
init_config() ->
<<"# nori.config.yaml — Configuration for OpenAPI code generation
#
# Run: gleam run -m nori/cli -- generate
# Docs: See nori.config.example.yaml for all options
# Path to your OpenAPI spec
spec: ./nori.yaml
# Output configuration per target
output:
gleam:
enabled: true
dir: ./src/generated
generated_suffix: false
typescript:
enabled: true
dir: ./src/api
generated_suffix: true
use_interfaces: true
use_exports: true
readonly_properties: false
react_query:
enabled: false
swr:
enabled: false
fetch:
enabled: false
"/utf8>>.
-file("src/nori/cli.gleam", 559).
-spec init_openapi_spec() -> binary().
init_openapi_spec() ->
<<"openapi: \"3.1.0\"
info:
title: My API
version: \"1.0.0\"
description: Your API description here
servers:
- url: http://localhost:3000
paths:
/health:
get:
operationId: healthCheck
summary: Health check
responses:
\"200\":
description: OK
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
required:
- ok
"/utf8>>.
-file("src/nori/cli.gleam", 587).
-spec init_ts_types_template() -> binary().
init_ts_types_template() ->
<<"// Generated by nori - Do not edit manually
//
// Edit templates/typescript_types.hbs to customize this output.
// Available context variables:
// types[] — list of type definitions
// .is_record — true for object types (has .fields[])
// .is_enum — true for enum types (has .enum_values)
// .is_union — true for union types (has .union_members)
// .is_alias — true for type aliases (has .alias_target)
// .name — type name (PascalCase)
// .description — optional JSDoc description
// .use_exports — whether to add 'export' keyword
// .use_interfaces — whether to use 'interface' vs 'type'
// fields[] — list of fields (inside is_record)
// .name — field name
// .ts_type — TypeScript type string
// .optional — true if field is optional (adds ?)
// .readonly — true if readonly modifier
{{#each types}}
{{#if is_record}}
{{#if has_description}}
/** {{description}} */
{{/if}}
{{#if use_exports}}export {{/if}}{{#if use_interfaces}}interface {{name}} {
{{/if}}{{#unless use_interfaces}}type {{name}} = {
{{/unless}}{{#each fields}}
{{#if readonly}}readonly {{/if}}{{name}}{{#if optional}}?{{/if}}: {{ts_type}};
{{/each}}
}
{{/if}}
{{#if is_enum}}
{{#if has_description}}
/** {{description}} */
{{/if}}
{{#if use_exports}}export {{/if}}type {{name}} = {{enum_values}};
{{/if}}
{{#if is_union}}
{{#if has_description}}
/** {{description}} */
{{/if}}
{{#if use_exports}}export {{/if}}type {{name}} = {{union_members}};
{{/if}}
{{#if is_alias}}
{{#if has_description}}
/** {{description}} */
{{/if}}
{{#if use_exports}}export {{/if}}type {{name}} = {{alias_target}};
{{/if}}
{{/each}}
"/utf8>>.
-file("src/nori/cli.gleam", 641).
-spec init_ts_client_template() -> binary().
init_ts_client_template() ->
<<"// Generated by nori - Do not edit manually
//
// Edit templates/typescript_client.hbs to customize this output.
// Available context variables:
// type_imports — import statement for types
// config_type — ClientConfig interface definition
// create_client — configure() factory function
// functions[] — list of endpoint functions
// .function_text — complete function source code
{{type_imports}}
{{config_type}}
{{create_client}}
{{#each functions}}
{{function_text}}
{{/each}}
"/utf8>>.
-file("src/nori/cli.gleam", 665).
-spec init_ts_react_query_template() -> binary().
init_ts_react_query_template() ->
<<"// Generated by nori - Do not edit manually
//
// Edit templates/typescript_react_query.hbs to customize this output.
// Available context variables:
// rq_imports — import { useQuery, useMutation } from '@tanstack/react-query'
// type_imports — import types
// client_imports — import client functions
// key_factories[] — query key factory objects
// .factory_text — complete factory source code
// hooks[] — list of hook functions
// .hook_text — complete hook source code
{{rq_imports}}
{{type_imports}}
{{client_imports}}
{{#if has_factories}}
{{#each key_factories}}
{{factory_text}}
{{/each}}
{{/if}}
{{#each hooks}}
{{hook_text}}
{{/each}}
"/utf8>>.
-file("src/nori/cli.gleam", 695).
-spec init_ts_swr_template() -> binary().
init_ts_swr_template() ->
<<"// Generated by nori - Do not edit manually
//
// Edit templates/typescript_swr.hbs to customize this output.
// Available context variables:
// swr_imports — import useSWR from 'swr'
// type_imports — import types
// client_imports — import client functions
// hooks[] — list of hook functions
// .hook_text — complete hook source code
{{swr_imports}}
{{type_imports}}
{{client_imports}}
{{#each hooks}}
{{hook_text}}
{{/each}}
"/utf8>>.
-file("src/nori/cli.gleam", 460).
-spec init_command() -> glint:command(nil).
init_command() ->
glint:command_help(
<<"Initialize OpenAPI code generation in a Gleam project.\n\n"/utf8,
"Creates config file, templates directory, and a starter spec."/utf8>>,
fun() ->
glint:command(
fun(_, _, _) ->
gleam_stdlib:println(
<<"Initializing OpenAPI code generation..."/utf8>>
),
case simplifile_erl:is_file(<<"nori.config.yaml"/utf8>>) of
{ok, true} ->
gleam_stdlib:println(
<<" Exists: nori.config.yaml"/utf8>>
);
_ ->
case simplifile:write(
<<"nori.config.yaml"/utf8>>,
init_config()
) of
{ok, _} ->
gleam_stdlib:println(
<<" Created: nori.config.yaml"/utf8>>
);
{error, _} ->
gleam_stdlib:println(
<<" Error creating nori.config.yaml"/utf8>>
)
end
end,
case simplifile:create_directory_all(<<"templates"/utf8>>) of
{ok, _} ->
nil;
{error, _} ->
nil
end,
write_init_file(
<<"templates/typescript_types.hbs"/utf8>>,
init_ts_types_template()
),
write_init_file(
<<"templates/typescript_client.hbs"/utf8>>,
init_ts_client_template()
),
write_init_file(
<<"templates/typescript_react_query.hbs"/utf8>>,
init_ts_react_query_template()
),
write_init_file(
<<"templates/typescript_swr.hbs"/utf8>>,
init_ts_swr_template()
),
case simplifile_erl:is_file(<<"nori.yaml"/utf8>>) of
{ok, true} ->
gleam_stdlib:println(<<" Exists: nori.yaml"/utf8>>);
_ ->
case simplifile:write(
<<"nori.yaml"/utf8>>,
init_openapi_spec()
) of
{ok, _} ->
gleam_stdlib:println(
<<" Created: nori.yaml"/utf8>>
);
{error, _} ->
gleam_stdlib:println(
<<" Error creating nori.yaml"/utf8>>
)
end
end,
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(<<"Done! Next steps:"/utf8>>),
gleam_stdlib:println(
<<" 1. Edit nori.yaml with your API spec"/utf8>>
),
gleam_stdlib:println(
<<" 2. Edit nori.config.yaml to configure output"/utf8>>
),
gleam_stdlib:println(
<<" 3. Run: gleam run -m nori/cli -- generate"/utf8>>
),
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<"Customize templates in templates/*.hbs to change generated code."/utf8>>
)
end
)
end
).
-file("src/nori/cli.gleam", 721).
-spec bundle_command() -> glint:command(nil).
bundle_command() ->
glint:command_help(
<<"Bundle a multi-file OpenAPI spec into a single file."/utf8>>,
fun() ->
glint:named_arg(
<<"spec-path"/utf8>>,
fun(Spec_path) ->
glint:flag(
begin
_pipe = glint:string_flag(<<"output"/utf8>>),
_pipe@1 = glint:flag_default(
_pipe,
<<"nori.gen.yaml"/utf8>>
),
glint:flag_help(
_pipe@1,
<<"Output file path for the bundled spec"/utf8>>
)
end,
fun(Output) ->
glint:command(
fun(Named, _, Flags) ->
Spec = Spec_path(Named),
Output_file@1 = case Output(Flags) of
{ok, Output_file} -> Output_file;
_assert_fail ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"nori/cli"/utf8>>,
function => <<"bundle_command"/utf8>>,
line => 734,
value => _assert_fail,
start => 20460,
'end' => 20502,
pattern_start => 20471,
pattern_end => 20486}
)
end,
gleam_stdlib:println(
<<"Bundling spec: "/utf8, Spec/binary>>
),
case nori@bundler:bundle(Spec) of
{error, Err} ->
gleam_stdlib:println(
<<"Error: Failed to bundle spec"/utf8>>
),
case Err of
{resolve_error, _} ->
gleam_stdlib:println(
<<" Reference resolution failed"/utf8>>
);
{decode_error, Msg} ->
gleam_stdlib:println(
<<" Decode error: "/utf8,
Msg/binary>>
)
end;
{ok, Yaml_str} ->
case simplifile:write(
Output_file@1,
Yaml_str
) of
{ok, _} ->
gleam_stdlib:println(
<<"Bundled spec written to: "/utf8,
Output_file@1/binary>>
);
{error, _} ->
gleam_stdlib:println(
<<"Error: Failed to write output file: "/utf8,
Output_file@1/binary>>
)
end
end
end
)
end
)
end
)
end
).
-file("src/nori/cli.gleam", 760).
-spec validate_command() -> glint:command(nil).
validate_command() ->
glint:command_help(
<<"Validate an OpenAPI spec."/utf8>>,
fun() ->
glint:named_arg(
<<"spec-path"/utf8>>,
fun(Spec_path) ->
glint:command(
fun(Named, _, _) ->
Spec = Spec_path(Named),
gleam_stdlib:println(
<<"Validating spec: "/utf8, Spec/binary>>
),
case nori@yaml:parse_file(Spec) of
{error, Err} ->
gleam_stdlib:println(
<<"Error: Failed to parse spec file"/utf8>>
),
case Err of
{file_error, Path, Msg} ->
gleam_stdlib:println(
<<<<<<" File error ("/utf8,
Path/binary>>/binary,
"): "/utf8>>/binary,
Msg/binary>>
);
{yaml_syntax_error, Msg@1} ->
gleam_stdlib:println(
<<" YAML syntax error: "/utf8,
Msg@1/binary>>
);
{yaml_decode_error, _} ->
gleam_stdlib:println(
<<" Failed to decode OpenAPI document"/utf8>>
)
end;
{ok, Doc} ->
case nori@validator:validate(Doc) of
valid ->
gleam_stdlib:println(
<<"Spec is valid."/utf8>>
);
{invalid, Errors} ->
gleam_stdlib:println(
<<<<"Spec has "/utf8,
(erlang:integer_to_binary(
erlang:length(
Errors
)
))/binary>>/binary,
" validation error(s):"/utf8>>
),
gleam@list:each(
Errors,
fun(Err@1) ->
gleam_stdlib:println(
<<" - "/utf8,
(nori@validator@errors:to_string(
Err@1
))/binary>>
)
end
)
end,
case nori@capability:check(Doc) of
{ok, _} ->
nil;
{error, Issues} ->
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<<<<<"Spec uses "/utf8,
(erlang:integer_to_binary(
erlang:length(
Issues
)
))/binary>>/binary,
" unsupported capabilit"/utf8>>/binary,
(case erlang:length(Issues) of
1 ->
<<"y:"/utf8>>;
_ ->
<<"ies:"/utf8>>
end)/binary>>
),
gleam@list:each(
Issues,
fun(Issue) ->
gleam_stdlib:println(
nori@capability:issue_to_string(
Issue
)
)
end
)
end
end
end
)
end
)
end
).
-file("src/nori/cli.gleam", 33).
-spec main() -> nil.
main() ->
_pipe = glint:new(),
_pipe@1 = glint:with_name(_pipe, <<"nori/cli"/utf8>>),
_pipe@2 = glint:as_module(_pipe@1),
_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()),
_pipe@6 = glint:add(_pipe@5, [<<"bundle"/utf8>>], bundle_command()),
_pipe@7 = glint:add(_pipe@6, [<<"validate"/utf8>>], validate_command()),
glint:run(_pipe@7, erlang:element(4, argv:load())).