mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
Add Printer enum and warn_user! macros for unified verbosity control
Introduces a uv-style Printer enum (Silent/Quiet/Default/Verbose) and warn_user!/warn_user_once! macros in fabro-util, wires --quiet/--verbose global flags into the CLI, and converts the `fabro init` deprecation warning as a proof of concept. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
20f2a3b8bb
commit
71573ad9ec
5 changed files with 254 additions and 4 deletions
|
|
@ -28,6 +28,14 @@ pub(crate) struct GlobalArgs {
|
|||
#[arg(long, global = true)]
|
||||
pub no_upgrade_check: bool,
|
||||
|
||||
/// Suppress non-essential output
|
||||
#[arg(long, global = true, conflicts_with = "verbose")]
|
||||
pub quiet: bool,
|
||||
|
||||
/// Enable verbose output
|
||||
#[arg(long, global = true, conflicts_with = "quiet")]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Execution mode: standalone (in-process) or server (delegate to API)
|
||||
#[cfg(feature = "server")]
|
||||
#[arg(long, global = true, value_parser = parse_execution_mode)]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use anyhow::Result;
|
|||
use args::{Commands, GlobalArgs, LONG_VERSION, RunCommands};
|
||||
use clap::Parser;
|
||||
use fabro_telemetry::{git, panic as tel_panic, sanitize, sender};
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
use rustls::crypto::ring::default_provider;
|
||||
use tracing::debug;
|
||||
|
|
@ -98,6 +99,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
}
|
||||
|
||||
let Cli { globals, command } = cli;
|
||||
let _printer = Printer::from_flags(globals.quiet, globals.verbose);
|
||||
let command_name = command.name().to_string();
|
||||
|
||||
let (config_log_level, upgrade_check_enabled) = {
|
||||
|
|
@ -197,10 +199,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
}
|
||||
Commands::Repo(ns) => commands::repo::dispatch(ns).await?,
|
||||
Commands::Init => {
|
||||
eprintln!(
|
||||
"{} `fabro init` is deprecated, use `fabro repo init` instead",
|
||||
console::Style::new().yellow().apply_to("warning:")
|
||||
);
|
||||
fabro_util::warn_user!("`fabro init` is deprecated, use `fabro repo init` instead");
|
||||
commands::repo::init::run_init().await?;
|
||||
}
|
||||
Commands::Install { web_url } => {
|
||||
|
|
@ -421,4 +420,29 @@ mod tests {
|
|||
_ => panic!("unexpected command variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_quiet_flag() {
|
||||
let cli =
|
||||
Cli::try_parse_from(["fabro", "--quiet", "config", "show"]).expect("should parse");
|
||||
assert!(cli.globals.quiet);
|
||||
assert!(!cli.globals.verbose);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_verbose_flag() {
|
||||
let cli =
|
||||
Cli::try_parse_from(["fabro", "--verbose", "config", "show"]).expect("should parse");
|
||||
assert!(!cli.globals.quiet);
|
||||
assert!(cli.globals.verbose);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quiet_and_verbose_conflict() {
|
||||
let result = Cli::try_parse_from(["fabro", "--quiet", "--verbose", "config", "show"]);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"should fail when both --quiet and --verbose"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,14 @@ pub mod backoff;
|
|||
pub mod check_report;
|
||||
pub mod env;
|
||||
pub mod path;
|
||||
pub mod printer;
|
||||
pub mod redact;
|
||||
pub mod run_log;
|
||||
pub mod terminal;
|
||||
pub mod text;
|
||||
pub mod version;
|
||||
pub mod warnings;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use console;
|
||||
pub use warnings::WARNINGS;
|
||||
|
|
|
|||
155
lib/crates/fabro-util/src/printer.rs
Normal file
155
lib/crates/fabro-util/src/printer.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Printer {
|
||||
/// Suppresses all output.
|
||||
Silent,
|
||||
/// Suppresses most output, but preserves "important" stdout.
|
||||
Quiet,
|
||||
/// Prints to standard streams.
|
||||
Default,
|
||||
/// Prints all output, including debug messages.
|
||||
Verbose,
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
/// Build from CLI flags. `quiet` wins if both are set.
|
||||
pub fn from_flags(quiet: bool, verbose: bool) -> Self {
|
||||
match (quiet, verbose) {
|
||||
(true, _) => Self::Quiet,
|
||||
(_, true) => Self::Verbose,
|
||||
_ => Self::Default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stdout writer — enabled for Default/Verbose.
|
||||
pub fn stdout(self) -> Stdout {
|
||||
match self {
|
||||
Self::Silent | Self::Quiet => Stdout::Disabled,
|
||||
Self::Default | Self::Verbose => Stdout::Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stdout for important messages — enabled for Quiet/Default/Verbose.
|
||||
pub fn stdout_important(self) -> Stdout {
|
||||
match self {
|
||||
Self::Silent => Stdout::Disabled,
|
||||
Self::Quiet | Self::Default | Self::Verbose => Stdout::Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stderr writer — enabled for Default/Verbose.
|
||||
pub fn stderr(self) -> Stderr {
|
||||
match self {
|
||||
Self::Silent | Self::Quiet => Stderr::Disabled,
|
||||
Self::Default | Self::Verbose => Stderr::Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Stdout {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl std::fmt::Write for Stdout {
|
||||
#[allow(clippy::print_stdout)]
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Enabled => print!("{s}"),
|
||||
Self::Disabled => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Stderr {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl std::fmt::Write for Stderr {
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Enabled => eprint!("{s}"),
|
||||
Self::Disabled => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_flags_default() {
|
||||
assert_eq!(Printer::from_flags(false, false), Printer::Default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_flags_quiet() {
|
||||
assert_eq!(Printer::from_flags(true, false), Printer::Quiet);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_flags_verbose() {
|
||||
assert_eq!(Printer::from_flags(false, true), Printer::Verbose);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_flags_quiet_wins() {
|
||||
assert_eq!(Printer::from_flags(true, true), Printer::Quiet);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_silent() {
|
||||
assert_eq!(Printer::Silent.stdout(), Stdout::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_quiet() {
|
||||
assert_eq!(Printer::Quiet.stdout(), Stdout::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_default() {
|
||||
assert_eq!(Printer::Default.stdout(), Stdout::Enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_verbose() {
|
||||
assert_eq!(Printer::Verbose.stdout(), Stdout::Enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_important_silent() {
|
||||
assert_eq!(Printer::Silent.stdout_important(), Stdout::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdout_important_quiet() {
|
||||
assert_eq!(Printer::Quiet.stdout_important(), Stdout::Enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stderr_silent() {
|
||||
assert_eq!(Printer::Silent.stderr(), Stderr::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stderr_quiet() {
|
||||
assert_eq!(Printer::Quiet.stderr(), Stderr::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stderr_default() {
|
||||
assert_eq!(Printer::Default.stderr(), Stderr::Enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stderr_verbose() {
|
||||
assert_eq!(Printer::Verbose.stderr(), Stderr::Enabled);
|
||||
}
|
||||
}
|
||||
57
lib/crates/fabro-util/src/warnings.rs
Normal file
57
lib/crates/fabro-util/src/warnings.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use std::collections::HashSet;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
/// Set of already-emitted warnings (for `warn_user_once!` deduplication).
|
||||
pub static WARNINGS: LazyLock<Mutex<HashSet<String>>> =
|
||||
LazyLock::new(|| Mutex::new(HashSet::new()));
|
||||
|
||||
/// Emit a styled `warning: {message}` to stderr.
|
||||
#[macro_export]
|
||||
macro_rules! warn_user {
|
||||
($($arg:tt)*) => {{
|
||||
let message = format!($($arg)*);
|
||||
let style = $crate::console::Style::new().yellow().bold();
|
||||
eprintln!("{} {message}", style.apply_to("warning:"));
|
||||
}};
|
||||
}
|
||||
|
||||
/// Like [`warn_user!`], but only emits each unique message once per process.
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once {
|
||||
($($arg:tt)*) => {{
|
||||
let message = format!($($arg)*);
|
||||
let mut set = $crate::WARNINGS.lock().unwrap();
|
||||
if set.insert(message.clone()) {
|
||||
drop(set);
|
||||
$crate::warn_user!("{message}");
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::WARNINGS;
|
||||
|
||||
#[test]
|
||||
fn warn_user_once_deduplicates() {
|
||||
let before = WARNINGS.lock().unwrap().len();
|
||||
warn_user_once!("dup-test-{}", "alpha");
|
||||
let after_first = WARNINGS.lock().unwrap().len();
|
||||
warn_user_once!("dup-test-{}", "alpha");
|
||||
let after_second = WARNINGS.lock().unwrap().len();
|
||||
assert_eq!(after_first, before + 1);
|
||||
assert_eq!(
|
||||
after_second, after_first,
|
||||
"duplicate should not grow the set"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warn_user_once_different_messages() {
|
||||
let before = WARNINGS.lock().unwrap().len();
|
||||
warn_user_once!("unique-msg-beta-1");
|
||||
warn_user_once!("unique-msg-beta-2");
|
||||
let after = WARNINGS.lock().unwrap().len();
|
||||
assert_eq!(after, before + 2);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue