diff --git a/src/cigogne.gleam b/src/cigogne.gleam index 74e0e48..ea2691f 100644 --- a/src/cigogne.gleam +++ b/src/cigogne.gleam @@ -111,6 +111,12 @@ pub fn main() { cigogne_config |> config.merge(config) |> update_config() + cli.InitConfig -> cigogne_config |> update_config() + cli.PrintUnapplied(config:) -> + cigogne_config + |> config.merge(config) + |> create_engine() + |> result.map(print_unapplied) } |> result.map_error(print_error) } @@ -521,12 +527,27 @@ pub fn get_non_applied_migrations( fn show(engine: MigrationEngine) -> Nil { let migration_names = engine.applied |> list.map(migration.to_fullname) + let to_apply = engine.non_applied |> list.map(migration.to_fullname) io.println( - "Applied migrations: [\n" <> migration_names |> string.join(",\n\t") <> "]", + "Applied migrations: [\n\t" + <> migration_names |> string.join(",\n\t") + <> "\n]\n", + ) + io.println( + "Migrations to apply: [\n\t" <> to_apply |> string.join(",\n\t") <> "\n]", ) } +fn print_unapplied(migration_engine: MigrationEngine) -> Nil { + io.print("Unapplied migrations:") + + use unapplied_mig <- list.each(migration_engine.non_applied) + + io.println("\n\n--- == " <> migration.to_fullname(unapplied_mig) <> " ==\n") + io.println(unapplied_mig.queries_up |> string.join("\n")) +} + /// Print a MigrateError to the standard error stream. pub fn print_error(error: CigogneError) -> Nil { io.println_error(get_error_message(error)) @@ -535,8 +556,8 @@ pub fn print_error(error: CigogneError) -> Nil { fn get_error_message(error: CigogneError) -> String { case error { CompoundError(errors:) -> - "Many errors happened ! Here is the list:\n" - <> errors |> list.map(get_error_message) |> string.join("\n\n") + "Many errors happened ! Here is the list:\n " + <> errors |> list.map(get_error_message) |> string.join("\n ") ConfigError(error:) -> "Configuration error: " <> config.get_error_message(error) DatabaseError(error:) -> @@ -545,10 +566,10 @@ fn get_error_message(error: CigogneError) -> String { LibNotIncluded(name:) -> "There were no migrations for library " <> name - <> " !\nDid you include it ?" + <> " ! Did you include it ?" MigrationError(error:) -> "Migration error: " <> migration.get_error_message(error) - NothingToApply -> "There is no migration to apply !\nYou are up-to-date !" + NothingToApply -> "There is no migration to apply ! You are up-to-date !" NothingToRollback -> "There is no migration to rollback !" ParserError(error:) -> "Parser error: " <> parser_formatter.get_error_message(error) diff --git a/src/cigogne/config.gleam b/src/cigogne/config.gleam index 434417b..372d007 100644 --- a/src/cigogne/config.gleam +++ b/src/cigogne/config.gleam @@ -359,13 +359,13 @@ pub fn get_migrations_folder(migrations_config: MigrationsConfig) -> String { pub fn get_error_message(error: ConfigError) { case error { AppNameError -> - "Couldn't get the application's name !\nAre you in a folder with a gleam.toml file ?" + "Couldn't get the application's name ! Are you in a folder with a gleam.toml file ?" AppNotFound(name:) -> "Could not find an application with name " <> name - <> "\nDid you correctly add it to your project ?" + <> " Did you correctly add it to your project ?" ConfigFileNotFound(path:) -> - "Could not find this project's config file.\nNote: it should be at " + "Could not find this project's config file. Note: it should be at " <> path FSError(error:) -> "FS error: " <> fs.get_error_message(error) } diff --git a/src/cigogne/internal/cli.gleam b/src/cigogne/internal/cli.gleam index 8693e33..fa031fc 100644 --- a/src/cigogne/internal/cli.gleam +++ b/src/cigogne/internal/cli.gleam @@ -11,6 +11,8 @@ pub type CliActions { IncludeLib(config: config.Config, lib_name: String) RemoveLib(config: config.Config, lib_name: String) UpdateConfig(config: config.Config) + InitConfig + PrintUnapplied(config: config.Config) } pub fn get_action( @@ -27,6 +29,8 @@ pub fn get_action( |> cli_lib.add_action(include_lib_action(application_name)) |> cli_lib.add_action(remove_lib_action(application_name)) |> cli_lib.add_action(update_config_action(application_name)) + |> cli_lib.add_action(init_config_action()) + |> cli_lib.add_action(print_unapplied_action(application_name)) |> cli_lib.run(args) } @@ -71,7 +75,7 @@ fn show_migrations_action( ) -> cli_lib.Action(CliActions) { cli_lib.create_action( ["show"], - "Show the last / currently applied migration", + "Show data about the current state of migrations", { use config <- cli_lib.map_action(config_decoder(application_name)) ShowMigrations(config) @@ -80,7 +84,7 @@ fn show_migrations_action( } fn migrate_up_all_action(application_name: String) -> cli_lib.Action(CliActions) { - cli_lib.create_action(["up-all"], "Apply all non-applied migrations", { + cli_lib.create_action(["all"], "Apply all non-applied migrations", { use config <- cli_lib.map_action(config_decoder(application_name)) MigrateUpAll(config) }) @@ -108,7 +112,7 @@ fn new_migration_action(application_name: String) -> cli_lib.Action(CliActions) fn include_lib_action(application_name: String) -> cli_lib.Action(CliActions) { cli_lib.create_action( - ["include-lib"], + ["include"], "Include migrations from specified library", { use config <- cli_lib.then_action(config_decoder(application_name)) @@ -126,7 +130,7 @@ fn include_lib_action(application_name: String) -> cli_lib.Action(CliActions) { fn remove_lib_action(application_name: String) -> cli_lib.Action(CliActions) { cli_lib.create_action( - ["remove-lib"], + ["remove"], "Remove migrations from specified library", { use config <- cli_lib.then_action(config_decoder(application_name)) @@ -144,8 +148,8 @@ fn remove_lib_action(application_name: String) -> cli_lib.Action(CliActions) { fn update_config_action(application_name: String) -> cli_lib.Action(CliActions) { cli_lib.create_action( - ["update-config"], - "Create or update cigogne's config for your project", + ["config", "update"], + "Update cigogne's config for your project with provided options", { use config <- cli_lib.then_action(config_decoder(application_name)) cli_lib.options(UpdateConfig(config:)) @@ -153,6 +157,27 @@ fn update_config_action(application_name: String) -> cli_lib.Action(CliActions) ) } +fn init_config_action() -> cli_lib.Action(CliActions) { + cli_lib.create_action( + ["config", "init"], + "Create cigogne's config for your project", + { cli_lib.options(InitConfig) }, + ) +} + +fn print_unapplied_action( + application_name: String, +) -> cli_lib.Action(CliActions) { + cli_lib.create_action( + ["print", "unapplied"], + "Print all unapplied migrations as SQL strings", + { + use config <- cli_lib.then_action(config_decoder(application_name)) + cli_lib.options(PrintUnapplied(config:)) + }, + ) +} + fn config_decoder( application_name: String, ) -> cli_lib.ActionDecoder(config.Config) { diff --git a/src/cigogne/internal/cli_lib.gleam b/src/cigogne/internal/cli_lib.gleam index b88a002..94fae05 100644 --- a/src/cigogne/internal/cli_lib.gleam +++ b/src/cigogne/internal/cli_lib.gleam @@ -290,10 +290,8 @@ fn print_help(command: Command(a)) -> Nil { io.println("Usage: gleam run -m " <> command.name <> " []") io.println("") io.println("Available actions:") - list.each(command.actions |> list.reverse, fn(action) { - let action_path = action.action_path |> string.join(" ") - io.println(" " <> action_path <> " - " <> action.help) - }) + + print_actions(command.actions) io.println("") io.println( @@ -303,6 +301,24 @@ fn print_help(command: Command(a)) -> Nil { ) } +fn print_actions(actions: List(Action(a))) -> Nil { + let actions_data = + actions + |> list.map(fn(a) { #(a.action_path |> string.join(" "), a.help) }) + let max_action_length = + list.fold(actions_data, 0, fn(acc, d) { int.max(acc, string.length(d.0)) }) + let action_column_width = { max_action_length / 4 } * 4 + 4 + + list.each(actions_data |> list.reverse, fn(action) { + io.println( + " " + <> action.0 |> string.pad_end(action_column_width, " ") + <> " - " + <> action.1, + ) + }) +} + fn print_help_action(action: Action(a)) -> Nil { { action.action_path |> string.join(" ") <> "\n" <> action.help } |> box_text(80) @@ -310,15 +326,29 @@ fn print_help_action(action: Action(a)) -> Nil { io.println("") io.println("Available flags:") - list.each(action.decoder.flag_defs, fn(flag) { + + print_flags(action.decoder.flag_defs) +} + +fn print_flags(flags: List(Flag)) -> Nil { + let flags_data = + flags + |> list.map(fn(f) { + #(print_flag(f.name) <> " " <> f.type_, f.description, f.aliases) + }) + let max_flag_length = + list.fold(flags_data, 0, fn(acc, d) { int.max(acc, string.length(d.0)) }) + let flag_column_width = { max_flag_length / 4 } * 4 + 4 + + list.each(flags_data, fn(flag) { { - justify_left(print_flag(flag.name) <> " " <> flag.type_, 20) + flag.0 |> string.pad_end(flag_column_width, " ") <> " - " - <> flag.description + <> flag.1 <> "\n" - <> string.repeat(" ", 24) + <> string.repeat(" ", flag_column_width + 4) <> "Aliases: " - <> flag.aliases |> list.map(print_flag) |> string.join(", ") + <> flag.2 |> list.map(print_flag) |> string.join(", ") } |> io.println() }) @@ -332,13 +362,6 @@ fn print_flag(flag_name: String) { } } -fn justify_left(text: String, width: Int) { - case string.length(text) { - i if i < width -> text <> string.repeat(" ", width - i) - _ -> text - } -} - fn box_text(text: String, max_width: Int) -> String { let lines = center_text(text, max_width - 4) |> string.split("\n") let actual_width = string.length(lines |> list.first |> result.unwrap("")) + 4 diff --git a/src/cigogne/internal/database.gleam b/src/cigogne/internal/database.gleam index b4fae06..48eaacc 100644 --- a/src/cigogne/internal/database.gleam +++ b/src/cigogne/internal/database.gleam @@ -311,7 +311,7 @@ fn describe_query_error(error: pog.QueryError) -> String { pog.ConnectionUnavailable -> "CONNECTION UNAVAILABLE" pog.ConstraintViolated(message, _constraint, _detail) -> message pog.PostgresqlError(_code, _name, message) -> - "Postgresql error : " <> message + "Postgresql error: " <> message pog.UnexpectedArgumentCount(expected, got) -> "Expected " <> int.to_string(expected) @@ -321,8 +321,8 @@ fn describe_query_error(error: pog.QueryError) -> String { pog.UnexpectedArgumentType(expected, got) -> "Expected argument of type " <> expected <> ", got " <> got <> " !" pog.UnexpectedResultType(errs) -> - "Unexpected result type ! \n" - <> list.map(errs, describe_decode_error) |> string.join("\n") + "Unexpected result types !\n " + <> list.map(errs, describe_decode_error) |> string.join("\n ") pog.QueryTimeout -> "Query Timeout" } } diff --git a/src/cigogne/internal/fs.gleam b/src/cigogne/internal/fs.gleam index b56c76c..6697991 100644 --- a/src/cigogne/internal/fs.gleam +++ b/src/cigogne/internal/fs.gleam @@ -91,9 +91,8 @@ fn read_files(paths: List(String)) -> Result(List(File), FSError) { pub fn get_error_message(error: FSError) -> String { case error { CompoundError(errors:) -> - "List of errors : [\n" - <> errors |> list.map(get_error_message) |> string.join(",\n\t") - <> "\n]" + "Many FS errors happened: \n " + <> errors |> list.map(get_error_message) |> string.join("\n ") MissingFileError(file:) -> "Could not find file at path " <> file MissingFolderError(folder:) -> "Could not find folder at path " <> folder <> "/" diff --git a/src/cigogne/internal/parser_formatter.gleam b/src/cigogne/internal/parser_formatter.gleam index 0e008a5..d8c582d 100644 --- a/src/cigogne/internal/parser_formatter.gleam +++ b/src/cigogne/internal/parser_formatter.gleam @@ -297,7 +297,7 @@ pub fn get_error_message(error: ParserError) -> String { UnfinishedLiteral(context:) -> "Invalid migration: Unfinished " <> context WrongFormat(name:) -> name - <> " isn't a valid migration name !\nIt should be YYYYMMDDHHmmss-" + <> " isn't a valid migration name ! It should be YYYYMMDDHHmmss-" NotASQLFile(filepath:) -> filepath <> " is not a .sql file" } } diff --git a/src/cigogne/migration.gleam b/src/cigogne/migration.gleam index 8339cfc..e4e795a 100644 --- a/src/cigogne/migration.gleam +++ b/src/cigogne/migration.gleam @@ -209,23 +209,22 @@ pub fn is_zero_migration(migration: Migration) -> Bool { pub fn get_error_message(error: MigrationError) -> String { case error { CompoundError(errors:) -> - "Many errors happened : [" - <> errors |> list.map(get_error_message) |> string.join(",\n\t") - <> "]" + "Many migration errors happened: \n " + <> errors |> list.map(get_error_message) |> string.join(",\n ") FileHashChanged(fullname:) -> "Hash of file " <> fullname - <> " has changed !\nDid you modify the file after applying the migration ?" + <> " has changed ! Did you modify the file after applying the migration ?" MigrationNotFound(fullname:) -> "Could not find a matching migration file for " <> fullname - <> ".\nCheck there is a file named " + <> ". Check there is a file named " <> fullname <> ".sql in your migrations folder." NameTooLongError(name:) -> "Name " <> name - <> "is too long !\nMigration names shouldn't exceed 255 characters." + <> "is too long ! Migration names shouldn't exceed 255 characters." NothingToMergeError -> "Could not merge 0 migrations" } }