Reuse FormatResult and FormatterIterationError in format_stdin.rs (#6985)

## Summary

Ensures that we use the same error types and messages. Also renames
those struct to `FormatCommand*` for consistency, and removes the
`FormatCommandResult::Skipped` variant in favor of skipping in the
iterator directly.
This commit is contained in:
Charlie Marsh 2023-08-29 13:41:53 -04:00 committed by GitHub
parent 8d1610d960
commit 5de95d7054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 70 deletions

View file

@ -61,19 +61,23 @@ pub(crate) fn format(
let start = Instant::now(); let start = Instant::now();
let (results, errors): (Vec<_>, Vec<_>) = paths let (results, errors): (Vec<_>, Vec<_>) = paths
.into_par_iter() .into_par_iter()
.map(|entry| { .filter_map(|entry| match entry {
let entry = entry?; Ok(entry) => {
let path = entry.path(); let path = entry.path();
if let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
SourceType::from(path) let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
{ SourceType::from(path)
else {
// Ignore any non-Python files.
return None;
};
let line_length = resolver.resolve(path, &pyproject_config).line_length; let line_length = resolver.resolve(path, &pyproject_config).line_length;
let options = PyFormatOptions::from_source_type(source_type) let options = PyFormatOptions::from_source_type(source_type)
.with_line_width(LineWidth::from(NonZeroU16::from(line_length))); .with_line_width(LineWidth::from(NonZeroU16::from(line_length)));
format_path(path, options, mode) Some(format_path(path, options, mode))
} else {
Ok(FormatResult::Skipped)
} }
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
}) })
.partition_map(|result| match result { .partition_map(|result| match result {
Ok(diagnostic) => Left(diagnostic), Ok(diagnostic) => Left(diagnostic),
@ -127,40 +131,39 @@ pub(crate) fn format(
} }
} }
/// Format the file at the given [`Path`].
#[tracing::instrument(skip_all, fields(path = %path.display()))] #[tracing::instrument(skip_all, fields(path = %path.display()))]
fn format_path( fn format_path(
path: &Path, path: &Path,
options: PyFormatOptions, options: PyFormatOptions,
mode: FormatMode, mode: FormatMode,
) -> Result<FormatResult, FormatterIterationError> { ) -> Result<FormatCommandResult, FormatCommandError> {
let unformatted = std::fs::read_to_string(path) let unformatted = std::fs::read_to_string(path)
.map_err(|err| FormatterIterationError::Read(path.to_path_buf(), err))?; .map_err(|err| FormatCommandError::Read(Some(path.to_path_buf()), err))?;
let formatted = { let formatted = {
let span = span!(Level::TRACE, "format_path_without_io", path = %path.display()); let span = span!(Level::TRACE, "format_path_without_io", path = %path.display());
let _enter = span.enter(); let _enter = span.enter();
format_module(&unformatted, options) format_module(&unformatted, options)
.map_err(|err| FormatterIterationError::FormatModule(path.to_path_buf(), err))? .map_err(|err| FormatCommandError::FormatModule(Some(path.to_path_buf()), err))?
}; };
let formatted = formatted.as_code(); let formatted = formatted.as_code();
if formatted.len() == unformatted.len() && formatted == unformatted { if formatted.len() == unformatted.len() && formatted == unformatted {
Ok(FormatResult::Unchanged) Ok(FormatCommandResult::Unchanged)
} else { } else {
if mode.is_write() { if mode.is_write() {
std::fs::write(path, formatted.as_bytes()) std::fs::write(path, formatted.as_bytes())
.map_err(|err| FormatterIterationError::Write(path.to_path_buf(), err))?; .map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
} }
Ok(FormatResult::Formatted) Ok(FormatCommandResult::Formatted)
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, is_macro::Is)]
enum FormatResult { pub(crate) enum FormatCommandResult {
/// The file was formatted. /// The file was formatted.
Formatted, Formatted,
/// The file was unchanged, as the formatted contents matched the existing contents. /// The file was unchanged, as the formatted contents matched the existing contents.
Unchanged, Unchanged,
/// The file was skipped, as it was not a Python file.
Skipped,
} }
#[derive(Debug)] #[derive(Debug)]
@ -171,23 +174,19 @@ struct FormatResultSummary {
formatted: usize, formatted: usize,
/// The number of files that were unchanged. /// The number of files that were unchanged.
unchanged: usize, unchanged: usize,
/// The number of files that were skipped.
skipped: usize,
} }
impl FormatResultSummary { impl FormatResultSummary {
fn new(diagnostics: Vec<FormatResult>, mode: FormatMode) -> Self { fn new(diagnostics: Vec<FormatCommandResult>, mode: FormatMode) -> Self {
let mut summary = Self { let mut summary = Self {
mode, mode,
formatted: 0, formatted: 0,
unchanged: 0, unchanged: 0,
skipped: 0,
}; };
for diagnostic in diagnostics { for diagnostic in diagnostics {
match diagnostic { match diagnostic {
FormatResult::Formatted => summary.formatted += 1, FormatCommandResult::Formatted => summary.formatted += 1,
FormatResult::Unchanged => summary.unchanged += 1, FormatCommandResult::Unchanged => summary.unchanged += 1,
FormatResult::Skipped => summary.skipped += 1,
} }
} }
summary summary
@ -235,14 +234,14 @@ impl Display for FormatResultSummary {
/// An error that can occur while formatting a set of files. /// An error that can occur while formatting a set of files.
#[derive(Error, Debug)] #[derive(Error, Debug)]
enum FormatterIterationError { pub(crate) enum FormatCommandError {
Ignore(#[from] ignore::Error), Ignore(#[from] ignore::Error),
Read(PathBuf, io::Error), Read(Option<PathBuf>, io::Error),
Write(PathBuf, io::Error), Write(Option<PathBuf>, io::Error),
FormatModule(PathBuf, FormatModuleError), FormatModule(Option<PathBuf>, FormatModuleError),
} }
impl Display for FormatterIterationError { impl Display for FormatCommandError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Ignore(err) => { Self::Ignore(err) => {
@ -267,31 +266,43 @@ impl Display for FormatterIterationError {
} }
} }
Self::Read(path, err) => { Self::Read(path, err) => {
write!( if let Some(path) = path {
f, write!(
"{}{}{} {err}", f,
"Failed to read ".bold(), "{}{}{} {err}",
fs::relativize_path(path).bold(), "Failed to read ".bold(),
":".bold() fs::relativize_path(path).bold(),
) ":".bold()
)
} else {
write!(f, "{}{} {err}", "Failed to read".bold(), ":".bold())
}
} }
Self::Write(path, err) => { Self::Write(path, err) => {
write!( if let Some(path) = path {
f, write!(
"{}{}{} {err}", f,
"Failed to write ".bold(), "{}{}{} {err}",
fs::relativize_path(path).bold(), "Failed to write ".bold(),
":".bold() fs::relativize_path(path).bold(),
) ":".bold()
)
} else {
write!(f, "{}{} {err}", "Failed to write".bold(), ":".bold())
}
} }
Self::FormatModule(path, err) => { Self::FormatModule(path, err) => {
write!( if let Some(path) = path {
f, write!(
"{}{}{} {err}", f,
"Failed to format ".bold(), "{}{}{} {err}",
fs::relativize_path(path).bold(), "Failed to format ".bold(),
":".bold() fs::relativize_path(path).bold(),
) ":".bold()
)
} else {
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
}
} }
} }
} }

View file

@ -1,12 +1,14 @@
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use std::path::Path;
use anyhow::Result; use anyhow::Result;
use log::warn;
use ruff_python_formatter::{format_module, PyFormatOptions}; use ruff_python_formatter::{format_module, PyFormatOptions};
use ruff_workspace::resolver::python_file_at_path; use ruff_workspace::resolver::python_file_at_path;
use crate::args::{FormatArguments, Overrides}; use crate::args::{FormatArguments, Overrides};
use crate::commands::format::FormatMode; use crate::commands::format::{FormatCommandError, FormatCommandResult, FormatMode};
use crate::resolve::resolve; use crate::resolve::resolve;
use crate::stdin::read_from_stdin; use crate::stdin::read_from_stdin;
use crate::ExitStatus; use crate::ExitStatus;
@ -32,26 +34,48 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &Overrides) -> Resu
} }
// Format the file. // Format the file.
let unformatted = read_from_stdin()?; let path = cli.stdin_filename.as_deref();
let options = cli let options = path
.stdin_filename
.as_deref()
.map(PyFormatOptions::from_extension) .map(PyFormatOptions::from_extension)
.unwrap_or_default(); .unwrap_or_default();
let formatted = format_module(&unformatted, options)?; match format_source(path, options, mode) {
Ok(result) => match mode {
match mode { FormatMode::Write => Ok(ExitStatus::Success),
FormatMode::Write => { FormatMode::Check => {
stdout().lock().write_all(formatted.as_code().as_bytes())?; if result.is_formatted() {
Ok(ExitStatus::Success) Ok(ExitStatus::Failure)
} } else {
FormatMode::Check => { Ok(ExitStatus::Success)
if formatted.as_code().len() == unformatted.len() && formatted.as_code() == unformatted }
{
Ok(ExitStatus::Success)
} else {
Ok(ExitStatus::Failure)
} }
},
Err(err) => {
warn!("{err}");
Ok(ExitStatus::Error)
} }
} }
} }
/// Format source code read from `stdin`.
fn format_source(
path: Option<&Path>,
options: PyFormatOptions,
mode: FormatMode,
) -> Result<FormatCommandResult, FormatCommandError> {
let unformatted = read_from_stdin()
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err))?;
let formatted = format_module(&unformatted, options)
.map_err(|err| FormatCommandError::FormatModule(path.map(Path::to_path_buf), err))?;
let formatted = formatted.as_code();
if formatted.len() == unformatted.len() && formatted == unformatted {
Ok(FormatCommandResult::Unchanged)
} else {
if mode.is_write() {
stdout()
.lock()
.write_all(formatted.as_bytes())
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
}
Ok(FormatCommandResult::Formatted)
}
}