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)]
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
#[arg(long, global = true)]
pub dry_run: bool,

View File

@@ -1,7 +1,13 @@
use super::{Argv, Cmd};
fn compose() -> Argv {
Argv::new("docker").arg("compose")
// compose prints its own progress, which --quiet has to ask it to stop
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
@@ -22,7 +28,7 @@ impl Run {
impl Cmd for Run {
fn argv(&self) -> Argv {
compose()
compose(false)
.arg("run")
.arg("--rm")
.arg(&self.service)
@@ -35,7 +41,7 @@ pub struct Config;
impl Cmd for Config {
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 {
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 {
fn argv(&self) -> Argv {
compose().arg("up").arg("--detach")
compose(self.quiet).arg("up").arg("--detach")
}
}
pub struct Start {
service: String,
pub struct Start<'a> {
pub service: &'a str,
pub quiet: bool,
}
impl Start {
pub fn service(service: &str) -> Self {
Self {
service: service.to_string(),
}
}
}
impl Cmd for Start {
impl Cmd for Start<'_> {
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 {
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"));
}
note!("found app {app_name}");
note!(ctx, "found app {app_name}");
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)?;
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");
@@ -41,7 +41,7 @@ pub fn make_command(ctx: &Ctx, app: &PathBuf, name: &str) -> Result<()> {
create_dir(ctx, &commands_dir)?;
touch(ctx, &commands_dir.join("__init__.py"))?;
note!("created module {app_name}.management.commands")
note!(ctx, "created module {app_name}.management.commands")
};
write_new(
@@ -50,7 +50,7 @@ pub fn make_command(ctx: &Ctx, app: &PathBuf, name: &str) -> Result<()> {
DEBUG_TEMPLATE.as_bytes(),
)?;
note!("created command {app_name}.management.commands.{name}");
note!(ctx, "created command {app_name}.management.commands.{name}");
Ok(())
}

View File

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

View File

@@ -45,11 +45,12 @@ fn restore_cluster(ctx: &Ctx, db: &Database, restore: &dyn Cmd, file: &Path) ->
continue;
}
note!("{line}");
note!(ctx, "{line}");
}
if existing > 0 {
note!(
ctx,
"left {existing} existing role{} alone",
if existing == 1 { "" } else { "s" }
);
@@ -66,11 +67,15 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
let dump = Dump::of(file)?;
let db = Database::resolve(ctx)?;
note!("stopping all containers");
Stop.run(ctx)?;
note!(ctx, "stopping all containers");
Stop { quiet: ctx.quiet }.run(ctx)?;
note!("starting db container");
Start::service(&db.service).run(ctx)?;
note!(ctx, "starting db container");
Start {
service: &db.service,
quiet: ctx.quiet,
}
.run(ctx)?;
let remote = remote_dump();
@@ -121,7 +126,7 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
let restore = db.restore_with(kind);
// the name of what actually runs, so the message cannot drift from it
let tool = restore.argv().program().to_string();
note!("restoring database with {tool}");
note!(ctx, "restoring database with {tool}");
when_ready(
ctx,
@@ -149,7 +154,7 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
wait_until_ready(ctx, &db)?;
if ctx.dry_run {
note!("would restore with {tool}");
note!(ctx, "would restore with {tool}");
} else {
// 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
@@ -190,9 +195,9 @@ pub fn import(ctx: &Ctx, file: &Path) -> Result<()> {
let _ = Rm::recursive(&remote).in_container(&db.container).run(ctx);
}
note!("restarting containers");
Stop.run(ctx)?;
Up.run(ctx)?;
note!(ctx, "restarting containers");
Stop { quiet: ctx.quiet }.run(ctx)?;
Up { quiet: ctx.quiet }.run(ctx)?;
Ok(())
}
@@ -208,7 +213,7 @@ pub fn dump(ctx: &Ctx, file: &Path, format: Format, gzip: bool) -> Result<()> {
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
// 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();
PgDump::new(&db.user, &db.name, "d")

View File

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

View File

@@ -20,12 +20,13 @@ fn main() -> ExitCode {
let ctx = Ctx {
verbose: args.verbose,
dry_run: args.dry_run,
quiet: args.quiet,
};
// said once here rather than by each command, so every line that follows
// reads as the plan it is
if ctx.dry_run {
note!("dry run, nothing will be changed");
note!(ctx, "dry run, nothing will be changed");
}
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
// data a command was asked for
// progress, on stderr and only when it was asked for
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 {
($($arg:tt)*) => { eprintln!("warning: {}", format_args!($($arg)*)) };
}