feat: silence progress with --quiet

This commit is contained in:
2026-09-08 14:16:08 +00:00
parent cabbbde438
commit 61f35f5ae4
9 changed files with 67 additions and 45 deletions

View File

@@ -16,6 +16,10 @@ pub struct Ahab {
#[arg(short, long, global = true)] #[arg(short, long, global = true)]
pub verbose: bool, pub verbose: bool,
/// Print only what was asked for, not the progress along the way
#[arg(short, long, global = true, conflicts_with = "verbose")]
pub quiet: bool,
/// Print the docker commands that would run, without running them /// Print the docker commands that would run, without running them
#[arg(long, global = true)] #[arg(long, global = true)]
pub dry_run: bool, pub dry_run: bool,

View File

@@ -1,7 +1,13 @@
use super::{Argv, Cmd}; use super::{Argv, Cmd};
fn compose() -> Argv { // compose prints its own progress, which --quiet has to ask it to stop
Argv::new("docker").arg("compose") fn compose(quiet: bool) -> Argv {
let argv = Argv::new("docker").arg("compose");
match quiet {
true => argv.flag("--progress", "quiet"),
false => argv,
}
} }
// docker compose run --rm, which runs the image's entrypoint and so fixes up the // docker compose run --rm, which runs the image's entrypoint and so fixes up the
@@ -22,7 +28,7 @@ impl Run {
impl Cmd for Run { impl Cmd for Run {
fn argv(&self) -> Argv { fn argv(&self) -> Argv {
compose() compose(false)
.arg("run") .arg("run")
.arg("--rm") .arg("--rm")
.arg(&self.service) .arg(&self.service)
@@ -35,7 +41,7 @@ pub struct Config;
impl Cmd for Config { impl Cmd for Config {
fn argv(&self) -> Argv { fn argv(&self) -> Argv {
compose().arg("config").flag("--format", "json") compose(false).arg("config").flag("--format", "json")
} }
} }
@@ -54,39 +60,37 @@ impl Ps {
impl Cmd for Ps { impl Cmd for Ps {
fn argv(&self) -> Argv { fn argv(&self) -> Argv {
compose().arg("ps").arg("--quiet").arg(&self.service) compose(false).arg("ps").arg("--quiet").arg(&self.service)
} }
} }
pub struct Up; pub struct Up {
pub quiet: bool,
}
impl Cmd for Up { impl Cmd for Up {
fn argv(&self) -> Argv { fn argv(&self) -> Argv {
compose().arg("up").arg("--detach") compose(self.quiet).arg("up").arg("--detach")
} }
} }
pub struct Start { pub struct Start<'a> {
service: String, pub service: &'a str,
pub quiet: bool,
} }
impl Start { impl Cmd for Start<'_> {
pub fn service(service: &str) -> Self {
Self {
service: service.to_string(),
}
}
}
impl Cmd for Start {
fn argv(&self) -> Argv { fn argv(&self) -> Argv {
compose().arg("start").arg(&self.service) compose(self.quiet).arg("start").arg(self.service)
} }
} }
pub struct Stop;
pub struct Stop {
pub quiet: bool,
}
impl Cmd for Stop { impl Cmd for Stop {
fn argv(&self) -> Argv { fn argv(&self) -> Argv {
compose().arg("stop") compose(self.quiet).arg("stop")
} }
} }

View File

@@ -24,7 +24,7 @@ pub fn make_command(ctx: &Ctx, app: &PathBuf, name: &str) -> Result<()> {
return Err(anyhow!("directory {app_name} does not exist")); return Err(anyhow!("directory {app_name} does not exist"));
} }
note!("found app {app_name}"); note!(ctx, "found app {app_name}");
let management_dir = app_dir.join("management"); let management_dir = app_dir.join("management");
@@ -32,7 +32,7 @@ pub fn make_command(ctx: &Ctx, app: &PathBuf, name: &str) -> Result<()> {
create_dir(ctx, &management_dir)?; create_dir(ctx, &management_dir)?;
touch(ctx, &management_dir.join("__init__.py"))?; touch(ctx, &management_dir.join("__init__.py"))?;
note!("created module {app_name}.management") note!(ctx, "created module {app_name}.management")
}; };
let commands_dir = management_dir.join("commands"); let commands_dir = management_dir.join("commands");
@@ -41,7 +41,7 @@ pub fn make_command(ctx: &Ctx, app: &PathBuf, name: &str) -> Result<()> {
create_dir(ctx, &commands_dir)?; create_dir(ctx, &commands_dir)?;
touch(ctx, &commands_dir.join("__init__.py"))?; touch(ctx, &commands_dir.join("__init__.py"))?;
note!("created module {app_name}.management.commands") note!(ctx, "created module {app_name}.management.commands")
}; };
write_new( write_new(
@@ -50,7 +50,7 @@ pub fn make_command(ctx: &Ctx, app: &PathBuf, name: &str) -> Result<()> {
DEBUG_TEMPLATE.as_bytes(), DEBUG_TEMPLATE.as_bytes(),
)?; )?;
note!("created command {app_name}.management.commands.{name}"); note!(ctx, "created command {app_name}.management.commands.{name}");
Ok(()) Ok(())
} }

View File

@@ -28,7 +28,7 @@ pub fn add(ctx: &Ctx, paths: &[PathBuf], force: bool, store: Option<&Path>) -> R
let mut failed = 0; let mut failed = 0;
for path in paths { for path in paths {
if let Err(e) = link_one(ctx, &repo, path, force, &report) { if let Err(e) = link_one(ctx, &repo, path, force, &report) {
note!("error: {e:#}"); note!(ctx, "error: {e:#}");
failed += 1; failed += 1;
} }
} }
@@ -52,7 +52,7 @@ pub fn restore(ctx: &Ctx, paths: &[PathBuf], all: bool, store: Option<&Path>) ->
}; };
if paths.is_empty() { if paths.is_empty() {
note!("nothing in the store for this repository"); note!(ctx, "nothing in the store for this repository");
return Ok(()); return Ok(());
} }
@@ -63,7 +63,7 @@ pub fn restore(ctx: &Ctx, paths: &[PathBuf], all: bool, store: Option<&Path>) ->
let mut failed = 0; let mut failed = 0;
for path in &paths { for path in &paths {
if let Err(e) = restore_one(ctx, &repo, path, &report) { if let Err(e) = restore_one(ctx, &repo, path, &report) {
note!("error: {e:#}"); note!(ctx, "error: {e:#}");
failed += 1; failed += 1;
} }
} }

