use std::io::Write;
use std::process::{Command, Stdio};
use crate::error::{Error, Result};
pub const LINE_LENGTH: usize = 120;
pub fn format_source(file: &str, source: &str, width: usize) -> Result<String> {
let mut child = Command::new("ruff")
.arg("format")
.arg("--line-length")
.arg(width.to_string())
.arg("--stdin-filename")
.arg(file)
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|source| Error::Format {
file: file.to_string(),
message: match source.kind() {
std::io::ErrorKind::NotFound => {
"`ruff` was not found on PATH; install it (e.g. `pip install ruff` \
or see https://docs.astral.sh/ruff/installation/) — crozier defers \
Python formatting to ruff"
.to_string()
}
_ => format!("could not run `ruff`: {source}"),
},
})?;
child
.stdin
.take()
.expect("stdin was piped")
.write_all(source.as_bytes())
.map_err(|source| Error::Format {
file: file.to_string(),
message: format!("could not write source to ruff: {source}"),
})?;
let output = child.wait_with_output().map_err(|source| Error::Format {
file: file.to_string(),
message: format!("waiting on ruff failed: {source}"),
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(Error::Format {
file: file.to_string(),
message: format!("ruff exited with {}: {}", output.status, stderr.trim()),
});
}
String::from_utf8(output.stdout).map_err(|source| Error::Format {
file: file.to_string(),
message: format!("ruff produced non-UTF-8 output: {source}"),
})
}
#[cfg(test)]
mod tests {
use super::{format_source, LINE_LENGTH};
use crate::error::Error;
#[test]
fn formats_poorly_spaced_source() {
let out = format_source("x.py", "x={'a':1,'b':2}", LINE_LENGTH).unwrap();
assert_eq!(out, "x = {\"a\": 1, \"b\": 2}\n");
}
#[test]
fn wraps_at_the_requested_width() {
let src = "f(aaaaaaaaaa, bbbbbbbbbb, cccccccccc, dddddddddd, eeeeeeeeee)\n";
assert_eq!(format_source("x.py", src, 200).unwrap(), src);
assert!(format_source("x.py", src, 20).unwrap().contains(",\n"));
}
#[test]
fn invalid_python_is_a_format_error() {
let err = format_source("x.py", "def (:\n", LINE_LENGTH).unwrap_err();
assert!(matches!(err, Error::Format { .. }));
}
}