feat: add verbose and dry run

This commit is contained in:
2026-09-07 14:12:53 +00:00
parent ab93e7ef4f
commit b2a0c08256
5 changed files with 83 additions and 29 deletions

View File

@@ -8,8 +8,15 @@ use clap_complete::Shell;
pub struct Ahab {
#[command(subcommand)]
pub command: Commands,
}
/// Print each docker command as it runs
#[arg(short, long, global = true)]
pub verbose: bool,
/// Print the docker commands that would run, without running them
#[arg(long, global = true)]
pub dry_run: bool,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
/// Django related subcommands

View File

@@ -2,10 +2,9 @@ use anyhow::{Context, Result, bail};
use std::{
fmt::Display,
process::{Child, Command, ExitStatus, Stdio},
sync::OnceLock,
};
use crate::debug_eprintln;
pub struct Args(Vec<String>);
impl From<&str> for Args {
@@ -54,8 +53,9 @@ impl CommandBuilder {
}
pub fn build(self) -> Result<Command> {
debug_eprintln!("running `{self}`");
if options().verbose {
eprintln!("running `{self}`");
}
let (first, rest) = self.args.split_first().context("empty args")?;
let mut command = Command::new(first);
command.args(rest);
@@ -72,6 +72,11 @@ impl CommandBuilder {
}
pub fn exec(self) -> Result<()> {
if options().dry_run {
eprintln!("would run `{self}`");
return Ok(());
}
let shown = self.to_string();
let status = self.build()?.spawn()?.wait()?;
@@ -83,6 +88,11 @@ impl CommandBuilder {
}
pub fn exec_redirect_stdout(self, stdio: Stdio) -> Result<()> {
if options().dry_run {
eprintln!("would run `{self}`");
return Ok(());
}
let shown = self.to_string();
let status = self.build()?.stdout(stdio).spawn()?.wait()?;
@@ -100,3 +110,23 @@ fn check(command: &str, status: ExitStatus) -> Result<()> {
None => bail!("`{command}` was killed by a signal"),
}
}
#[derive(Default, Clone, Copy)]
pub struct Options {
pub verbose: bool,
pub dry_run: bool,
}
static OPTIONS: OnceLock<Options> = OnceLock::new();
pub fn set_options(options: Options) {
let _ = OPTIONS.set(options);
}
fn options() -> Options {
OPTIONS.get().copied().unwrap_or_default()
}
pub fn is_dry_run() -> bool {
options().dry_run
}

View File

@@ -1,4 +1,4 @@
use ahab::{cli, scripts};
use ahab::{cli, command_builder, scripts};
use anyhow::Result;
use clap::Parser;
@@ -6,6 +6,11 @@ use clap::Parser;
fn main() -> Result<()> {
let args = cli::Ahab::parse();
command_builder::set_options(command_builder::Options {
verbose: args.verbose,
dry_run: args.dry_run,
});
match args.command {
cli::Commands::Django { command } => match command {
cli::Django::Bash => scripts::django::bash(),

View File

@@ -10,7 +10,8 @@ use std::{
use super::docker_compose;
use crate::cli::Format;
use crate::{command_builder::CommandBuilder, compose::Compose, debug_eprintln};
use crate::command_builder;
use crate::{command_builder::CommandBuilder, compose::Compose};
const CUSTOM_MAGIC: &[u8] = b"PGDMP";
const TAR_MAGIC: &[u8] = b"toc.dat";
@@ -193,7 +194,10 @@ fn piped(db: &Database, script: &str, input: &Path) -> Result<std::process::Comm
}
fn wait_until_ready(db: &Database) -> Result<()> {
debug_eprintln!("waiting until pg_isready");
if command_builder::is_dry_run() {
return Ok(());
}
let deadline = Instant::now() + READY_TIMEOUT;
loop {
@@ -319,25 +323,29 @@ pub fn import(file: &Path) -> Result<()> {
}
wait_until_ready(&db)?;
match (kind, restore.as_deref()) {
// psql's output is read rather than streamed here, to keep the expected
// role errors out of the way
(Kind::Cluster, Some(script)) => restore_cluster(&db, script, file)?,
(_, restore) => {
let status = match restore {
Some(script) => piped(&db, script, file)?.spawn()?.wait()?,
None => in_container(&db)
.args("pg_restore -U")
.arg(&db.user)
.arg(format!("--dbname={}", db.name))
.arg(&remote)
.build()?
.spawn()?
.wait()?,
};
if command_builder::is_dry_run() {
eprintln!("would restore with {tool}");
} else {
match (kind, restore.as_deref()) {
// psql's output is read rather than streamed here, to keep the expected
// role errors out of the way
(Kind::Cluster, Some(script)) => restore_cluster(&db, script, file)?,
(_, restore) => {
let status = match restore {
Some(script) => piped(&db, script, file)?.spawn()?.wait()?,
None => in_container(&db)
.args("pg_restore -U")
.arg(&db.user)
.arg(format!("--dbname={}", db.name))
.arg(&remote)
.build()?
.spawn()?
.wait()?,
};
if !status.success() {
bail!("{tool} failed, the database is left empty");
if !status.success() {
bail!("{tool} failed, the database is left empty");
}
}
}
}