feat(fb-26): extend ProgressMode with quiet field, update from_flags signature

Add `quiet: bool` to `Human` variant and expand `from_flags` to three
args (`json`, `quiet`, `plain_env`). Update `handle`/`handle_human`
accordingly; add four targeted unit tests (TDD).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-07 19:31:01 +09:00
parent c8a8bc9045
commit dd33902f5a
2 changed files with 31 additions and 11 deletions

View File

@@ -370,7 +370,7 @@ fn run(cli: &Cli) -> anyhow::Result<()> {
// the channel and emits per-step events into it. When the // the channel and emits per-step events into it. When the
// call returns, the `Sender` drops and the display thread // call returns, the `Sender` drops and the display thread
// sees `recv()` return Err — exits cleanly. // sees `recv()` return Err — exits cleanly.
let mode = progress::ProgressMode::from_flags(cli.json); let mode = progress::ProgressMode::from_flags(cli.json, false, false);
let (tx, rx) = std::sync::mpsc::channel::<kebab_app::IngestEvent>(); let (tx, rx) = std::sync::mpsc::channel::<kebab_app::IngestEvent>();
let display_handle = std::thread::spawn(move || { let display_handle = std::thread::spawn(move || {
progress::ProgressDisplay::new(mode).run(rx) progress::ProgressDisplay::new(mode).run(rx)

View File

@@ -39,18 +39,22 @@ pub enum ProgressMode {
Json, Json,
/// stdout reserved for the final report; stderr gets an indicatif /// stdout reserved for the final report; stderr gets an indicatif
/// `ProgressBar` (TTY) or one short line per event (non-TTY). /// `ProgressBar` (TTY) or one short line per event (non-TTY).
Human { tty: bool }, Human { tty: bool, quiet: bool },
} }
impl ProgressMode { impl ProgressMode {
/// Pick the right mode from caller flags. /// Pick the right mode from caller flags.
pub fn from_flags(json: bool) -> Self { ///
/// - `json`: `--json` flag — takes priority, returns `Json`.
/// - `quiet`: `--quiet` flag — suppresses human-readable stderr when `Human`.
/// - `plain_env`: `KEBAB_PROGRESS=plain` — forces `tty=false` even in a TTY,
/// for CI environments that emulate a TTY with a pty wrapper.
pub fn from_flags(json: bool, quiet: bool, plain_env: bool) -> Self {
if json { if json {
Self::Json Self::Json
} else { } else {
Self::Human { let tty = !plain_env && std::io::stderr().is_terminal();
tty: std::io::stderr().is_terminal(), Self::Human { tty, quiet }
}
} }
} }
} }
@@ -83,7 +87,7 @@ impl ProgressDisplay {
fn handle(&mut self, event: &IngestEvent) -> anyhow::Result<()> { fn handle(&mut self, event: &IngestEvent) -> anyhow::Result<()> {
match self.mode { match self.mode {
ProgressMode::Json => emit_json(event), ProgressMode::Json => emit_json(event),
ProgressMode::Human { tty } => self.handle_human(event, tty), ProgressMode::Human { tty, quiet } => self.handle_human(event, tty, quiet),
} }
} }
@@ -96,7 +100,8 @@ impl ProgressDisplay {
/// `ScanStarted` arm and §2.4a's ordering invariant /// `ScanStarted` arm and §2.4a's ordering invariant
/// (`ScanStarted` < everything else) guarantees it is `Some` by /// (`ScanStarted` < everything else) guarantees it is `Some` by
/// the time later events arrive. /// the time later events arrive.
fn handle_human(&mut self, event: &IngestEvent, tty: bool) -> anyhow::Result<()> { fn handle_human(&mut self, event: &IngestEvent, tty: bool, quiet: bool) -> anyhow::Result<()> {
let _ = quiet; // used in Task 3; suppress unused warning for now
match event { match event {
IngestEvent::ScanStarted { root } => { IngestEvent::ScanStarted { root } => {
let bar = ProgressBar::new_spinner().with_message(format!("scanning {root}")); let bar = ProgressBar::new_spinner().with_message(format!("scanning {root}"));
@@ -216,20 +221,35 @@ mod tests {
#[test] #[test]
fn from_flags_json_takes_priority_over_tty() { fn from_flags_json_takes_priority_over_tty() {
// --json forces Json regardless of TTY state. assert_eq!(ProgressMode::from_flags(true, false, false), ProgressMode::Json);
assert_eq!(ProgressMode::from_flags(true), ProgressMode::Json);
} }
#[test] #[test]
fn from_flags_human_reflects_stderr_tty() { fn from_flags_human_reflects_stderr_tty() {
// We can't synthesize a TTY in tests, but we can assert the // We can't synthesize a TTY in tests, but we can assert the
// shape — mode is Human { tty: <something> } when --json=false. // shape — mode is Human { tty: <something> } when --json=false.
match ProgressMode::from_flags(false) { match ProgressMode::from_flags(false, false, false) {
ProgressMode::Human { .. } => {} ProgressMode::Human { .. } => {}
other => panic!("expected Human mode, got {other:?}"), other => panic!("expected Human mode, got {other:?}"),
} }
} }
#[test]
fn from_flags_quiet_sets_quiet_field() {
match ProgressMode::from_flags(false, true, false) {
ProgressMode::Human { quiet: true, .. } => {}
other => panic!("expected Human{{quiet:true}}, got {other:?}"),
}
}
#[test]
fn from_flags_plain_env_forces_tty_false() {
match ProgressMode::from_flags(false, false, true) {
ProgressMode::Human { tty: false, .. } => {}
other => panic!("expected Human{{tty:false}}, got {other:?}"),
}
}
#[test] #[test]
fn now_rfc3339_parses_back() { fn now_rfc3339_parses_back() {
let s = now_rfc3339().unwrap(); let s = now_rfc3339().unwrap();