334 lines
8.5 KiB
Rust
334 lines
8.5 KiB
Rust
use dioxus_autofmt::{IndentOptions, IndentType};
|
|
use rayon::prelude::*;
|
|
use std::{fs, io, path::Path, process::exit};
|
|
|
|
use super::*;
|
|
|
|
// For reference, the rustfmt main.rs file
|
|
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
|
|
|
|
/// Format some rsx
|
|
#[derive(Clone, Debug, Parser)]
|
|
pub struct Autoformat {
|
|
/// Run rustfmt before the dioxus formatter`
|
|
#[clap(long)]
|
|
pub rustfmt: bool,
|
|
|
|
/// Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits
|
|
/// with 1 and prints a diff if formatting is required.
|
|
#[clap(short, long)]
|
|
pub check: bool,
|
|
|
|
/// Input rsx (selection)
|
|
#[clap(short, long)]
|
|
pub raw: Option<String>,
|
|
|
|
/// Input file
|
|
#[clap(short, long)]
|
|
pub file: Option<String>,
|
|
|
|
/// Split attributes in lines or not
|
|
#[clap(short, long, default_value = "false")]
|
|
pub split_line_attributes: bool,
|
|
}
|
|
|
|
impl Autoformat {
|
|
// Todo: autoformat the entire crate
|
|
pub async fn autoformat(self) -> Result<()> {
|
|
let Autoformat {
|
|
check,
|
|
raw,
|
|
file,
|
|
split_line_attributes,
|
|
rustfmt: do_rustfmt,
|
|
..
|
|
} = self;
|
|
|
|
// Default to formatting the project
|
|
if raw.is_none() && file.is_none() {
|
|
println!("format project !");
|
|
if let Err(e) = autoformat_project(check, split_line_attributes, do_rustfmt).await {
|
|
eprintln!("error formatting project: {}", e);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if let Some(raw) = raw {
|
|
let indent = indentation_for(".", self.split_line_attributes)?;
|
|
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
|
|
println!("{}", inner);
|
|
} else {
|
|
// exit process with error
|
|
eprintln!("error formatting codeblock");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// Format single file
|
|
if let Some(file) = file {
|
|
refactor_file(file, split_line_attributes, do_rustfmt)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn refactor_file(file: String, split_line_attributes: bool, do_rustfmt: bool) -> Result<(), Error> {
|
|
let indent = indentation_for(".", split_line_attributes)?;
|
|
let file_content = if file == "-" {
|
|
let mut contents = String::new();
|
|
std::io::stdin().read_to_string(&mut contents)?;
|
|
Ok(contents)
|
|
} else {
|
|
fs::read_to_string(&file)
|
|
};
|
|
let Ok(mut s) = file_content else {
|
|
eprintln!("failed to open file: {}", file_content.unwrap_err());
|
|
exit(1);
|
|
};
|
|
|
|
if do_rustfmt {
|
|
s = dioxus_autofmt::rustfmt(&s).ok_or_else(|| Error::ParseError("Syntax Error".into()))?;
|
|
}
|
|
|
|
let edits = dioxus_autofmt::fmt_file(&s, indent);
|
|
let out = dioxus_autofmt::apply_formats(&s, edits);
|
|
|
|
if file == "-" {
|
|
print!("{}", out);
|
|
} else if let Err(e) = fs::write(&file, out) {
|
|
eprintln!("failed to write formatted content to file: {e}",);
|
|
} else {
|
|
println!("formatted {}", file);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
use std::ffi::OsStr;
|
|
fn get_project_files() -> Vec<PathBuf> {
|
|
let mut files = Vec::new();
|
|
for result in ignore::Walk::new("./") {
|
|
let path = result.unwrap().into_path();
|
|
if let Some(ext) = path.extension() {
|
|
if ext == OsStr::new("rs") {
|
|
files.push(path);
|
|
}
|
|
}
|
|
}
|
|
files
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn is_target_dir(file: &Path) -> bool {
|
|
let stripped = if let Ok(cwd) = std::env::current_dir() {
|
|
file.strip_prefix(cwd).unwrap_or(file)
|
|
} else {
|
|
file
|
|
};
|
|
if let Some(first) = stripped.components().next() {
|
|
first.as_os_str() == "target"
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn format_file(path: impl AsRef<Path>, indent: IndentOptions, do_rustfmt: bool) -> Result<usize> {
|
|
let mut contents = fs::read_to_string(&path)?;
|
|
let mut if_write = false;
|
|
if do_rustfmt {
|
|
let formatted = dioxus_autofmt::rustfmt(&contents)
|
|
.ok_or_else(|| Error::ParseError("Syntax Error".into()))?;
|
|
if contents != formatted {
|
|
if_write = true;
|
|
contents = formatted;
|
|
}
|
|
}
|
|
|
|
println!("at {} : {:#?}", path.as_ref().display(), &contents);
|
|
|
|
let edits = dioxus_autofmt::fmt_file(&contents, indent);
|
|
let len = edits.len();
|
|
|
|
if !edits.is_empty() {
|
|
if_write = true;
|
|
}
|
|
|
|
if if_write {
|
|
let out = dioxus_autofmt::apply_formats(&contents, edits);
|
|
fs::write(path, out)?;
|
|
}
|
|
|
|
Ok(len)
|
|
}
|
|
|
|
/// Read every .rs file accessible when considering the .gitignore and try to format it
|
|
///
|
|
/// Runs using Tokio for multithreading, so it should be really really fast
|
|
///
|
|
/// Doesnhttps://www.rustwiki.org.cn/zh-CN/std/'t do mod-descending, so it will still try to format unreachable files. TODO.
|
|
async fn autoformat_project(
|
|
check: bool,
|
|
split_line_attributes: bool,
|
|
do_rustfmt: bool,
|
|
) -> Result<()> {
|
|
let files_to_format = get_project_files();
|
|
|
|
dbg!(&files_to_format);
|
|
|
|
if files_to_format.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
let indent = indentation_for(&files_to_format[0], split_line_attributes)?;
|
|
|
|
let counts = files_to_format
|
|
.into_par_iter()
|
|
.map(|path| {
|
|
let res = format_file(&path, indent.clone(), do_rustfmt);
|
|
match res {
|
|
Ok(cnt) => Some(cnt),
|
|
Err(err) => {
|
|
eprintln!("error formatting file : {}\n{:#?}", path.display(), err);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let files_formatted: usize = counts.into_iter().flatten().sum();
|
|
|
|
if files_formatted > 0 && check {
|
|
eprintln!("{} files needed formatting", files_formatted);
|
|
exit(1);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn indentation_for(
|
|
file_or_dir: impl AsRef<Path>,
|
|
split_line_attributes: bool,
|
|
) -> Result<IndentOptions> {
|
|
let out = std::process::Command::new("cargo")
|
|
.args(["fmt", "--", "--print-config", "current"])
|
|
.arg(file_or_dir.as_ref())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::inherit())
|
|
.output()?;
|
|
if !out.status.success() {
|
|
return Err(Error::CargoError("cargo fmt failed".into()));
|
|
}
|
|
|
|
let config = String::from_utf8_lossy(&out.stdout);
|
|
|
|
let hard_tabs = config
|
|
.lines()
|
|
.find(|line| line.starts_with("hard_tabs "))
|
|
.and_then(|line| line.split_once('='))
|
|
.map(|(_, value)| value.trim() == "true")
|
|
.ok_or_else(|| {
|
|
Error::RuntimeError("Could not find hard_tabs option in rustfmt config".into())
|
|
})?;
|
|
let tab_spaces = config
|
|
.lines()
|
|
.find(|line| line.starts_with("tab_spaces "))
|
|
.and_then(|line| line.split_once('='))
|
|
.map(|(_, value)| value.trim().parse::<usize>())
|
|
.ok_or_else(|| {
|
|
Error::RuntimeError("Could not find tab_spaces option in rustfmt config".into())
|
|
})?
|
|
.map_err(|_| {
|
|
Error::RuntimeError("Could not parse tab_spaces option in rustfmt config".into())
|
|
})?;
|
|
|
|
Ok(IndentOptions::new(
|
|
if hard_tabs {
|
|
IndentType::Tabs
|
|
} else {
|
|
IndentType::Spaces
|
|
},
|
|
tab_spaces,
|
|
split_line_attributes,
|
|
))
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn collect_rs_files(folder: &impl AsRef<Path>, files: &mut Vec<PathBuf>) {
|
|
if is_target_dir(folder.as_ref()) {
|
|
return;
|
|
}
|
|
let Ok(folder) = folder.as_ref().read_dir() else {
|
|
return;
|
|
};
|
|
// load the gitignore
|
|
for entry in folder {
|
|
let Ok(entry) = entry else {
|
|
continue;
|
|
};
|
|
let path = entry.path();
|
|
if path.is_dir() {
|
|
collect_rs_files(&path, files);
|
|
}
|
|
if let Some(ext) = path.extension() {
|
|
if ext == "rs" && !is_target_dir(&path) {
|
|
files.push(path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_auto_fmt() {
|
|
let test_rsx = r#"
|
|
//
|
|
|
|
|
|
|
|
div {}
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
"#
|
|
.to_string();
|
|
|
|
let fmt = Autoformat {
|
|
rustfmt: false,
|
|
check: false,
|
|
raw: Some(test_rsx),
|
|
file: None,
|
|
split_line_attributes: false,
|
|
};
|
|
|
|
fmt.autoformat().await.unwrap();
|
|
}
|
|
|
|
/*#[test]
|
|
fn spawn_properly() {
|
|
let out = Command::new("dioxus")
|
|
.args([
|
|
"fmt",
|
|
"-f",
|
|
r#"
|
|
//
|
|
|
|
rsx! {
|
|
|
|
div {}
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
"#,
|
|
])
|
|
.output()
|
|
.expect("failed to execute process");
|
|
|
|
dbg!(out);
|
|
}*/
|