View File

@@ -86,7 +86,10 @@ fn repo_components(ctx: &Ctx, root: &Path) -> Result<PathBuf> {
if let Some(components) = components_from_remote(&url) { if let Some(components) = components_from_remote(&url) {
return Ok(components); return Ok(components);
} }
note!("could not parse git remote `{url}`, falling back to the checkout name"); note!(
ctx,
"could not parse git remote `{url}`, falling back to the checkout name"
);
} }
let name = root let name = root

View File

@@ -45,11 +45,12 @@ fn restore_cluster(ctx: &Ctx, db: &Database, restore: &dyn Cmd, file: &Path) ->
continue; continue;
} }
note!("{line}"); note!(ctx, "{line}");
} }
if existing > 0 { if existing > 0 {
note!( note!(
ctx,
"left {existing} existing role{} alone", "left {existing} existing role{} alone",
if existing == 1 { "" } else { "s" } if existing == 1 { "" } else { "s" }
); );
@@ -66,11 +67,15 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
let dump = Dump::of(file)?; let dump = Dump::of(file)?;
let db = Database::resolve(ctx)?; let db = Database::resolve(ctx)?;
note!("stopping all containers"); note!(ctx, "stopping all containers");
Stop.run(ctx)?; Stop { quiet: ctx.quiet }.run(ctx)?;
note!("starting db container"); note!(ctx, "starting db container");
Start::service(&db.service).run(ctx)?; Start {
service: &db.service,
quiet: ctx.quiet,
}
.run(ctx)?;
let remote = remote_dump(); let remote = remote_dump();
@@ -121,7 +126,7 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
let restore = db.restore_with(kind); let restore = db.restore_with(kind);
// the name of what actually runs, so the message cannot drift from it // the name of what actually runs, so the message cannot drift from it
let tool = restore.argv().program().to_string(); let tool = restore.argv().program().to_string();
note!("restoring database with {tool}"); note!(ctx, "restoring database with {tool}");
when_ready( when_ready(
ctx, ctx,
@@ -149,7 +154,7 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
wait_until_ready(ctx, &db)?; wait_until_ready(ctx, &db)?;
if ctx.dry_run { if ctx.dry_run {
note!("would restore with {tool}"); note!(ctx, "would restore with {tool}");
} else { } else {
// a directory dump was copied in whole, so pg_restore reads it from the // a directory dump was copied in whole, so pg_restore reads it from the
// container; every other shape is fed in on stdin, through gunzip when it // container; every other shape is fed in on stdin, through gunzip when it
@@ -190,9 +195,9 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
let _ = Rm::recursive(&remote).in_container(&db.container).run(ctx); let _ = Rm::recursive(&remote).in_container(&db.container).run(ctx);
} }
note!("restarting containers"); note!(ctx, "restarting containers");
Stop.run(ctx)?; Stop { quiet: ctx.quiet }.run(ctx)?;
Up.run(ctx)?; Up { quiet: ctx.quiet }.run(ctx)?;
Ok(()) Ok(())
} }
@@ -208,7 +213,7 @@ pub fn dump(ctx: &Ctx, file: &Path, format: Format, gzip: bool) -> Result<()> {
return dump_directory(ctx, &db, file); return dump_directory(ctx, &db, file);
} }
note!("dumping to local file {}", file.to_string_lossy()); note!(ctx, "dumping to local file {}", file.to_string_lossy());
// written beside the target and renamed once the dump succeeds, so a failure // written beside the target and renamed once the dump succeeds, so a failure
// cannot destroy the dump that is already there // cannot destroy the dump that is already there
@@ -259,7 +264,7 @@ fn dump_directory(ctx: &Ctx, db: &Database, target: &Path) -> Result<()> {
); );
} }
note!("dumping to local directory {}", target.display()); note!(ctx, "dumping to local directory {}", target.display());
let remote = remote_dump(); let remote = remote_dump();
PgDump::new(&db.user, &db.name, "d") PgDump::new(&db.user, &db.name, "d")

