Bump clap version to 3.2 (#706)

* bump clap to 3.1.17

sea-orm-migration: bump clap version to 3.1.17

sea-orm-cli: use clap derive API instead of builder API

sea-orm-migration: use clap derive

* Fix clippy warnings

* Migration CLI verbose with default value

* bump clap to 3.2

Co-authored-by: Thanh Van <tvt@smonv.com>
Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
This commit is contained in:
smonv 2022-06-26 19:52:33 +07:00 committed by GitHub
parent ca3809a6d3
commit 580fa90023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 425 additions and 418 deletions

View File

@ -29,7 +29,7 @@ path = "src/bin/sea.rs"
required-features = ["codegen"]
[dependencies]
clap = { version = "^2.33.3" }
clap = { version = "^3.2", features = ["env", "derive"] }
dotenv = { version = "^0.15" }
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
sea-orm-codegen = { version = "^0.8.0", path = "../sea-orm-codegen", optional = true }

View File

@ -1,17 +1,24 @@
use clap::StructOpt;
use dotenv::dotenv;
use sea_orm_cli::*;
use sea_orm_cli::{handle_error, run_generate_command, run_migrate_command, Cli, Commands};
#[async_std::main]
async fn main() {
dotenv().ok();
let matches = cli::build_cli().get_matches();
let cli = Cli::parse();
let verbose = cli.verbose;
match matches.subcommand() {
("generate", Some(matches)) => run_generate_command(matches)
.await
match cli.command {
Commands::Generate { command } => {
run_generate_command(command, verbose)
.await
.unwrap_or_else(handle_error);
}
Commands::Migrate {
migration_dir,
command,
} => run_migrate_command(command, migration_dir.as_str(), verbose)
.unwrap_or_else(handle_error),
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
_ => unreachable!("You should never see this message"),
}
}

View File

@ -1,19 +1,26 @@
//! COPY FROM bin/main.rs
use clap::StructOpt;
use dotenv::dotenv;
use sea_orm_cli::*;
use sea_orm_cli::{handle_error, run_generate_command, run_migrate_command, Cli, Commands};
#[async_std::main]
async fn main() {
dotenv().ok();
let matches = cli::build_cli().get_matches();
let cli = Cli::parse();
let verbose = cli.verbose;
match matches.subcommand() {
("generate", Some(matches)) => run_generate_command(matches)
.await
match cli.command {
Commands::Generate { command } => {
run_generate_command(command, verbose)
.await
.unwrap_or_else(handle_error);
}
Commands::Migrate {
migration_dir,
command,
} => run_migrate_command(command, migration_dir.as_str(), verbose)
.unwrap_or_else(handle_error),
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
_ => unreachable!("You should never see this message"),
}
}

View File

@ -1,129 +1,160 @@
use crate::migration::get_subcommands;
use clap::{App, AppSettings, Arg, SubCommand};
use clap::{ArgGroup, Parser, Subcommand};
pub fn build_cli() -> App<'static, 'static> {
let entity_subcommand = SubCommand::with_name("generate")
.about("Codegen related commands")
.setting(AppSettings::VersionlessSubcommands)
.subcommand(
SubCommand::with_name("entity")
.about("Generate entity")
.arg(
Arg::with_name("DATABASE_URL")
.long("database-url")
.short("u")
.help("Database URL")
.takes_value(true)
.required(true)
.env("DATABASE_URL"),
)
.arg(
Arg::with_name("DATABASE_SCHEMA")
.long("database-schema")
.short("s")
.help("Database schema")
.long_help("Database schema\n \
- For MySQL, this argument is ignored.\n \
- For PostgreSQL, this argument is optional with default value 'public'.")
.takes_value(true)
.env("DATABASE_SCHEMA"),
)
.arg(
Arg::with_name("OUTPUT_DIR")
.long("output-dir")
.short("o")
.help("Entity file output directory")
.takes_value(true)
.default_value("./"),
)
.arg(
Arg::with_name("INCLUDE_HIDDEN_TABLES")
.long("include-hidden-tables")
.help("Generate entity file for hidden tables (i.e. table name starts with an underscore)")
.takes_value(false),
)
.arg(
Arg::with_name("TABLES")
.long("tables")
.short("t")
.use_delimiter(true)
.help("Generate entity file for specified tables only (comma seperated)")
.takes_value(true)
.conflicts_with("INCLUDE_HIDDEN_TABLES"),
)
.arg(
Arg::with_name("EXPANDED_FORMAT")
.long("expanded-format")
.help("Generate entity file of expanded format")
.takes_value(false)
.conflicts_with("COMPACT_FORMAT"),
)
.arg(
Arg::with_name("COMPACT_FORMAT")
.long("compact-format")
.help("Generate entity file of compact format")
.takes_value(false)
.conflicts_with("EXPANDED_FORMAT"),
)
.arg(
Arg::with_name("WITH_SERDE")
.long("with-serde")
.help("Automatically derive serde Serialize / Deserialize traits for the entity (none, serialize, deserialize, both)")
.takes_value(true)
.default_value("none")
)
.arg(
Arg::with_name("MAX_CONNECTIONS")
.long("max-connections")
.help("The maximum amount of connections to use when connecting to the database.")
.takes_value(true)
.default_value("1")
),
)
.setting(AppSettings::SubcommandRequiredElseHelp);
#[derive(Parser, Debug)]
#[clap(version)]
pub struct Cli {
#[clap(action, global = true, short, long, help = "Show debug messages")]
pub verbose: bool,
let arg_migration_dir = Arg::with_name("MIGRATION_DIR")
.long("migration-dir")
.short("d")
.help("Migration script directory")
.takes_value(true)
.default_value("./migration");
let mut migrate_subcommands = SubCommand::with_name("migrate")
.about("Migration related commands")
.subcommand(
SubCommand::with_name("init")
.about("Initialize migration directory")
.arg(arg_migration_dir.clone()),
)
.subcommand(
SubCommand::with_name("generate")
.about("Generate a new, empty migration")
.arg(
Arg::with_name("MIGRATION_NAME")
.help("Name of the new migation")
.required(true)
.takes_value(true),
)
.arg(arg_migration_dir.clone()),
)
.arg(arg_migration_dir.clone());
for subcommand in get_subcommands() {
migrate_subcommands =
migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone()));
}
App::new("sea-orm-cli")
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::VersionlessSubcommands)
.subcommand(entity_subcommand)
.subcommand(migrate_subcommands)
.arg(
Arg::with_name("VERBOSE")
.long("verbose")
.short("v")
.help("Show debug messages")
.takes_value(false)
.global(true),
)
.setting(AppSettings::SubcommandRequiredElseHelp)
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, PartialEq, Debug)]
pub enum Commands {
#[clap(about = "Codegen related commands")]
#[clap(arg_required_else_help = true)]
Generate {
#[clap(subcommand)]
command: GenerateSubcommands,
},
#[clap(about = "Migration related commands")]
Migrate {
#[clap(
value_parser,
global = true,
short = 'd',
long,
help = "Migration script directory",
default_value = "./migration"
)]
migration_dir: String,
#[clap(subcommand)]
command: Option<MigrateSubcommands>,
},
}
#[derive(Subcommand, PartialEq, Debug)]
pub enum MigrateSubcommands {
#[clap(about = "Initialize migration directory")]
Init,
#[clap(about = "Generate a new, empty migration")]
Generate {
#[clap(
value_parser,
long,
required = true,
help = "Name of the new migration"
)]
migration_name: String,
},
#[clap(about = "Drop all tables from the database, then reapply all migrations")]
Fresh,
#[clap(about = "Rollback all applied migrations, then reapply all migrations")]
Refresh,
#[clap(about = "Rollback all applied migrations")]
Reset,
#[clap(about = "Check the status of all migrations")]
Status,
#[clap(about = "Apply pending migrations")]
Up {
#[clap(
value_parser,
short,
long,
default_value = "1",
help = "Number of pending migrations to be rolled back"
)]
num: u32,
},
#[clap(value_parser, about = "Rollback applied migrations")]
Down {
#[clap(
value_parser,
short,
long,
default_value = "1",
help = "Number of pending migrations to be rolled back"
)]
num: u32,
},
}
#[derive(Subcommand, PartialEq, Debug)]
pub enum GenerateSubcommands {
#[clap(about = "Generate entity")]
#[clap(arg_required_else_help = true)]
#[clap(group(ArgGroup::new("formats").args(&["compact-format", "expanded-format"])))]
#[clap(group(ArgGroup::new("group-tables").args(&["tables", "include-hidden-tables"])))]
Entity {
#[clap(action, long, help = "Generate entity file of compact format")]
compact_format: bool,
#[clap(action, long, help = "Generate entity file of expanded format")]
expanded_format: bool,
#[clap(
action,
long,
help = "Generate entity file for hidden tables (i.e. table name starts with an underscore)"
)]
include_hidden_tables: bool,
#[clap(
value_parser,
short = 't',
long,
use_value_delimiter = true,
takes_value = true,
help = "Generate entity file for specified tables only (comma separated)"
)]
tables: Option<String>,
#[clap(
value_parser,
long,
default_value = "1",
help = "The maximum amount of connections to use when connecting to the database."
)]
max_connections: u32,
#[clap(
value_parser,
short = 'o',
long,
default_value = "./",
help = "Entity file output directory"
)]
output_dir: String,
#[clap(
value_parser,
short = 's',
long,
env = "DATABASE_SCHEMA",
default_value = "public",
long_help = "Database schema\n \
- For MySQL, this argument is ignored.\n \
- For PostgreSQL, this argument is optional with default value 'public'."
)]
database_schema: String,
#[clap(
value_parser,
short = 'u',
long,
env = "DATABASE_URL",
help = "Database URL"
)]
database_url: String,
#[clap(
value_parser,
long,
default_value = "none",
help = "Automatically derive serde Serialize / Deserialize traits for the entity (none, serialize, deserialize, both)"
)]
with_serde: String,
},
}

