import argv import filepath import gleam/dict import gleam/io import gleam/list import gleam/result import gleam/string import parrot/internal/cli import parrot/internal/codegen import parrot/internal/config import parrot/internal/db import parrot/internal/errors import parrot/internal/lib import parrot/internal/project import parrot/internal/shellout import parrot/internal/spinner import parrot/internal/sqlc import simplifile pub fn main() { let cmd: Result(cli.Command, String) = case argv.load().arguments { [] -> { cli.parse_env("DATABASE_URL") |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["--env-var", env] -> { cli.parse_env(env) |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["-e", env] -> { cli.parse_env(env) |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["--sqlite", file_path] -> { Ok(cli.Generate(sqlc.SQLite, file_path)) } ["help"] -> Ok(cli.Usage) _ -> Ok(cli.Usage) } case cmd { Error(e) -> io.println(lib.red("Error: " <> e)) Ok(cmd) -> case cmd { cli.Usage -> io.println(cli.usage) cli.Generate(engine:, db:) -> { let result = cmd_gen(engine, db) case result { Error(e) -> io.println(lib.red("\nError: " <> errors.err_to_string(e))) Ok(_) -> io.println(lib.green("SQL successfully generated!")) } } } } } fn cmd_gen(engine: sqlc.Engine, db: String) -> Result(Nil, errors.ParrotError) { let db = case db { "sqlite://" <> db -> db "sqlite:" <> db -> db db -> db } let files = lib.walk(project.src()) let queries = files |> dict.to_list |> list.map(fn(file) { let #(_, files) = file list.map(files, fn(file) { let file = case file { "./" <> rest -> rest x -> x } filepath.join("../..", file) }) }) |> list.flatten() let sqlc_binary = sqlc.sqlc_binary_path() let sqlc_dir = filepath.directory_name(sqlc_binary) let schema_file = filepath.join(sqlc_dir, "schema.sql") let sqlc_file = filepath.join(sqlc_dir, "sqlc.json") let queries_file = filepath.join(sqlc_dir, "queries.json") let _ = simplifile.create_directory_all(sqlc_dir) let spinner = spinner.new("downloading sqlc binary") |> spinner.start() let _ = case sqlc.download_binary() { Error(_) -> spinner.complete_current(spinner, spinner.orange_warning()) Ok(_) -> spinner.complete_current(spinner, spinner.green_checkmark()) } let spinner = spinner.new("verifying sqlc binary") |> spinner.start() let _ = case sqlc.verify_binary() { Error(_) -> spinner.complete_current(spinner, spinner.orange_warning()) Ok(_) -> spinner.complete_current(spinner, spinner.green_checkmark()) } let sqlc_json = sqlc.gen_sqlc_json(engine, queries) let _ = simplifile.write(sqlc_file, sqlc_json) let spinner = spinner.new("fetching schema") |> spinner.start() use schema_sql <- result.try(case engine { sqlc.MySQL -> { use schema <- result.try(db.fetch_schema_mysql(db)) Ok(schema) } sqlc.PostgreSQL -> { use schema <- result.try(db.fetch_schema_postgresql(db)) // this is an edge case with the postgres schema dump. // sqlc does not like those lines from postgres 17. let schema = schema |> string.split("\n") |> list.filter(fn(line) { !string.starts_with(line, "\\restrict") && !string.starts_with(line, "\\unrestrict") }) |> string.join("\n") Ok(schema) } sqlc.SQLite -> { use schema <- result.try(db.fetch_schema_sqlite(db)) let sql = string.trim(schema) Ok(sql) } }) let _ = simplifile.write(schema_file, schema_sql) spinner.complete_current(spinner, spinner.green_checkmark()) let spinner = spinner.new("generating gleam code") |> spinner.start() let gen_result = shellout.command( run: "./sqlc", with: ["generate", "--file", "sqlc.json"], in: sqlc_dir, opt: [], ) use _ <- result.try(case gen_result { Ok(_) -> Ok(Nil) Error(error) -> { let #(_, error) = error Error(errors.SqlcGenerateError(error)) } }) let project_name = project.project_name() let config = config.Config( gleam_module_out_path: project_name <> "/sql.gleam", json_file_path: queries_file, ) use gen_result <- result.try(codegen.codegen_from_config(config)) spinner.complete_current(spinner, spinner.green_checkmark()) let spinner = spinner.new("formatting generated code") |> spinner.start() let output_path = filepath.join(project.src(), project_name <> "/sql.gleam") let stdout_format = shellout.command( run: "gleam", with: ["format", output_path], in: project.root(), opt: [], ) use _ <- result.try(case stdout_format { Ok(_) -> Ok(Nil) Error(error) -> { let #(_, error) = error Error(errors.GleamFormatError(error)) } }) spinner.complete_current(spinner, spinner.green_checkmark()) gen_result.unknown_types |> list.unique() |> list.each(fn(unknown) { io.println(lib.yellow("unknown column type: " <> unknown)) }) io.println("") Ok(Nil) }