Current section

Files

Jump to
parrot src parrot.gleam
Raw

src/parrot.gleam

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)
}