Current section
Files
Jump to
Current section
Files
src/sqlode@cli.erl
-module(sqlode@cli).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/sqlode/cli.gleam").
-export([decide_color_emission/2, 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/sqlode/cli.gleam", 54).
?DOC(false).
-spec decide_color_emission(boolean(), {ok, binary()} | {error, nil}) -> boolean().
decide_color_emission(Is_terminal, No_color_env) ->
No_color_active = case No_color_env of
{ok, Value} ->
Value /= <<""/utf8>>;
{error, nil} ->
false
end,
Is_terminal andalso not No_color_active.
-file("src/sqlode/cli.gleam", 44).
?DOC(
" Decide whether `--help` output should carry ANSI escape codes.\n"
" Wraps the FFI probes for stdout's TTY status and the `NO_COLOR`\n"
" environment variable so the policy itself is a small pure\n"
" function that the test suite can pin without mocking the BEAM\n"
" primitives. See `decide_color_emission` for the rule.\n"
).
-spec should_emit_color() -> boolean().
should_emit_color() ->
decide_color_emission(
sqlode_ffi:is_stdout_terminal(),
sqlode_ffi:no_color_env()
).
-file("src/sqlode/cli.gleam", 221).
-spec version_command() -> glint:command(nil).
version_command() ->
glint:command_help(
<<"Print the sqlode version and exit."/utf8>>,
fun() ->
glint:command(
fun(_, _, _) ->
gleam_stdlib:println(
<<"sqlode v"/utf8, (<<"0.31.0"/utf8>>)/binary>>
)
end
)
end
).
-file("src/sqlode/cli.gleam", 264).
?DOC(
" Run `gleam format` on the just-written files so the generated\n"
" records and decoder chains break across lines instead of running\n"
" past the project's line-width ruler. Treated as best-effort: the\n"
" files have already been written and are syntactically correct, so\n"
" a missing `gleam` binary or a non-zero formatter exit is reported\n"
" as a warning and the command still exits 0. (#543)\n"
).
-spec format_generated_files(list(binary())) -> nil.
format_generated_files(Written) ->
case sqlode@internal@formatter:format_files(Written) of
{ok, nil} ->
gleam_stdlib:println(<<" Formatted generated code"/utf8>>);
{error, Error} ->
gleam_stdlib:println_error(
<<" Warning: "/utf8,
(sqlode@internal@formatter:error_to_string(Error))/binary>>
)
end.
-file("src/sqlode/cli.gleam", 351).
-spec ensure_parent_directory(binary()) -> {ok, nil} | {error, binary()}.
ensure_parent_directory(Dir) ->
case Dir of
<<""/utf8>> ->
{ok, nil};
<<"."/utf8>> ->
{ok, nil};
_ ->
case simplifile:create_directory_all(Dir) of
{ok, nil} ->
{ok, nil};
{error, Err} ->
{error,
<<<<<<"failed to create directory "/utf8, Dir/binary>>/binary,
": "/utf8>>/binary,
(simplifile:describe_error(Err))/binary>>}
end
end.
-file("src/sqlode/cli.gleam", 368).
-spec validate_init_flags(binary(), binary()) -> {ok, nil} | {error, binary()}.
validate_init_flags(Engine, Runtime) ->
_pipe = case Engine of
<<"postgresql"/utf8>> ->
{ok, nil};
<<"sqlite"/utf8>> ->
{ok, nil};
<<"mysql"/utf8>> ->
{ok, nil};
_ ->
{error,
<<<<"unsupported engine \""/utf8, Engine/binary>>/binary,
"\"; expected postgresql, sqlite, or mysql"/utf8>>}
end,
gleam@result:'try'(_pipe, fun(_) -> case Runtime of
<<"raw"/utf8>> ->
{ok, nil};
<<"native"/utf8>> ->
{ok, nil};
_ ->
{error,
<<<<"unsupported runtime \""/utf8, Runtime/binary>>/binary,
"\"; expected raw or native"/utf8>>}
end end).
-file("src/sqlode/cli.gleam", 389).
-spec config_template(binary(), binary()) -> binary().
config_template(Engine, Runtime) ->
<<<<<<<<<<<<<<<<"version: \"2\"
sql:
- schema: \"db/schema.sql\"
queries: \"db/query.sql\"
engine: \""/utf8,
Engine/binary>>/binary,
"\"\n"/utf8>>/binary,
" gen:\n"/utf8>>/binary,
" gleam:\n"/utf8>>/binary,
" out: \"src/db\"\n"/utf8>>/binary,
" runtime: \""/utf8>>/binary,
Runtime/binary>>/binary,
"\"\n"/utf8>>.
-file("src/sqlode/cli.gleam", 426).
-spec starter_schema(binary()) -> binary().
starter_schema(Engine) ->
case Engine of
<<"sqlite"/utf8>> ->
<<"CREATE TABLE authors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
bio TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
"/utf8>>;
<<"mysql"/utf8>> ->
<<"CREATE TABLE authors (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name TEXT NOT NULL,
bio TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
"/utf8>>;
_ ->
<<"CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
bio TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
"/utf8>>
end.
-file("src/sqlode/cli.gleam", 462).
-spec starter_query(binary()) -> binary().
starter_query(Engine) ->
Get_placeholder = case Engine of
<<"mysql"/utf8>> ->
<<"?"/utf8>>;
<<"sqlite"/utf8>> ->
<<"?"/utf8>>;
_ ->
<<"$1"/utf8>>
end,
<<<<<<<<<<<<<<<<<<<<<<"-- name: GetAuthor :one
SELECT id, name, bio
FROM authors
WHERE id = "/utf8,
Get_placeholder/binary>>/binary,
";\n"/utf8>>/binary,
"\n"/utf8>>/binary,
"-- name: ListAuthors :many\n"/utf8>>/binary,
"SELECT id, name\n"/utf8>>/binary,
"FROM authors\n"/utf8>>/binary,
"ORDER BY name;\n"/utf8>>/binary,
"\n"/utf8>>/binary,
"-- name: CreateAuthor :exec\n"/utf8>>/binary,
"INSERT INTO authors (name, bio)\n"/utf8>>/binary,
"VALUES (sqlode.arg(author_name), sqlode.narg(bio));\n"/utf8>>.
-file("src/sqlode/cli.gleam", 397).
-spec create_stub_files(binary(), binary()) -> nil.
create_stub_files(Base_dir, Engine) ->
Schema_content = starter_schema(Engine),
Query_content = starter_query(Engine),
Db_dir = filepath:join(Base_dir, <<"db"/utf8>>),
Schema_path = filepath:join(Db_dir, <<"schema.sql"/utf8>>),
Query_path = filepath:join(Db_dir, <<"query.sql"/utf8>>),
_ = simplifile:create_directory_all(Db_dir),
case simplifile_erl:is_file(Schema_path) of
{ok, true} ->
gleam_stdlib:println(
<<<<" Skipped "/utf8, Schema_path/binary>>/binary,
" (already exists)"/utf8>>
);
_ ->
case simplifile:write(Schema_path, Schema_content) of
{ok, _} ->
gleam_stdlib:println(
<<" Created "/utf8, Schema_path/binary>>
);
{error, _} ->
gleam_stdlib:println(
<<" Warning: failed to create "/utf8,
Schema_path/binary>>
)
end
end,
case simplifile_erl:is_file(Query_path) of
{ok, true} ->
gleam_stdlib:println(
<<<<" Skipped "/utf8, Query_path/binary>>/binary,
" (already exists)"/utf8>>
);
_ ->
case simplifile:write(Query_path, Query_content) of
{ok, _} ->
gleam_stdlib:println(
<<" Created "/utf8, Query_path/binary>>
);
{error, _} ->
gleam_stdlib:println(
<<" Warning: failed to create "/utf8,
Query_path/binary>>
)
end
end.
-file("src/sqlode/cli.gleam", 305).
-spec run_init(binary(), binary(), binary()) -> nil.
run_init(Path, Engine, Runtime) ->
case validate_init_flags(Engine, Runtime) of
{error, Msg} ->
gleam_stdlib:println_error(<<"Error: "/utf8, Msg/binary>>),
init:stop(1);
{ok, nil} ->
Template = config_template(Engine, Runtime),
case simplifile_erl:is_file(Path) of
{ok, true} ->
gleam_stdlib:println_error(
<<<<"Error: "/utf8, Path/binary>>/binary,
" already exists"/utf8>>
),
init:stop(1);
_ ->
Parent_dir = filepath:directory_name(Path),
case ensure_parent_directory(Parent_dir) of
{error, Msg@1} ->
gleam_stdlib:println_error(
<<"Error: "/utf8, Msg@1/binary>>
),
init:stop(1);
{ok, nil} ->
case simplifile:write(Path, Template) of
{ok, _} ->
gleam_stdlib:println(
<<" Created "/utf8, Path/binary>>
),
create_stub_files(Parent_dir, Engine);
{error, Err} ->
gleam_stdlib:println_error(
<<<<<<"Error: failed to write "/utf8,
Path/binary>>/binary,
": "/utf8>>/binary,
(simplifile:describe_error(Err))/binary>>
),
init:stop(1)
end
end
end
end.
-file("src/sqlode/cli.gleam", 120).
-spec init_command() -> glint:command(nil).
init_command() ->
begin
glint:flag(
begin
_pipe = glint:string_flag(<<"output"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<"./sqlode.yaml"/utf8>>),
glint:flag_help(
_pipe@1,
<<"Output path for generated config (default: ./sqlode.yaml)"/utf8>>
)
end,
fun(Output_path) ->
glint:flag(
begin
_pipe@2 = glint:string_flag(<<"engine"/utf8>>),
_pipe@3 = glint:flag_default(
_pipe@2,
<<"postgresql"/utf8>>
),
glint:flag_help(
_pipe@3,
<<"Target engine: postgresql | sqlite | mysql (default: postgresql)"/utf8>>
)
end,
fun(Engine_flag) ->
glint:flag(
begin
_pipe@4 = glint:string_flag(<<"runtime"/utf8>>),
_pipe@5 = glint:flag_default(
_pipe@4,
<<"raw"/utf8>>
),
glint:flag_help(
_pipe@5,
<<"Generated runtime: raw | native (default: raw)"/utf8>>
)
end,
fun(Runtime_flag) ->
glint:command_help(
<<"Scaffold a sqlode.yaml plus starter db/schema.sql and db/query.sql.
Examples:
sqlode init
sqlode init --output=./config/sqlode.yaml
sqlode init --engine=sqlite --runtime=native
sqlode init --engine=mysql"/utf8>>,
fun() ->
glint:command(
fun(_, _, Flags) ->
Path = begin
_pipe@6 = Output_path(Flags),
gleam@result:unwrap(
_pipe@6,
<<"./sqlode.yaml"/utf8>>
)
end,
Engine = begin
_pipe@7 = Engine_flag(Flags),
gleam@result:unwrap(
_pipe@7,
<<"postgresql"/utf8>>
)
end,
Runtime = begin
_pipe@8 = Runtime_flag(
Flags
),
gleam@result:unwrap(
_pipe@8,
<<"raw"/utf8>>
)
end,
run_init(Path, Engine, Runtime)
end
)
end
)
end
)
end
)
end
)
end.
-file("src/sqlode/cli.gleam", 279).
-spec autodiscover_config() -> {ok, binary()} | {error, binary()}.
autodiscover_config() ->
Found = gleam@list:filter(
[<<"sqlode.yaml"/utf8>>,
<<"sqlode.yml"/utf8>>,
<<"sqlc.yaml"/utf8>>,
<<"sqlc.yml"/utf8>>,
<<"sqlc.json"/utf8>>],
fun(Path) -> case simplifile_erl:is_file(Path) of
{ok, true} ->
true;
_ ->
false
end end
),
case Found of
[] ->
{error,
<<<<"No config file found. Looked for: "/utf8,
(gleam@string:join(
[<<"sqlode.yaml"/utf8>>,
<<"sqlode.yml"/utf8>>,
<<"sqlc.yaml"/utf8>>,
<<"sqlc.yml"/utf8>>,
<<"sqlc.json"/utf8>>],
<<", "/utf8>>
))/binary>>/binary,
". Create one with `sqlode init` or pass --config=<path>."/utf8>>};
[Single] ->
{ok, Single};
Multiple ->
{error,
<<<<"Multiple config files found: "/utf8,
(gleam@string:join(Multiple, <<", "/utf8>>))/binary>>/binary,
". Pick one explicitly with --config=<path>."/utf8>>}
end.
-file("src/sqlode/cli.gleam", 272).
-spec resolve_config_path(binary()) -> {ok, binary()} | {error, binary()}.
resolve_config_path(Flag_value) ->
case Flag_value of
<<""/utf8>> ->
autodiscover_config();
Explicit ->
{ok, Explicit}
end.
-file("src/sqlode/cli.gleam", 196).
-spec run_verify(binary()) -> nil.
run_verify(Flag_value) ->
case resolve_config_path(Flag_value) of
{error, Message} ->
gleam_stdlib:println_error(<<"Error: "/utf8, Message/binary>>),
init:stop(1);
{ok, Config_path} ->
gleam_stdlib:println(
<<"Verifying config: "/utf8, Config_path/binary>>
),
case sqlode@internal@verify:run(Config_path) of
{error, Err} ->
gleam_stdlib:println_error(
<<"Error: "/utf8,
(sqlode@internal@verify:error_to_string(Err))/binary>>
),
init:stop(1);
{ok, Report} ->
gleam_stdlib:println(
sqlode@internal@verify:report_to_string(Report)
),
case erlang:element(2, Report) of
[] ->
nil;
_ ->
init:stop(1)
end
end
end.
-file("src/sqlode/cli.gleam", 162).
-spec verify_command() -> glint:command(nil).
verify_command() ->
begin
glint:flag(
begin
_pipe = glint:string_flag(<<"config"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<""/utf8>>),
glint:flag_help(
_pipe@1,
<<"Path to config file. Default: auto-discover sqlode.yaml, sqlode.yml, sqlc.yaml, sqlc.yml, or sqlc.json in the current directory."/utf8>>
)
end,
fun(Config_path) ->
glint:command_help(
<<"Run static verification on the project without writing files.
Loads the config, parses every schema and query the generator would
use, and runs the full analyser pass. Any query-analysis error,
schema warning under strict_views, or policy violation (for example
`query_parameter_limit`) is collected into a single report.
The command exits non-zero when at least one finding is reported —
suitable for CI gates before the generation step runs.
Examples:
sqlode verify
sqlode verify --config=./custom.yaml"/utf8>>,
fun() ->
glint:command(
fun(_, _, Flags) ->
Flag_value = begin
_pipe@2 = Config_path(Flags),
gleam@result:unwrap(_pipe@2, <<""/utf8>>)
end,
run_verify(Flag_value)
end
)
end
)
end
)
end.
-file("src/sqlode/cli.gleam", 229).
-spec run_generate(binary()) -> nil.
run_generate(Flag_value) ->
case resolve_config_path(Flag_value) of
{error, Message} ->
gleam_stdlib:println_error(<<"Error: "/utf8, Message/binary>>),
init:stop(1);
{ok, Config_path} ->
gleam_stdlib:println(
<<"Loading config from: "/utf8, Config_path/binary>>
),
case sqlode@internal@generate:run(Config_path) of
{ok, Written} ->
gleam_stdlib:println(<<""/utf8>>),
gleam_stdlib:println(
<<<<"Successfully generated "/utf8,
(erlang:integer_to_binary(
erlang:length(Written)
))/binary>>/binary,
" files"/utf8>>
),
gleam@list:each(
Written,
fun(Path) ->
gleam_stdlib:println(
<<" Generated: "/utf8, Path/binary>>
)
end
),
format_generated_files(Written);
{error, Error} ->
gleam_stdlib:println_error(
<<"Error: "/utf8,
(sqlode@internal@generate:error_to_string(Error))/binary>>
),
init:stop(1)
end
end.
-file("src/sqlode/cli.gleam", 90).
-spec generate_command() -> glint:command(nil).
generate_command() ->
begin
glint:flag(
begin
_pipe = glint:string_flag(<<"config"/utf8>>),
_pipe@1 = glint:flag_default(_pipe, <<""/utf8>>),
glint:flag_help(
_pipe@1,
<<"Path to config file. Default: auto-discover sqlode.yaml, sqlode.yml, sqlc.yaml, sqlc.yml, or sqlc.json in the current directory."/utf8>>
)
end,
fun(Config_path) ->
glint:command_help(
<<"Parse SQL schema and queries, emit Gleam code.
Without --config, generate auto-discovers sqlode.yaml, sqlode.yml,
sqlc.yaml, sqlc.yml, or sqlc.json in the current directory and fails
if more than one candidate exists.
Examples:
sqlode generate
sqlode generate --config=./custom.yaml"/utf8>>,
fun() ->
glint:command(
fun(_, _, Flags) ->
Flag_value = begin
_pipe@2 = Config_path(Flags),
gleam@result:unwrap(_pipe@2, <<""/utf8>>)
end,
run_generate(Flag_value)
end
)
end
)
end
)
end.
-file("src/sqlode/cli.gleam", 14).
-spec app() -> glint:glint(nil).
app() ->
Base = begin
_pipe = glint:new(),
glint:with_name(_pipe, <<"sqlode"/utf8>>)
end,
With_styling = case should_emit_color() of
true ->
_pipe@1 = Base,
glint:pretty_help(_pipe@1, glint:default_pretty_help());
false ->
Base
end,
_pipe@2 = With_styling,
_pipe@3 = glint:add(_pipe@2, [<<"generate"/utf8>>], generate_command()),
_pipe@4 = glint:add(_pipe@3, [<<"init"/utf8>>], init_command()),
_pipe@5 = glint:add(_pipe@4, [<<"verify"/utf8>>], verify_command()),
glint:add(_pipe@5, [<<"version"/utf8>>], version_command()).