Repository: casey/just
Pull request #1015 (2021-11-01 06:13:24)

Add color to `just --fmt --check` diff


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5204c551..d6f664a6 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 9697940e..85739c74 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 f64b5d38..faeea38e 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 f2659831..c0adeb3f 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 3dc0d6d4..f1430c13 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>> {


Test patch:
diff --git a/tests/fmt.rs b/tests/fmt.rs
index 16530967..9dfdb8e4 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();