View File

@@ -2,4 +2,5 @@
pub struct Ctx { pub struct Ctx {
pub verbose: bool, pub verbose: bool,
pub dry_run: bool, pub dry_run: bool,
pub quiet: bool,
} }

View File

@@ -20,12 +20,13 @@ fn main() -> ExitCode {
let ctx = Ctx { let ctx = Ctx {
verbose: args.verbose, verbose: args.verbose,
dry_run: args.dry_run, dry_run: args.dry_run,
quiet: args.quiet,
}; };
// said once here rather than by each command, so every line that follows // said once here rather than by each command, so every line that follows
// reads as the plan it is // reads as the plan it is
if ctx.dry_run { if ctx.dry_run {
note!("dry run, nothing will be changed"); note!(ctx, "dry run, nothing will be changed");
} }
match run(&ctx, args.command) { match run(&ctx, args.command) {

View File

@@ -1,9 +1,13 @@
// progress and warnings go to stderr, so what a caller pipes is only ever the // progress, on stderr and only when it was asked for
// data a command was asked for
macro_rules! note { macro_rules! note {
($($arg:tt)*) => { eprintln!($($arg)*) }; ($ctx:expr, $($arg:tt)*) => {
if !$ctx.quiet {
eprintln!($($arg)*)
}
};
} }
// not progress: it speaks about the result, so --quiet keeps it
macro_rules! warning { macro_rules! warning {
($($arg:tt)*) => { eprintln!("warning: {}", format_args!($($arg)*)) }; ($($arg:tt)*) => { eprintln!("warning: {}", format_args!($($arg)*)) };
} }