Split commands.rs into separate files (#2792)

This commit is contained in:
Charlie Marsh 2023-02-11 21:58:13 -05:00 committed by GitHub
parent d827a9156e
commit 77e65c9ff5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 456 additions and 384 deletions

View file

@ -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<usize> {
// 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)
}

View file

@ -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(())
}

View file

@ -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;

View file

@ -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(())
}

View file

@ -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<Diagnostics> {
// 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::<Vec<_>>(),
&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)
}

View file

@ -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<String> {
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<Diagnostics> {
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)
}

View file

@ -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(())
}

View file

@ -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(())
}