View File

@ -1,23 +1,29 @@
use chrono::Local;
use clap::ArgMatches;
use regex::Regex;
use sea_orm_codegen::{EntityTransformer, OutputFile, WithSerde};
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command, str::FromStr};
use tracing_subscriber::{prelude::*, EnvFilter};
use url::Url;
pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
match matches.subcommand() {
("entity", Some(args)) => {
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
let tables = args
.values_of("TABLES")
.unwrap_or_default()
.collect::<Vec<_>>();
let expanded_format = args.is_present("EXPANDED_FORMAT");
let with_serde = args.value_of("WITH_SERDE").unwrap();
if args.is_present("VERBOSE") {
use crate::{GenerateSubcommands, MigrateSubcommands};
pub async fn run_generate_command(
command: GenerateSubcommands,
verbose: bool,
) -> Result<(), Box<dyn Error>> {
match command {
GenerateSubcommands::Entity {
compact_format: _,
expanded_format,
include_hidden_tables,
tables,
max_connections,
output_dir,
database_schema,
database_url,
with_serde,
} => {
if verbose {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_test_writer()
@ -35,18 +41,9 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
.try_init();
}
let max_connections = args
.value_of("MAX_CONNECTIONS")
.map(str::parse::<u32>)
.transpose()?
.unwrap();
// The database should be a valid URL that can be parsed
// protocol://username:password@host/database_name
let url = Url::parse(
args.value_of("DATABASE_URL")
.expect("No database url could be found"),
)?;
let url = Url::parse(&database_url)?;
// Make sure we have all the required url components
//
@ -68,6 +65,11 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
}
}
let tables = match tables {
Some(t) => t,
_ => "".to_string(),
};
// Closures for filtering tables
let filter_tables = |table: &str| -> bool {
if !tables.is_empty() {
@ -76,6 +78,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
true
};
let filter_hidden_tables = |table: &str| -> bool {
if include_hidden_tables {
true
@ -149,7 +152,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
use sea_schema::postgres::discovery::SchemaDiscovery;
use sqlx::Postgres;
let schema = args.value_of("DATABASE_SCHEMA").unwrap_or("public");
let schema = &database_schema;
let connection = connect::<Postgres>(max_connections, url.as_str()).await?;
let schema_discovery = SchemaDiscovery::new(connection, schema);
let schema = schema_discovery.discover().await;
@ -165,9 +168,9 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
};
let output = EntityTransformer::transform(table_stmts)?
.generate(expanded_format, WithSerde::from_str(with_serde).unwrap());
.generate(expanded_format, WithSerde::from_str(&with_serde).unwrap());
let dir = Path::new(output_dir);
let dir = Path::new(&output_dir);
fs::create_dir_all(dir)?;
for OutputFile { name, content } in output.files.iter() {
@ -184,8 +187,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
.wait()?;
}
}
_ => unreachable!("You should never see this message"),
};
}
Ok(())
}
@ -201,103 +203,106 @@ where
.map_err(Into::into)
}
pub fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
let migrate_subcommand = matches.subcommand();
// If it's `migrate init`
if let ("init", Some(args)) = migrate_subcommand {
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
let migration_dir = match migration_dir.ends_with('/') {
true => migration_dir.to_string(),
false => format!("{}/", migration_dir),
};
println!("Initializing migration directory...");
macro_rules! write_file {
($filename: literal) => {
let fn_content = |content: String| content;
write_file!($filename, $filename, fn_content);
};
($filename: literal, $template: literal, $fn_content: expr) => {
let filepath = [&migration_dir, $filename].join("");
println!("Creating file `{}`", filepath);
let path = Path::new(&filepath);
let prefix = path.parent().unwrap();
fs::create_dir_all(prefix).unwrap();
let mut file = fs::File::create(path)?;
let content = include_str!(concat!("../template/migration/", $template));
let content = $fn_content(content.to_string());
file.write_all(content.as_bytes())?;
pub fn run_migrate_command(
command: Option<MigrateSubcommands>,
migration_dir: &str,
verbose: bool,
) -> Result<(), Box<dyn Error>> {
match command {
Some(MigrateSubcommands::Init) => {
let migration_dir = match migration_dir.ends_with('/') {
true => migration_dir.to_string(),
false => format!("{}/", migration_dir),
};
println!("Initializing migration directory...");
macro_rules! write_file {
($filename: literal) => {
let fn_content = |content: String| content;
write_file!($filename, $filename, fn_content);
};
($filename: literal, $template: literal, $fn_content: expr) => {
let filepath = [&migration_dir, $filename].join("");
println!("Creating file `{}`", filepath);
let path = Path::new(&filepath);
let prefix = path.parent().unwrap();
fs::create_dir_all(prefix).unwrap();
let mut file = fs::File::create(path)?;
let content = include_str!(concat!("../template/migration/", $template));
let content = $fn_content(content.to_string());
file.write_all(content.as_bytes())?;
};
}
write_file!("src/lib.rs");
write_file!("src/m20220101_000001_create_table.rs");
write_file!("src/main.rs");
write_file!("Cargo.toml", "_Cargo.toml", |content: String| {
let ver = format!(
"^{}.{}.0",
env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR")
);
content.replace("<sea-orm-migration-version>", &ver)
});
write_file!("README.md");
println!("Done!");
// Early exit!
return Ok(());
}
write_file!("src/lib.rs");
write_file!("src/m20220101_000001_create_table.rs");
write_file!("src/main.rs");
write_file!("Cargo.toml", "_Cargo.toml", |content: String| {
let ver = format!(
"^{}.{}.0",
env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR")
);
content.replace("<sea-orm-migration-version>", &ver)
});
write_file!("README.md");
println!("Done!");
// Early exit!
return Ok(());
} else if let ("generate", Some(args)) = migrate_subcommand {
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
let migration_name = args.value_of("MIGRATION_NAME").unwrap();
println!("Generating new migration...");
Some(MigrateSubcommands::Generate { migration_name }) => {
println!("Generating new migration...");
// build new migration filename
let now = Local::now();
let migration_name = format!(
"m{}_{}",
now.format("%Y%m%d_%H%M%S").to_string(),
migration_name
);
// build new migration filename
let now = Local::now();
let migration_name = format!("m{}_{}", now.format("%Y%m%d_%H%M%S"), migration_name);
create_new_migration(&migration_name, migration_dir)?;
update_migrator(&migration_name, migration_dir)?;
return Ok(());
}
let (subcommand, migration_dir, steps, verbose) = match migrate_subcommand {
// Catch all command with pattern `migrate xxx`
(subcommand, Some(args)) => {
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
let steps = args.value_of("NUM_MIGRATION");
let verbose = args.is_present("VERBOSE");
(subcommand, migration_dir, steps, verbose)
create_new_migration(&migration_name, migration_dir)?;
update_migrator(&migration_name, migration_dir)?;
return Ok(());
}
// Catch command `migrate`, this will be treated as `migrate up`
_ => {
let migration_dir = matches.value_of("MIGRATION_DIR").unwrap();
let verbose = matches.is_present("VERBOSE");
("up", migration_dir, None, verbose)
let (subcommand, migration_dir, steps, verbose) = match command {
Some(MigrateSubcommands::Fresh) => ("fresh", migration_dir, None, verbose),
Some(MigrateSubcommands::Refresh) => ("refresh", migration_dir, None, verbose),
Some(MigrateSubcommands::Reset) => ("reset", migration_dir, None, verbose),
Some(MigrateSubcommands::Status) => ("status", migration_dir, None, verbose),
Some(MigrateSubcommands::Up { num }) => ("up", migration_dir, Some(num), verbose),
Some(MigrateSubcommands::Down { num }) => {
("down", migration_dir, Some(num), verbose)
}
_ => ("up", migration_dir, None, verbose),
};
// Construct the `--manifest-path`
let manifest_path = if migration_dir.ends_with('/') {
format!("{}Cargo.toml", migration_dir)
} else {
format!("{}/Cargo.toml", migration_dir)
};
// Construct the arguments that will be supplied to `cargo` command
let mut args = vec![
"run",
"--manifest-path",
manifest_path.as_str(),
"--",
subcommand,
];
let mut num: String = "".to_string();
if let Some(steps) = steps {
num = steps.to_string();
}
if !num.is_empty() {
args.extend(["-n", num.as_str()])
}
if verbose {
args.push("-v");
}
// Run migrator CLI on user's behalf
println!("Running `cargo {}`", args.join(" "));
Command::new("cargo").args(args).spawn()?.wait()?;
}
};
// Construct the `--manifest-path`
let manifest_path = if migration_dir.ends_with('/') {
format!("{}Cargo.toml", migration_dir)
} else {
format!("{}/Cargo.toml", migration_dir)
};
// Construct the arguments that will be supplied to `cargo` command
let mut args = vec![
"run",
"--manifest-path",
manifest_path.as_str(),
"--",
subcommand,
];
if let Some(steps) = steps {
args.extend(["-n", steps]);
}
if verbose {
args.push("-v");
}
// Run migrator CLI on user's behalf
println!("Running `cargo {}`", args.join(" "));
Command::new("cargo").args(args).spawn()?.wait()?;
Ok(())
}
@ -310,7 +315,7 @@ fn create_new_migration(migration_name: &str, migration_dir: &str) -> Result<(),
let migration_template =
include_str!("../template/migration/src/m20220101_000001_create_table.rs");
let migration_content =
migration_template.replace("m20220101_000001_create_table", &migration_name);
migration_template.replace("m20220101_000001_create_table", migration_name);
let mut migration_file = fs::File::create(migration_filepath)?;
migration_file.write_all(migration_content.as_bytes())?;
Ok(())
@ -327,7 +332,7 @@ fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box<
let mut updated_migrator_content = migrator_content.clone();
// create a backup of the migrator file in case something goes wrong
let migrator_backup_filepath = migrator_filepath.clone().with_file_name("lib.rs.bak");
let migrator_backup_filepath = migrator_filepath.with_file_name("lib.rs.bak");
fs::copy(&migrator_filepath, &migrator_backup_filepath)?;
let mut migrator_file = fs::File::create(&migrator_filepath)?;
@ -348,7 +353,7 @@ fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box<
.map(|migration| format!(" Box::new({}::Migration),", migration))
.collect::<Vec<String>>()
.join("\n");
boxed_migrations.push_str("\n");
boxed_migrations.push('\n');
let boxed_migrations = format!("vec![\n{} ]\n", boxed_migrations);
let vec_regex = Regex::new(r"vec!\[[\s\S]+\]\n")?;
let updated_migrator_content = vec_regex.replace(&updated_migrator_content, &boxed_migrations);
@ -368,25 +373,30 @@ where
#[cfg(test)]
mod tests {
use clap::StructOpt;
use super::*;
use crate::cli;
use clap::AppSettings;
use crate::{Cli, Commands};
#[test]
#[should_panic(
expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase"
)]
fn test_generate_entity_no_protocol() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
.get_matches_from(vec![
"generate",
"entity",
"--database-url",
"://root:root@localhost:3306/database",
]);
let cli = Cli::parse_from(vec![
"sea-orm-cli",
"generate",
"entity",
"--database-url",
"://root:root@localhost:3306/database",
]);
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
match cli.command {
Commands::Generate { command } => {
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
}
_ => unreachable!(),
}
}
#[test]
@ -394,16 +404,20 @@ mod tests {
expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306"
)]
fn test_generate_entity_no_database_section() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
.get_matches_from(vec![
"generate",
"entity",
"--database-url",
"postgresql://root:root@localhost:3306",
]);
let cli = Cli::parse_from(vec![
"sea-orm-cli",
"generate",
"entity",
"--database-url",
"postgresql://root:root@localhost:3306",
]);
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
match cli.command {
Commands::Generate { command } => {
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
}
_ => unreachable!(),
}
}
#[test]
@ -411,61 +425,77 @@ mod tests {
expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/"
)]
fn test_generate_entity_no_database_path() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
.get_matches_from(vec![
"generate",
"entity",
"--database-url",
"mysql://root:root@localhost:3306/",
]);
let cli = Cli::parse_from(vec![
"sea-orm-cli",
"generate",
"entity",
"--database-url",
"mysql://root:root@localhost:3306/",
]);
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
match cli.command {
Commands::Generate { command } => {
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
}
_ => unreachable!(),
}
}
#[test]
#[should_panic(expected = "No username was found in the database url")]
fn test_generate_entity_no_username() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
.get_matches_from(vec![
"generate",
"entity",
"--database-url",
"mysql://:root@localhost:3306/database",
]);
let cli = Cli::parse_from(vec![
"sea-orm-cli",
"generate",
"entity",
"--database-url",
"mysql://:root@localhost:3306/database",
]);
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
match cli.command {
Commands::Generate { command } => {
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
}
_ => unreachable!(),
}
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: PoolTimedOut")]
fn test_generate_entity_no_password() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
.get_matches_from(vec![
"generate",
"entity",
"--database-url",
"mysql://root:@localhost:3306/database",
]);
let cli = Cli::parse_from(vec![
"sea-orm-cli",
"generate",
"entity",
"--database-url",
"mysql://root:@localhost:3306/database",
]);
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
match cli.command {
Commands::Generate { command } => {
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
}
_ => unreachable!(),
}
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EmptyHost")]
fn test_generate_entity_no_host() {
let matches = cli::build_cli()
.setting(AppSettings::NoBinaryName)
.get_matches_from(vec![
"generate",
"entity",
"--database-url",
"postgres://root:root@/database",
]);
let cli = Cli::parse_from(vec![
"sea-orm-cli",
"generate",
"entity",
"--database-url",
"postgres://root:root@/database",
]);
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
match cli.command {
Commands::Generate { command } => {
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
}
_ => unreachable!(),
}
}
#[test]
fn test_create_new_migration() {

View File

@ -1,7 +1,6 @@
pub mod cli;
#[cfg(feature = "codegen")]
pub mod commands;
pub mod migration;
pub use cli::*;
#[cfg(feature = "codegen")]

View File

@ -1,49 +0,0 @@
use clap::{App, AppSettings, Arg, SubCommand};
pub fn build_cli() -> App<'static, 'static> {
let mut app = App::new("sea-schema-migration")
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::VersionlessSubcommands)
.arg(
Arg::with_name("VERBOSE")
.long("verbose")
.short("v")
.help("Show debug messages")
.takes_value(false)
.global(true),
);
for subcommand in get_subcommands() {
app = app.subcommand(subcommand);
}
app
}
pub fn get_subcommands() -> Vec<App<'static, 'static>> {
vec![
SubCommand::with_name("fresh")
.about("Drop all tables from the database, then reapply all migrations"),
SubCommand::with_name("refresh")
.about("Rollback all applied migrations, then reapply all migrations"),
SubCommand::with_name("reset").about("Rollback all applied migrations"),
SubCommand::with_name("status").about("Check the status of all migrations"),
SubCommand::with_name("up")
.about("Apply pending migrations")
.arg(
Arg::with_name("NUM_MIGRATION")
.long("num")
.short("n")
.help("Number of pending migrations to be applied")
.takes_value(true),
),
SubCommand::with_name("down")
.about("Rollback applied migrations")
.arg(
Arg::with_name("NUM_MIGRATION")
.long("num")
.short("n")
.help("Number of pending migrations to be rolled back")
.takes_value(true)
.default_value("1"),
),
]
}

