From 0b71db10e2b6e09f5ecc6c38907ad239de8f378f Mon Sep 17 00:00:00 2001 From: azjezz Date: Mon, 9 Dec 2024 02:13:25 +0100 Subject: [PATCH] feat: implement dry run for the formatter Signed-off-by: azjezz --- Cargo.lock | 21 +++++++++++- Cargo.toml | 1 + crates/cli/src/commands/format.rs | 20 +++++++++-- crates/service/Cargo.toml | 1 + crates/service/src/formatter/mod.rs | 23 +++++++------ crates/service/src/lib.rs | 2 ++ crates/service/src/utils/mod.rs | 53 +++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 crates/service/src/utils/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c26a636..264420e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,6 +529,15 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "diffy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3041965b7a63e70447ec818a46b1e5297f7fcae3058356d226c02750c4e6cb" +dependencies = [ + "nu-ansi-term 0.50.1", +] + [[package]] name = "digest" version = "0.10.7" @@ -1181,6 +1190,7 @@ dependencies = [ "ahash", "async-walkdir", "config", + "diffy", "futures", "glob-match", "mago-ast", @@ -1352,6 +1362,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2073,7 +2092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "regex", "sharded-slab", diff --git a/Cargo.toml b/Cargo.toml index 7e6b296..1799ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ termtree = "0.5.1" bitflags = "2.6.0" wasm-bindgen = "0.2.97" serde-wasm-bindgen = "0.4" +diffy = "0.4.0" [dependencies] mago-cli = { workspace = true } diff --git a/crates/cli/src/commands/format.rs b/crates/cli/src/commands/format.rs index 97868e0..b48e1d8 100644 --- a/crates/cli/src/commands/format.rs +++ b/crates/cli/src/commands/format.rs @@ -21,6 +21,8 @@ This command will format source files according to the rules defined in the conf pub struct FormatCommand { #[arg(long, short = 'w', help = "The width of the printed source code", value_name = "WIDTH")] pub print_width: Option, + #[arg(long, short = 'd', help = "Run the command without writing any changes to disk")] + pub dry_run: bool, } pub async fn execute(command: FormatCommand, mut configuration: Configuration) -> i32 { @@ -35,9 +37,21 @@ pub async fn execute(command: FormatCommand, mut configuration: Configuration) - let service = FormatterService::new(configuration.format, interner.clone(), source_manager.clone()); - let count = service.run().await.unwrap_or_else(bail); + let changed = service.run(command.dry_run).await.unwrap_or_else(bail); - mago_feedback::info!("formatted {} source files successfully", count); + if changed == 0 { + mago_feedback::info!("All source files are already formatted"); - 0 + return 0; + } + + if command.dry_run { + mago_feedback::info!("Found {} source files that need formatting", changed); + + 1 + } else { + mago_feedback::info!("Formatted {} source files successfully", changed); + + 0 + } } diff --git a/crates/service/Cargo.toml b/crates/service/Cargo.toml index f246555..651cdc1 100644 --- a/crates/service/Cargo.toml +++ b/crates/service/Cargo.toml @@ -35,3 +35,4 @@ tracing = { workspace = true } config = { workspace = true } toml = { workspace = true } num_cpus = { workspace = true } +diffy = { workspace = true } diff --git a/crates/service/src/formatter/mod.rs b/crates/service/src/formatter/mod.rs index d71c096..4e7aee9 100644 --- a/crates/service/src/formatter/mod.rs +++ b/crates/service/src/formatter/mod.rs @@ -9,6 +9,7 @@ use mago_source::SourceIdentifier; use mago_source::SourceManager; use crate::formatter::config::FormatterConfiguration; +use crate::utils; pub mod config; @@ -29,13 +30,13 @@ impl FormatterService { } /// Runs the formatting process. - pub async fn run(&self) -> Result { + pub async fn run(&self, dry_run: bool) -> Result { // Process sources concurrently - self.process_sources(self.source_manager.user_defined_source_ids().collect()).await + self.process_sources(self.source_manager.user_defined_source_ids().collect(), dry_run).await } #[inline] - async fn process_sources(&self, source_ids: Vec) -> Result { + async fn process_sources(&self, source_ids: Vec, dry_run: bool) -> Result { let settings = self.configuration.get_settings(); let mut handles = Vec::with_capacity(source_ids.len()); @@ -71,7 +72,7 @@ impl FormatterService { format_pb.inc(1); write_pb.inc(1); - return Result::<_, SourceError>::Ok(()); + return Result::::Ok(false); } mago_feedback::debug!("> formatting program: {}", interner.lookup(&program.source.0)); @@ -83,21 +84,21 @@ impl FormatterService { mago_feedback::debug!("> writing program: {}", interner.lookup(&program.source.0)); // Step 4: write the formatted source - manager.write(source.identifier, formatted)?; + let changed = utils::apply_changes(&interner, &manager, &source, formatted, dry_run)?; write_pb.inc(1); mago_feedback::debug!("< formatted program: {}", interner.lookup(&program.source.0)); - Result::<_, SourceError>::Ok(()) + Result::::Ok(changed) } })); } - let mut count = 0; + let mut changed = 0; for handle in handles { - handle.await.expect("failed to format files, this should never happen.")?; - - count += 1; + if handle.await.expect("failed to format files, this should never happen.")? { + changed += 1; + } } remove_progress_bar(source_pb); @@ -105,6 +106,6 @@ impl FormatterService { remove_progress_bar(format_pb); remove_progress_bar(write_pb); - Ok(count) + Ok(changed) } } diff --git a/crates/service/src/lib.rs b/crates/service/src/lib.rs index 9dad4e9..08accd2 100644 --- a/crates/service/src/lib.rs +++ b/crates/service/src/lib.rs @@ -1,3 +1,5 @@ +mod utils; + pub mod ast; pub mod config; pub mod formatter; diff --git a/crates/service/src/utils/mod.rs b/crates/service/src/utils/mod.rs new file mode 100644 index 0000000..edc0487 --- /dev/null +++ b/crates/service/src/utils/mod.rs @@ -0,0 +1,53 @@ +use mago_interner::ThreadedInterner; +use mago_source::error::SourceError; +use mago_source::Source; +use mago_source::SourceManager; + +/// Applies changes to the source file. +/// +/// If `dry_run` is `true`, it compares the original and modified content, +/// displays a diff with context around changes, and does not write to disk. +/// +/// If `dry_run` is `false`, it writes the formatted content to the source manager. +/// +/// # Arguments +/// +/// * `interner` - Reference to the `ThreadedInterner`. +/// * `source_manager` - Reference to the `SourceManager`. +/// * `source` - Reference to the `Source` being processed. +/// * `changed_code` - The formatted content as a `String`. +/// * `dry_run` - Boolean flag indicating whether to perform a dry run. +/// +/// # Returns +/// +/// * `Result` - A result indicating whether the source was changed. +pub fn apply_changes( + interner: &ThreadedInterner, + source_manager: &SourceManager, + source: &Source, + changed_code: String, + dry_run: bool, +) -> Result { + let original_content = interner.lookup(&source.content); + if original_content == changed_code { + return Ok(false); + } + + if dry_run { + let source_name = interner.lookup(&source.identifier.0); + + mago_feedback::progress::GLOBAL_PROGRESS_MANAGER.suspend(|| { + println!("diff of '{}':", source_name); + println!( + "{}", + diffy::PatchFormatter::new() + .with_color() + .fmt_patch(&diffy::create_patch(original_content, changed_code.as_str())) + ); + }); + } else { + source_manager.write(source.identifier, changed_code)?; + } + + Ok(true) +}