diff --git a/src/commands/link.rs b/src/commands/link.rs index 5ae669f..7b1b7b6 100644 --- a/src/commands/link.rs +++ b/src/commands/link.rs @@ -12,7 +12,7 @@ use anyhow::{Result, anyhow, bail}; use self::store::{Repo, ignored, resolve, symlink_metadata_opt, tracked}; use crate::ctx::Ctx; use crate::fsops::{move_path, place_link, prune_empty, remove_file, rename, suffixed}; -use crate::output::{note, warning}; +use crate::output::{line, note, warning}; const BACKUP_SUFFIX: &str = ".ahab-bak"; @@ -200,10 +200,10 @@ pub fn list(ctx: &Ctx, store: Option<&Path>) -> Result<()> { let repo = Repo::discover(ctx, store)?; let stored = stored_paths(&repo, &repo.store)?; - println!("store: {}", repo.store.display()); + line!("store: {}", repo.store.display()); if stored.is_empty() { - println!("nothing in the store for this repository"); + line!("nothing in the store for this repository"); return Ok(()); } @@ -215,7 +215,7 @@ pub fn list(ctx: &Ctx, store: Option<&Path>) -> Result<()> { Stored::Taken => "shadowed", }; - println!("\t{:<11}{}", format!("{verb}:"), rel.display()); + line!("\t{:<11}{}", format!("{verb}:"), rel.display()); } Ok(()) @@ -237,10 +237,10 @@ impl Report { fn line(&self, verb: &str, path: &Path) { // worth naming once per run if !self.named.replace(true) { - println!("store: {}", self.store.display()); + line!("store: {}", self.store.display()); } - println!("\t{:<11}{}", format!("{verb}:"), path.display()); + line!("\t{:<11}{}", format!("{verb}:"), path.display()); } } diff --git a/src/commands/link/check.rs b/src/commands/link/check.rs index 637431d..b13f869 100644 --- a/src/commands/link/check.rs +++ b/src/commands/link/check.rs @@ -7,6 +7,7 @@ use anyhow::{Result, anyhow}; use super::store::{Repo, resolve, symlink_metadata_opt}; use crate::cmd::{Cmd, LsFiles}; use crate::ctx::Ctx; +use crate::output::{line, text}; pub fn check( ctx: &Ctx, @@ -55,17 +56,17 @@ fn print_porcelain(exposed: &[Exposed], null: bool) { for item in exposed { match &item.dest { - Some(dest) => print!("{} {} -> {}{end}", item.code(), item.name, dest.display()), - None => print!("{} {}{end}", item.code(), item.name), + Some(dest) => text!("{} {} -> {}{end}", item.code(), item.name, dest.display()), + None => text!("{} {}{end}", item.code(), item.name), } } } fn print_listing(repo: &Repo, exposed: &[Exposed]) { - println!("store: {}", repo.store.display()); + line!("store: {}", repo.store.display()); if exposed.is_empty() { - println!("nothing outside the store, a sandbox would see tracked files only"); + line!("nothing outside the store, a sandbox would see tracked files only"); return; } @@ -93,11 +94,11 @@ fn print_listing(repo: &Repo, exposed: &[Exposed]) { continue; } - println!("\n{heading}\n{hint}"); + line!("\n{heading}\n{hint}"); for item in items { match &item.dest { - Some(dest) => println!("\t{} -> {}", item.name, dest.display()), - None => println!("\t{}", item.name), + Some(dest) => line!("\t{} -> {}", item.name, dest.display()), + None => line!("\t{}", item.name), } } } diff --git a/src/commands/status.rs b/src/commands/status.rs index 4c6bdc5..0da1340 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -5,6 +5,7 @@ use anyhow::Result; use crate::cmd::{Cmd, Ps}; use crate::commands::link; use crate::ctx::Ctx; +use crate::output::line; use crate::project::Project; // which services ahab picked, what it would talk to, what the store holds @@ -18,7 +19,7 @@ pub fn status(ctx: &Ctx, store: Option<&Path>) -> Result<()> { } }; - println!("services: {}", project.names().join(", ")); + line!("services: {}", project.names().join(", ")); role(ctx, "django", project.django(), &project); role(ctx, "postgres", project.postgres(), &project); @@ -30,16 +31,16 @@ pub fn status(ctx: &Ctx, store: Option<&Path>) -> Result<()> { // what the store holds, and where to look for what it does not fn stored(ctx: &Ctx, store: Option<&Path>) { match link::stored_summary(ctx, store) { - Err(e) => println!("store: {e:#}"), + Err(e) => line!("store: {e:#}"), Ok((store, linked, other)) => { - println!("store: {}", store.display()); - println!("\tlinked: {linked}"); + line!("store: {}", store.display()); + line!("\tlinked: {linked}"); if other > 0 { - println!("\tnot linked: {other} (see `ahab link list`)"); + line!("\tnot linked: {other} (see `ahab link list`)"); } - println!("\t`ahab link check` lists what a sandbox can still read"); + line!("\t`ahab link check` lists what a sandbox can still read"); } } } @@ -49,7 +50,7 @@ fn role(ctx: &Ctx, role: &str, detected: Result, project: &Project) { let service = match detected { Ok(service) => service, Err(e) => { - println!("{role}: {e:#}"); + line!("{role}: {e:#}"); return; } }; @@ -59,12 +60,12 @@ fn role(ctx: &Ctx, role: &str, detected: Result, project: &Project) { .unwrap_or("built from the project") .to_string(); - println!("{role}: {service} ({image})"); + line!("{role}: {service} ({image})"); match Ps::id_of(&service).capture(ctx) { - Ok(id) if id.trim().is_empty() => println!("\tcontainer: not running"), - Ok(id) => println!("\tcontainer: {}", short(id.trim())), - Err(e) => println!("\tcontainer: {e:#}"), + Ok(id) if id.trim().is_empty() => line!("\tcontainer: not running"), + Ok(id) => line!("\tcontainer: {}", short(id.trim())), + Err(e) => line!("\tcontainer: {e:#}"), } if role == "postgres" { @@ -74,8 +75,8 @@ fn role(ctx: &Ctx, role: &str, detected: Result, project: &Project) { None => " (default, nothing in the environment)", }; - println!("\tuser: {user}{}", source("POSTGRES_USER")); - println!("\tdatabase: {database}{}", source("POSTGRES_DB")); + line!("\tuser: {user}{}", source("POSTGRES_USER")); + line!("\tdatabase: {database}{}", source("POSTGRES_DB")); } } diff --git a/src/output.rs b/src/output.rs index 5b7e7c5..10448f1 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,3 +1,6 @@ +use std::fmt::Arguments; +use std::io::{self, Write}; + // progress, on stderr and only when it was asked for macro_rules! note { ($ctx:expr, $($arg:tt)*) => { @@ -12,4 +15,29 @@ macro_rules! warning { ($($arg:tt)*) => { eprintln!("warning: {}", format_args!($($arg)*)) }; } -pub(crate) use {note, warning}; +// what a command was asked for, on stdout +macro_rules! line { + ($($arg:tt)*) => { $crate::output::write_line(format_args!($($arg)*)) }; +} + +// the same, for a caller that terminates its own entries +macro_rules! text { + ($($arg:tt)*) => { $crate::output::write_text(format_args!($($arg)*)) }; +} + +pub(crate) fn write_line(args: Arguments) { + finish(writeln!(io::stdout(), "{args}")); +} + +pub(crate) fn write_text(args: Arguments) { + finish(write!(io::stdout(), "{args}")); +} + +// a reader leaving early ends the pipe; println! would panic instead +fn finish(written: io::Result<()>) { + if written.is_err() { + std::process::exit(0); + } +} + +pub(crate) use {line, note, text, warning};