Packages

A fun, friendly, and type-safe ORM for Gleam! Compose type-safe SQL queries, generate decoders and types from schemas, and with a fun CLI!

Current section

Files

Jump to
funsies src cli_project project.gleam
Raw

src/cli_project/project.gleam

//// A set of utilities to work within a Gleam project and to make the CLI work. Some functions are from the Squirre repo, thanks so much!
//// These help me orient myself within a gleam project, and also the creation of the `generate.gleam` file which is used to actually generate the code.
////
import filepath
import funsies/orm_gen
import gleam/dict
import gleam/erlang
import gleam/io
import gleam/list
import gleam/result
import gleam/string
import globlin
import globlin_fs
import shellout
import simplifile
import tom
/// Returns the project's name, read from the `gleam.toml` file.
/// FROM SQUIRREL PROJECT, TYSM
///
pub fn name() -> Result(String, Nil) {
let configuration_path = filepath.join(root(), "gleam.toml")
use configuration <- try_nil(simplifile.read(configuration_path))
use toml <- try_nil(tom.parse(configuration))
use name <- try_nil(tom.get_string(toml, ["name"]))
Ok(name)
}
fn try_nil(
result: Result(a, b),
then do: fn(a) -> Result(c, Nil),
) -> Result(c, Nil) {
result.try(result.replace_error(result, Nil), do)
}
/// Finds the path of the project's `src` directory.
/// This recursively walks up from the current directory until it finds a
/// `gleam.toml` and builds it from there.
/// FROM SQUIRRLE PROJECT, TYSM
///
pub fn src() -> String {
filepath.join(root(), "src")
}
/// Finds the path leading to the project's root folder. This recursively walks
/// up from the current directory until it finds a `gleam.toml`.
/// FROM SWQUIRREL PROJECT, TYSM
fn root() -> String {
find_root(".")
}
fn find_root(path: String) -> String {
let toml = filepath.join(path, "gleam.toml")
case simplifile.is_file(toml) {
Ok(False) | Error(_) -> find_root(filepath.join("..", path))
Ok(True) -> path
}
}
/// Finds the schema files (looks in src/schema/*.gleam), and returns them as a dictionary of the file_path and the name of the schema (by removing everything except hte final clause without the extensions)
///
pub fn find_schema_files() -> Result(dict.Dict(String, String), Nil) {
let current_root = root()
io.println("Current root: " <> current_root)
let assert Ok(cwd) = simplifile.current_directory()
let assert Ok(pattern) =
globlin.new_pattern(filepath.join(cwd, "/src/schema/*.gleam"))
case globlin_fs.glob(pattern, returning: globlin_fs.RegularFiles) {
Ok(files) -> {
io.println("Files found: " <> files |> string.join(", "))
files
|> list.map(fn(file) {
let file_names = file |> filepath.base_name()
let file_paths = "schema/" <> file_names
#(
file_paths,
file |> filepath.base_name() |> filepath.strip_extension(),
)
})
|> dict.from_list()
|> Ok()
}
Error(err) -> {
io.debug(err)
Error(Nil)
}
}
}
/// This has a bad name. Basically what it does is create the generate file, which is used to generate the types, decoders, and the `insert` function.
/// The reason the insert function has to be generated is because it felt like the easiest way to maintain complete type-safety.
///
pub fn create_files(input: dict.Dict(String, String)) {
let values = dict.values(input)
simplifile.create_directory(src() <> "/funs/")
let code =
"// code to be run to generate the types and decoders"
<> "\n// DO NOT MODIFY"
<> "\n // generated by `funsies`"
<> "\n\nimport funsies/decoder"
<> "\nimport funsies/orm_gen"
<> "\n\n"
<> string.concat(
list.map(values, fn(val) { "import schema/" <> val <> "\n" }),
)
<> "\n"
<> "\n"
<> "pub fn main() {"
<> string.concat(
list.map(values, fn(val) {
"decoder.generate_row_type("
<> val
<> "."
<> val
<> "()"
<> ")"
<> "\n"
<> "decoder.generate_decoder_code("
<> val
<> "."
<> val
<> "()"
<> ")"
<> "\n"
<> "orm_gen.create_insert("
<> "\""
<> "src/funs/insert.gleam"
<> "\", "
<> val
<> "."
<> val
<> "())"
<> "\n"
}),
)
<> "}"
simplifile.write(src() <> "/funs/generate.gleam", code)
}
/// Also a really bad name haha. This function is just a helper to run the gleam code that creates everything (the generate file).
///
pub fn work() {
let assert Ok(cwd) = simplifile.current_directory()
case
shellout.command(
"gleam",
["run", "-m", "funs/generate"],
in: cwd <> "/",
opt: [],
)
{
Ok(res) -> {
io.println("Successfully generated types and decoders.")
Ok(Nil)
}
Error(e) -> {
io.debug("Error generating types and decoders")
Error(Nil)
}
}
}