View File

@ -19,7 +19,7 @@ path = "src/lib.rs"
[dependencies]
async-trait = { version = "^0.1" }
clap = { version = "^2.33" }
clap = { version = "^3.2", features = ["env", "derive"] }
dotenv = { version = "^0.15" }
sea-orm = { version = "^0.8.0", path = "../", default-features = false, features = ["macros"] }
sea-orm-cli = { version = "^0.8.1", path = "../sea-orm-cli", default-features = false }

View File

@ -1,10 +1,10 @@
use clap::{App, AppSettings, Arg};
use clap::Parser;
use dotenv::dotenv;
use std::{fmt::Display, process::exit};
use tracing_subscriber::{prelude::*, EnvFilter};
use sea_orm::{Database, DbConn};
use sea_orm_cli::migration::get_subcommands;
use sea_orm_cli::MigrateSubcommands;
use super::MigratorTrait;
@ -15,27 +15,26 @@ where
dotenv().ok();
let url = std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
let db = &Database::connect(&url).await.unwrap();
let app = build_cli();
get_matches(migrator, db, app).await;
let cli = Cli::parse();
run_migrate(migrator, db, cli.command, cli.verbose).await;
}
pub async fn get_matches<M>(_: M, db: &DbConn, app: App<'static, 'static>)
where
pub async fn run_migrate<M>(
_: M,
db: &DbConn,
command: Option<MigrateSubcommands>,
verbose: bool,
) where
M: MigratorTrait,
{
let matches = app.get_matches();
let mut verbose = false;
let filter = match matches.subcommand() {
(_, None) => "sea_orm_migration=info",
(_, Some(args)) => match args.is_present("VERBOSE") {
true => {
verbose = true;
"debug"
}
false => "sea_orm_migration=info",
},
let filter = match verbose {
true => "debug",
false => "sea_orm_migration=info",
};
let filter_layer = EnvFilter::try_new(filter).unwrap();
if verbose {
let fmt_layer = tracing_subscriber::fmt::layer();
tracing_subscriber::registry()
@ -52,44 +51,27 @@ where
.with(fmt_layer)
.init()
};
match matches.subcommand() {
("fresh", _) => M::fresh(db).await,
("refresh", _) => M::refresh(db).await,
("reset", _) => M::reset(db).await,
("status", _) => M::status(db).await,
("up", None) => M::up(db, None).await,
("down", None) => M::down(db, Some(1)).await,
("up", Some(args)) => {
let str = args.value_of("NUM_MIGRATION").unwrap_or_default();
let steps = str.parse().ok();
M::up(db, steps).await
}
("down", Some(args)) => {
let str = args.value_of("NUM_MIGRATION").unwrap();
let steps = str.parse().ok().unwrap_or(1);
M::down(db, Some(steps)).await
}
match command {
Some(MigrateSubcommands::Fresh) => M::fresh(db).await,
Some(MigrateSubcommands::Refresh) => M::refresh(db).await,
Some(MigrateSubcommands::Reset) => M::reset(db).await,
Some(MigrateSubcommands::Status) => M::status(db).await,
Some(MigrateSubcommands::Up { num }) => M::up(db, Some(num)).await,
Some(MigrateSubcommands::Down { num }) => M::down(db, Some(num)).await,
_ => M::up(db, None).await,
}
.unwrap_or_else(handle_error);
}
pub fn build_cli() -> App<'static, 'static> {
let mut app = App::new("sea-orm-migration")
.version(env!("CARGO_PKG_VERSION"))
.setting(AppSettings::VersionlessSubcommands)
.arg(
Arg::with_name("VERBOSE")
.long("verbose")
.short("v")
.help("Show debug messages")
.takes_value(false)
.global(true),
);
for subcommand in get_subcommands() {
app = app.subcommand(subcommand);
}
app
#[derive(Parser)]
#[clap(version)]
pub struct Cli {
#[clap(action, short = 'v', long, global = true, help = "Show debug messages")]
verbose: bool,
#[clap(subcommand)]
command: Option<MigrateSubcommands>,
}
fn handle_error<E>(error: E)