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:
parent
ca3809a6d3
commit
580fa90023
@ -29,7 +29,7 @@ path = "src/bin/sea.rs"
|
|||||||
required-features = ["codegen"]
|
required-features = ["codegen"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "^2.33.3" }
|
clap = { version = "^3.2", features = ["env", "derive"] }
|
||||||
dotenv = { version = "^0.15" }
|
dotenv = { version = "^0.15" }
|
||||||
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
|
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
|
||||||
sea-orm-codegen = { version = "^0.8.0", path = "../sea-orm-codegen", optional = true }
|
sea-orm-codegen = { version = "^0.8.0", path = "../sea-orm-codegen", optional = true }
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
|
use clap::StructOpt;
|
||||||
use dotenv::dotenv;
|
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_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
let matches = cli::build_cli().get_matches();
|
let cli = Cli::parse();
|
||||||
|
let verbose = cli.verbose;
|
||||||
|
|
||||||
match matches.subcommand() {
|
match cli.command {
|
||||||
("generate", Some(matches)) => run_generate_command(matches)
|
Commands::Generate { command } => {
|
||||||
|
run_generate_command(command, verbose)
|
||||||
.await
|
.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),
|
.unwrap_or_else(handle_error),
|
||||||
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
|
|
||||||
_ => unreachable!("You should never see this message"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
//! COPY FROM bin/main.rs
|
//! COPY FROM bin/main.rs
|
||||||
|
|
||||||
|
use clap::StructOpt;
|
||||||
use dotenv::dotenv;
|
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_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
let matches = cli::build_cli().get_matches();
|
let cli = Cli::parse();
|
||||||
|
let verbose = cli.verbose;
|
||||||
|
|
||||||
match matches.subcommand() {
|
match cli.command {
|
||||||
("generate", Some(matches)) => run_generate_command(matches)
|
Commands::Generate { command } => {
|
||||||
|
run_generate_command(command, verbose)
|
||||||
.await
|
.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),
|
.unwrap_or_else(handle_error),
|
||||||
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
|
|
||||||
_ => unreachable!("You should never see this message"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,129 +1,160 @@
|
|||||||
use crate::migration::get_subcommands;
|
use clap::{ArgGroup, Parser, Subcommand};
|
||||||
use clap::{App, AppSettings, Arg, SubCommand};
|
|
||||||
|
|
||||||
pub fn build_cli() -> App<'static, 'static> {
|
#[derive(Parser, Debug)]
|
||||||
let entity_subcommand = SubCommand::with_name("generate")
|
#[clap(version)]
|
||||||
.about("Codegen related commands")
|
pub struct Cli {
|
||||||
.setting(AppSettings::VersionlessSubcommands)
|
#[clap(action, global = true, short, long, help = "Show debug messages")]
|
||||||
.subcommand(
|
pub verbose: bool,
|
||||||
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);
|
|
||||||
|
|
||||||
let arg_migration_dir = Arg::with_name("MIGRATION_DIR")
|
#[clap(subcommand)]
|
||||||
.long("migration-dir")
|
pub command: Commands,
|
||||||
.short("d")
|
}
|
||||||
.help("Migration script directory")
|
|
||||||
.takes_value(true)
|
#[derive(Subcommand, PartialEq, Debug)]
|
||||||
.default_value("./migration");
|
pub enum Commands {
|
||||||
let mut migrate_subcommands = SubCommand::with_name("migrate")
|
#[clap(about = "Codegen related commands")]
|
||||||
.about("Migration related commands")
|
#[clap(arg_required_else_help = true)]
|
||||||
.subcommand(
|
Generate {
|
||||||
SubCommand::with_name("init")
|
#[clap(subcommand)]
|
||||||
.about("Initialize migration directory")
|
command: GenerateSubcommands,
|
||||||
.arg(arg_migration_dir.clone()),
|
},
|
||||||
)
|
#[clap(about = "Migration related commands")]
|
||||||
.subcommand(
|
Migrate {
|
||||||
SubCommand::with_name("generate")
|
#[clap(
|
||||||
.about("Generate a new, empty migration")
|
value_parser,
|
||||||
.arg(
|
global = true,
|
||||||
Arg::with_name("MIGRATION_NAME")
|
short = 'd',
|
||||||
.help("Name of the new migation")
|
long,
|
||||||
.required(true)
|
help = "Migration script directory",
|
||||||
.takes_value(true),
|
default_value = "./migration"
|
||||||
)
|
)]
|
||||||
.arg(arg_migration_dir.clone()),
|
migration_dir: String,
|
||||||
)
|
|
||||||
.arg(arg_migration_dir.clone());
|
#[clap(subcommand)]
|
||||||
for subcommand in get_subcommands() {
|
command: Option<MigrateSubcommands>,
|
||||||
migrate_subcommands =
|
},
|
||||||
migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone()));
|
}
|
||||||
}
|
|
||||||
|
#[derive(Subcommand, PartialEq, Debug)]
|
||||||
App::new("sea-orm-cli")
|
pub enum MigrateSubcommands {
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
#[clap(about = "Initialize migration directory")]
|
||||||
.setting(AppSettings::VersionlessSubcommands)
|
Init,
|
||||||
.subcommand(entity_subcommand)
|
#[clap(about = "Generate a new, empty migration")]
|
||||||
.subcommand(migrate_subcommands)
|
Generate {
|
||||||
.arg(
|
#[clap(
|
||||||
Arg::with_name("VERBOSE")
|
value_parser,
|
||||||
.long("verbose")
|
long,
|
||||||
.short("v")
|
required = true,
|
||||||
.help("Show debug messages")
|
help = "Name of the new migration"
|
||||||
.takes_value(false)
|
)]
|
||||||
.global(true),
|
migration_name: String,
|
||||||
)
|
},
|
||||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
#[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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use clap::ArgMatches;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sea_orm_codegen::{EntityTransformer, OutputFile, WithSerde};
|
use sea_orm_codegen::{EntityTransformer, OutputFile, WithSerde};
|
||||||
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command, str::FromStr};
|
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command, str::FromStr};
|
||||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
use crate::{GenerateSubcommands, MigrateSubcommands};
|
||||||
match matches.subcommand() {
|
|
||||||
("entity", Some(args)) => {
|
pub async fn run_generate_command(
|
||||||
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
command: GenerateSubcommands,
|
||||||
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
|
verbose: bool,
|
||||||
let tables = args
|
) -> Result<(), Box<dyn Error>> {
|
||||||
.values_of("TABLES")
|
match command {
|
||||||
.unwrap_or_default()
|
GenerateSubcommands::Entity {
|
||||||
.collect::<Vec<_>>();
|
compact_format: _,
|
||||||
let expanded_format = args.is_present("EXPANDED_FORMAT");
|
expanded_format,
|
||||||
let with_serde = args.value_of("WITH_SERDE").unwrap();
|
include_hidden_tables,
|
||||||
if args.is_present("VERBOSE") {
|
tables,
|
||||||
|
max_connections,
|
||||||
|
output_dir,
|
||||||
|
database_schema,
|
||||||
|
database_url,
|
||||||
|
with_serde,
|
||||||
|
} => {
|
||||||
|
if verbose {
|
||||||
let _ = tracing_subscriber::fmt()
|
let _ = tracing_subscriber::fmt()
|
||||||
.with_max_level(tracing::Level::DEBUG)
|
.with_max_level(tracing::Level::DEBUG)
|
||||||
.with_test_writer()
|
.with_test_writer()
|
||||||
@ -35,18 +41,9 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
|||||||
.try_init();
|
.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
|
// The database should be a valid URL that can be parsed
|
||||||
// protocol://username:password@host/database_name
|
// protocol://username:password@host/database_name
|
||||||
let url = Url::parse(
|
let url = Url::parse(&database_url)?;
|
||||||
args.value_of("DATABASE_URL")
|
|
||||||
.expect("No database url could be found"),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Make sure we have all the required url components
|
// 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
|
// Closures for filtering tables
|
||||||
let filter_tables = |table: &str| -> bool {
|
let filter_tables = |table: &str| -> bool {
|
||||||
if !tables.is_empty() {
|
if !tables.is_empty() {
|
||||||
@ -76,6 +78,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
|||||||
|
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter_hidden_tables = |table: &str| -> bool {
|
let filter_hidden_tables = |table: &str| -> bool {
|
||||||
if include_hidden_tables {
|
if include_hidden_tables {
|
||||||
true
|
true
|
||||||
@ -149,7 +152,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
|||||||
use sea_schema::postgres::discovery::SchemaDiscovery;
|
use sea_schema::postgres::discovery::SchemaDiscovery;
|
||||||
use sqlx::Postgres;
|
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 connection = connect::<Postgres>(max_connections, url.as_str()).await?;
|
||||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
||||||
let schema = schema_discovery.discover().await;
|
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)?
|
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)?;
|
fs::create_dir_all(dir)?;
|
||||||
|
|
||||||
for OutputFile { name, content } in output.files.iter() {
|
for OutputFile { name, content } in output.files.iter() {
|
||||||
@ -184,8 +187,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
|||||||
.wait()?;
|
.wait()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!("You should never see this message"),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -201,11 +203,13 @@ where
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
pub fn run_migrate_command(
|
||||||
let migrate_subcommand = matches.subcommand();
|
command: Option<MigrateSubcommands>,
|
||||||
// If it's `migrate init`
|
migration_dir: &str,
|
||||||
if let ("init", Some(args)) = migrate_subcommand {
|
verbose: bool,
|
||||||
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
match command {
|
||||||
|
Some(MigrateSubcommands::Init) => {
|
||||||
let migration_dir = match migration_dir.ends_with('/') {
|
let migration_dir = match migration_dir.ends_with('/') {
|
||||||
true => migration_dir.to_string(),
|
true => migration_dir.to_string(),
|
||||||
false => format!("{}/", migration_dir),
|
false => format!("{}/", migration_dir),
|
||||||
@ -243,38 +247,31 @@ pub fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error
|
|||||||
println!("Done!");
|
println!("Done!");
|
||||||
// Early exit!
|
// Early exit!
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if let ("generate", Some(args)) = migrate_subcommand {
|
}
|
||||||
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
|
Some(MigrateSubcommands::Generate { migration_name }) => {
|
||||||
let migration_name = args.value_of("MIGRATION_NAME").unwrap();
|
|
||||||
println!("Generating new migration...");
|
println!("Generating new migration...");
|
||||||
|
|
||||||
// build new migration filename
|
// build new migration filename
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let migration_name = format!(
|
let migration_name = format!("m{}_{}", now.format("%Y%m%d_%H%M%S"), migration_name);
|
||||||
"m{}_{}",
|
|
||||||
now.format("%Y%m%d_%H%M%S").to_string(),
|
|
||||||
migration_name
|
|
||||||
);
|
|
||||||
|
|
||||||
create_new_migration(&migration_name, migration_dir)?;
|
create_new_migration(&migration_name, migration_dir)?;
|
||||||
update_migrator(&migration_name, migration_dir)?;
|
update_migrator(&migration_name, migration_dir)?;
|
||||||
return Ok(());
|
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)
|
|
||||||
}
|
|
||||||
// Catch command `migrate`, this will be treated as `migrate up`
|
|
||||||
_ => {
|
_ => {
|
||||||
let migration_dir = matches.value_of("MIGRATION_DIR").unwrap();
|
let (subcommand, migration_dir, steps, verbose) = match command {
|
||||||
let verbose = matches.is_present("VERBOSE");
|
Some(MigrateSubcommands::Fresh) => ("fresh", migration_dir, None, verbose),
|
||||||
("up", 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`
|
// Construct the `--manifest-path`
|
||||||
let manifest_path = if migration_dir.ends_with('/') {
|
let manifest_path = if migration_dir.ends_with('/') {
|
||||||
format!("{}Cargo.toml", migration_dir)
|
format!("{}Cargo.toml", migration_dir)
|
||||||
@ -289,8 +286,13 @@ pub fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error
|
|||||||
"--",
|
"--",
|
||||||
subcommand,
|
subcommand,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let mut num: String = "".to_string();
|
||||||
if let Some(steps) = steps {
|
if let Some(steps) = steps {
|
||||||
args.extend(["-n", steps]);
|
num = steps.to_string();
|
||||||
|
}
|
||||||
|
if !num.is_empty() {
|
||||||
|
args.extend(["-n", num.as_str()])
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
args.push("-v");
|
args.push("-v");
|
||||||
@ -298,6 +300,9 @@ pub fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error
|
|||||||
// Run migrator CLI on user's behalf
|
// Run migrator CLI on user's behalf
|
||||||
println!("Running `cargo {}`", args.join(" "));
|
println!("Running `cargo {}`", args.join(" "));
|
||||||
Command::new("cargo").args(args).spawn()?.wait()?;
|
Command::new("cargo").args(args).spawn()?.wait()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +315,7 @@ fn create_new_migration(migration_name: &str, migration_dir: &str) -> Result<(),
|
|||||||
let migration_template =
|
let migration_template =
|
||||||
include_str!("../template/migration/src/m20220101_000001_create_table.rs");
|
include_str!("../template/migration/src/m20220101_000001_create_table.rs");
|
||||||
let migration_content =
|
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)?;
|
let mut migration_file = fs::File::create(migration_filepath)?;
|
||||||
migration_file.write_all(migration_content.as_bytes())?;
|
migration_file.write_all(migration_content.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -327,7 +332,7 @@ fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box<
|
|||||||
let mut updated_migrator_content = migrator_content.clone();
|
let mut updated_migrator_content = migrator_content.clone();
|
||||||
|
|
||||||
// create a backup of the migrator file in case something goes wrong
|
// 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)?;
|
fs::copy(&migrator_filepath, &migrator_backup_filepath)?;
|
||||||
let mut migrator_file = fs::File::create(&migrator_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))
|
.map(|migration| format!(" Box::new({}::Migration),", migration))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
boxed_migrations.push_str("\n");
|
boxed_migrations.push('\n');
|
||||||
let boxed_migrations = format!("vec![\n{} ]\n", boxed_migrations);
|
let boxed_migrations = format!("vec![\n{} ]\n", boxed_migrations);
|
||||||
let vec_regex = Regex::new(r"vec!\[[\s\S]+\]\n")?;
|
let vec_regex = Regex::new(r"vec!\[[\s\S]+\]\n")?;
|
||||||
let updated_migrator_content = vec_regex.replace(&updated_migrator_content, &boxed_migrations);
|
let updated_migrator_content = vec_regex.replace(&updated_migrator_content, &boxed_migrations);
|
||||||
@ -368,25 +373,30 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use clap::StructOpt;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cli;
|
use crate::{Cli, Commands};
|
||||||
use clap::AppSettings;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic(
|
||||||
expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase"
|
expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase"
|
||||||
)]
|
)]
|
||||||
fn test_generate_entity_no_protocol() {
|
fn test_generate_entity_no_protocol() {
|
||||||
let matches = cli::build_cli()
|
let cli = Cli::parse_from(vec![
|
||||||
.setting(AppSettings::NoBinaryName)
|
"sea-orm-cli",
|
||||||
.get_matches_from(vec![
|
|
||||||
"generate",
|
"generate",
|
||||||
"entity",
|
"entity",
|
||||||
"--database-url",
|
"--database-url",
|
||||||
"://root:root@localhost:3306/database",
|
"://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]
|
#[test]
|
||||||
@ -394,16 +404,20 @@ mod tests {
|
|||||||
expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306"
|
expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306"
|
||||||
)]
|
)]
|
||||||
fn test_generate_entity_no_database_section() {
|
fn test_generate_entity_no_database_section() {
|
||||||
let matches = cli::build_cli()
|
let cli = Cli::parse_from(vec![
|
||||||
.setting(AppSettings::NoBinaryName)
|
"sea-orm-cli",
|
||||||
.get_matches_from(vec![
|
|
||||||
"generate",
|
"generate",
|
||||||
"entity",
|
"entity",
|
||||||
"--database-url",
|
"--database-url",
|
||||||
"postgresql://root:root@localhost:3306",
|
"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]
|
#[test]
|
||||||
@ -411,61 +425,77 @@ mod tests {
|
|||||||
expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/"
|
expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/"
|
||||||
)]
|
)]
|
||||||
fn test_generate_entity_no_database_path() {
|
fn test_generate_entity_no_database_path() {
|
||||||
let matches = cli::build_cli()
|
let cli = Cli::parse_from(vec![
|
||||||
.setting(AppSettings::NoBinaryName)
|
"sea-orm-cli",
|
||||||
.get_matches_from(vec![
|
|
||||||
"generate",
|
"generate",
|
||||||
"entity",
|
"entity",
|
||||||
"--database-url",
|
"--database-url",
|
||||||
"mysql://root:root@localhost:3306/",
|
"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]
|
#[test]
|
||||||
#[should_panic(expected = "No username was found in the database url")]
|
#[should_panic(expected = "No username was found in the database url")]
|
||||||
fn test_generate_entity_no_username() {
|
fn test_generate_entity_no_username() {
|
||||||
let matches = cli::build_cli()
|
let cli = Cli::parse_from(vec![
|
||||||
.setting(AppSettings::NoBinaryName)
|
"sea-orm-cli",
|
||||||
.get_matches_from(vec![
|
|
||||||
"generate",
|
"generate",
|
||||||
"entity",
|
"entity",
|
||||||
"--database-url",
|
"--database-url",
|
||||||
"mysql://:root@localhost:3306/database",
|
"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]
|
#[test]
|
||||||
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: PoolTimedOut")]
|
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: PoolTimedOut")]
|
||||||
fn test_generate_entity_no_password() {
|
fn test_generate_entity_no_password() {
|
||||||
let matches = cli::build_cli()
|
let cli = Cli::parse_from(vec![
|
||||||
.setting(AppSettings::NoBinaryName)
|
"sea-orm-cli",
|
||||||
.get_matches_from(vec![
|
|
||||||
"generate",
|
"generate",
|
||||||
"entity",
|
"entity",
|
||||||
"--database-url",
|
"--database-url",
|
||||||
"mysql://root:@localhost:3306/database",
|
"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]
|
#[test]
|
||||||
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EmptyHost")]
|
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EmptyHost")]
|
||||||
fn test_generate_entity_no_host() {
|
fn test_generate_entity_no_host() {
|
||||||
let matches = cli::build_cli()
|
let cli = Cli::parse_from(vec![
|
||||||
.setting(AppSettings::NoBinaryName)
|
"sea-orm-cli",
|
||||||
.get_matches_from(vec![
|
|
||||||
"generate",
|
"generate",
|
||||||
"entity",
|
"entity",
|
||||||
"--database-url",
|
"--database-url",
|
||||||
"postgres://root:root@/database",
|
"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]
|
#[test]
|
||||||
fn test_create_new_migration() {
|
fn test_create_new_migration() {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
pub mod cli;
|
pub mod cli;
|
||||||
#[cfg(feature = "codegen")]
|
#[cfg(feature = "codegen")]
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod migration;
|
|
||||||
|
|
||||||
pub use cli::*;
|
pub use cli::*;
|
||||||
#[cfg(feature = "codegen")]
|
#[cfg(feature = "codegen")]
|
||||||
|
@ -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"),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
@ -19,7 +19,7 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { version = "^0.1" }
|
async-trait = { version = "^0.1" }
|
||||||
clap = { version = "^2.33" }
|
clap = { version = "^3.2", features = ["env", "derive"] }
|
||||||
dotenv = { version = "^0.15" }
|
dotenv = { version = "^0.15" }
|
||||||
sea-orm = { version = "^0.8.0", path = "../", default-features = false, features = ["macros"] }
|
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 }
|
sea-orm-cli = { version = "^0.8.1", path = "../sea-orm-cli", default-features = false }
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use clap::{App, AppSettings, Arg};
|
use clap::Parser;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use std::{fmt::Display, process::exit};
|
use std::{fmt::Display, process::exit};
|
||||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||||
|
|
||||||
use sea_orm::{Database, DbConn};
|
use sea_orm::{Database, DbConn};
|
||||||
use sea_orm_cli::migration::get_subcommands;
|
use sea_orm_cli::MigrateSubcommands;
|
||||||
|
|
||||||
use super::MigratorTrait;
|
use super::MigratorTrait;
|
||||||
|
|
||||||
@ -15,27 +15,26 @@ where
|
|||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
let url = std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
|
let url = std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
|
||||||
let db = &Database::connect(&url).await.unwrap();
|
let db = &Database::connect(&url).await.unwrap();
|
||||||
let app = build_cli();
|
let cli = Cli::parse();
|
||||||
get_matches(migrator, db, app).await;
|
|
||||||
|
run_migrate(migrator, db, cli.command, cli.verbose).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_matches<M>(_: M, db: &DbConn, app: App<'static, 'static>)
|
pub async fn run_migrate<M>(
|
||||||
where
|
_: M,
|
||||||
|
db: &DbConn,
|
||||||
|
command: Option<MigrateSubcommands>,
|
||||||
|
verbose: bool,
|
||||||
|
) where
|
||||||
M: MigratorTrait,
|
M: MigratorTrait,
|
||||||
{
|
{
|
||||||
let matches = app.get_matches();
|
let filter = match verbose {
|
||||||
let mut verbose = false;
|
true => "debug",
|
||||||
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",
|
false => "sea_orm_migration=info",
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter_layer = EnvFilter::try_new(filter).unwrap();
|
let filter_layer = EnvFilter::try_new(filter).unwrap();
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
let fmt_layer = tracing_subscriber::fmt::layer();
|
let fmt_layer = tracing_subscriber::fmt::layer();
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
@ -52,44 +51,27 @@ where
|
|||||||
.with(fmt_layer)
|
.with(fmt_layer)
|
||||||
.init()
|
.init()
|
||||||
};
|
};
|
||||||
match matches.subcommand() {
|
|
||||||
("fresh", _) => M::fresh(db).await,
|
match command {
|
||||||
("refresh", _) => M::refresh(db).await,
|
Some(MigrateSubcommands::Fresh) => M::fresh(db).await,
|
||||||
("reset", _) => M::reset(db).await,
|
Some(MigrateSubcommands::Refresh) => M::refresh(db).await,
|
||||||
("status", _) => M::status(db).await,
|
Some(MigrateSubcommands::Reset) => M::reset(db).await,
|
||||||
("up", None) => M::up(db, None).await,
|
Some(MigrateSubcommands::Status) => M::status(db).await,
|
||||||
("down", None) => M::down(db, Some(1)).await,
|
Some(MigrateSubcommands::Up { num }) => M::up(db, Some(num)).await,
|
||||||
("up", Some(args)) => {
|
Some(MigrateSubcommands::Down { num }) => M::down(db, Some(num)).await,
|
||||||
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
|
|
||||||
}
|
|
||||||
_ => M::up(db, None).await,
|
_ => M::up(db, None).await,
|
||||||
}
|
}
|
||||||
.unwrap_or_else(handle_error);
|
.unwrap_or_else(handle_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_cli() -> App<'static, 'static> {
|
#[derive(Parser)]
|
||||||
let mut app = App::new("sea-orm-migration")
|
#[clap(version)]
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
pub struct Cli {
|
||||||
.setting(AppSettings::VersionlessSubcommands)
|
#[clap(action, short = 'v', long, global = true, help = "Show debug messages")]
|
||||||
.arg(
|
verbose: bool,
|
||||||
Arg::with_name("VERBOSE")
|
|
||||||
.long("verbose")
|
#[clap(subcommand)]
|
||||||
.short("v")
|
command: Option<MigrateSubcommands>,
|
||||||
.help("Show debug messages")
|
|
||||||
.takes_value(false)
|
|
||||||
.global(true),
|
|
||||||
);
|
|
||||||
for subcommand in get_subcommands() {
|
|
||||||
app = app.subcommand(subcommand);
|
|
||||||
}
|
|
||||||
app
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_error<E>(error: E)
|
fn handle_error<E>(error: E)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user