Skip to content

Commit

Permalink
Add a formatter CLI for debugging (#4809)
Browse files Browse the repository at this point in the history
* Add a formatter CLI for debugging

This adds a ruff_python_formatter cli modelled aber `rustfmt` that i use for debugging

* clippy

* Add print IR and print comments options

Tested with `cargo run --bin ruff_python_formatter -- --print-ir --print-comments scratch.py`
  • Loading branch information
konstin authored Jun 5, 2023
1 parent 576e0c7 commit d1d0696
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 11 deletions.
72 changes: 68 additions & 4 deletions crates/ruff_python_formatter/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
#![allow(clippy::print_stdout)]

use std::path::PathBuf;

use clap::{command, Parser};
use anyhow::{bail, Context, Result};
use clap::{command, Parser, ValueEnum};
use rustpython_parser::lexer::lex;
use rustpython_parser::{parse_tokens, Mode};

use ruff_formatter::SourceCode;
use ruff_python_ast::source_code::CommentRangesBuilder;

use crate::format_node;

#[derive(ValueEnum, Clone, Debug)]
pub enum Emit {
/// Write back to the original files
Files,
/// Write to stdout
Stdout,
}

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Python file to round-trip.
#[arg(required = true)]
pub file: PathBuf,
/// Python files to format. If there are none, stdin will be used. `-` as stdin is not supported
pub files: Vec<PathBuf>,
#[clap(long)]
pub emit: Option<Emit>,
/// Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits with 1 and prints
/// a diff if formatting is required.
#[clap(long)]
pub check: bool,
#[clap(long)]
pub print_ir: bool,
#[clap(long)]
pub print_comments: bool,
}

pub fn format_and_debug_print(input: &str, cli: &Cli) -> Result<String> {
let mut tokens = Vec::new();
let mut comment_ranges = CommentRangesBuilder::default();

for result in lex(input, Mode::Module) {
let (token, range) = match result {
Ok((token, range)) => (token, range),
Err(err) => bail!("Source contains syntax errors {err:?}"),
};

comment_ranges.visit_token(&token, range);
tokens.push(Ok((token, range)));
}

let comment_ranges = comment_ranges.finish();

// Parse the AST.
let python_ast = parse_tokens(tokens, Mode::Module, "<filename>")
.with_context(|| "Syntax error in input")?;

let formatted = format_node(&python_ast, &comment_ranges, input)?;
if cli.print_ir {
println!("{}", formatted.document().display(SourceCode::new(input)));
}
if cli.print_comments {
println!(
"{:?}",
formatted.context().comments().debug(SourceCode::new(input))
);
}
Ok(formatted
.print()
.with_context(|| "Failed to print the formatter IR")?
.as_code()
.to_string())
}
52 changes: 45 additions & 7 deletions crates/ruff_python_formatter/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,53 @@
use std::fs;
use std::io::{stdout, Read, Write};
use std::{fs, io};

use anyhow::Result;
use anyhow::{bail, Context, Result};
use clap::Parser as ClapParser;

use ruff_python_formatter::cli::Cli;
use ruff_python_formatter::format_module;
use ruff_python_formatter::cli::{format_and_debug_print, Cli, Emit};

/// Read a `String` from `stdin`.
pub(crate) fn read_from_stdin() -> Result<String> {
let mut buffer = String::new();
io::stdin().lock().read_to_string(&mut buffer)?;
Ok(buffer)
}

#[allow(clippy::print_stdout)]
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_to_string(cli.file)?;
println!("{}", format_module(&contents)?.as_code());
let cli: Cli = Cli::parse();

if cli.files.is_empty() {
if !matches!(cli.emit, None | Some(Emit::Stdout)) {
bail!(
"Can only write to stdout when formatting from stdin, but you asked for {:?}",
cli.emit
);
}
let input = read_from_stdin()?;
let formatted = format_and_debug_print(&input, &cli)?;
if cli.check {
if formatted == input {
return Ok(());
}
bail!("Content not correctly formatted")
}
stdout().lock().write_all(formatted.as_bytes())?;
} else {
for file in &cli.files {
let input = fs::read_to_string(file)
.with_context(|| format!("Could not read {}: ", file.display()))?;
let formatted = format_and_debug_print(&input, &cli)?;
match cli.emit {
Some(Emit::Stdout) => stdout().lock().write_all(formatted.as_bytes())?,
None | Some(Emit::Files) => {
fs::write(file, formatted.as_bytes()).with_context(|| {
format!("Could not write to {}, exiting", file.display())
})?;
}
}
}
}

Ok(())
}

0 comments on commit d1d0696

Please sign in to comment.