diff --git a/Cargo.lock b/Cargo.lock index 5204c55..d6f664a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,17 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "camino" version = "1.0.5" @@ -443,6 +454,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.25" @@ -469,6 +486,10 @@ name = "similar" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +dependencies = [ + "bstr", + "unicode-segmentation", +] [[package]] name = "snafu" diff --git a/Cargo.toml b/Cargo.toml index 9697940..85739c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ lexiclean = "0.0.1" libc = "0.2.0" log = "0.4.4" regex = "1.5.4" -similar = "2.1.0" snafu = "0.6.0" strum_macros = "0.22.0" target = "2.0.0" @@ -44,6 +43,10 @@ features = ["wrap_help"] version = "3.1.1" features = ["termination"] +[dependencies.similar] +version = "2.1.0" +features = ["unicode"] + [dependencies.strum] version = "0.22.0" features = ["derive"] diff --git a/src/color.rs b/src/color.rs index f64b5d3..faeea38 100644 --- a/src/color.rs +++ b/src/color.rs @@ -95,6 +95,14 @@ impl Color { self.restyle(Style::new().fg(Green)) } + pub(crate) fn diff_added(self) -> Self { + self.restyle(Style::new().fg(Green)) + } + + pub(crate) fn diff_deleted(self) -> Self { + self.restyle(Style::new().fg(Red)) + } + pub(crate) fn active(&self) -> bool { match self.use_color { UseColor::Always => true, diff --git a/src/common.rs b/src/common.rs index f265983..c0adeb3 100644 --- a/src/common.rs +++ b/src/common.rs @@ -5,7 +5,7 @@ pub(crate) use std::{ env, ffi::{OsStr, OsString}, fmt::{self, Debug, Display, Formatter}, - fs::{self, File}, + fs, io::{self, Cursor, Write}, iter::{self, FromIterator}, mem, diff --git a/src/subcommand.rs b/src/subcommand.rs index 3dc0d6d..f1430c1 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -77,7 +77,7 @@ impl Subcommand { justfile.run(config, &search, overrides, &[])? } Dump => Self::dump(ast), - Format => Self::format(config, ast, &src, &search)?, + Format => Self::format(config, &search, &src, ast)?, List => Self::list(config, justfile), Run { arguments, @@ -255,33 +255,47 @@ impl Subcommand { Ok(()) } - fn format(config: &Config, ast: Ast, src: &str, search: &Search) -> Result<(), Error<'static>> { + fn format(config: &Config, search: &Search, src: &str, ast: Ast) -> Result<(), Error<'static>> { config.require_unstable("The `--fmt` command is currently unstable.")?; - let src_formatted = ast.to_string(); + let formatted = ast.to_string(); + if config.check { - if src == src_formatted { - Ok(()) - } else { - print!( - "{}", - similar::udiff::unified_diff(similar::Algorithm::Patience, &src, &src_formatted, 2, None) - ); + return if formatted != src { + use similar::{ChangeTag, TextDiff}; + + let diff = TextDiff::configure() + .algorithm(similar::Algorithm::Patience) + .diff_lines(src, &formatted); + + for op in diff.ops() { + for change in diff.iter_changes(op) { + let (symbol, color) = match change.tag() { + ChangeTag::Delete => ("-", config.color.stderr().diff_deleted()), + ChangeTag::Equal => (" ", config.color.stderr()), + ChangeTag::Insert => ("+", config.color.stderr().diff_added()), + }; + + eprint!("{}{}{}{}", color.prefix(), symbol, change, color.suffix()); + } + } + Err(Error::FormatCheckFoundDiff) - } - } else if let Err(io_error) = - File::create(&search.justfile).and_then(|mut file| file.write_all(&src_formatted.as_bytes())) - { - Err(Error::WriteJustfile { - justfile: search.justfile.clone(), - io_error, - }) - } else { - if config.verbosity.loud() { - eprintln!("Wrote justfile to `{}`", search.justfile.display()); - } - Ok(()) + } else { + Ok(()) + }; } + + fs::write(&search.justfile, formatted).map_err(|io_error| Error::WriteJustfile { + justfile: search.justfile.clone(), + io_error, + })?; + + if config.verbosity.loud() { + eprintln!("Wrote justfile to `{}`", search.justfile.display()); + } + + Ok(()) } fn init(config: &Config) -> Result<(), Error<'static>> { diff --git a/tests/fmt.rs b/tests/fmt.rs index 1653096..9dfdb8e 100644 --- a/tests/fmt.rs +++ b/tests/fmt.rs @@ -22,7 +22,7 @@ test! { } test! { - name: dry_run_ok, + name: check_ok, justfile: r#" # comment with spaces @@ -42,20 +42,29 @@ deps: } test! { - name: dry_run_found_diff, + name: check_found_diff, justfile: "x:=``\n", args: ("--unstable", "--fmt", "--check"), - stdout: " - @@ -1 +1 @@ - -x:=`` - +x := `` - ", stderr: " + -x:=`` + +x := `` error: Formatted justfile differs from original. ", status: EXIT_FAILURE, } +test! { + name: check_diff_color, + justfile: "x:=``\n", + args: ("--unstable", "--fmt", "--check", "--color", "always"), + stderr: " + \u{1b}[31m-x:=`` + \u{1b}[0m\u{1b}[32m+x := `` + \u{1b}[0m\u{1b}[1;31merror\u{1b}[0m: \u{1b}[1mFormatted justfile differs from original.\u{1b}[0m + ", + status: EXIT_FAILURE, +} + #[test] fn unstable_passed() { let tmp = tempdir();