use futures::{stream::FuturesUnordered, StreamExt}; use std::{fs, process::exit}; use super::*; // For reference, the rustfmt main.rs file // https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs /// Build the Rust WASM app and all of its assets. #[derive(Clone, Debug, Parser)] pub struct Autoformat { /// 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, /// Input file #[clap(short, long)] pub file: Option, } impl Autoformat { // Todo: autoformat the entire crate pub async fn autoformat(self) -> Result<()> { // Default to formatting the project if self.raw.is_none() && self.file.is_none() { if let Err(e) = autoformat_project(self.check).await { eprintln!("error formatting project: {}", e); exit(1); } } if let Some(raw) = self.raw { if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0) { println!("{}", inner); } else { // exit process with error eprintln!("error formatting codeblock"); exit(1); } } // Format single file if let Some(file) = self.file { let file_content = fs::read_to_string(&file); match file_content { Ok(s) => { let edits = dioxus_autofmt::fmt_file(&s); let out = dioxus_autofmt::apply_formats(&s, edits); match fs::write(&file, out) { Ok(_) => { println!("formatted {}", file); } Err(e) => { eprintln!("failed to write formatted content to file: {}", e); } } } Err(e) => { eprintln!("failed to open file: {}", e); exit(1); } } } Ok(()) } } /// 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 /// /// Doesn't do mod-descending, so it will still try to format unreachable files. TODO. async fn autoformat_project(check: bool) -> Result<()> { let crate_config = crate::CrateConfig::new()?; let mut files_to_format = vec![]; collect_rs_files(&crate_config.crate_dir, &mut files_to_format); let counts = files_to_format .into_iter() .filter(|file| { if file.components().any(|f| f.as_os_str() == "target") { return false; } true }) .map(|path| async { let _path = path.clone(); let res = tokio::spawn(async move { let contents = tokio::fs::read_to_string(&path).await?; let edits = dioxus_autofmt::fmt_file(&contents); let len = edits.len(); if !edits.is_empty() { let out = dioxus_autofmt::apply_formats(&contents, edits); tokio::fs::write(&path, out).await?; } Ok(len) as Result }) .await; if res.is_err() { eprintln!("error formatting file: {}", _path.display()); } res }) .collect::>() .collect::>() .await; let files_formatted: usize = counts .into_iter() .map(|f| match f { Ok(Ok(res)) => res, _ => 0, }) .sum(); if files_formatted > 0 && check { eprintln!("{} files needed formatting", files_formatted); exit(1); } Ok(()) } fn collect_rs_files(folder: &PathBuf, files: &mut Vec) { let Ok(folder) = folder.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" { files.push(path); } } } } #[test] fn spawn_properly() { let out = Command::new("dioxus") .args([ "fmt", "-f", r#" // rsx! { div {} } // // // "#, ]) .output() .expect("failed to execute process"); dbg!(out); }