mod commands;
mod config;
mod generators;
mod runtime_db;
mod utils;
use clap::{Parser, Subcommand};
use colored::Colorize;
#[derive(Parser)]
#[command(name = "tideorm")]
#[command(author = "TideORM Contributors")]
#[command(version)]
#[command(about = "Command-line interface for TideORM - A powerful Rust ORM", long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[arg(short, long, global = true, default_value = "tideorm.toml")]
config: String,
#[arg(short, long, global = true)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Migrate {
#[command(subcommand)]
command: Option<MigrateCommands>,
},
#[command(subcommand, name = "make")]
Make(MakeCommands),
#[command(subcommand, name = "db")]
Db(DbCommands),
Init {
#[arg(default_value = ".")]
name: String,
#[arg(short, long, default_value = "postgres")]
database: String,
},
Config,
Models,
Schema {
#[arg(short, long)]
table: Option<String>,
},
}
#[derive(Subcommand)]
enum MigrateCommands {
Run {
#[arg(short, long)]
path: Option<String>,
#[arg(long)]
pretend: bool,
#[arg(long)]
force: bool,
#[arg(long)]
step: Option<u32>,
},
#[command(name = "generate", alias = "gen")]
Generate {
name: String,
#[arg(long)]
create: Option<String>,
#[arg(short = 'a', long)]
table: Option<String>,
#[arg(short, long)]
fields: Option<String>,
},
Up {
#[arg(long)]
step: Option<u32>,
#[arg(long)]
migration: Option<String>,
#[arg(long)]
pretend: bool,
#[arg(long)]
force: bool,
},
Down {
#[arg(long, default_value = "1")]
step: u32,
#[arg(long)]
migration: Option<String>,
#[arg(long)]
pretend: bool,
#[arg(long)]
force: bool,
},
Redo {
#[arg(long, default_value = "1")]
step: u32,
#[arg(long)]
pretend: bool,
#[arg(long)]
force: bool,
},
Fresh {
#[arg(long)]
seed: bool,
#[arg(long)]
seeder: Option<String>,
#[arg(long)]
force: bool,
},
Reset {
#[arg(long)]
force: bool,
#[arg(long)]
pretend: bool,
},
Refresh {
#[arg(long)]
seed: bool,
#[arg(long)]
step: Option<u32>,
#[arg(long)]
force: bool,
},
Mark {
#[arg(long)]
migration: String,
#[arg(long)]
unmark: bool,
#[arg(long)]
force: bool,
},
Status,
History {
#[arg(short, long, default_value = "10")]
limit: u32,
},
}
#[derive(Subcommand)]
enum MakeCommands {
#[command(name = "model")]
Model {
name: String,
#[arg(short, long)]
table: Option<String>,
#[arg(short, long)]
fields: Option<String>,
#[arg(short, long)]
relations: Option<String>,
#[arg(long, alias = "trans")]
translatable: Option<String>,
#[arg(long, alias = "attach-single")]
attachments_single: Option<String>,
#[arg(long, alias = "attach-multi")]
attachments_multi: Option<String>,
#[arg(long, alias = "idx")]
indexed: Option<String>,
#[arg(long, alias = "uniq")]
unique: Option<String>,
#[arg(long, alias = "null")]
nullable: Option<String>,
#[arg(
long,
alias = "soft-delete",
action = clap::ArgAction::Set,
num_args = 0..=1,
default_missing_value = "true"
)]
soft_deletes: Option<bool>,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
default_missing_value = "true"
)]
timestamps: Option<bool>,
#[arg(
long,
action = clap::ArgAction::Set,
num_args = 0..=1,
default_missing_value = "true"
)]
tokenize: Option<bool>,
#[arg(short, long, default_value = "src/models")]
output: String,
#[arg(long)]
migration: bool,
#[arg(long)]
seeder: bool,
#[arg(long)]
factory: bool,
#[arg(short, long)]
all: bool,
},
#[command(name = "migration")]
Migration {
name: String,
#[arg(long)]
create: Option<String>,
#[arg(short = 'a', long)]
table: Option<String>,
#[arg(short, long)]
fields: Option<String>,
#[arg(short, long, default_value = "src/migrations")]
output: String,
},
#[command(name = "seeder")]
Seeder {
name: String,
#[arg(short, long)]
model: Option<String>,
#[arg(short = 'n', long, default_value = "10")]
count: u32,
#[arg(short, long, default_value = "src/seeders")]
output: String,
},
#[command(name = "factory")]
Factory {
name: String,
#[arg(short, long)]
model: Option<String>,
#[arg(short, long, default_value = "src/factories")]
output: String,
},
}
#[derive(Subcommand)]
enum DbCommands {
Seed {
#[arg(short, long, alias = "class")]
seeder: Option<String>,
#[arg(long)]
force: bool,
},
Fresh {
#[arg(long)]
force: bool,
},
Status,
Check,
Create {
#[arg(short, long)]
name: Option<String>,
},
Drop {
#[arg(short, long)]
name: Option<String>,
#[arg(long)]
force: bool,
},
Wipe {
#[arg(long)]
drop_types: bool,
#[arg(long)]
force: bool,
},
Table {
name: String,
},
Tables,
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
if cli.verbose {
print_banner();
}
let result = match cli.command {
Commands::Migrate { command } => match command {
Some(cmd) => commands::migrate::handle_subcommand(&cli.config, cmd, cli.verbose).await,
None => commands::migrate::run(&cli.config, None, false, false, None).await,
},
Commands::Make(cmd) => commands::make::handle(&cli.config, cmd, cli.verbose).await,
Commands::Db(cmd) => commands::db::handle(&cli.config, cmd, cli.verbose).await,
Commands::Init { name, database } => {
commands::init::run(&name, &database, cli.verbose).await
}
Commands::Config => commands::config::show(&cli.config, cli.verbose).await,
Commands::Models => commands::models::list(&cli.config, cli.verbose).await,
Commands::Schema { table } => commands::schema::show(&cli.config, table, cli.verbose).await,
};
match result {
Ok(()) => {
if cli.verbose {
println!("\n{}", "✓ Command completed successfully".green());
}
}
Err(e) => {
eprintln!("{} {}", "Error:".red().bold(), e);
std::process::exit(1);
}
}
}
fn print_banner() {
println!(
"{}",
r#"
╔════════════════════════════════════════════════════════════════╗
║ ║
║ ████████╗██╗██████╗ ███████╗ ██████╗ ██████╗ ███╗ ███╗ ║
║ ╚══██╔══╝██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗████╗ ████║ ║
║ ██║ ██║██║ ██║█████╗ ██║ ██║██████╔╝██╔████╔██║ ║
║ ██║ ██║██║ ██║██╔══╝ ██║ ██║██╔══██╗██║╚██╔╝██║ ║
║ ██║ ██║██████╔╝███████╗╚██████╔╝██║ ██║██║ ╚═╝ ██║ ║
║ ╚═╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ║
║ ║
║ Command Line Interface ║
╚════════════════════════════════════════════════════════════════╝
"#
.cyan()
);
}
#[cfg(test)]
mod tests {
use super::{Cli, Commands, MakeCommands};
use clap::Parser;
fn model_flags(args: &[&str]) -> (Option<bool>, Option<bool>, Option<bool>) {
let cli = Cli::try_parse_from(args).expect("make model should parse");
match cli.command {
Commands::Make(MakeCommands::Model {
timestamps,
soft_deletes,
tokenize,
..
}) => (timestamps, soft_deletes, tokenize),
_ => panic!("expected a `make model` command"),
}
}
#[test]
fn model_flags_are_absent_until_supplied() {
assert_eq!(
model_flags(&["tideorm", "make", "model", "User"]),
(None, None, None)
);
}
#[test]
fn model_flags_record_explicit_values() {
assert_eq!(
model_flags(&["tideorm", "make", "model", "User", "--timestamps=true"]),
(Some(true), None, None)
);
assert_eq!(
model_flags(&["tideorm", "make", "model", "User", "--timestamps=false"]),
(Some(false), None, None)
);
assert_eq!(
model_flags(&["tideorm", "make", "model", "User", "--timestamps"]),
(Some(true), None, None)
);
assert_eq!(
model_flags(&["tideorm", "make", "model", "User", "--soft-deletes"]),
(None, Some(true), None)
);
assert_eq!(
model_flags(&["tideorm", "make", "model", "User", "--tokenize"]),
(None, None, Some(true))
);
}
}