Skip to content

Commit 965f415

Browse files
authored
[ty] Add a --quiet mode (#19233)
Adds a `--quiet` flag which silences diagnostic, warning logs, and messages like "all checks passed" while retaining summary messages that indicate problems, e.g., the number of diagnostics. I'm a bit on the fence regarding filtering out warning logs, because it can omit important details, e.g., the message that a fatal diagnostic was encountered. Let's discuss that in #19233 (comment) The implementation recycles the `Printer` abstraction used in uv, which is intended to replace all direct usage of `std::io::stdout`. See #19233 (comment) I ended up futzing with the progress bar more than I probably should have to ensure it was also using the printer, but it doesn't seem like a big deal. See #19233 (comment) Closes astral-sh/ty#772
1 parent 83b5bbf commit 965f415

File tree

7 files changed

+335
-44
lines changed

7 files changed

+335
-44
lines changed

crates/ty/docs/cli.md

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ty/src/lib.rs

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
mod args;
22
mod logging;
3+
mod printer;
34
mod python_version;
45
mod version;
56

67
pub use args::Cli;
78
use ty_static::EnvVars;
89

9-
use std::io::{self, BufWriter, Write, stdout};
10+
use std::fmt::Write;
1011
use std::process::{ExitCode, Termination};
1112

1213
use anyhow::Result;
1314
use std::sync::Mutex;
1415

1516
use crate::args::{CheckCommand, Command, TerminalColor};
1617
use crate::logging::setup_tracing;
18+
use crate::printer::Printer;
1719
use anyhow::{Context, anyhow};
1820
use clap::{CommandFactory, Parser};
1921
use colored::Colorize;
@@ -25,7 +27,7 @@ use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
2527
use salsa::plumbing::ZalsaDatabase;
2628
use ty_project::metadata::options::ProjectOptionsOverrides;
2729
use ty_project::watch::ProjectWatcher;
28-
use ty_project::{Db, DummyReporter, Reporter, watch};
30+
use ty_project::{Db, watch};
2931
use ty_project::{ProjectDatabase, ProjectMetadata};
3032
use ty_server::run_server;
3133

@@ -42,14 +44,16 @@ pub fn run() -> anyhow::Result<ExitStatus> {
4244
Command::Check(check_args) => run_check(check_args),
4345
Command::Version => version().map(|()| ExitStatus::Success),
4446
Command::GenerateShellCompletion { shell } => {
47+
use std::io::stdout;
48+
4549
shell.generate(&mut Cli::command(), &mut stdout());
4650
Ok(ExitStatus::Success)
4751
}
4852
}
4953
}
5054

5155
pub(crate) fn version() -> Result<()> {
52-
let mut stdout = BufWriter::new(io::stdout().lock());
56+
let mut stdout = Printer::default().stream_for_requested_summary().lock();
5357
let version_info = crate::version::version();
5458
writeln!(stdout, "ty {}", &version_info)?;
5559
Ok(())
@@ -61,6 +65,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
6165
let verbosity = args.verbosity.level();
6266
let _guard = setup_tracing(verbosity, args.color.unwrap_or_default())?;
6367

68+
let printer = Printer::default().with_verbosity(verbosity);
69+
6470
tracing::warn!(
6571
"ty is pre-release software and not ready for production use. \
6672
Expect to encounter bugs, missing features, and fatal errors.",
@@ -125,7 +131,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
125131
}
126132

127133
let project_options_overrides = ProjectOptionsOverrides::new(config_file, options);
128-
let (main_loop, main_loop_cancellation_token) = MainLoop::new(project_options_overrides);
134+
let (main_loop, main_loop_cancellation_token) =
135+
MainLoop::new(project_options_overrides, printer);
129136

130137
// Listen to Ctrl+C and abort the watch mode.
131138
let main_loop_cancellation_token = Mutex::new(Some(main_loop_cancellation_token));
@@ -143,7 +150,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
143150
main_loop.run(&mut db)?
144151
};
145152

