Current section

Files

Jump to
squeal src squeal.gleam
Raw

src/squeal.gleam

import argv
import filepath
import gleam/io
import gleam/list
import gleam/result
import gleam/string
import glint
import simplifile
pub fn main() {
glint.new()
|> glint.pretty_help(glint.default_pretty_help())
|> glint.add([], execute())
|> glint.run(argv.load().arguments)
}
pub type FormatOptions {
FormatOptions(
tab_width: Int,
use_tabs: Bool,
keyword_case: Casing,
identifier_case: Casing,
data_type_case: Casing,
function_case: Casing,
indent_style: IndentStyle,
logical_operator_new_line_before: Bool,
expression_width: Int,
lines_between_queries: Int,
dense_operators: Bool,
newline_before_semicolon: Bool,
dialect: Dialect,
)
}
pub const default_options = FormatOptions(
tab_width: 2,
use_tabs: False,
keyword_case: Uppercase,
identifier_case: Uppercase,
data_type_case: Uppercase,
function_case: Uppercase,
indent_style: Standard,
logical_operator_new_line_before: True,
expression_width: 50,
lines_between_queries: 1,
dense_operators: False,
newline_before_semicolon: False,
dialect: Sql,
)
const default = default_options
pub type Casing {
Preserve
Uppercase
Lowercase
}
pub fn casing_from_string(string: String) -> Result(Casing, Nil) {
case string {
"preserve" -> Ok(Preserve)
"upper" -> Ok(Uppercase)
"lower" -> Ok(Lowercase)
_ -> Error(Nil)
}
}
pub fn casing_to_string(casing: Casing) -> String {
case casing {
Preserve -> "preserve"
Uppercase -> "upper"
Lowercase -> "lower"
}
}
pub type IndentStyle {
Standard
TabularLeft
TabularRight
}
pub fn indent_style_from_string(string: String) -> Result(IndentStyle, Nil) {
case string {
"standard" -> Ok(Standard)
"tableft" -> Ok(TabularLeft)
"tabright" -> Ok(TabularRight)
_ -> Error(Nil)
}
}
pub fn indent_style_to_string(style: IndentStyle) -> String {
case style {
Standard -> "standard"
TabularLeft -> "tableft"
TabularRight -> "tabright"
}
}
pub type Dialect {
Sql
Postgresql
Sqlite
MySql
MariaSql
}
pub fn dialect_from_string(string: String) -> Result(Dialect, Nil) {
case string {
"sql" -> Ok(Sql)
"postgresql" -> Ok(Postgresql)
"postgres" -> Ok(Postgresql)
"sqlite" -> Ok(Sqlite)
"mysql" -> Ok(MySql)
"mariasql" -> Ok(MariaSql)
_ -> Error(Nil)
}
}
pub fn dialect_to_string(dialect: Dialect) -> String {
case dialect {
Sql -> "sql"
Postgresql -> "postgresql"
Sqlite -> "sqlite"
MySql -> "mysql"
MariaSql -> "mariasql"
}
}
@external(javascript, "./squeal_ffi.mjs", "format")
pub fn format(sql: String, options: FormatOptions) -> String
// CLI
fn execute() -> glint.Command(Nil) {
use width <- glint.flag(width_flag())
use tabs <- glint.flag(tabs_flag())
use keywordcase <- glint.flag(keywordcase_flag())
use identifiercase <- glint.flag(identifiercase_flag())
use datatypecase <- glint.flag(datatypecase_flag())
use functioncase <- glint.flag(functioncase_flag())
use indentstyle <- glint.flag(indentstyle_flag())
use logicalopnewline <- glint.flag(logicalopnewline_flag())
use expressionwidth <- glint.flag(expressionwidth_flag())
use linesbetween <- glint.flag(linesbetween_flag())
use denseoperators <- glint.flag(denseoperators_flag())
use newlinesemi <- glint.flag(newlinesemi_flag())
use dialect <- glint.flag(dialect_flag())
use _, _, flags <- glint.command()
let assert Ok(width) = width(flags)
let assert Ok(tabs) = tabs(flags)
let assert Ok(keywordcase) =
keywordcase(flags)
|> result.nil_error()
|> result.then(casing_from_string)
let assert Ok(identifiercase) =
identifiercase(flags)
|> result.nil_error()
|> result.then(casing_from_string)
let assert Ok(datatypecase) =
datatypecase(flags)
|> result.nil_error()
|> result.then(casing_from_string)
let assert Ok(functioncase) =
functioncase(flags)
|> result.nil_error()
|> result.then(casing_from_string)
let assert Ok(indentstyle) =
indentstyle(flags)
|> result.nil_error()
|> result.then(indent_style_from_string)
let assert Ok(logicalopnewline) = logicalopnewline(flags)
let assert Ok(expressionwidth) = expressionwidth(flags)
let assert Ok(linesbetween) = linesbetween(flags)
let assert Ok(denseoperators) = denseoperators(flags)
let assert Ok(newlinesemi) = newlinesemi(flags)
let assert Ok(dialect) =
dialect(flags)
|> result.nil_error()
|> result.then(dialect_from_string)
let options =
FormatOptions(
tab_width: width,
use_tabs: tabs,
keyword_case: keywordcase,
identifier_case: identifiercase,
data_type_case: datatypecase,
function_case: functioncase,
indent_style: indentstyle,
logical_operator_new_line_before: logicalopnewline,
expression_width: expressionwidth,
lines_between_queries: linesbetween,
dense_operators: denseoperators,
newline_before_semicolon: newlinesemi,
dialect: dialect,
)
let assert Ok(dir) = simplifile.current_directory()
get_sql_files(dir, [])
|> list.map(fn(file) {
let assert Ok(content) = simplifile.read(file)
let formatted = format(content, options)
simplifile.write(file, formatted)
})
Nil
}
fn get_sql_files(dir: String, files: List(String)) -> List(String) {
let assert Ok(contents) = simplifile.read_directory(dir)
contents
|> list.map(fn(path) {
let full_path = filepath.join(dir, path)
let file_type = case simplifile.file_info(full_path) {
Ok(info) -> info |> simplifile.file_info_type()
Error(_) -> {
io.println_error("Couldn't get file-type of '" <> full_path <> "'.")
simplifile.Other
}
}
case file_type, path |> string.ends_with(".sql") {
simplifile.File, True -> Ok([full_path, ..files])
simplifile.Directory, _ -> Ok(get_sql_files(full_path, files))
_, _ -> Error(Nil)
}
})
|> result.values()
|> list.flatten()
}
fn width_flag() {
glint.int_flag("width")
|> glint.flag_default(default.tab_width)
}
fn tabs_flag() {
glint.bool_flag("tabs")
|> glint.flag_default(default.use_tabs)
}
fn keywordcase_flag() {
glint.string_flag("keywordcase")
|> glint.flag_default("upper")
}
fn identifiercase_flag() {
glint.string_flag("identifiercase")
|> glint.flag_default("upper")
}
fn datatypecase_flag() {
glint.string_flag("datatypecase")
|> glint.flag_default("upper")
}
fn functioncase_flag() {
glint.string_flag("functioncase")
|> glint.flag_default("upper")
}
fn indentstyle_flag() {
glint.string_flag("indentstyle")
|> glint.flag_default("standard")
}
fn logicalopnewline_flag() {
glint.bool_flag("logicalopnewline")
|> glint.flag_default(True)
}
fn expressionwidth_flag() {
glint.int_flag("expressionwidth")
|> glint.flag_default(default.expression_width)
}
fn linesbetween_flag() {
glint.int_flag("linesbetween")
|> glint.flag_default(default.lines_between_queries)
}
fn denseoperators_flag() {
glint.bool_flag("denseoperators")
|> glint.flag_default(default.dense_operators)
}
fn newlinesemi_flag() {
glint.bool_flag("newlinesemi")
|> glint.flag_default(default.newline_before_semicolon)
}
fn dialect_flag() {
glint.string_flag("dialect")
|> glint.flag_default("sql")
}