Giter Site home page Giter Site logo

deep-diff2's Introduction

lambdaisland/deep-diff2

CircleCI cljdoc badge Clojars Project

Recursively compare Clojure or ClojureScript data structures, and produce a colorized diff of the result.

screenshot showing REPL example

Deep-diff2 is foremost intended for creating visual diffs for human consumption, if you want to programatically diff/patch Clojure data structures then Editscript may be a better fit, see this write-up by Huahai Yang.

Lambda Island Open Source

Thank you! deep-diff2 is made possible thanks to our generous backers. Become a backer on OpenCollective so that we can continue to make deep-diff2 better.

 

deep-diff2 is part of a growing collection of quality Clojure libraries created and maintained by the fine folks at Gaiwan.

Pay it forward by becoming a backer on our OpenCollective, so that we continue to enjoy a thriving Clojure ecosystem.

You can find an overview of all our different projects at lambdaisland/open-source.

 

 

Installation

deps.edn

lambdaisland/deep-diff2 {:mvn/version "2.11.216"}

project.clj

[lambdaisland/deep-diff2 "2.11.216"]

Use

(require '[lambdaisland.deep-diff2 :as ddiff])

(ddiff/pretty-print (ddiff/diff {:a 1 :b 2} {:a 1 :c 3}))

Diffing

lambdaisland.deep-diff2/diff takes two arguments and returns a "diff", a data structure that contains markers for insertions, deletions, or mismatches. These are records with - and + fields.

(ddiff/diff {:a 1 :b 2} {:a 1 :b 3})
{:a 1, :b #lambdaisland.deep_diff.diff.Mismatch{:- 2, :+ 3}}

Printing

You can pass this diff to lambdaisland.deep-diff2/pretty-print. This function uses Puget and Fipp to format the diff and print the result to standard out.

For fine grained control you can create a custom Puget printer, and supply it to pretty-print.

(def narrow-printer (ddiff/printer {:width 10}))

(ddiff/pretty-print (ddiff/diff {:a 1 :b 2} {:a 1 :b 3}) narrow-printer)

For more advanced uses like incorporating diffs into your own Fipp documents, see lambdaisland.deep-diff2.printer/format-doc, lambdaisland.deep-diff2.printer/print-doc.

Minimizing

If you are only interested in the changes, and not in any values that haven't changed, then you can use ddiff/minimize to return a more compact diff.

This is especially useful for potentially large nested data structures, for example a JSON response coming from a web service.

(-> (ddiff/diff {:a "apple" :b "pear"} {:a "apple" :b "banana"})
    ddiff/minimize
    ddiff/pretty-print)
;; {:b -"pear" +"banana"}

Print handlers for custom or built-in types

In recent versions deep-diff2 initializes its internal copy of Puget with {:print-fallback :print}, meaning it will fall back to using the system printer, which you can extend by extending the print-method multimethod.

This also means that we automatically pick up additional handlers installed by libraries, such as time-literals.

You can also register print handlers for deep-diff2 specifically by using lambdaisland.deep-diff2.printer-impl/register-print-handler!, or by passing an :extra-handlers map to printer.

If you are dealing with printing of custom types you might find that there are multiple print implementations you need to keep up-to-date, see lambdaisland.data-printers for a high-level API that can work with all the commonly used print implementations.

Example of a custom type

See repl_sessions/custom_type.clj for the full code and results.

(deftype Degrees [amount unit]
  Object
  (equals [this that]
    (and (instance? Degrees that)
         (= amount (.-amount that))
         (= unit (.-unit that)))))

;; Using system handler fallback
(defmethod print-method Degrees [degrees out]
  (.write out (str (.-amount degrees) "°" (.-unit degrees))))
  
;; OR Using a Puget-specific handler
(lambdaisland.deep-diff2.printer-impl/register-print-handler!
 `Degrees
 (fn [printer value]
   [:span
    (lambdaisland.deep-diff2.puget.color/document printer :number (str (.-amount value)))
    (lambdaisland.deep-diff2.puget.color/document printer :tag "°")
    (lambdaisland.deep-diff2.puget.color/document printer :keyword (str (.-unit value)))]))

Set up a custom print handler with different colors by utilizing Puget library

Sometimes, we need to tune the colors to:

  • Ensure adequate contrast on a different background.
  • Ensure readability by people who are colorblind.
  • Match your editor or main diff tool's color scheme.

Config of Puget

Fortunately, the Puget library included in deep-diff2 already allows customization through a custom printer.

In the Puget libray, 8-bit scheme is expressed via [:fg-256 5 n] where n is between 0 and 255. We can combine foreground and background, for example, like so: [:fg-256 5 226 :bg-256 5 56].

24-bit scheme is expressed via [:fg-256 2 r g b] where r g b are each between 0 and 255. Foreground and background can be combined, for example: [:fg-256 2 205 236 255 :bg-256 2 110 22 188].

An example of customizing color

For example, if we change the :lambdaisland.deep-diff2.printer-impl/deletion from [:red] to [:bg-256 5 13], the color code it outputs will change from \u001b[31m to \u001b[48;5;13m

user=> (use 'lambdaisland.deep-diff2)
nil
user=> (def color-printer (printer {:color-scheme {:lambdaisland.deep-diff2.printer-impl/deletion [:bg-256 5 13]}}))
#'user/color-printer
user=> (pretty-print (diff {:a 1} {:b 2}) color-printer)
{+:b 2, -:a 1}

That results in the following highlighting: screenshot showing color customization

Time, data literal

A common use case is diffing and printing Java date and time objects (java.util.Date, java.time.*, java.sql.Date|Time|DateTime).

Chances are you already have print handlers (and data readers) set up for these via the time-literals library (perhaps indirectly by pulling in tick. In that case these should just work.

(ddiff/diff #inst "2019-04-09T14:57:46.128-00:00"
            #inst "2019-04-10T14:57:46.128-00:00")

or

(import '[java.sql Timestamp])
(ddiff/diff (Timestamp. 0)
            (doto (Timestamp. 1000) (.setNanos 101)))

If you need to diff a rich set of time literal, using

(require '[time-literals.read-write])
(require '[lambdaisland.deep-diff2 :as ddiff])
(time-literals.read-write/print-time-literals-clj!)
(ddiff/pretty-print (ddiff/diff #time/date "2039-01-01" #time/date-time "2018-07-05T08:08:44.026"))

Deep-diff 1 vs 2

The original deep-diff only worked on Clojure, not ClojureScript. In porting the code to CLJC we were forced to make some breaking changes. To not break existing consumers we decided to move both the namespaces and the released artifact to new names, so the old and new deep-diff can exist side by side.

We also had to fork Puget to make it cljc compatible. This required breaking changes as well, making it unlikely these changes will make it upstream, so instead we vendor our own copy of Puget under lambdaisland.deep-diff2.puget.*. This does mean we don't automatically pick up custom Puget print handlers, unless they are also registered with our own copy of Puget. See above for more info on that.

When starting new projects you should use lambdaisland/deep-diff2. However if you have existing code that uses lambdaisland/deep-diff and you don't need the ClojureScript support then it is not necessary to upgrade. The old version still works fine (on Clojure).

You can upgrade of course, simply by replacing all namespace names from lambdaisland.deep-diff to lambdaisland.deep-diff2. If you are only using the top-level API (diff, printer, pretty-print) and you aren't using custom print handlers, then things should work exactly the same. If you find that deep-diff 2 behaves differently then please file an issue, you may have found a regression.

The old code still lives on the deep-diff-1 branch, and we do accept bugfix patches there, so we may put out bugfix releases of the original deep-diff in the future. When in doubt check the CHANGELOG.

Contributing

We warmly welcome patches to deep-diff2. Please keep in mind the following:

  • adhere to the LambdaIsland Clojure Style Guide
  • write patches that solve a problem
  • start by stating the problem, then supply a minimal solution *
  • by contributing you agree to license your contributions as EPL 1.0
  • don't break the contract with downstream consumers **
  • don't break the tests

We would very much appreciate it if you also

  • update the CHANGELOG and README
  • add tests for new functionality

We recommend opening an issue first, before opening a pull request. That way we can make sure we agree what the problem is, and discuss how best to solve it. This is especially true if you add new dependencies, or significantly increase the API surface. In cases like these we need to decide if these changes are in line with the project's goals.

* This goes for features too, a feature needs to solve a problem. State the problem it solves first, only then move on to solving it.

** Projects that have a version that starts with 0. may still see breaking changes, although we also consider the level of community adoption. The more widespread a project is, the less likely we're willing to introduce breakage. See LambdaIsland-flavored Versioning for more info.

Credits

This library builds upon clj-diff, which implements a diffing algorithm for sequences, and clj-arrangements, which makes disparate types sortable.

Pretty printing and colorization are handled by Puget and Fipp.

This library was originally developed as part of the Kaocha test runner.

Another library that implements a form of data structure diffing is editscript.

License

Copyright © 2018-2024 Arne Brasseur and contributors

Available under the terms of the Eclipse Public License 1.0, see LICENSE.txt

deep-diff2's People

Contributors

alysbrooks avatar ariela147 avatar borkdude avatar djblue avatar evinasgu avatar humorless avatar ikitommi avatar jarrodctaylor avatar latacora-paul avatar lread avatar nwjsmith avatar plexus avatar projectfrank avatar rutledgepaulv avatar sogaiu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

deep-diff2's Issues

Extract cljs port of puget

As part of porting deep-diff to clojurescript, we ported Puget to ClojureScript. Sadly this version is not compatible with upstream puget, since we can't do type dispatching in the same way. This also means that people need to explicitly add print handlers for custom types (although apparently there's an option to fall back to the regular printer, we should enable that).

Having puget available in ClojureScript for rendering of cljs data structures is very useful, and we should pull that out into its own lib/artifact.

Show diff at deeper level (Sets)

In certain cases the diff shows higher up in the hierarchy then expected. As an example:

  (require '[lambdaisland.deep-diff2 :as diff])

  (def d1 #{{:foo 1M} {:bar 2}})
  (def d2 #{{:foo 1} {:bar 2}})
  (diff/pretty-print (diff/diff d1 d2))
  ;; #{+{:foo 1} -{:foo 1M} {:bar 2}}

  (def d1 #{{:foo 1M}})
  (def d2 #{{:foo 1}})
  (diff/pretty-print (diff/diff d1 d2))
  ;; #{{:foo -1M +1}} 

I would have expected the diff to appear in the first case the same as in the second case.

Hashmap key order shouldn't matter

It shouldn't matter in which order I add keys to a hashmap:

(ddiff/diff {:name "Alyysa P Hacker" :age 40} {:age 40 :name "Alyssa P Hacker"})

Expected

{:name #lambdaisland.deep_diff2.diff_impl.Mismatch{:- "Alyysa P Hacker", :+ "Alyssa P Hacker"}, :age 40}

Actual

The :age key is removed and re-added:

{#lambdaisland.deep_diff2.diff_impl.Insertion{:+ :age} 40, :name #lambdaisland.deep_diff2.diff_impl.Mismatch{:- "Alyysa P Hacker", :+ "Alyssa P Hacker"}, #lambdaisland.deep_diff2.diff_impl.Deletion{:- :age} 40}

Fails with Records when inserted keys

(defrecord ARecord [])

(diff/diff (map->ARecord {}) (map->ARecord {:a 1}))
; Exception: java.lang.ClassCastException: lambdaisland.deep_diff.diff_test.ARecord cannot be cast to clojure.lang.IFn at clojure.core$juxt$fn__5544.invoke (core.clj:2586)
;    ...
;    lambdaisland.deep_diff.diff$diff_map.invokeStatic (diff.clj:125)
;    lambdaisland.deep_diff.diff$diff_map.invoke (diff.clj:106)
;    lambdaisland.deep_diff.diff$eval2918$fn__2919.invoke (diff.clj:162)
;    lambdaisland.deep_diff.diff$eval2796$fn__2797$G__2787__2804.invoke (diff.clj:11)
;    lambdaisland.deep_diff.diff$diff.invokeStatic (diff.clj:136)
;    lambdaisland.deep_diff.diff$diff.invoke (diff.clj:134)
;    lambdaisland.deep_diff.diff_test$fn__16191$fn__16204.invoke (diff_test.clj:107)
;    lambdaisland.deep_diff.diff_test$fn__16191.invokeStatic (diff_test.clj:33)
;    lambdaisland.deep_diff.diff_test/fn (diff_test.clj:19)

Prettyprinted diff mangled when using advanced compilation in cljs

Thanks for the awesome tool! We are currently using it in an internal web-based integration testing tool. It's working great right up until trying to display a pretty printed version of diffs.

It seems the pretty printer messes up the diff when compiled with advanced optimizations.

Here is a shell session demonstrating the problem:

❯ tree
.
├── deps.edn
└── src
    └── ddiff_bug
        └── core.cljs

3 directories, 2 files
❯ bat deps.edn 
   1   {:paths ["src"]
   2    :deps  { org.clojure/clojurescript {:mvn/version "1.11.132"}
   3            lambdaisland/deep-diff2    {:mvn/version "2.11.216"}}}
❯ bat src/ddiff_bug/core.cljs 
   1   (ns ddiff-bug.core
   2       (:require [lambdaisland.deep-diff2 :as ddiff]))
   3
   4   
   5   (enable-console-print!)
   6   
   7   (defn diff-view
   8     [a b]
   9     (let [diff    (ddiff/diff a b)
  10           diffstr (with-out-str (ddiff/pretty-print diff))]
  11       (println "\nDocument A")
  12       (println a)
  13       (println "\nDocument B")
  14       (println b)
  15       (println "\nDiff")
  16       (println diff)
  17       (println "\nPretty printed diff")
  18       (println diffstr)))
  19   
  20   (defn -main
  21     [& args]
  22     (diff-view {"Hi" {:foo 424128, :bar 22140, :baz 243000}}
  23                {"Ho" {:foo 424128, :bar 22140, :baz 243000}}))
  24   
  25   
  26   (-main)
  27   
❯ clj -M --main cljs.main --target node --output-to main-dev.js --optimizations none --compile ddiff-bug.core

❯ clj -M --main cljs.main --target node --output-to main-prod.js --optimizations advanced --compile ddiff-bug.core

❯ node main-dev.js

Document A
{Hi {:foo 424128, :bar 22140, :baz 243000}}

Document B
{Ho {:foo 424128, :bar 22140, :baz 243000}}

Diff
{#lambdaisland.deep-diff2.diff-impl.Deletion{:- Hi} {:foo 424128, :bar 22140, :baz 243000}, #lambdaisland.deep-diff2.diff-impl.Insertion{:+ Ho} {:foo 424128, :bar 22140, :baz 243000}}

Pretty printed diff
{+"Ho" {:bar 22140, :baz 243000, :foo 424128}, -"Hi" {:bar 22140, :baz 243000, :foo 424128}}

❯ node main-prod.js 

Document A
{Hi {:foo 424128, :bar 22140, :baz 243000}}

Document B
{Ho {:foo 424128, :bar 22140, :baz 243000}}

Diff
{#lambdaisland.deep-diff2.diff-impl.Deletion{:- Hi} {:foo 424128, :bar 22140, :baz 243000}, #lambdaisland.deep-diff2.diff-impl.Insertion{:+ Ho} {:foo 424128, :bar 22140, :baz 243000}}

Pretty printed diff
{#Xo {:+ "Ho"} {:bar 22140, :baz 243000, :foo 424128},
 #Wo {:- "Hi"} {:bar 22140, :baz 243000, :foo 424128}}

It seems minimized names (as seen in the Xo and Wo tags here) are confusing the printer. The two tags seen here are minimized versions of $lambdaisland$deep_diff2$diff_impl$Insertion$$ and $lambdaisland$deep_diff2$diff_impl$Deletion$$

Reflection warnings

on:

[lambdaisland/deep-diff "0.0-25"]

some:

Reflection warning, lambdaisland/deep_diff/diff.clj:103:15 - reference to field getName can't be resolved.
Reflection warning, lambdaisland/deep_diff/diff.clj:146:34 - reference to field getClass can't be resolved.
Reflection warning, lambdaisland/deep_diff/diff.clj:146:24 - reference to field isArray can't be resolved.
Reflection warning, lambdaisland/deep_diff/printer.clj:80:27 - reference to field getName can't be resolved.

Parameterize color schemes

Currently, colors are hard-coded, but there are various situations where you might want to set different colors:

  • To ensure adequate contrast on a different background
  • To ensure readability by people who are colorblind
  • To match your editor or main diff tool's color scheme.

In addition to general usefulness, parameterizing color schemes may address #14 and would be required for lambdaisland/kaocha#403.

  • Allow for multiple built-in schemes: at least dark and light, and possibly a colorblind friendly scheme (if we can't make the dark and light schemes adequately colorblind) or a "classic" scheme if we end up tweaking colors.
  • Allow for customization of the scheme

Cljdoc analysis is failing

Issue

Cljdoc analysis is failing for the much loved deep-diff2

(I'm going to assume you care because you have a cljdoc badge on your readme 🙂)

Symptom

Analysis job shows (logs aren't saved forever so I'll paste too):

2022-05-16 06:36:27,679 ERROR cljdoc-analyzer.runner - STDERR
 {:clojure.main/message
 "Execution error (FileNotFoundException) at lambdaisland.deep-diff2.diff-test/eval4071$loading (diff_test.cljc:1).\nCould not locate clojure/test/check__init.class, clojure/test/check.clj or clojure/test/check.cljc on classpath.\n",
 :clojure.main/triage
 {:clojure.error/class java.io.FileNotFoundException,
  :clojure.error/line 1,
  :clojure.error/cause
  "Could not locate clojure/test/check__init.class, clojure/test/check.clj or clojure/test/check.cljc on classpath.",
  :clojure.error/symbol
  lambdaisland.deep-diff2.diff-test/eval4071$loading,
  :clojure.error/source "diff_test.cljc",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.ExceptionInfo,
    :message
    "Could not generate Clojure documentation for lambdaisland.deep-diff2.diff-test",
    :data {},
    :at
    [cljdoc_analyzer.metagetta.utils$default_exception_handler
     invokeStatic
     "utils.clj"
     118]}
   {:type clojure.lang.Compiler$CompilerException,
    :message
    "Syntax error compiling at (lambdaisland/deep_diff2/diff_test.cljc:1:1).",
    :data
    {:clojure.error/phase :compile-syntax-check,
     :clojure.error/line 1,
     :clojure.error/column 1,
     :clojure.error/source "lambdaisland/deep_diff2/diff_test.cljc"},
    :at [clojure.lang.Compiler load "Compiler.java" 7648]}
   {:type java.io.FileNotFoundException,
    :message
    "Could not locate clojure/test/check__init.class, clojure/test/check.clj or clojure/test/check.cljc on classpath.",
    :at [clojure.lang.RT load "RT.java" 462]}],
  :trace
  [[clojure.lang.RT load "RT.java" 462]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6839 invoke "core.clj" 6126]
   [clojure.core$load invokeStatic "core.clj" 6125]
   [clojure.core$load doInvoke "core.clj" 6109]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$load_one invokeStatic "core.clj" 5908]
   [clojure.core$load_one invoke "core.clj" 5903]
   [clojure.core$load_lib$fn__6780 invoke "core.clj" 5948]
   [clojure.core$load_lib invokeStatic "core.clj" 5947]
   [clojure.core$load_lib doInvoke "core.clj" 5928]
   [clojure.lang.RestFn applyTo "RestFn.java" 142]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.core$load_libs invokeStatic "core.clj" 5985]
   [clojure.core$load_libs doInvoke "core.clj" 5969]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.core$require invokeStatic "core.clj" 6007]
   [clojure.core$require doInvoke "core.clj" 6007]
   [clojure.lang.RestFn invoke "RestFn.java" 512]
   [lambdaisland.deep_diff2.diff_test$eval4071$loading__6721__auto____4072
    invoke
    "diff_test.cljc"
    1]
   [lambdaisland.deep_diff2.diff_test$eval4071
    invokeStatic
    "diff_test.cljc"
    1]
   [lambdaisland.deep_diff2.diff_test$eval4071
    invoke
    "diff_test.cljc"
    1]
   [clojure.lang.Compiler eval "Compiler.java" 7177]
   [clojure.lang.Compiler eval "Compiler.java" 7166]
   [clojure.lang.Compiler load "Compiler.java" 7636]
   [clojure.lang.RT loadResourceScript "RT.java" 381]
   [clojure.lang.RT loadResourceScript "RT.java" 372]
   [clojure.lang.RT load "RT.java" 459]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6839 invoke "core.clj" 6126]
   [clojure.core$load invokeStatic "core.clj" 6125]
   [clojure.core$load doInvoke "core.clj" 6109]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$load_one invokeStatic "core.clj" 5908]
   [clojure.core$load_one invoke "core.clj" 5903]
   [clojure.core$load_lib$fn__6780 invoke "core.clj" 5948]
   [clojure.core$load_lib invokeStatic "core.clj" 5947]
   [clojure.core$load_lib doInvoke "core.clj" 5928]
   [clojure.lang.RestFn applyTo "RestFn.java" 142]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.core$load_libs invokeStatic "core.clj" 5985]
   [clojure.core$load_libs doInvoke "core.clj" 5969]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.core$require invokeStatic "core.clj" 6007]
   [clojure.core$require doInvoke "core.clj" 6007]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [cljdoc_analyzer.metagetta.clojure$read_ns$fn__1615
    invoke
    "clojure.clj"
    101]
   [cljdoc_analyzer.metagetta.clojure$read_ns
    invokeStatic
    "clojure.clj"
    100]
   [cljdoc_analyzer.metagetta.clojure$read_ns invoke "clojure.clj" 95]
   [cljdoc_analyzer.metagetta.clojure$read_namespaces$fn__1628
    invoke
    "clojure.clj"
    167]
   [clojure.core$map$fn__5866 invoke "core.clj" 2755]
   [clojure.lang.LazySeq sval "LazySeq.java" 42]
   [clojure.lang.LazySeq seq "LazySeq.java" 51]
   [clojure.lang.Cons next "Cons.java" 39]
   [clojure.lang.RT boundedLength "RT.java" 1792]
   [clojure.lang.RestFn applyTo "RestFn.java" 130]
   [clojure.core$apply invokeStatic "core.clj" 665]
   [clojure.core$mapcat invokeStatic "core.clj" 2783]
   [clojure.core$mapcat doInvoke "core.clj" 2783]
   [clojure.lang.RestFn invoke "RestFn.java" 423]
   [cljdoc_analyzer.metagetta.clojure$read_namespaces
    invokeStatic
    "clojure.clj"
    167]
   [cljdoc_analyzer.metagetta.clojure$read_namespaces
    invoke
    "clojure.clj"
    131]
   [cljdoc_analyzer.metagetta.main$read_namespaces
    invokeStatic
    "main.clj"
    72]
   [cljdoc_analyzer.metagetta.main$read_namespaces
    invoke
    "main.clj"
    68]
   [cljdoc_analyzer.metagetta.main$get_metadata$fn__1786
    invoke
    "main.clj"
    103]
   [clojure.core$mapv$fn__8445 invoke "core.clj" 6912]
   [clojure.lang.ArraySeq reduce "ArraySeq.java" 111]
   [clojure.core$reduce invokeStatic "core.clj" 6827]
   [clojure.core$mapv invokeStatic "core.clj" 6903]
   [clojure.core$mapv invoke "core.clj" 6903]
   [cljdoc_analyzer.metagetta.main$get_metadata
    invokeStatic
    "main.clj"
    100]
   [cljdoc_analyzer.metagetta.main$get_metadata invoke "main.clj" 86]
   [cljdoc_analyzer.metagetta.main$_main invokeStatic "main.clj" 134]
   [cljdoc_analyzer.metagetta.main$_main invoke "main.clj" 109]
   [clojure.lang.AFn applyToHelper "AFn.java" 154]
   [clojure.lang.AFn applyTo "AFn.java" 144]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.core$apply invokeStatic "core.clj" 665]
   [clojure.main$main_opt invokeStatic "main.clj" 514]
   [clojure.main$main_opt invoke "main.clj" 510]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause
  "Could not locate clojure/test/check__init.class, clojure/test/check.clj or clojure/test/check.cljc on classpath."}}

Execution error (FileNotFoundException) at lambdaisland.deep-diff2.diff-test/eval4071$loading (diff_test.cljc:1).
Could not locate clojure/test/check__init.class, clojure/test/check.clj or clojure/test/check.cljc on classpath.

Diagnosis

If I list the jar contents:

$ jar tf ~/.m2/repository/lambdaisland/deep-diff2/2.2.124/deep-diff2-2.2.124.jar

META-INF/MANIFEST.MF
META-INF/
lambdaisland/
lambdaisland/deep_diff2/
lambdaisland/deep_diff2/puget/
lambdaisland/deep_diff2/puget/color/
META-INF/maven/
META-INF/maven/lambdaisland/
META-INF/maven/lambdaisland/deep-diff2/
lambdaisland/deep_diff2_test.cljc
lambdaisland/deep_diff2/printer_impl.cljc
lambdaisland/deep_diff2/printer_test.cljc
lambdaisland/deep_diff2/diff_impl.cljc
lambdaisland/deep_diff2/puget_test.cljc
lambdaisland/deep_diff2/puget/dispatch.cljc
lambdaisland/deep_diff2/puget/color/ansi.cljc
lambdaisland/deep_diff2/puget/color/html.cljc
lambdaisland/deep_diff2/puget/printer.cljc
lambdaisland/deep_diff2/puget/color.cljc
lambdaisland/deep_diff2/diff_test.cljc
lambdaisland/deep_diff2.cljc
META-INF/maven/lambdaisland/deep-diff2/pom.xml
META-INF/maven/lambdaisland/deep-diff2/pom.properties

we can see all test sources are included (ex. deep_diff2_test.cljc) - most likely not on purpose.

When cljdoc tries to analyze the jar, the test libs the test sources depend on aren’t included as deps for the jar therefore they won’t be found and… then analysis fails.

I'm not sure how the release jar is built, but the pom.xml currently includes the test dir which might be the causing those test sources to be included.

Consider tweaking colors to draw eye to differences

Thanks so much for the kaocha, I am really enjoying using it!

Some of my integration tests include the comparison of largish data structures. It can take my eyeballs a while to find the diff between expected and actual in these data structures.

I do sometimes use the search feature in my terminal to search for + and - and that can help and I was thinking focusing only on the differences, as #13 suggests, would help.

But I am also thinking perhaps a different choice of colors might help. I like the choice of colors chosen for differences, but I find that the red can take a while to spot because it is already used in non-diff syntax highlighting.

My initial thoughts are:

  1. change the red to something else in default syntax highlighting, maybe blue.
  2. take advantage of background colors to really draw the eye to changes. We might even be able to keep the normal syntax highlighting foreground colors with this scheme.

I am happy to give some color schemes a whirl for review, then a PR with changes, if there is interest in this idea.

Print string-based diff if there's a mismatch of two strings

I sometimes need to write tests for functions that output strings, in particular multi-line strings. Right now deep-diff2 will just say -<expected-string> +<actual-string>, which isn't very useful for longer strings and strings containing newlines.
Right now I try to work around this by splitting the strings on newlines and letting deep-diff2 diff the arrays, which at least will highlight the lines with changes, but it's not great.

I think it'd be useful to print a string-based diff, like git-diff would, if both expressions for a mismatch are string.

For no newlines they could still be printed in place, but for newlines it likely would make sense to introduce a linebreak and then show the diff without worrying about indentation.

I found deep-diff2.printer-impl/print-mismatch, and (and (string? (:- expr)) (string? (:+ expr))) seems like an easy enough condition there, so I'm playing around with it.

Would you consider this for deep-diff2, or do you see problems with the approach? Perhaps it could be an option to not break any existing consumers.

Closure compilation failed

When attempting to do a minified build the following error is thrown

Closure compilation failed with 1 errors
--- lambdaisland/deep_diff2/puget/color/html.cljc:42
Cannot convert ECMASCRIPT_2018 feature "RegExp Lookbehind" to targeted output language.

This seems to be the culprit:
image

Round-trip error

From property-based tests running on CI:

[:fail] expected: {:result true}. actual: {:shrunk {:total-nodes-visited 122, :depth 17, :pass? false, :result false, :result-data nil, :time-shrinking-ms 43, :smallest [{{##NaN 0} 0} {}]}, :failed-after-ms 266, :num-tests 100, :seed 1656705885608, :fail [{{S8.di092E8-!ubS -31168388659169371747161610336919826167194645301356227082938375168857766470903802989439005434804565N, ##NaN H5U} #:!9BIU60.-{I:T 5/9}} {}], :result false, :result-data nil, :failing-size 99, :pass? false, :test-var "round-trip-diff"}

FAIL in lambdaisland.deep-diff2.diff-test/round-trip-diff (diff_test.cljc:211)
expected: {:result true}
  actual: {:shrunk {:total-nodes-visited 122, :depth 17, :pass? false, :result false, :result-data nil, :time-shrinking-ms 43, :smallest [{{##NaN 0} 0} {}]}, :failed-after-ms 266, :num-tests 100, :seed 1656705885608, :fail [{{S8.di092E8-!ubS -31168388659169371747161610336919826167194645301356227082938375168857766470903802989439005434804565N, ##NaN H5U} #:!9BIU60.-{I:T 5/9}} {}], :result false, :result-data nil, :failing-size 99, :pass? false, :test-var "round-trip-diff"}

Looks like the case to potentially add to our unit-tests is [{{##NaN 0} 0} {}]

Fall back to the system printer

See comments on #12 , apparently we can make puget fall back to the system printer. I think it makes sense to do that unless specified otherwise.

Sadly this would constiute a breaking change, but without it custom types don't print in any meaningful way, and if people have registered their own print handlers they will still take precedence, so I think we can still consider this.

`#uuid` and `#inst` printing

Hi there, I noticed that UUIDs and java.util.Dates print as objects rather than as their tagged literal form.

(ddiff/pretty-print (ddiff/diff #inst "2019-04-01T14:38:55.998-00:00"
                                #inst "2019-04-01T14:39:11.732-00:00"))

prints as

-#<java.util.Date@5c6429fc Mon Apr 01 10:38:55 EDT 2019>
+#<java.util.Date@69ad72db Mon Apr 01 10:39:11 EDT 2019>

and

(ddiff/pretty-print (ddiff/diff #uuid "70fa1988-a1d0-42f3-9c81-8d60ac3e999a"
                                #uuid "0f2386f7-0587-46c7-8211-b3919f07fca8"))

prints as

-#<java.util.UUID@68e1706a 70fa1988-a1d0-42f3-9c81-8d60ac3e999a>
+#<java.util.UUID@5a4ce851 0f2386f7-0587-46c7-8211-b3919f07fca8>

would you be interested in a patch that includes printers for these?

How to skip printing values same in both structures?

How to skip printing values same in both structures? I am sorry, I am overwhelmed by fipp and puget and haven't managed to understand how to only print +/-/mismatches. Is there a way to override print-other to be a noop or something? Sorry and thank you!

Improve set handling

We currently handle sets with the same logic that we use for sequential collection, but sets have no ordering, leading to issues. For instance, even when comparing a set with itself ddiff may imagine there are differences.

(let [s #{false 5}]
  (ddiff/diff s s))
;; => #{{:- 5} false {:+ 5}}

I think we want to add a separate diff-set function, which loops over the keys of set A, marking any element that's absent in set B as a deletion, and then runs over any remaining elemnts in set B, marking them as additions. We don't mark any as replacements.

ddiff/diff hangs on nil

Hi :) I tracked down a weird hanging issue to deep-diff/diff. A minimal repro case:

❯ clj -Sdeps '{:deps {lambdaisland/deep-diff {:mvn/version "0.0-25"}}}'
Clojure 1.10.0
user=> (require '[lambdaisland.deep-diff :as ddiff])

user=> user=> (ddiff/diff {:foo :bar} {:foo :bar})
{:foo :bar}

user=> (ddiff/diff {:foo :bar} {nil :wat :foo :bar})
{:foo :bar, #lambdaisland.deep_diff.diff.Insertion{:+ nil} :wat}

user=> (ddiff/diff {nil :wat :foo :bar} {:foo :bar})
;;;; Here we just hang; probably falling into an infinite loop/recur?

ddiff not terminating

Thanks for providing deep-diff, it's a super usable tool.

We do think we've found a bug in version 2.0.108 though. We we're able to minimize the test case to this:

(require
  '[lambdaisland.deep-diff2 :as ddiff])

(def actual [nil])
(def expected [{}])

;; works
(ddiff/diff actual expected)

;; does not terminate
(ddiff/diff expected actual)

So the last line seems to never terminate. Any ideas what might cause this?

Fails with records with deleted keys

Similar to #3, but applies to records with deletions. I believe this applies to both deep-diff and deep-diff2.

This arises from invoking the record as a function in diff-map which works for maps, but not records since they don't automatically implement IFn.

I can put together a minimal repro and a PR in the next couple days.

lambdaisland.deep-diff.diff> (defrecord MyRecord [])
lambdaisland.deep_diff.diff.MyRecord
lambdaisland.deep-diff.diff> (diff (map->MyRecord {:foo "bar"}) (map->MyRecord {}))
Execution error (ClassCastException) at lambdaisland.deep-diff.diff/diff-map$fn (diff.clj:116).
class lambdaisland.deep_diff.diff.MyRecord cannot be cast to class clojure.lang.IFn (lambdaisland.deep_diff.diff.MyRecord is in unnamed module of loader clojure.lang.DynamicClassLoader @11f4ce76; clojure.lang.IFn is in unnamed module of loader 'app')

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.