146-
let mut stdout = stdout().lock();
153+
let mut stdout = printer.stream_for_requested_summary().lock();
147154
match std::env::var(EnvVars::TY_MEMORY_REPORT).as_deref() {
148155
Ok("short") => write!(stdout, "{}", db.salsa_memory_dump().display_short())?,
149156
Ok("mypy_primer") => write!(stdout, "{}", db.salsa_memory_dump().display_mypy_primer())?,
@@ -192,12 +199,16 @@ struct MainLoop {
192199
/// The file system watcher, if running in watch mode.
193200
watcher: Option<ProjectWatcher>,
194201

202+
/// Interface for displaying information to the user.
203+
printer: Printer,
204+
195205
project_options_overrides: ProjectOptionsOverrides,
196206
}
197207

198208
impl MainLoop {
199209
fn new(
200210
project_options_overrides: ProjectOptionsOverrides,
211+
printer: Printer,
201212
) -> (Self, MainLoopCancellationToken) {
202213
let (sender, receiver) = crossbeam_channel::bounded(10);
203214

@@ -207,6 +218,7 @@ impl MainLoop {
207218
receiver,
208219
watcher: None,
209220
project_options_overrides,
221+
printer,
210222
},
211223
MainLoopCancellationToken { sender },
212224
)
@@ -223,32 +235,24 @@ impl MainLoop {
223235

224236
// Do not show progress bars with `--watch`, indicatif does not seem to
225237
// handle cancelling independent progress bars very well.
226-
self.run_with_progress::<DummyReporter>(db)?;
238+
// TODO(zanieb): We can probably use `MultiProgress` to handle this case in the future.
239+
self.printer = self.printer.with_no_progress();
240+
self.run(db)?;
227241

228242
Ok(ExitStatus::Success)
229243
}
230244

231245
fn run(self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
232-
self.run_with_progress::<IndicatifReporter>(db)
233-
}
234-
235-
fn run_with_progress<R>(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
236-
where
237-
R: Reporter + Default + 'static,
238-
{
239246
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
240247

241-
let result = self.main_loop::<R>(db);
248+
let result = self.main_loop(db);
242249

243250
tracing::debug!("Exiting main loop");
244251

245252
result
246253
}
247254

248-
fn main_loop<R>(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus>
249-
where
250-
R: Reporter + Default + 'static,
251-
{
255+
fn main_loop(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
252256
// Schedule the first check.
253257
tracing::debug!("Starting main loop");
254258

@@ -264,7 +268,7 @@ impl MainLoop {
264268
// to prevent blocking the main loop here.
265269
rayon::spawn(move || {
266270
match salsa::Cancelled::catch(|| {
267-
let mut reporter = R::default();
271+
let mut reporter = IndicatifReporter::from(self.printer);
268272
db.check_with_reporter(&mut reporter)
269273
}) {
270274
Ok(result) => {
@@ -299,10 +303,12 @@ impl MainLoop {
299303
return Ok(ExitStatus::Success);
300304
}
301305

302-
let mut stdout = stdout().lock();
303-
304306
if result.is_empty() {
305-
writeln!(stdout, "{}", "All checks passed!".green().bold())?;
307+
writeln!(
308+
self.printer.stream_for_success_summary(),
309+
"{}",
310+
"All checks passed!".green().bold()
311+
)?;
306312

307313
if self.watcher.is_none() {
308314
return Ok(ExitStatus::Success);
@@ -311,14 +317,19 @@ impl MainLoop {
311317
let mut max_severity = Severity::Info;
312318
let diagnostics_count = result.len();
313319

320+
let mut stdout = self.printer.stream_for_details().lock();
314321
for diagnostic in result {
315-
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
322+
// Only render diagnostics if they're going to be displayed, since doing
323+
// so is expensive.
324+
if stdout.is_enabled() {
325+
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
326+
}
316327

317328
max_severity = max_severity.max(diagnostic.severity());
318329
}
319330

320331
writeln!(
321-
stdout,
332+
self.printer.stream_for_failure_summary(),
322333
"Found {} diagnostic{}",
323334
diagnostics_count,
324335
if diagnostics_count > 1 { "s" } else { "" }
@@ -378,27 +389,53 @@ impl MainLoop {
378389
}
379390

380391
/// A progress reporter for `ty check`.
381-
#[derive(Default)]
382-
struct IndicatifReporter(Option<indicatif::ProgressBar>);
392+
enum IndicatifReporter {
393+
/// A constructed reporter that is not yet ready, contains the target for the progress bar.
394+
Pending(indicatif::ProgressDrawTarget),
395+
/// A reporter that is ready, containing a progress bar to report to.
396+
///
397+
/// Initialization of the bar is deferred to [`ty_project::ProgressReporter::set_files`] so we
398+
/// do not initialize the bar too early as it may take a while to collect the number of files to
399+
/// process and we don't want to display an empty "0/0" bar.
400+
Initialized(indicatif::ProgressBar),
401+
}
402+
403+
impl From<Printer> for IndicatifReporter {
404+
fn from(printer: Printer) -> Self {
405+
Self::Pending(printer.progress_target())
406+
}
407+
}
383408

384-
impl ty_project::Reporter for IndicatifReporter {
409+
impl ty_project::ProgressReporter for IndicatifReporter {
385410
fn set_files(&mut self, files: usize) {
386-
let progress = indicatif::ProgressBar::new(files as u64);
387-
progress.set_style(
411+
let target = match std::mem::replace(
412+
self,
413+
IndicatifReporter::Pending(indicatif::ProgressDrawTarget::hidden()),
414+
) {
415+
Self::Pending(target) => target,
416+
Self::Initialized(_) => panic!("The progress reporter should only be initialized once"),
417+
};
418+
419+
let bar = indicatif::ProgressBar::with_draw_target(Some(files as u64), target);
420+
bar.set_style(
388421
indicatif::ProgressStyle::with_template(
389422
"{msg:8.dim} {bar:60.green/dim} {pos}/{len} files",
390423
)
391424
.unwrap()
392425
.progress_chars("--"),
393426
);
394-
progress.set_message("Checking");
395-
396-
self.0 = Some(progress);
427+
bar.set_message("Checking");
428+
*self = Self::Initialized(bar);
397429
}
398430

399431
fn report_file(&self, _file: &ruff_db::files::File) {
400-
if let Some(ref progress_bar) = self.0 {
401-
progress_bar.inc(1);
432+
match self {
433+
IndicatifReporter::Initialized(progress_bar) => {
434+
progress_bar.inc(1);
435+
}
436+
IndicatifReporter::Pending(_) => {
437+
panic!("`report_file` called before `set_files`")
438+
}
402439
}
403440
}
404441
}

crates/ty/src/logging.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,32 @@ pub(crate) struct Verbosity {
2424
help = "Use verbose output (or `-vv` and `-vvv` for more verbose output)",
2525
action = clap::ArgAction::Count,
2626
global = true,
27+
overrides_with = "quiet",
2728
)]
2829
verbose: u8,
30+
31+
#[arg(
32+
long,
33+
help = "Use quiet output",
34+
action = clap::ArgAction::Count,
35+
global = true,
36+
overrides_with = "verbose",
37+
)]
38+
quiet: u8,
2939
}
3040

3141
impl Verbosity {
32-
/// Returns the verbosity level based on the number of `-v` flags.
42+
/// Returns the verbosity level based on the number of `-v` and `-q` flags.
3343
///
3444
/// Returns `None` if the user did not specify any verbosity flags.
3545
pub(crate) fn level(&self) -> VerbosityLevel {
46+
// `--quiet` and `--verbose` are mutually exclusive in Clap, so we can just check one first.
47+
match self.quiet {
48+
0 => {}
49+
_ => return VerbosityLevel::Quiet,
50+
// TODO(zanieb): Add support for `-qq` with a "silent" mode
51+
}
52+
3653
match self.verbose {
3754
0 => VerbosityLevel::Default,
3855
1 => VerbosityLevel::Verbose,
@@ -42,9 +59,14 @@ impl Verbosity {
4259
}
4360
}
4461

45-
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
62+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
4663
pub(crate) enum VerbosityLevel {
64+
/// Quiet output. Only shows Ruff and ty events up to the [`ERROR`](tracing::Level::ERROR).
65+
/// Silences output except for summary information.
66+
Quiet,
67+
4768
/// Default output level. Only shows Ruff and ty events up to the [`WARN`](tracing::Level::WARN).
69+
#[default]
4870
Default,
4971

5072
/// Enables verbose output. Emits Ruff and ty events up to the [`INFO`](tracing::Level::INFO).
@@ -62,6 +84,7 @@ pub(crate) enum VerbosityLevel {
6284
impl VerbosityLevel {
6385
const fn level_filter(self) -> LevelFilter {
6486
match self {
87+
VerbosityLevel::Quiet => LevelFilter::ERROR,
6588
VerbosityLevel::Default => LevelFilter::WARN,
6689
VerbosityLevel::Verbose => LevelFilter::INFO,
6790
VerbosityLevel::ExtraVerbose => LevelFilter::DEBUG,

0 commit comments

Comments
 (0)