use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use crate::error::{Result, SurqlError};
use crate::settings::{Settings, SettingsBuilder};
pub mod db;
pub mod fmt;
pub mod migrate;
pub mod orchestrate;
pub mod schema;
pub const EXIT_FAILURE: u8 = 1;
#[must_use]
pub fn run() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
let cli = match Cli::try_parse_from(&args) {
Ok(cli) => cli,
Err(err) => {
err.exit();
}
};
match execute(cli) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
fmt::error(format!("error: {err}"));
ExitCode::from(EXIT_FAILURE)
}
}
}
pub fn execute(cli: Cli) -> Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.map_err(|e| SurqlError::Io {
reason: format!("failed to start async runtime: {e}"),
})?;
runtime.block_on(dispatch(cli))
}
async fn dispatch(cli: Cli) -> Result<()> {
let global = &cli.global;
match cli.command {
Command::Version => {
print_version();
Ok(())
}
Command::Db(cmd) => db::run(cmd, global).await,
Command::Migrate(cmd) => migrate::run(cmd, global).await,
Command::Schema(cmd) => schema::run(cmd, global).await,
Command::Orchestrate(cmd) => orchestrate::run(cmd, global).await,
}
}
pub fn print_version() {
println!("surql {}", env!("CARGO_PKG_VERSION"));
}
#[derive(Debug, Parser)]
#[command(
name = "surql",
about = "Code-first database toolkit for SurrealDB",
version,
propagate_version = true,
disable_help_subcommand = true
)]
pub struct Cli {
#[command(flatten)]
pub global: GlobalOpts,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Clone, clap::Args)]
pub struct GlobalOpts {
#[arg(long = "config", global = true, value_name = "PATH")]
pub config: Option<PathBuf>,
#[arg(long = "verbose", short = 'v', global = true)]
pub verbose: bool,
}
impl GlobalOpts {
pub fn settings(&self) -> Result<Settings> {
let mut builder = SettingsBuilder::default();
if let Some(path) = &self.config {
let cwd = path
.parent()
.map_or_else(|| PathBuf::from("."), PathBuf::from);
builder = builder.cwd(cwd);
}
builder.load()
}
}
#[derive(Debug, Subcommand)]
pub enum Command {
Version,
#[command(subcommand)]
Db(db::DbCommand),
#[command(subcommand)]
Migrate(migrate::MigrateCommand),
#[command(subcommand)]
Schema(schema::SchemaCommand),
#[command(subcommand)]
Orchestrate(orchestrate::OrchestrateCommand),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_version_command() {
let cli = Cli::try_parse_from(["surql", "version"]).unwrap();
assert!(matches!(cli.command, Command::Version));
}
#[test]
fn rejects_unknown_command() {
let err = Cli::try_parse_from(["surql", "bogus"]).unwrap_err();
assert_eq!(err.exit_code(), 2);
}
#[test]
fn config_flag_is_accepted_before_subcommand() {
let cli = Cli::try_parse_from(["surql", "--config", "/tmp/c.toml", "db", "info"]).unwrap();
assert!(cli.global.config.is_some());
}
}