diff --git a/crates/ruff_cli/src/commands.rs b/crates/ruff_cli/src/commands.rs deleted file mode 100644 index 37e508f62a..0000000000 --- a/crates/ruff_cli/src/commands.rs +++ /dev/null @@ -1,383 +0,0 @@ -use std::fs::remove_dir_all; -use std::io::{self, BufWriter, Read, Write}; -use std::path::{Path, PathBuf}; -use std::time::Instant; - -use anyhow::{bail, Result}; -use colored::control::SHOULD_COLORIZE; -use colored::Colorize; -use ignore::Error; -use itertools::Itertools; -use log::{debug, error}; -use mdcat::terminal::{TerminalProgram, TerminalSize}; -use mdcat::{Environment, ResourceAccess, Settings}; -use path_absolutize::path_dedot; -use pulldown_cmark::{Options, Parser}; -#[cfg(not(target_family = "wasm"))] -use rayon::prelude::*; -use ruff::cache::CACHE_DIR_NAME; -use ruff::linter::add_noqa_to_path; -use ruff::logging::LogLevel; -use ruff::message::{Location, Message}; -use ruff::registry::{Linter, Rule, RuleNamespace}; -use ruff::resolver::PyprojectDiscovery; -use ruff::settings::flags; -use ruff::{fix, fs, packaging, resolver, warn_user_once, AutofixAvailability, IOError}; -use serde::Serialize; -use syntect::parsing::SyntaxSet; -use walkdir::WalkDir; - -use crate::args::{HelpFormat, Overrides}; -use crate::cache; -use crate::diagnostics::{lint_path, lint_stdin, Diagnostics}; -use crate::iterators::par_iter; - -pub mod linter; - -/// Run the linter over a collection of files. -pub fn run( - files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, - overrides: &Overrides, - cache: flags::Cache, - autofix: fix::FixMode, -) -> Result { - // Collect all the Python files to check. - let start = Instant::now(); - let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; - let duration = start.elapsed(); - debug!("Identified files to lint in: {:?}", duration); - - if paths.is_empty() { - warn_user_once!("No Python files found under the given path(s)"); - return Ok(Diagnostics::default()); - } - - // Initialize the cache. - if matches!(cache, flags::Cache::Enabled) { - match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => { - if let Err(e) = cache::init(&settings.cli.cache_dir) { - error!( - "Failed to initialize cache at {}: {e:?}", - settings.cli.cache_dir.to_string_lossy() - ); - } - } - PyprojectDiscovery::Hierarchical(default) => { - for settings in std::iter::once(default).chain(resolver.iter()) { - if let Err(e) = cache::init(&settings.cli.cache_dir) { - error!( - "Failed to initialize cache at {}: {e:?}", - settings.cli.cache_dir.to_string_lossy() - ); - } - } - } - } - }; - - // Discover the package root for each Python file. - let package_roots = packaging::detect_package_roots( - &paths - .iter() - .flatten() - .map(ignore::DirEntry::path) - .collect::>(), - &resolver, - pyproject_strategy, - ); - - let start = Instant::now(); - let mut diagnostics: Diagnostics = par_iter(&paths) - .map(|entry| { - match entry { - Ok(entry) => { - let path = entry.path(); - let package = path - .parent() - .and_then(|parent| package_roots.get(parent)) - .and_then(|package| *package); - let settings = resolver.resolve_all(path, pyproject_strategy); - lint_path(path, package, settings, cache, autofix) - .map_err(|e| (Some(path.to_owned()), e.to_string())) - } - Err(e) => Err(( - if let Error::WithPath { path, .. } = e { - Some(path.clone()) - } else { - None - }, - e.io_error() - .map_or_else(|| e.to_string(), io::Error::to_string), - )), - } - .unwrap_or_else(|(path, message)| { - if let Some(path) = &path { - error!( - "{}{}{} {message}", - "Failed to lint ".bold(), - fs::relativize_path(path).bold(), - ":".bold() - ); - let settings = resolver.resolve(path, pyproject_strategy); - if settings.rules.enabled(&Rule::IOError) { - Diagnostics::new(vec![Message { - kind: IOError { message }.into(), - location: Location::default(), - end_location: Location::default(), - fix: None, - filename: format!("{}", path.display()), - source: None, - }]) - } else { - Diagnostics::default() - } - } else { - error!("{} {message}", "Encountered error:".bold()); - Diagnostics::default() - } - }) - }) - .reduce(Diagnostics::default, |mut acc, item| { - acc += item; - acc - }); - - diagnostics.messages.sort_unstable(); - let duration = start.elapsed(); - debug!("Checked {:?} files in: {:?}", paths.len(), duration); - - Ok(diagnostics) -} - -/// Read a `String` from `stdin`. -fn read_from_stdin() -> Result { - let mut buffer = String::new(); - io::stdin().lock().read_to_string(&mut buffer)?; - Ok(buffer) -} - -/// Run the linter over a single file, read from `stdin`. -pub fn run_stdin( - filename: Option<&Path>, - pyproject_strategy: &PyprojectDiscovery, - overrides: &Overrides, - autofix: fix::FixMode, -) -> Result { - if let Some(filename) = filename { - if !resolver::python_file_at_path(filename, pyproject_strategy, overrides)? { - return Ok(Diagnostics::default()); - } - } - let settings = match pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => settings, - PyprojectDiscovery::Hierarchical(settings) => settings, - }; - let package_root = filename - .and_then(Path::parent) - .and_then(|path| packaging::detect_package_root(path, &settings.lib.namespace_packages)); - let stdin = read_from_stdin()?; - let mut diagnostics = lint_stdin(filename, package_root, &stdin, &settings.lib, autofix)?; - diagnostics.messages.sort_unstable(); - Ok(diagnostics) -} - -/// Add `noqa` directives to a collection of files. -pub fn add_noqa( - files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, - overrides: &Overrides, -) -> Result { - // Collect all the files to check. - let start = Instant::now(); - let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; - let duration = start.elapsed(); - debug!("Identified files to lint in: {:?}", duration); - - if paths.is_empty() { - warn_user_once!("No Python files found under the given path(s)"); - return Ok(0); - } - - let start = Instant::now(); - let modifications: usize = par_iter(&paths) - .flatten() - .filter_map(|entry| { - let path = entry.path(); - let settings = resolver.resolve(path, pyproject_strategy); - match add_noqa_to_path(path, settings) { - Ok(count) => Some(count), - Err(e) => { - error!("Failed to add noqa to {}: {e}", path.to_string_lossy()); - None - } - } - }) - .sum(); - - let duration = start.elapsed(); - debug!("Added noqa to files in: {:?}", duration); - - Ok(modifications) -} - -/// Print the user-facing configuration settings. -pub fn show_settings( - files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, - overrides: &Overrides, -) -> Result<()> { - // Collect all files in the hierarchy. - let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; - - // Print the list of files. - let Some(entry) = paths - .iter() - .flatten() - .sorted_by(|a, b| a.path().cmp(b.path())).next() else { - bail!("No files found under the given path"); - }; - let path = entry.path(); - let settings = resolver.resolve(path, pyproject_strategy); - - let mut stdout = BufWriter::new(io::stdout().lock()); - write!(stdout, "Resolved settings for: {path:?}")?; - write!(stdout, "{settings:#?}")?; - - Ok(()) -} - -/// Show the list of files to be checked based on current settings. -pub fn show_files( - files: &[PathBuf], - pyproject_strategy: &PyprojectDiscovery, - overrides: &Overrides, -) -> Result<()> { - // Collect all files in the hierarchy. - let (paths, _resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; - - if paths.is_empty() { - warn_user_once!("No Python files found under the given path(s)"); - return Ok(()); - } - - // Print the list of files. - let mut stdout = BufWriter::new(io::stdout().lock()); - for entry in paths - .iter() - .flatten() - .sorted_by(|a, b| a.path().cmp(b.path())) - { - writeln!(stdout, "{}", entry.path().to_string_lossy())?; - } - - Ok(()) -} - -#[derive(Serialize)] -struct Explanation<'a> { - code: &'a str, - linter: &'a str, - summary: &'a str, -} - -/// Explain a `Rule` to the user. -pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> { - let (linter, _) = Linter::parse_code(rule.code()).unwrap(); - let mut stdout = BufWriter::new(io::stdout().lock()); - let mut output = String::new(); - - match format { - HelpFormat::Text | HelpFormat::Markdown => { - output.push_str(&format!("# {} ({})", rule.as_ref(), rule.code())); - output.push('\n'); - output.push('\n'); - - let (linter, _) = Linter::parse_code(rule.code()).unwrap(); - output.push_str(&format!("Derived from the **{}** linter.", linter.name())); - output.push('\n'); - output.push('\n'); - - if let Some(autofix) = rule.autofixable() { - output.push_str(match autofix.available { - AutofixAvailability::Sometimes => "Autofix is sometimes available.", - AutofixAvailability::Always => "Autofix is always available.", - }); - output.push('\n'); - output.push('\n'); - } - - if let Some(explanation) = rule.explanation() { - output.push_str(explanation.trim()); - } else { - output.push_str("Message formats:"); - for format in rule.message_formats() { - output.push('\n'); - output.push_str(&format!("* {format}")); - } - } - } - HelpFormat::Json => { - output.push_str(&serde_json::to_string_pretty(&Explanation { - code: rule.code(), - linter: linter.name(), - summary: rule.message_formats()[0], - })?); - } - }; - - match format { - HelpFormat::Json | HelpFormat::Text => { - writeln!(stdout, "{output}")?; - } - HelpFormat::Markdown => { - let parser = Parser::new_ext( - &output, - Options::ENABLE_TASKLISTS | Options::ENABLE_STRIKETHROUGH, - ); - - let cwd = std::env::current_dir()?; - let env = &Environment::for_local_directory(&cwd)?; - - let terminal = if SHOULD_COLORIZE.should_colorize() { - TerminalProgram::detect() - } else { - TerminalProgram::Dumb - }; - - let settings = &Settings { - resource_access: ResourceAccess::LocalOnly, - syntax_set: SyntaxSet::load_defaults_newlines(), - terminal_capabilities: terminal.capabilities(), - terminal_size: TerminalSize::detect().unwrap_or_default(), - }; - - mdcat::push_tty(settings, env, &mut stdout, parser)?; - } - }; - Ok(()) -} - -/// Clear any caches in the current directory or any subdirectories. -pub fn clean(level: LogLevel) -> Result<()> { - let mut stderr = BufWriter::new(io::stderr().lock()); - for entry in WalkDir::new(&*path_dedot::CWD) - .into_iter() - .filter_map(Result::ok) - .filter(|entry| entry.file_type().is_dir()) - { - let cache = entry.path().join(CACHE_DIR_NAME); - if cache.is_dir() { - if level >= LogLevel::Default { - writeln!( - stderr, - "Removing cache at: {}", - fs::relativize_path(&cache).bold() - )?; - } - remove_dir_all(&cache)?; - } - } - Ok(()) -} diff --git a/crates/ruff_cli/src/commands/add_noqa.rs b/crates/ruff_cli/src/commands/add_noqa.rs new file mode 100644 index 0000000000..abe916a8de --- /dev/null +++ b/crates/ruff_cli/src/commands/add_noqa.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; +use std::time::Instant; + +use anyhow::Result; +use log::{debug, error}; +#[cfg(not(target_family = "wasm"))] +use rayon::prelude::*; + +use ruff::linter::add_noqa_to_path; +use ruff::resolver::PyprojectDiscovery; +use ruff::{resolver, warn_user_once}; + +use crate::args::Overrides; +use crate::iterators::par_iter; + +/// Add `noqa` directives to a collection of files. +pub fn add_noqa( + files: &[PathBuf], + pyproject_strategy: &PyprojectDiscovery, + overrides: &Overrides, +) -> Result { + // Collect all the files to check. + let start = Instant::now(); + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + let duration = start.elapsed(); + debug!("Identified files to lint in: {:?}", duration); + + if paths.is_empty() { + warn_user_once!("No Python files found under the given path(s)"); + return Ok(0); + } + + let start = Instant::now(); + let modifications: usize = par_iter(&paths) + .flatten() + .filter_map(|entry| { + let path = entry.path(); + let settings = resolver.resolve(path, pyproject_strategy); + match add_noqa_to_path(path, settings) { + Ok(count) => Some(count), + Err(e) => { + error!("Failed to add noqa to {}: {e}", path.to_string_lossy()); + None + } + } + }) + .sum(); + + let duration = start.elapsed(); + debug!("Added noqa to files in: {:?}", duration); + + Ok(modifications) +} diff --git a/crates/ruff_cli/src/commands/clean.rs b/crates/ruff_cli/src/commands/clean.rs new file mode 100644 index 0000000000..dee19f7dc4 --- /dev/null +++ b/crates/ruff_cli/src/commands/clean.rs @@ -0,0 +1,34 @@ +use std::fs::remove_dir_all; +use std::io::{self, BufWriter, Write}; + +use anyhow::Result; +use colored::Colorize; +use path_absolutize::path_dedot; +use walkdir::WalkDir; + +use ruff::cache::CACHE_DIR_NAME; +use ruff::fs; +use ruff::logging::LogLevel; + +/// Clear any caches in the current directory or any subdirectories. +pub fn clean(level: LogLevel) -> Result<()> { + let mut stderr = BufWriter::new(io::stderr().lock()); + for entry in WalkDir::new(&*path_dedot::CWD) + .into_iter() + .filter_map(Result::ok) + .filter(|entry| entry.file_type().is_dir()) + { + let cache = entry.path().join(CACHE_DIR_NAME); + if cache.is_dir() { + if level >= LogLevel::Default { + writeln!( + stderr, + "Removing cache at: {}", + fs::relativize_path(&cache).bold() + )?; + } + remove_dir_all(&cache)?; + } + } + Ok(()) +} diff --git a/crates/ruff_cli/src/commands/mod.rs b/crates/ruff_cli/src/commands/mod.rs new file mode 100644 index 0000000000..a4deee4a14 --- /dev/null +++ b/crates/ruff_cli/src/commands/mod.rs @@ -0,0 +1,17 @@ +pub use add_noqa::add_noqa; +pub use clean::clean; +pub use linter::linter; +pub use rule::rule; +pub use run::run; +pub use run_stdin::run_stdin; +pub use show_files::show_files; +pub use show_settings::show_settings; + +mod add_noqa; +mod clean; +mod linter; +mod rule; +mod run; +mod run_stdin; +mod show_files; +mod show_settings; diff --git a/crates/ruff_cli/src/commands/rule.rs b/crates/ruff_cli/src/commands/rule.rs new file mode 100644 index 0000000000..c6698f3229 --- /dev/null +++ b/crates/ruff_cli/src/commands/rule.rs @@ -0,0 +1,98 @@ +use std::io::{self, BufWriter, Write}; + +use anyhow::Result; +use colored::control::SHOULD_COLORIZE; +use mdcat::terminal::{TerminalProgram, TerminalSize}; +use mdcat::{Environment, ResourceAccess, Settings}; +use pulldown_cmark::{Options, Parser}; +use serde::Serialize; +use syntect::parsing::SyntaxSet; + +use ruff::registry::{Linter, Rule, RuleNamespace}; +use ruff::AutofixAvailability; + +use crate::args::HelpFormat; + +#[derive(Serialize)] +struct Explanation<'a> { + code: &'a str, + linter: &'a str, + summary: &'a str, +} + +/// Explain a `Rule` to the user. +pub fn rule(rule: &Rule, format: HelpFormat) -> Result<()> { + let (linter, _) = Linter::parse_code(rule.code()).unwrap(); + let mut stdout = BufWriter::new(io::stdout().lock()); + let mut output = String::new(); + + match format { + HelpFormat::Text | HelpFormat::Markdown => { + output.push_str(&format!("# {} ({})", rule.as_ref(), rule.code())); + output.push('\n'); + output.push('\n'); + + let (linter, _) = Linter::parse_code(rule.code()).unwrap(); + output.push_str(&format!("Derived from the **{}** linter.", linter.name())); + output.push('\n'); + output.push('\n'); + + if let Some(autofix) = rule.autofixable() { + output.push_str(match autofix.available { + AutofixAvailability::Sometimes => "Autofix is sometimes available.", + AutofixAvailability::Always => "Autofix is always available.", + }); + output.push('\n'); + output.push('\n'); + } + + if let Some(explanation) = rule.explanation() { + output.push_str(explanation.trim()); + } else { + output.push_str("Message formats:"); + for format in rule.message_formats() { + output.push('\n'); + output.push_str(&format!("* {format}")); + } + } + } + HelpFormat::Json => { + output.push_str(&serde_json::to_string_pretty(&Explanation { + code: rule.code(), + linter: linter.name(), + summary: rule.message_formats()[0], + })?); + } + }; + + match format { + HelpFormat::Json | HelpFormat::Text => { + writeln!(stdout, "{output}")?; + } + HelpFormat::Markdown => { + let parser = Parser::new_ext( + &output, + Options::ENABLE_TASKLISTS | Options::ENABLE_STRIKETHROUGH, + ); + + let cwd = std::env::current_dir()?; + let env = &Environment::for_local_directory(&cwd)?; + + let terminal = if SHOULD_COLORIZE.should_colorize() { + TerminalProgram::detect() + } else { + TerminalProgram::Dumb + }; + + let settings = &Settings { + resource_access: ResourceAccess::LocalOnly, + syntax_set: SyntaxSet::load_defaults_newlines(), + terminal_capabilities: terminal.capabilities(), + terminal_size: TerminalSize::detect().unwrap_or_default(), + }; + + mdcat::push_tty(settings, env, &mut stdout, parser)?; + } + }; + Ok(()) +} diff --git a/crates/ruff_cli/src/commands/run.rs b/crates/ruff_cli/src/commands/run.rs new file mode 100644 index 0000000000..aa1517a1b1 --- /dev/null +++ b/crates/ruff_cli/src/commands/run.rs @@ -0,0 +1,138 @@ +use std::io::{self}; +use std::path::PathBuf; +use std::time::Instant; + +use anyhow::Result; +use colored::Colorize; +use ignore::Error; +use log::{debug, error}; +#[cfg(not(target_family = "wasm"))] +use rayon::prelude::*; + +use ruff::message::{Location, Message}; +use ruff::registry::Rule; +use ruff::resolver::PyprojectDiscovery; +use ruff::settings::flags; +use ruff::{fix, fs, packaging, resolver, warn_user_once, IOError}; + +use crate::args::Overrides; +use crate::cache; +use crate::diagnostics::{lint_path, Diagnostics}; +use crate::iterators::par_iter; + +/// Run the linter over a collection of files. +pub fn run( + files: &[PathBuf], + pyproject_strategy: &PyprojectDiscovery, + overrides: &Overrides, + cache: flags::Cache, + autofix: fix::FixMode, +) -> Result { + // Collect all the Python files to check. + let start = Instant::now(); + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + let duration = start.elapsed(); + debug!("Identified files to lint in: {:?}", duration); + + if paths.is_empty() { + warn_user_once!("No Python files found under the given path(s)"); + return Ok(Diagnostics::default()); + } + + // Initialize the cache. + if matches!(cache, flags::Cache::Enabled) { + match &pyproject_strategy { + PyprojectDiscovery::Fixed(settings) => { + if let Err(e) = cache::init(&settings.cli.cache_dir) { + error!( + "Failed to initialize cache at {}: {e:?}", + settings.cli.cache_dir.to_string_lossy() + ); + } + } + PyprojectDiscovery::Hierarchical(default) => { + for settings in std::iter::once(default).chain(resolver.iter()) { + if let Err(e) = cache::init(&settings.cli.cache_dir) { + error!( + "Failed to initialize cache at {}: {e:?}", + settings.cli.cache_dir.to_string_lossy() + ); + } + } + } + } + }; + + // Discover the package root for each Python file. + let package_roots = packaging::detect_package_roots( + &paths + .iter() + .flatten() + .map(ignore::DirEntry::path) + .collect::>(), + &resolver, + pyproject_strategy, + ); + + let start = Instant::now(); + let mut diagnostics: Diagnostics = par_iter(&paths) + .map(|entry| { + match entry { + Ok(entry) => { + let path = entry.path(); + let package = path + .parent() + .and_then(|parent| package_roots.get(parent)) + .and_then(|package| *package); + let settings = resolver.resolve_all(path, pyproject_strategy); + lint_path(path, package, settings, cache, autofix) + .map_err(|e| (Some(path.to_owned()), e.to_string())) + } + Err(e) => Err(( + if let Error::WithPath { path, .. } = e { + Some(path.clone()) + } else { + None + }, + e.io_error() + .map_or_else(|| e.to_string(), io::Error::to_string), + )), + } + .unwrap_or_else(|(path, message)| { + if let Some(path) = &path { + error!( + "{}{}{} {message}", + "Failed to lint ".bold(), + fs::relativize_path(path).bold(), + ":".bold() + ); + let settings = resolver.resolve(path, pyproject_strategy); + if settings.rules.enabled(&Rule::IOError) { + Diagnostics::new(vec![Message { + kind: IOError { message }.into(), + location: Location::default(), + end_location: Location::default(), + fix: None, + filename: format!("{}", path.display()), + source: None, + }]) + } else { + Diagnostics::default() + } + } else { + error!("{} {message}", "Encountered error:".bold()); + Diagnostics::default() + } + }) + }) + .reduce(Diagnostics::default, |mut acc, item| { + acc += item; + acc + }); + + diagnostics.messages.sort_unstable(); + let duration = start.elapsed(); + debug!("Checked {:?} files in: {:?}", paths.len(), duration); + + Ok(diagnostics) +} diff --git a/crates/ruff_cli/src/commands/run_stdin.rs b/crates/ruff_cli/src/commands/run_stdin.rs new file mode 100644 index 0000000000..554634c8ed --- /dev/null +++ b/crates/ruff_cli/src/commands/run_stdin.rs @@ -0,0 +1,42 @@ +use std::io::{self, Read}; +use std::path::Path; + +use anyhow::Result; + +use ruff::resolver::PyprojectDiscovery; +use ruff::{fix, packaging, resolver}; + +use crate::args::Overrides; +use crate::diagnostics::{lint_stdin, Diagnostics}; + +/// Read a `String` from `stdin`. +fn read_from_stdin() -> Result { + let mut buffer = String::new(); + io::stdin().lock().read_to_string(&mut buffer)?; + Ok(buffer) +} + +/// Run the linter over a single file, read from `stdin`. +pub fn run_stdin( + filename: Option<&Path>, + pyproject_strategy: &PyprojectDiscovery, + overrides: &Overrides, + autofix: fix::FixMode, +) -> Result { + if let Some(filename) = filename { + if !resolver::python_file_at_path(filename, pyproject_strategy, overrides)? { + return Ok(Diagnostics::default()); + } + } + let settings = match pyproject_strategy { + PyprojectDiscovery::Fixed(settings) => settings, + PyprojectDiscovery::Hierarchical(settings) => settings, + }; + let package_root = filename + .and_then(Path::parent) + .and_then(|path| packaging::detect_package_root(path, &settings.lib.namespace_packages)); + let stdin = read_from_stdin()?; + let mut diagnostics = lint_stdin(filename, package_root, &stdin, &settings.lib, autofix)?; + diagnostics.messages.sort_unstable(); + Ok(diagnostics) +} diff --git a/crates/ruff_cli/src/commands/show_files.rs b/crates/ruff_cli/src/commands/show_files.rs new file mode 100644 index 0000000000..93e4ca4946 --- /dev/null +++ b/crates/ruff_cli/src/commands/show_files.rs @@ -0,0 +1,37 @@ +use std::io::{self, BufWriter, Write}; +use std::path::PathBuf; + +use anyhow::Result; +use itertools::Itertools; + +use ruff::resolver::PyprojectDiscovery; +use ruff::{resolver, warn_user_once}; + +use crate::args::Overrides; + +/// Show the list of files to be checked based on current settings. +pub fn show_files( + files: &[PathBuf], + pyproject_strategy: &PyprojectDiscovery, + overrides: &Overrides, +) -> Result<()> { + // Collect all files in the hierarchy. + let (paths, _resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + + if paths.is_empty() { + warn_user_once!("No Python files found under the given path(s)"); + return Ok(()); + } + + // Print the list of files. + let mut stdout = BufWriter::new(io::stdout().lock()); + for entry in paths + .iter() + .flatten() + .sorted_by(|a, b| a.path().cmp(b.path())) + { + writeln!(stdout, "{}", entry.path().to_string_lossy())?; + } + + Ok(()) +} diff --git a/crates/ruff_cli/src/commands/show_settings.rs b/crates/ruff_cli/src/commands/show_settings.rs new file mode 100644 index 0000000000..448d12a4e4 --- /dev/null +++ b/crates/ruff_cli/src/commands/show_settings.rs @@ -0,0 +1,36 @@ +use std::io::{self, BufWriter, Write}; +use std::path::PathBuf; + +use anyhow::{bail, Result}; +use itertools::Itertools; + +use ruff::resolver; +use ruff::resolver::PyprojectDiscovery; + +use crate::args::Overrides; + +/// Print the user-facing configuration settings. +pub fn show_settings( + files: &[PathBuf], + pyproject_strategy: &PyprojectDiscovery, + overrides: &Overrides, +) -> Result<()> { + // Collect all files in the hierarchy. + let (paths, resolver) = resolver::python_files_in_path(files, pyproject_strategy, overrides)?; + + // Print the list of files. + let Some(entry) = paths + .iter() + .flatten() + .sorted_by(|a, b| a.path().cmp(b.path())).next() else { + bail!("No files found under the given path"); + }; + let path = entry.path(); + let settings = resolver.resolve(path, pyproject_strategy); + + let mut stdout = BufWriter::new(io::stdout().lock()); + write!(stdout, "Resolved settings for: {path:?}")?; + write!(stdout, "{settings:#?}")?; + + Ok(()) +} diff --git a/crates/ruff_cli/src/main.rs b/crates/ruff_cli/src/main.rs index 32392d95a3..8976349b8d 100644 --- a/crates/ruff_cli/src/main.rs +++ b/crates/ruff_cli/src/main.rs @@ -101,7 +101,7 @@ quoting the executed command, along with the relevant file contents and `pyproje match command { Command::Rule { rule, format } => commands::rule(&rule, format)?, - Command::Linter { format } => commands::linter::linter(format), + Command::Linter { format } => commands::linter(format), Command::Clean => commands::clean(log_level)?, Command::GenerateShellCompletion { shell } => { shell.generate(&mut Args::command(), &mut io::stdout());