diff --git a/.VERSION_PREFIX b/.VERSION_PREFIX
new file mode 100644
index 0000000..cb94c17
--- /dev/null
+++ b/.VERSION_PREFIX
@@ -0,0 +1 @@
+2.13
\ No newline at end of file
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 5bc9eda..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-version: 2.1
-
-orbs:
- kaocha: lambdaisland/kaocha@dev:first
- clojure: lambdaisland/clojure@dev:first
-
-commands:
- checkout_and_run:
- parameters:
- clojure_version:
- type: string
- steps:
- - checkout
- - clojure/with_cache:
- cache_version: << parameters.clojure_version >>
- steps:
- - run: clojure -e '(println (System/getProperty "java.runtime.name") (System/getProperty "java.runtime.version") "\nClojure" (clojure-version))'
- - kaocha/execute:
- args: "unit --reporter documentation --plugin cloverage --codecov"
- clojure_version: << parameters.clojure_version >>
- - kaocha/upload_codecov:
- flags: unit
-
-jobs:
- java-11-clojure-1_10:
- executor: clojure/openjdk11
- steps: [{checkout_and_run: {clojure_version: "1.10.0"}}]
-
- java-9-clojure-1_9:
- executor: clojure/openjdk9
- steps: [{checkout_and_run: {clojure_version: "1.9.0"}}]
-
- java-8-clojure-1_10:
- executor: clojure/openjdk8
- steps: [{checkout_and_run: {clojure_version: "1.10.0"}}]
-
- java-8-clojure-1_9:
- executor: clojure/openjdk8
- steps: [{checkout_and_run: {clojure_version: "1.9.0"}}]
-
-workflows:
- kaocha_test:
- jobs:
- - java-11-clojure-1_10
- - java-9-clojure-1_9
- - java-8-clojure-1_10
- - java-8-clojure-1_9
diff --git a/.dir-locals.el b/.dir-locals.el
deleted file mode 100644
index c9629d5..0000000
--- a/.dir-locals.el
+++ /dev/null
@@ -1 +0,0 @@
-((nil . ((cider-clojure-cli-global-options . "-A:dev:test"))))
diff --git a/.github/workflows/add_to_project_board.yml b/.github/workflows/add_to_project_board.yml
new file mode 100644
index 0000000..39f7c02
--- /dev/null
+++ b/.github/workflows/add_to_project_board.yml
@@ -0,0 +1,9 @@
+name: Add new pr or issue to project board
+
+on: [issues]
+
+jobs:
+ add-to-project:
+ uses: lambdaisland/open-source/.github/workflows/add-to-project-board.yml@main
+ secrets: inherit
+
diff --git a/.github/workflows/bb.yml b/.github/workflows/bb.yml
new file mode 100644
index 0000000..25317de
--- /dev/null
+++ b/.github/workflows/bb.yml
@@ -0,0 +1,52 @@
+name: bb
+
+on: [push, pull_request]
+
+jobs:
+
+ clojure:
+
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ # It is important to install java before installing clojure tools which needs java
+ # exclusions: babashka, clj-kondo and cljstyle
+ - name: Prepare java
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: '11'
+
+ - name: Install clojure tools
+ uses: DeLaGuardo/setup-clojure@10.1
+ with:
+ # Install just one or all simultaneously
+ # The value must indicate a particular version of the tool, or use 'latest'
+ # to always provision the latest version
+ bb: latest
+
+ # Optional step:
+ - name: Cache clojure dependencies
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.m2/repository
+ ~/.gitlibs
+ ~/.deps.clj
+ # List all files containing dependencies:
+ key: cljdeps-${{ hashFiles('deps.edn') }}
+ # key: cljdeps-${{ hashFiles('deps.edn', 'bb.edn') }}
+ # key: cljdeps-${{ hashFiles('project.clj') }}
+ # key: cljdeps-${{ hashFiles('build.boot') }}
+ restore-keys: cljdeps-
+
+ - name: Execute bb tests
+ run: |
+ bb test:bb
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..4ca015e
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,44 @@
+name: Continuous Delivery
+
+on: push
+
+jobs:
+ Kaocha:
+ runs-on: ${{matrix.sys.os}}
+
+ strategy:
+ matrix:
+ sys:
+ # - { os: macos-latest, shell: bash }
+ - { os: ubuntu-latest, shell: bash }
+ # - { os: windows-latest, shell: powershell }
+
+ defaults:
+ run:
+ shell: ${{matrix.sys.shell}}
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: 🔧 Install java
+ uses: actions/setup-java@v1
+ with:
+ java-version: '25'
+
+ - name: 🔧 Install clojure
+ uses: DeLaGuardo/setup-clojure@master
+ with:
+ cli: '1.12.3.1577'
+
+ - name: 🗝 maven cache
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.m2
+ ~/.gitlibs
+ key: ${{ runner.os }}-maven-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: 🧪 Run tests
+ run: bin/kaocha
diff --git a/.gitignore b/.gitignore
index 5ae188f..9faeae1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,22 @@
.nrepl-port
target
repl
-bin
scratch.clj
+/out/
+/checkouts/
+/target/
+/.cljs_node_repl/
+/node_modules/
+pom.xml
+pom.xml.asc
+*.jar
+*.classout
+package.json
+yarn.lock
+.shadow-cljs
+resources/public/ui
+resources/public/
+.store
+package-lock.json
+.cache
+*.iml
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10bb375..05a3f0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,141 @@
## Changed
+# 2.13.231 (2026-04-07 / 8d35b69)
+
+## Changed
+
+- General dependency and tooling version bumps
+
+## Fixed
+
+- Fixed puget printer for futures
+
+## Changed
+
+- Fix `mimimize` when inserting/removing map keys (should preserve associated
+ value)
+- Add `minimise` variant
+
+# 2.12.219 (2025-02-06 / 9e6942a)
+
+## Changed
+
+- [BREAKING] Get smarter about diffing records, instead of simply diffing them
+ as maps. We now only recurse into records if the two compared values are both
+ records of the same type.
+- Bump dependencies: fipp, rrb-vector
+
+# 2.11.216 (2024-02-17 / e77c3bf)
+
+## Added
+
+- Diff / preserve metadata on collections
+
+## Fixed
+
+- Varying key order in maps should produce a consistent diff (#47)
+
+## Changed
+
+# 2.9.202 (2023-06-09 / 35494a0)
+
+## Added
+
+ - Add documentation for using a custom color scheme using custom data printers.
+
+## Fixed
+
+- Simplified internals when diffing maps for improved performance on many datasets. (Thanks [@latacora-paul](https://github.com/latacora-paul)!)
+
+## Changed
+
+# 2.8.190 (2023-03-30 / 34d5e17)
+
+## Added
+
+- Enable print tests in babashka
+- Add a `lambdaisland.deep-diff2/minimize` function, which removes any items
+ that haven't changed from the diff.
+
+## Fixed
+
+## Changed
+
+# 2.7.169 (2022-11-25 / 343811e)
+
+## Fixed
+
+- Fix printing of mismatch/deletion/insertion on Babashka
+
+# 2.6.166 (2022-11-25 / 06fec7e)
+
+## Fixed
+
+- Babashka compatibility
+
+# 2.5.151 (2022-11-21 / 92232a1)
+
+## Changed
+
+- [breaking] Fall back to the system printer when no deep-diff2 specific print handler is available for a given type. See the README for details.
+
+# 2.4.138 (2022-09-01 / 6196130)
+
+## Fixed
+
+- Fix issue (Fails with records with deleted keys)[https://github.com/lambdaisland/deep-diff2/issues/29]
+
+# 2.3.127 (2022-07-01 / a8186a5)
+
+## Fixed
+* Remove "test" directory from the main paths in `deps.edn` to fix Cljdoc builds. This change also makes the artifact (very slightly) smaller, reducing the JAR's size by 3KB, or about 15 percent.
+
+# 2.2.124 (2022-05-16 / 5a94bec)
+
+## Fixed
+
+- Bump clj-diff, to bring back compatibility with earlier java versions
+
+# 2.1.121 (2022-05-13 / bb0dd63)
+
+## Fixed
+
+- Bump clj-diff, which fixes an issue where the diffing would not terminate in
+ specific cases
+
+## Changed
+
+- Bump all dependencies to the latest version
+
+# 2.0.108 (2020-08-19 / e006fc5)
+
+## Changed
+
+- Switch to using lambdaisland/clj-diff, a fork of an upstream fork
+
+# 2.0.0-93 (2020-04-20 / 6ff9209)
+
+## Fixed
+
+- Fix unsupported cljs lookbehind regex in code inherited from Puget (Thanks [@JarrodCTaylor](https://github.com/JarrodCTaylor)!)
+
+# 2.0.0-84 (2020-04-01 / 9c2af83)
+
+## Fixed
+
+- Typos in deep_diff2.cljs resulting from naming changes
+
+# 2.0.0-72 (2020-03-27 / 2862182)
+
+## Added
+
+- Added support for ClojureScript (ported to CLJC)
+
+## Changed
+
+- Changed namespace and artifact (jar) names to include a "2" suffix, because of breaking changes.
+
# 0.0-47 (2019-04-11 / 27cf55c)
## Added
diff --git a/README.md b/README.md
index ee7b7de..d61b196 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,73 @@
-# lambdaisland/deep-diff
+# lambdaisland/deep-diff2
-[](https://circleci.com/gh/lambdaisland/deep-diff) [](https://cljdoc.org/d/lambdaisland/deep-diff) [](https://clojars.org/lambdaisland/deep-diff) [](https://codecov.io/gh/lambdaisland/deep-diff)
+[](https://github.com/lambdaisland/deep-diff2/actions/workflows/main.yml) [](https://cljdoc.org/d/lambdaisland/deep-diff2) [](https://clojars.org/lambdaisland/deep-diff2)
-Recursively compare Clojure data structures, and produce a colorized diff of the result.
+Recursively compare Clojure or ClojureScript data structures, and produce a colorized diff of the result.

-## Install
+Deep-diff2 is foremost intended for creating visual diffs for human consumption,
+if you want to programatically diff/patch Clojure data structures then
+[Editscript](https://github.com/juji-io/editscript) may be a better fit, see
+[this write-up by Huahai Yang](https://juji.io/blog/comparing-clojure-diff-libraries/).
-[](https://clojars.org/lambdaisland/deep-diff)
+
+## Lambda Island Open Source
+
+Thank you! deep-diff2 is made possible thanks to our generous backers. [Become a
+backer on OpenCollective](https://opencollective.com/lambda-island) 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](https://gaiwan.co).
+
+Pay it forward by [becoming a backer on our OpenCollective](http://opencollective.com/lambda-island),
+so that we continue to enjoy a thriving Clojure ecosystem.
+
+You can find an overview of all our different projects at [lambdaisland/open-source](https://github.com/lambdaisland/open-source).
+
+
+
+
+
+
+## Installation
+
+deps.edn
+
+```
+lambdaisland/deep-diff2 {:mvn/version "2.13.231"}
+```
+
+project.clj
+
+```
+[lambdaisland/deep-diff2 "2.13.231"]
+```
## Use
-- [API docs](https://cljdoc.org/d/lambdaisland/deep-diff/CURRENT)
+- [API docs](https://cljdoc.org/d/lambdaisland/deep-diff2/CURRENT)
``` clojure
-(require '[lambdaisland.deep-diff :as ddiff])
+(require '[lambdaisland.deep-diff2 :as ddiff])
(ddiff/pretty-print (ddiff/diff {:a 1 :b 2} {:a 1 :c 3}))
```
### Diffing
-`lambdaisland.deep-diff/diff` takes two arguments and returns a "diff", a data
+`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.
@@ -35,7 +78,7 @@ are records with `-` and `+` fields.
### Printing
-You can pass this diff to `lambdaisland.deep-diff/pretty-print`. This function
+You can pass this diff to `lambdaisland.deep-diff2/pretty-print`. This function
uses [Puget](https://github.com/greglook/puget) and
[Fipp](https://github.com/brandonbloom/fipp) to format the diff and print the
result to standard out.
@@ -49,12 +92,187 @@ For fine grained control you can create a custom Puget printer, and supply it to
(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-diff.printer/format-doc`, `lambdaisland.deep-diff.printer/print-doc`.
+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`.
-You can register print handlers for new types using
-`lambdaisland.deep-diff.printer/register-print-handler!`, or by passing and
+### 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.
+
+```clj
+(-> (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](https://github.com/henryw374/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](https://github.com/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](repl_sessions/custom_type.clj) for the full
+code and results.
+
+```clj
+(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 11]`, the color code it outputs will change from `\u001b[31m` to `\u001b[48;5;11m`
+
+```
+user=> (use 'lambdaisland.deep-diff2)
+nil
+user=> (def color-printer (printer {:color-scheme {:lambdaisland.deep-diff2/deletion [:bg-256 5 11]}}))
+#'user/color-printer
+user=> (pretty-print (diff {:a 1} {:b 2}) color-printer)
+{+:b 2, -:a 1}
+```
+That results in the following highlighting:
+
+
+### 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](https://github.com/henryw374/time-literals) library
+(perhaps indirectly by pulling in [tick](https://github.com/juxt/tick). In that
+case these should _just work_.
+
+```clj
+(ddiff/diff #inst "2019-04-09T14:57:46.128-00:00"
+ #inst "2019-04-10T14:57:46.128-00:00")
+```
+or
+```clj
+(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](https://nextjournal.com/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](https://github.com/lambdaisland/open-source#lambdaisland-flavored-versioning) for more info.
+
+
## Credits
This library builds upon
@@ -72,8 +290,10 @@ This library was originally developed as part of the
Another library that implements a form of data structure diffing is [editscript](https://github.com/juji-io/editscript).
+
## License
-Copyright © 2018 Arne Brasseur
+Copyright © 2018-2025 Arne Brasseur and contributors
Available under the terms of the Eclipse Public License 1.0, see LICENSE.txt
+
\ No newline at end of file
diff --git a/bb.edn b/bb.edn
new file mode 100644
index 0000000..17f86ff
--- /dev/null
+++ b/bb.edn
@@ -0,0 +1,10 @@
+{:deps
+ {lambdaisland/deep-diff2 {:local/root "."}
+ lambdaisland/open-source {:git/url "https://github.com/lambdaisland/open-source"
+ :git/sha "34ce20d7b2af747227c345b392fe92cb5f4d3cda"}}
+ :tasks
+ {test:bb {:doc "Run babashka tests with custom runner"
+ :extra-paths ["src" "test"]
+ :extra-deps {current/project {:local/root "."}
+ org.clojure/test.check {:mvn/version "1.1.1"}}
+ :task (exec 'lambdaisland.deep-diff2.runner/run-tests)}}}
diff --git a/bin/kaocha b/bin/kaocha
new file mode 100755
index 0000000..8ddb6f8
--- /dev/null
+++ b/bin/kaocha
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+[[ -d "node_modules/ws" ]] || npm install ws
+
+exec clojure -A:dev:test -M -m kaocha.runner "$@"
diff --git a/bin/proj b/bin/proj
new file mode 100755
index 0000000..b0582aa
--- /dev/null
+++ b/bin/proj
@@ -0,0 +1,14 @@
+#!/usr/bin/env bb
+
+(ns proj (:require [lioss.main :as lioss]))
+
+(lioss/main
+ {:license :epl
+ :group-id "lambdaisland"
+ :inception-year 2018
+ :description "Recursively compare Clojure or ClojureScript data structures, and produce a colorized diff of the result."})
+
+
+;; Local Variables:
+;; mode:clojure
+;; End:
diff --git a/color-scheme.png b/color-scheme.png
new file mode 100644
index 0000000..ebaf500
Binary files /dev/null and b/color-scheme.png differ
diff --git a/deps.edn b/deps.edn
index 55f5500..b8c7832 100644
--- a/deps.edn
+++ b/deps.edn
@@ -1,15 +1,24 @@
-{:paths ["src" "test"]
- :deps {org.clojure/clojure {:mvn/version "1.10.0"}
- mvxcvi/puget {:mvn/version "1.1.2"}
- fipp {:mvn/version "0.6.17"}
- org.clojure/core.rrb-vector {:mvn/version "0.0.14"}
- tech.droit/clj-diff {:mvn/version "1.0.1"}
- mvxcvi/arrangement {:mvn/version "1.2.0"}}
-
- :aliases
- {:dev
- {}
-
- :test
- {:extra-deps {lambdaisland/kaocha {:mvn/version "0.0-413"}
- org.clojure/test.check {:mvn/version "0.10.0-alpha4"}}}}}
+{:paths ["resources" "src"]
+ :deps {fipp/fipp {:mvn/version "0.6.29"}
+ org.clojure/core.rrb-vector {:mvn/version "0.2.1"}
+ lambdaisland/clj-diff {:mvn/version "1.4.78"}
+ mvxcvi/arrangement {:mvn/version "2.1.0"}}
+
+ :aliases {:cljs
+ {:extra-deps {org.clojure/clojurescript {:mvn/version "1.12.134"}}}
+
+ :dev
+ {}
+
+ :chui
+ {:extra-deps {lambdaisland/chui {:local/root "../chui"}
+ thheller/shadow-cljs {:mvn/version "3.3.8"}
+ garden/garden {:mvn/version "1.3.10"}}
+ :extra-paths ["../chui/resources" "../chui/dev"]}
+
+ :test
+ {:extra-paths ["test"]
+ :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}
+ com.lambdaisland/kaocha-cljs {:mvn/version "1.9.181"}
+ org.clojure/clojurescript {:mvn/version "1.12.134"}
+ org.clojure/test.check {:mvn/version "1.1.3"}}}}}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ac9cdbb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "chui",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "ws": "^8.20.0"
+ }
+}
diff --git a/pom.xml b/pom.xml
index 27d3bef..b662a33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,17 +1,20 @@
-
+
4.0.0
lambdaisland
- deep-diff
- 0.0-47
- deep-diff
- Recursively diff Clojure data structures.
- https://github.com/lambdaisland/deep-diff
+ deep-diff2
+ 2.13.231
+ deep-diff2
+ Recursively compare Clojure or ClojureScript data structures, and produce a colorized diff of the result.
+ https://github.com/lambdaisland/deep-diff2
2018
Lambda Island
https://lambdaisland.com
+
+ UTF-8
+
Eclipse Public License 1.0
@@ -19,63 +22,79 @@
- https://github.com/lambdaisland/deep-diff
- scm:git:git://github.com/lambdaisland/deep-diff.git
- scm:git:ssh://git@github.com/lambdaisland/deep-diff.git
- eb1ce3037540b72635e32c3dfc0fbe588d2e195f
+ https://github.com/lambdaisland/deep-diff2
+ scm:git:git://github.com/lambdaisland/deep-diff2.git
+ scm:git:ssh://git@github.com/lambdaisland/deep-diff2.git
+ 25bca3d85b2b4a66083de7ff48920af4c92a99fd
-
- org.clojure
- clojure
- 1.10.0
-
-
- mvxcvi
- puget
- 1.1.2
-
fipp
fipp
- 0.6.17
+ 0.6.29
org.clojure
core.rrb-vector
- 0.0.14
+ 0.2.1
- tech.droit
+ lambdaisland
clj-diff
- 1.0.1
+ 1.4.78
mvxcvi
arrangement
- 1.2.0
+ 2.1.0
- src
+ resources
+
+ resources
+
src
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+
+
org.apache.maven.plugins
maven-jar-plugin
- 2.4
+ 3.2.0
- eb1ce3037540b72635e32c3dfc0fbe588d2e195f
+ 25bca3d85b2b4a66083de7ff48920af4c92a99fd
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.6
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
@@ -91,4 +110,4 @@
https://clojars.org/repo
-
+
\ No newline at end of file
diff --git a/repl_sessions/custom_types.clj b/repl_sessions/custom_types.clj
new file mode 100644
index 0000000..3830ad2
--- /dev/null
+++ b/repl_sessions/custom_types.clj
@@ -0,0 +1,59 @@
+(ns repl-sessions.custom-types
+ (:require [lambdaisland.deep-diff2 :as ddiff]))
+
+;; Demonstration of how to set up print handlers for custom types.
+
+;; New custom type for reprsenting "degrees" amount, like Celcius or Fahrenheit.
+;; Using `deftype` and not `defrecord` because we handle `defrecord` instances
+;; already as if they are maps.
+
+(deftype Degrees [amount unit]
+ Object
+ (equals [this that] ; needed for proper diffing
+ (and (instance? Degrees that)
+ (= amount (.-amount that))
+ (= unit (.-unit that)))))
+
+;; No custom handler yet, defaults to Object#toString rendering:
+
+(pr-str (->Degrees 10 "C"))
+;; => "#object[custom_types.Degrees 0x75634af8 \"custom_types.Degrees@75634af8\"]"
+
+;; And so does ddiff
+(ddiff/pretty-print (ddiff/diff [(->Degrees 20 \C)]
+ [(->Degrees 80 \F)]))
+;; =>
+;; [-#object[custom_types.Degrees 0x660a955 "custom_types.Degrees@660a955"]
+;; +#object[custom_types.Degrees 0x40241557 "custom_types.Degrees@40241557"]]
+
+
+;; Now we set up a custom handler
+
+(defmethod print-method Degrees [degrees out]
+ (.write out (str (.-amount degrees) "°" (.-unit degrees))))
+
+
+(pr-str (->Degrees 10 "C"))
+;; => "10°C"
+
+(ddiff/pretty-print (ddiff/diff [(->Degrees 20 \C)]
+ [(->Degrees 20 \C)
+ (->Degrees 80 \F)]))
+;; => [-20°C +80°F]
+
+;; Add Puget handler, to tap into Puget's rich rendering. Will take precedence
+;; over `print-method`.
+
+(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)))]))
+
+(ddiff/pretty-print (->Degrees 20 \C))
+;; => 20°C (printed with specific colors)
+
+(ddiff/pretty-print (->Degrees 20 \C) (ddiff/printer {:color-markup :html-inline}))
+;;=> 20°C
diff --git a/repl_sessions/poke.clj b/repl_sessions/poke.clj
new file mode 100644
index 0000000..11ea252
--- /dev/null
+++ b/repl_sessions/poke.clj
@@ -0,0 +1,62 @@
+(ns repl-sessions.poke
+ (:require [lambdaisland.deep-diff2 :as ddiff]))
+
+(seq #{{:foo 1M} {:bar 2}}) ;; => ({:foo 1M} {:bar 2})
+(seq #{{:foo 1} {:bar 2}}) ;; => ({:bar 2} {:foo 1})
+
+(def d1 {{:foo 1M} {:bar 2}})
+(def d2 {{:foo 1} {:bar 2}})
+(ddiff/pretty-print (ddiff/diff d1 d2))
+;; #{+{:foo 1} -{:foo 1M} {:bar 2}}
+
+(def d1 #{{:foo 1M}})
+(def d2 #{{:foo 1}})
+(ddiff/pretty-print (ddiff/diff d1 d2))
+
+(-> (ddiff/diff {:a "apple" :b "pear"} {:a "apple" :b "banana"})
+ ddiff/minimize
+ ddiff/pretty-print)
+;; {:b -"pear" +"banana"}
+
+;; {:b -2 +3}
+
+[#{1.1197369622161879e-14 1.3019822841584656e-21 0.6875
+ #uuid "a907a7fe-d2eb-482d-b1cc-3acfc12daf55"
+ -30
+ :X/*!1:3
+ :u7*A/p?2IG5d*!Nl
+ :**d7ws
+ "ý"
+ "ÔB*àñS�¬ÚûV¡ç�¯±·á£H�
+ �û?'V$ëY;CL�k-oOV"
+ !U-h_C*A7/x0_n1
+ A-*wn./o_?4w18-!
+ "ìêܼà4�^¤mÐðkt�ê1_ò�· À�4\n@J\"2�9)cd-\t®"
+ y3W-2
+ #uuid "6d507164-f8b9-401d-8c44-d6b0e310c248"
+ "M"
+ :cy7-3
+ :w4/R.-s?9V5
+ #uuid "1bcb00c9-88b9-4eae-9fea-60600dfaefa0"
+ -20
+ #uuid "269ab6f9-f19d-4c9d-a0cb-51150e52e9f7"
+ -235024979
+ :O:m_9.9+A/N+usPa6.HA*G
+ 228944.657438457
+ :x/w?
+ :__+o+sut9!t/?0l
+ "�â��«"
+ false
+ #uuid "b6295f83-8176-47b5-946e-466f74226629"
+ e3zQ!E*5
+ :T5rb
+ :++y:2
+ -7364
+ zG/ex23
+ "¡"
+ -4318364480
+ :D+?2?!/Hrc!jA7z_2
+ :z-I/!8Uq+d?
+ -0.5588235294117647
+ -0.5925925925925926
+ -0.8108108108108109}]
diff --git a/repl_sessions/str_rep.clj b/repl_sessions/str_rep.clj
new file mode 100644
index 0000000..081a98c
--- /dev/null
+++ b/repl_sessions/str_rep.clj
@@ -0,0 +1,34 @@
+(ns str-rep
+ (:require [clojure.string :as str]))
+
+(defn left-pad [s len pad]
+ (concat (repeat (- len (count s)) pad) s))
+
+(defn int->hex [i]
+ (str/upper-case
+ (Integer/toHexString i)))
+
+(defn unicode-rep [char]
+ (apply str "\\u" (left-pad (int->hex (long char)) 4 \0)))
+
+(defn char-rep [char]
+ (cond
+ (= \backspace char)
+ "\\b"
+ (= \tab char)
+ "\\t"
+ (= \newline char)
+ "\\n"
+ (= \formfeed char)
+ "\\f"
+ (= \return char)
+ "\\r"
+ (< (long char) 32)
+ (unicode-rep char)
+ :else
+ (str char)))
+
+(defn str-rep [s]
+ (str "\""
+ (apply str (map char-rep s))
+ "\""))
diff --git a/screenshot.png b/screenshot.png
index e00e67e..668fe08 100644
Binary files a/screenshot.png and b/screenshot.png differ
diff --git a/shadow-cljs.edn b/shadow-cljs.edn
new file mode 100644
index 0000000..85c270b
--- /dev/null
+++ b/shadow-cljs.edn
@@ -0,0 +1,17 @@
+{:deps
+ {:aliases [:dev :chui]}
+
+ :dev-http
+ {8012 "classpath:public"}
+
+ :builds
+ {:main
+ {:target :browser-test
+ :runner-ns lambdaisland.chui.shadowrun
+ :test-dir "resources/public"
+ :asset-path "/ui"
+ :ns-regexp "-test$"
+ :devtools {:repl-pprint true}}}
+
+ :cache-blockers #{lambdaisland.chui.styles
+ lambdaisland.chui.test-data}}
diff --git a/src/lambdaisland/deep_diff/diff.clj b/src/lambdaisland/deep_diff/diff.clj
deleted file mode 100644
index cf70cf2..0000000
--- a/src/lambdaisland/deep_diff/diff.clj
+++ /dev/null
@@ -1,196 +0,0 @@
-(ns lambdaisland.deep-diff.diff
- (:require [clojure.data :as data]
- [clj-diff.core :as seq-diff]))
-
-(declare diff)
-
-(defrecord Mismatch [- +])
-(defrecord Deletion [-])
-(defrecord Insertion [+])
-
-(defprotocol Diff
- (diff-similar [x y]))
-
-;; For property based testing
-(defprotocol Undiff
- (left-undiff [x])
- (right-undiff [x]))
-
-(defn- shift-insertions [ins]
- (reduce (fn [res idx]
- (let [offset (apply + (map count (vals res)))]
- (assoc res (+ idx offset) (get ins idx))))
- {}
- (sort (keys ins))))
-
-(defn- replacements
- "Given a set of deletion indexes and a map of insertion index to value sequence,
- match up deletions and insertions into replacements, returning a map of
- replacements, a set of deletions, and a map of insertions."
- [[del ins]]
- ;; Loop over deletions, if they match up with an insertion, turn them into a
- ;; replacement. This could be a reduce over (sort del) tbh but it's already a
- ;; lot more readable than the first version.
- (loop [rep {}
- del del
- del-rest (sort del)
- ins ins]
- (if-let [d (first del-rest)]
- (if-let [i (seq (get ins d))] ;; matching insertion
- (recur (assoc rep d (first i))
- (disj del d)
- (next del-rest)
- (update ins d next))
-
- (if-let [i (seq (get ins (dec d)))]
- (recur (assoc rep d (first i))
- (disj del d)
- (next del-rest)
- (-> ins
- (dissoc (dec d))
- (assoc d (seq (concat (next i)
- (get ins d))))))
- (recur rep
- del
- (next del-rest)
- ins)))
- [rep del (into {}
- (remove (comp nil? val))
- (shift-insertions ins))])))
-
-(defn- del+ins
- "Wrapper around clj-diff that returns deletions and insertions as a set and map
- respectively."
- [exp act]
- (let [{del :- ins :+} (seq-diff/diff exp act)]
- [(into #{} del)
- (into {} (map (fn [[k & vs]] [k (vec vs)])) ins)]))
-
-(defn- diff-seq-replacements [replacements s]
- (map-indexed
- (fn [idx v]
- (if (contains? replacements idx)
- (diff v (get replacements idx))
- v))
- s))
-
-(defn- diff-seq-deletions [del s]
- (map
- (fn [v idx]
- (if (contains? del idx)
- (->Deletion v)
- v))
- s
- (range)))
-
-(defn- diff-seq-insertions [ins s]
- (reduce (fn [res [idx vs]]
- (concat (take (inc idx) res) (map ->Insertion vs) (drop (inc idx) res)))
- s
- ins))
-
-(defn- diff-seq [exp act]
- (let [[rep del ins] (replacements (del+ins exp act))]
- (->> exp
- (diff-seq-replacements rep)
- (diff-seq-deletions del)
- (diff-seq-insertions ins)
- (into []))))
-
-(defn- val-type [val]
- (let [t (type val)]
- (if (class? t)
- (symbol (.getName ^Class t))
- t)))
-
-(defn- diff-map [exp act]
- (first
- (let [exp-ks (keys exp)
- act-ks (concat (filter (set (keys act)) exp-ks)
- (remove (set exp-ks) (keys act)))
- [del ins] (del+ins exp-ks act-ks)]
- (reduce
- (fn [[m idx] k]
- [(cond-> m
- (contains? del idx)
- (assoc (->Deletion k) (exp k))
-
- (not (contains? del idx))
- (assoc k (diff (get exp k) (get act k)))
-
- (contains? ins idx)
- (into (map (juxt ->Insertion (partial get act))) (get ins idx)))
- (inc idx)])
- [(if (contains? ins -1)
- (into {} (map (juxt ->Insertion (partial get act))) (get ins -1))
- {}) 0]
- exp-ks))))
-
-(defn- diff-atom [exp act]
- (if (= exp act)
- exp
- (->Mismatch exp act)))
-
-(defn diff [exp act]
- (if (= (data/equality-partition exp) (data/equality-partition act))
- (diff-similar exp act)
- (diff-atom exp act)))
-
-(extend nil
- Diff
- {:diff-similar diff-atom})
-
-(extend Object
- Diff
- {:diff-similar (fn [exp act]
- (if (.isArray (.getClass ^Object exp))
- (diff-seq exp act)
- (diff-atom exp act)))})
-
-(extend-protocol Diff
- java.util.List
- (diff-similar [exp act] (diff-seq exp act))
-
- java.util.Set
- (diff-similar [exp act]
- (let [exp-seq (seq exp)
- act-seq (seq act)]
- (set (diff-seq exp-seq (concat (filter act exp-seq)
- (remove exp act-seq))))))
-
- java.util.Map
- (diff-similar [exp act] (diff-map exp act)))
-
-(extend-protocol Undiff
- java.util.List
- (left-undiff [s] (map left-undiff (remove #(instance? Insertion %) s)))
- (right-undiff [s] (map right-undiff (remove #(instance? Deletion %) s)))
-
- java.util.Set
- (left-undiff [s] (set (left-undiff (seq s))))
- (right-undiff [s] (set (right-undiff (seq s))))
-
- java.util.Map
- (left-undiff [m]
- (into {}
- (comp (remove #(instance? Insertion (key %)))
- (map (juxt (comp left-undiff key) (comp left-undiff val))))
- m))
- (right-undiff [m]
- (into {}
- (comp (remove #(instance? Deletion (key %)))
- (map (juxt (comp right-undiff key) (comp right-undiff val))))
- m))
-
- Mismatch
- (left-undiff [m] (get m :-))
- (right-undiff [m] (get m :+))
-
- Insertion
- (right-undiff [m] (get m :+))
-
- Deletion
- (left-undiff [m] (get m :-)))
-
-(extend nil Undiff {:left-undiff identity :right-undiff identity})
-(extend Object Undiff {:left-undiff identity :right-undiff identity})
diff --git a/src/lambdaisland/deep_diff/printer.clj b/src/lambdaisland/deep_diff/printer.clj
deleted file mode 100644
index 50bd5ad..0000000
--- a/src/lambdaisland/deep_diff/printer.clj
+++ /dev/null
@@ -1,159 +0,0 @@
-(ns lambdaisland.deep-diff.printer
- (:require [fipp.engine :as fipp]
- [fipp.visit :as fv]
- [puget.color :as color]
- [puget.dispatch]
- [puget.printer :as puget]
- [arrangement.core]
- [lambdaisland.deep-diff.diff :as diff])
- (:import (java.text SimpleDateFormat)
- (java.util TimeZone)
- (java.sql Timestamp)))
-
-(defn print-deletion [printer expr]
- (let [no-color (assoc printer :print-color false)]
- (color/document printer ::deletion [:span "-" (puget/format-doc no-color (:- expr))])))
-
-(defn print-insertion [printer expr]
- (let [no-color (assoc printer :print-color false)]
- (color/document printer ::insertion [:span "+" (puget/format-doc no-color (:+ expr))])))
-
-(defn print-mismatch [printer expr]
- [:group
- [:span ""] ;; needed here to make this :nest properly in kaocha.report/print-expr '=
- [:align
- (print-deletion printer expr) :line
- (print-insertion printer expr)]])
-
-(defn print-other [printer expr]
- (let [no-color (assoc printer :print-color false)]
- (color/document printer ::other [:span "-" (puget/format-doc no-color expr)])))
-
-(defn- map-handler [this value]
- (let [ks (#'puget/order-collection (:sort-keys this) value (partial sort-by first arrangement.core/rank))
- entries (map (partial puget/format-doc this) ks)]
- [:group
- (color/document this :delimiter "{")
- [:align (interpose [:span (:map-delimiter this) :line] entries)]
- (color/document this :delimiter "}")]))
-
-(def ^:private ^ThreadLocal thread-local-utc-date-format
- (proxy [ThreadLocal] []
- (initialValue []
- (doto (SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00")
- (.setTimeZone (TimeZone/getTimeZone "GMT"))))))
-
-(def ^:private print-date
- (puget/tagged-handler
- 'inst
- #(.format ^SimpleDateFormat (.get thread-local-utc-date-format) %)))
-
-(def ^:private ^ThreadLocal thread-local-utc-timestamp-format
- (proxy [ThreadLocal] []
- (initialValue []
- (doto (SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss")
- (.setTimeZone (TimeZone/getTimeZone "GMT"))))))
-
-(def ^:private print-timestamp
- (puget/tagged-handler
- 'inst
- #(str (.format ^SimpleDateFormat (.get thread-local-utc-timestamp-format) %)
- (format ".%09d-00:00" (.getNanos ^Timestamp %)))))
-
-(def ^:private print-calendar
- (puget/tagged-handler
- 'inst
- #(let [formatted (format "%1$tFT%1$tT.%1$tL%1$tz" %)
- offset-minutes (- (.length formatted) 2)]
- (str (subs formatted 0 offset-minutes)
- ":"
- (subs formatted offset-minutes)))))
-
-(def ^:private print-handlers
- {'lambdaisland.deep_diff.diff.Deletion
- print-deletion
-
- 'lambdaisland.deep_diff.diff.Insertion
- print-insertion
-
- 'lambdaisland.deep_diff.diff.Mismatch
- print-mismatch
-
- 'clojure.lang.PersistentArrayMap
- map-handler
-
- 'clojure.lang.PersistentHashMap
- map-handler
-
- 'clojure.lang.MapEntry
- (fn [printer value]
- (let [k (key value)
- v (val value)]
- (let [no-color (assoc printer :print-color false)]
- (cond
- (instance? lambdaisland.deep_diff.diff.Insertion k)
- [:span
- (print-insertion printer k)
- (if (coll? v) (:map-coll-separator printer) " ")
- (color/document printer ::insertion (puget/format-doc no-color v))]
-
- (instance? lambdaisland.deep_diff.diff.Deletion k)
- [:span
- (print-deletion printer k)
- (if (coll? v) (:map-coll-separator printer) " ")
- (color/document printer ::deletion (puget/format-doc no-color v))]
-
- :else
- [:span
- (puget/format-doc printer k)
- (if (coll? v) (:map-coll-separator printer) " ")
- (puget/format-doc printer v)]))))
-
- 'java.util.Date
- print-date
-
- 'java.util.GregorianCalendar
- print-calendar
-
- 'java.sql.Timestamp
- print-timestamp
-
- 'java.util.UUID
- (puget/tagged-handler 'uuid str)})
-
-(defn- print-handler-resolver [extra-handlers]
- (fn [^Class klz]
- (and klz (get (merge @#'print-handlers extra-handlers)
- (symbol (.getName klz))))))
-
-(defn register-print-handler!
- "Register an extra print handler.
-
- `type` must be a symbol of the fully qualified class name. `handler` is a
- Puget handler function of two arguments, `printer` and `value`."
- [type handler]
- (alter-var-root #'print-handlers assoc type handler))
-
-(defn puget-printer
- ([]
- (puget-printer {}))
- ([opts]
- (let [extra-handlers (:extra-handlers opts)]
- (puget/pretty-printer (merge {:width (or *print-length* 100)
- :print-color true
- :color-scheme {::deletion [:red]
- ::insertion [:green]
- ::other [:yellow]
- ;; puget uses green and red for
- ;; boolean/tag, but we want to reserve
- ;; those for diffed values.
- :boolean [:bold :cyan]
- :tag [:magenta]}
- :print-handlers (print-handler-resolver extra-handlers)}
- (dissoc opts :extra-handlers))))))
-
-(defn format-doc [expr printer]
- (puget/format-doc printer expr))
-
-(defn print-doc [doc printer]
- (fipp.engine/pprint-document doc {:width (:width printer)}))
diff --git a/src/lambdaisland/deep_diff.clj b/src/lambdaisland/deep_diff2.cljc
similarity index 60%
rename from src/lambdaisland/deep_diff.clj
rename to src/lambdaisland/deep_diff2.cljc
index fa27d50..83ffa78 100644
--- a/src/lambdaisland/deep_diff.clj
+++ b/src/lambdaisland/deep_diff2.cljc
@@ -1,6 +1,9 @@
-(ns lambdaisland.deep-diff
- (:require [lambdaisland.deep-diff.diff :as diff]
- [lambdaisland.deep-diff.printer :as printer]))
+(ns lambdaisland.deep-diff2
+ "Diff datastructures deeply, and pretty-print the result"
+ (:require
+ [lambdaisland.deep-diff2.diff-impl :as diff-impl]
+ [lambdaisland.deep-diff2.minimise-impl :as minimise]
+ [lambdaisland.deep-diff2.printer-impl :as printer-impl]))
(defn diff
"Compare two values recursively.
@@ -17,7 +20,7 @@
Insertions/Deletions in maps are marked by wrapping the key, even though the
change applies to the whole map entry."
[expected actual]
- (diff/diff expected actual))
+ (diff-impl/diff expected actual))
(defn printer
"Construct a Puget printer instance suitable for printing diffs.
@@ -26,9 +29,9 @@
`:extra-handlers` (a map from symbol to function), or by
using [[lambdaisland.deep-diff.printer/register-print-handler!]]"
([]
- (printer {}))
+ (printer {:print-fallback :print}))
([opts]
- (printer/puget-printer opts)))
+ (printer-impl/puget-printer opts)))
(defn pretty-print
"Pretty print a diff.
@@ -39,5 +42,15 @@
(pretty-print diff (printer)))
([diff printer]
(-> diff
- (printer/format-doc printer)
- (printer/print-doc printer))))
+ (printer-impl/format-doc printer)
+ (printer-impl/print-doc printer))))
+
+(defn minimise
+ "Return a minimal diff, removing any values that haven't changed."
+ [diff]
+ (minimise/minimise diff))
+
+(defn minimize
+ "Return a minimal diff, removing any values that haven't changed."
+ [diff]
+ (minimise/minimise diff))
diff --git a/src/lambdaisland/deep_diff2/diff_impl.cljc b/src/lambdaisland/deep_diff2/diff_impl.cljc
new file mode 100644
index 0000000..b0d18b6
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/diff_impl.cljc
@@ -0,0 +1,317 @@
+(ns lambdaisland.deep-diff2.diff-impl
+ (:require
+ [clojure.data :as data]
+ [clojure.set :as set]
+ [lambdaisland.clj-diff.core :as seq-diff]))
+
+(declare diff diff-similar diff-meta)
+
+(defrecord Mismatch [- +])
+(defrecord Deletion [-])
+(defrecord Insertion [+])
+
+(defprotocol Diff
+ (-diff-similar [x y]))
+
+;; For property based testing
+(defprotocol Undiff
+ (left-undiff [x])
+ (right-undiff [x]))
+
+(defn shift-insertions [ins]
+ (reduce (fn [res idx]
+ (let [offset (apply + (map count (vals res)))]
+ (assoc res (+ idx offset) (get ins idx))))
+ {}
+ (sort (keys ins))))
+
+(defn replacements
+ "Given a set of deletion indexes and a map of insertion index to value sequence,
+ match up deletions and insertions into replacements, returning a map of
+ replacements, a set of deletions, and a map of insertions."
+ [[del ins]]
+ ;; Loop over deletions, if they match up with an insertion, turn them into a
+ ;; replacement. This could be a reduce over (sort del) tbh but it's already a
+ ;; lot more readable than the first version.
+ (loop [rep {}
+ del del
+ del-rest (sort del)
+ ins ins]
+ (if-let [d (first del-rest)]
+ (if-let [i (seq (get ins d))] ;; matching insertion
+ (recur (assoc rep d (first i))
+ (disj del d)
+ (next del-rest)
+ (update ins d next))
+
+ (if-let [i (seq (get ins (dec d)))]
+ (recur (assoc rep d (first i))
+ (disj del d)
+ (next del-rest)
+ (-> ins
+ (dissoc (dec d))
+ (assoc d (seq (concat (next i)
+ (get ins d))))))
+ (recur rep
+ del
+ (next del-rest)
+ ins)))
+ [rep del (into {}
+ (remove (comp nil? val))
+ (shift-insertions ins))])))
+
+(defn del+ins
+ "Wrapper around clj-diff that returns deletions and insertions as a set and map
+ respectively."
+ [exp act]
+ (let [{del :- ins :+} (seq-diff/diff exp act)]
+ [(into #{} del)
+ (into {} (map (fn [[k & vs]] [k (vec vs)])) ins)]))
+
+(defn diff-seq-replacements [replacements s]
+ (map-indexed
+ (fn [idx v]
+ (if (contains? replacements idx)
+ (diff v (get replacements idx))
+ v))
+ s))
+
+(defn diff-seq-deletions [del s]
+ (map
+ (fn [v idx]
+ (if (contains? del idx)
+ (->Deletion v)
+ v))
+ s
+ (range)))
+
+(defn diff-seq-insertions [ins s]
+ (reduce (fn [res [idx vs]]
+ (concat (take (inc idx) res) (map ->Insertion vs) (drop (inc idx) res)))
+ s
+ ins))
+
+(defn diff-seq [exp act]
+ (let [[rep del ins] (replacements (del+ins exp act))]
+ (with-meta
+ (->> exp
+ (diff-seq-replacements rep)
+ (diff-seq-deletions del)
+ (diff-seq-insertions ins)
+ (into []))
+ (diff-meta exp act))))
+
+(defn diff-set [exp act]
+ (with-meta
+ (into
+ (into #{}
+ (map (fn [e]
+ (if (contains? act e)
+ e
+ (->Deletion e))))
+ exp)
+ (map ->Insertion)
+ (remove #(contains? exp %) act))
+ (diff-meta exp act)))
+
+(defn diff-map [exp act]
+ (if (not= (record? exp) (record? act))
+ ;; If one of them is a record, and the other one a plain map, that's a
+ ;; mismatch. The case where both of them are records, but of different
+ ;; types, is handled in [[diff]]
+ (->Mismatch exp act)
+ (with-meta
+ (let [exp-ks (set (keys exp))
+ act-ks (set (keys act))]
+ (reduce
+ (fn [m k]
+ (case [(contains? exp-ks k) (contains? act-ks k)]
+ [true false]
+ ;; The `dissoc` is only relevant for records, which at this point
+ ;; we are certain are of the same type. If the key is present in
+ ;; one and not in the other, we know it's an optional key (not part
+ ;; of the record base), and we can safely `dissoc` it while
+ ;; retaining the record type.
+ (assoc (dissoc m k) (->Deletion k) (get exp k))
+ [false true]
+ (assoc m (->Insertion k) (get act k))
+ [true true]
+ (assoc m k (diff (get exp k) (get act k)))
+ ;; `[false false]` will never occur because `k` necessarily
+ ;; originated from at least one of the two sets
+ ))
+ ;; In case of a record, we want to preserve the type, and you can't
+ ;; call `empty` on records, so we start from `exp` and assoc/dissoc.
+ (if (record? exp) exp {})
+ (set/union exp-ks act-ks)))
+ (diff-meta exp act))))
+
+(defn diff-meta [exp act]
+ (when (or (meta exp) (meta act))
+ (diff-map (meta exp) (meta act))))
+
+(defn primitive? [x]
+ (or (number? x) (string? x) (boolean? x) (inst? x) (keyword? x) (symbol? x)))
+
+(defn diff-atom [exp act]
+ (if (= exp act)
+ exp
+ (->Mismatch exp act)))
+
+(defn diff-similar [x y]
+ (if (primitive? x)
+ (diff-atom x y)
+ (-diff-similar x y)))
+
+(defn diffable? [exp]
+ (satisfies? Diff exp))
+
+;; ClojureScript has this, Clojure doesn't
+#?(:clj
+ (defn array? [x]
+ (and x (.isArray (class x)))))
+
+(defn diff [exp act]
+ (cond
+ (= exp act)
+ exp
+
+ (nil? exp)
+ (diff-atom exp act)
+
+ (record? exp)
+ (if (= (type exp) (type act))
+ (diff-map exp act)
+ ;; Either act is not a record, or it's a record of a different type, so
+ ;; that's a mismatch
+ (->Mismatch exp act))
+
+ (and (diffable? exp)
+ (= (data/equality-partition exp) (data/equality-partition act)))
+ (diff-similar exp act)
+
+ (array? exp)
+ (diff-seq exp act)
+
+ :else
+ (diff-atom exp act)))
+
+(extend-protocol Diff
+ #?(:clj java.util.Set :cljs cljs.core/PersistentHashSet)
+ (-diff-similar [exp act]
+ (diff-set exp act))
+ #?@(:clj
+ [java.util.List
+ (-diff-similar [exp act] (diff-seq exp act))
+
+ java.util.Map
+ (-diff-similar [exp act] (diff-map exp act))]
+
+ :cljs
+ [cljs.core/List
+ (-diff-similar [exp act] (diff-seq exp act))
+
+ cljs.core/PersistentVector
+ (-diff-similar [exp act] (diff-seq exp act))
+
+ cljs.core/EmptyList
+ (-diff-similar [exp act] (diff-seq exp act))
+
+ cljs.core/PersistentHashMap
+ (-diff-similar [exp act] (diff-map exp act))
+
+ cljs.core/PersistentArrayMap
+ (-diff-similar [exp act] (diff-map exp act))]))
+
+(extend-protocol Undiff
+ Mismatch
+ (left-undiff [m] (get m :-))
+ (right-undiff [m] (get m :+))
+
+ Insertion
+ (right-undiff [m] (get m :+))
+
+ Deletion
+ (left-undiff [m] (get m :-))
+
+ nil
+ (left-undiff [m] m)
+ (right-undiff [m] m)
+
+ #?(:clj Object :cljs default)
+ (left-undiff [m] m)
+ (right-undiff [m] m)
+
+ #?@(:clj
+ [java.util.List
+ (left-undiff [s] (map left-undiff (remove #(instance? Insertion %) s)))
+ (right-undiff [s] (map right-undiff (remove #(instance? Deletion %) s)))
+
+ java.util.Set
+ (left-undiff [s] (set (left-undiff (seq s))))
+ (right-undiff [s] (set (right-undiff (seq s))))
+
+ java.util.Map
+ (left-undiff [m]
+ (into {}
+ (comp (remove #(instance? Insertion (key %)))
+ (map (juxt (comp left-undiff key) (comp left-undiff val))))
+ m))
+ (right-undiff [m]
+ (into {}
+ (comp (remove #(instance? Deletion (key %)))
+ (map (juxt (comp right-undiff key) (comp right-undiff val))))
+ m))]
+
+ :cljs
+ [cljs.core/List
+ (left-undiff [s] (map left-undiff (remove #(instance? Insertion %) s)))
+ (right-undiff [s] (map right-undiff (remove #(instance? Deletion %) s)))
+
+ cljs.core/EmptyList
+ (left-undiff [s] (map left-undiff (remove #(instance? Insertion %) s)))
+ (right-undiff [s] (map right-undiff (remove #(instance? Deletion %) s)))
+
+ cljs.core/PersistentHashSet
+ (left-undiff [s] (set (left-undiff (seq s))))
+ (right-undiff [s] (set (right-undiff (seq s))))
+
+ cljs.core/PersistentTreeSet
+ (left-undiff [s] (set (left-undiff (seq s))))
+ (right-undiff [s] (set (right-undiff (seq s))))
+
+ cljs.core/PersistentVector
+ (left-undiff [s] (map left-undiff (remove #(instance? Insertion %) s)))
+ (right-undiff [s] (map right-undiff (remove #(instance? Deletion %) s)))
+
+ cljs.core/KeySeq
+ (left-undiff [s] (map left-undiff (remove #(instance? Insertion %) s)))
+ (right-undiff [s] (map right-undiff (remove #(instance? Deletion %) s)))
+
+ cljs.core/PersistentArrayMap
+ (left-undiff [m]
+ (into {}
+ (comp (remove #(instance? Insertion (key %)))
+ (map (juxt (comp left-undiff key) (comp left-undiff val))))
+ m))
+ (right-undiff [m]
+ (into {}
+ (comp (remove #(instance? Deletion (key %)))
+ (map (juxt (comp right-undiff key) (comp right-undiff val))))
+ m))
+
+ cljs.core/PersistentHashMap
+ (left-undiff [m]
+ (into {}
+ (comp (remove #(instance? Insertion (key %)))
+ (map (juxt (comp left-undiff key) (comp left-undiff val))))
+ m))
+ (right-undiff [m]
+ (into {}
+ (comp (remove #(instance? Deletion (key %)))
+ (map (juxt (comp right-undiff key) (comp right-undiff val))))
+ m))
+
+ cljs.core/UUID
+ (left-undiff [m] m)
+ (right-undiff [m] m)]))
diff --git a/src/lambdaisland/deep_diff2/minimise_impl.cljc b/src/lambdaisland/deep_diff2/minimise_impl.cljc
new file mode 100644
index 0000000..fabcfd8
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/minimise_impl.cljc
@@ -0,0 +1,53 @@
+(ns lambdaisland.deep-diff2.minimise-impl
+ "Provide API for manipulate the diff structure data "
+ (:require
+ [clojure.walk :refer [postwalk]]
+ #?(:clj [lambdaisland.deep-diff2.diff-impl]
+ :cljs [lambdaisland.deep-diff2.diff-impl :refer [Mismatch Deletion Insertion]]))
+ #?(:clj (:import [lambdaisland.deep_diff2.diff_impl Mismatch Deletion Insertion])))
+
+(defn diff-item?
+ "Checks if x is a Mismatch, Deletion, or Insertion"
+ [x]
+ (or (instance? Mismatch x)
+ (instance? Deletion x)
+ (instance? Insertion x)))
+
+(defn has-diff-item?
+ "Checks if there are any diff items in x or sub-tree of x"
+ [x]
+ (or (diff-item? x)
+ (and (map? x) (some #(or (has-diff-item? (key %))
+ (has-diff-item? (val %))) x))
+ (and (coll? x) (some has-diff-item? x))))
+
+(defn minimise
+ "Postwalk diff, removing values that are unchanged"
+ [o]
+ (cond
+ (diff-item? o)
+ o
+
+ (map-entry? o)
+ (cond
+ (has-diff-item? (key o))
+ o
+ (has-diff-item? (val o))
+ #?(:clj
+ (clojure.lang.MapEntry/create (key o) (minimise (val o)))
+ :cljs
+ (MapEntry. (key o) (minimise (val o)) nil)))
+
+ (record? o)
+ (into o (map minimise) o)
+
+ (map? o)
+ (into {} (keep minimise) o)
+
+ (coll? o)
+ (if (has-diff-item? o)
+ (into (empty o) (keep minimise) o)
+ (empty o))
+
+ :else
+ nil))
diff --git a/src/lambdaisland/deep_diff2/printer_impl.cljc b/src/lambdaisland/deep_diff2/printer_impl.cljc
new file mode 100644
index 0000000..959423c
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/printer_impl.cljc
@@ -0,0 +1,167 @@
+(ns lambdaisland.deep-diff2.printer-impl
+ (:require
+ [arrangement.core]
+ [fipp.engine :as fipp]
+ [lambdaisland.deep-diff2.diff-impl :as diff]
+ [lambdaisland.deep-diff2.puget.color :as color]
+ [lambdaisland.deep-diff2.puget.dispatch :as dispatch]
+ [lambdaisland.deep-diff2.puget.printer :as puget-printer]
+ #?(:cljs [goog.string :refer [format]]))
+ #?(:clj
+ (:import)))
+
+(defn print-deletion [printer expr]
+ (let [no-color (assoc printer :print-color false)]
+ (color/document printer ::deletion [:span "-" (puget-printer/format-doc no-color (:- expr))])))
+
+(defn print-insertion [printer expr]
+ (let [no-color (assoc printer :print-color false)]
+ (color/document printer ::insertion [:span "+" (puget-printer/format-doc no-color (:+ expr))])))
+
+(defn print-mismatch [printer expr]
+ [:group
+ [:span ""] ;; needed here to make this :nest properly in kaocha.report/print-expr '=
+ [:align
+ (print-deletion printer expr) :line
+ (print-insertion printer expr)]])
+
+(defn print-other [printer expr]
+ (let [no-color (assoc printer :print-color false)]
+ (color/document printer ::other [:span "-" (puget-printer/format-doc no-color expr)])))
+
+(defn- map-handler [this value]
+ (let [ks (#'puget-printer/order-collection (:sort-keys this) value (partial sort-by first arrangement.core/rank))
+ entries (map (partial puget-printer/format-doc this) ks)]
+ [:group
+ (color/document this :delimiter "{")
+ [:align (interpose [:span (:map-delimiter this) :line] entries)]
+ (color/document this :delimiter "}")]))
+
+(defn- map-entry-handler [printer value]
+ (let [k (key value)
+ v (val value)]
+ (let [no-color (assoc printer :print-color false)]
+ (cond
+ (instance? lambdaisland.deep_diff2.diff_impl.Insertion k)
+ [:span
+ (print-insertion printer k)
+ (if (coll? v) (:map-coll-separator printer) " ")
+ (color/document printer ::insertion (puget-printer/format-doc no-color v))]
+
+ (instance? lambdaisland.deep_diff2.diff_impl.Deletion k)
+ [:span
+ (print-deletion printer k)
+ (if (coll? v) (:map-coll-separator printer) " ")
+ (color/document printer ::deletion (puget-printer/format-doc no-color v))]
+
+ :else
+ [:span
+ (puget-printer/format-doc printer k)
+ (if (coll? v) (:map-coll-separator printer) " ")
+ (puget-printer/format-doc printer v)]))))
+
+(def print-handlers
+ (atom #?(:clj
+ {'lambdaisland.deep_diff2.diff_impl.Deletion
+ print-deletion
+
+ 'lambdaisland.deep_diff2.diff_impl.Insertion
+ print-insertion
+
+ 'lambdaisland.deep_diff2.diff_impl.Mismatch
+ print-mismatch
+
+ 'clojure.lang.PersistentArrayMap
+ map-handler
+
+ 'clojure.lang.PersistentHashMap
+ map-handler
+
+ 'clojure.lang.MapEntry
+ map-entry-handler}
+
+ :cljs
+ {'lambdaisland.deep-diff2.diff-impl/Deletion
+ print-deletion
+
+ 'lambdaisland.deep-diff2.diff-impl/Insertion
+ print-insertion
+
+ 'lambdaisland.deep-diff2.diff-impl/Mismatch
+ print-mismatch
+
+ 'cljs.core/PersistentArrayMap
+ map-handler
+
+ 'cljs.core/PersistentHashMap
+ map-handler
+
+ 'cljs.core/MapEntry
+ map-entry-handler})))
+
+(defn type-name
+ "Get the type of the given object as a string. For Clojure, gets the name of
+ the class of the object. For ClojureScript, gets either the `name` attribute
+ or the protocol name if the `name` attribute doesn't exist."
+ [x]
+ #?(:bb
+ (symbol (str (type x)))
+ :clj
+ (symbol (.getName (class x)))
+ :cljs
+ (let [t (type x)
+ n (.-name t)]
+ (if (empty? n)
+ (symbol (pr-str t))
+ (symbol n)))))
+
+(defn- print-handler-resolver [extra-handlers]
+ (fn [obj]
+ (and obj (get (merge @print-handlers extra-handlers)
+ (symbol (type-name obj))))))
+
+(defn register-print-handler!
+ "Register an extra print handler.
+
+ `type` must be a symbol of the fully qualified class name. `handler` is a
+ Puget handler function of two arguments, `printer` and `value`."
+ [type handler]
+ (swap! print-handlers assoc type handler))
+
+(defn- color-scheme-mapping
+ "Translates user-friendly keys to internal namespaced keys."
+ [colors]
+ (let [mapping {:lambdaisland.deep-diff2/deletion ::deletion
+ :lambdaisland.deep-diff2/insertion ::insertion
+ :lambdaisland.deep-diff2/other ::other}]
+ (reduce-kv (fn [m k v]
+ (assoc m (get mapping k k) v)) ;; Fallback to original key if not in mapping
+ {}
+ colors)))
+
+(defn puget-printer
+ ([]
+ (puget-printer {}))
+ ([opts]
+ (let [opts (update opts :color-scheme color-scheme-mapping)
+ extra-handlers (:extra-handlers opts)]
+ (puget-printer/pretty-printer (puget-printer/merge-options {:width (or *print-length* 100)
+ :print-color true
+ :color-scheme {::deletion [:red]
+ ::insertion [:green]
+ ::other [:yellow]
+ ;; lambdaisland.deep-diff2.puget uses green and red for
+ ;; boolean/tag, but we want to reserve
+ ;; those for diffed values.
+ :boolean [:bold :cyan]
+ :tag [:magenta]}
+ :print-handlers (dispatch/chained-lookup
+ (print-handler-resolver extra-handlers)
+ puget-printer/common-handlers)}
+ (dissoc opts :extra-handlers))))))
+
+(defn format-doc [expr printer]
+ (puget-printer/format-doc printer expr))
+
+(defn print-doc [doc printer]
+ (fipp.engine/pprint-document doc {:width (:width printer)}))
diff --git a/src/lambdaisland/deep_diff2/puget/color.cljc b/src/lambdaisland/deep_diff2/puget/color.cljc
new file mode 100644
index 0000000..52a0fc3
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/puget/color.cljc
@@ -0,0 +1,50 @@
+(ns lambdaisland.deep-diff2.puget.color
+ "Coloring multimethods to format text by adding markup.
+
+ #### Color Options
+
+ `:print-color`
+
+ When true, ouptut colored text from print functions.
+
+ `:color-markup`
+
+ - `:ansi` for color terminal text (default)
+ - `:html-inline` for inline-styled html
+ - `:html-classes` for html with semantic classes
+
+ `:color-scheme`
+
+ Map of syntax element keywords to color codes.
+ ")
+
+;; ## Coloring Multimethods
+(defn dispatch
+ "Dispatches to coloring multimethods. Element should be a key from
+ the color-scheme map."
+ [options element text]
+ (when (:print-color options)
+ (:color-markup options)))
+
+(defmulti document
+ "Constructs a pretty print document, which may be colored if
+ `:print-color` is true."
+ #'dispatch)
+
+(defmulti text
+ "Produces text colored according to the active color scheme. This is mostly
+ useful to clients which want to produce output which matches data printed by
+ Puget, but which is not directly printed by the library. Note that this
+ function still obeys the `:print-color` option."
+ #'dispatch)
+
+;; ## Default Markup
+;; The default transformation when there's no markup specified is to return the
+;; text unaltered.
+(defmethod document nil
+ [options element text]
+ text)
+
+(defmethod text nil
+ [options element text]
+ text)
diff --git a/src/lambdaisland/deep_diff2/puget/color/ansi.cljc b/src/lambdaisland/deep_diff2/puget/color/ansi.cljc
new file mode 100644
index 0000000..9009db6
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/puget/color/ansi.cljc
@@ -0,0 +1,74 @@
+(ns lambdaisland.deep-diff2.puget.color.ansi
+ "Coloring implementation that applies ANSI color codes to text designed to be
+ output to a terminal.
+
+ Use with a `:color-markup` of `:ansi`."
+ (:require
+ [clojure.string :as str]
+ [lambdaisland.deep-diff2.puget.color :as color]))
+
+(def sgr-code
+ "Map of symbols to numeric SGR (select graphic rendition) codes."
+ {:none 0
+ :bold 1
+ :underline 3
+ :blink 5
+ :reverse 7
+ :hidden 8
+ :strike 9
+ :black 30
+ :red 31
+ :green 32
+ :yellow 33
+ :blue 34
+ :magenta 35
+ :cyan 36
+ :white 37
+ :fg-256 38
+ :fg-reset 39
+ :bg-black 40
+ :bg-red 41
+ :bg-green 42
+ :bg-yellow 43
+ :bg-blue 44
+ :bg-magenta 45
+ :bg-cyan 46
+ :bg-white 47
+ :bg-256 48
+ :bg-reset 49})
+
+(defn esc
+ "Returns an ANSI escope string which will apply the given collection of SGR
+ codes."
+ [codes]
+ (let [codes (map sgr-code codes codes)
+ codes (str/join \; codes)]
+ (str \u001b \[ codes \m)))
+
+(defn escape
+ "Returns an ANSI escope string which will enact the given SGR codes."
+ [& codes]
+ (esc codes))
+
+(defn sgr
+ "Wraps the given string with SGR escapes to apply the given codes, then reset
+ the graphics."
+ [string & codes]
+ (str (esc codes) string (escape :none)))
+
+(defn strip
+ "Removes color codes from the given string."
+ [string]
+ (str/replace string #"\u001b\[[0-9;]*[mK]" ""))
+
+(defmethod color/document :ansi
+ [options element document]
+ (if-let [codes (-> options :color-scheme (get element) seq)]
+ [:span [:pass (esc codes)] document [:pass (escape :none)]]
+ document))
+
+(defmethod color/text :ansi
+ [options element text]
+ (if-let [codes (-> options :color-scheme (get element) seq)]
+ (str (esc codes) text (escape :none))
+ text))
diff --git a/src/lambdaisland/deep_diff2/puget/color/html.cljc b/src/lambdaisland/deep_diff2/puget/color/html.cljc
new file mode 100644
index 0000000..fb06faf
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/puget/color/html.cljc
@@ -0,0 +1,107 @@
+(ns lambdaisland.deep-diff2.puget.color.html
+ "Coloring implementation that wraps text in HTML tags to apply color.
+
+ Supports the following modes for `:color-markup`:
+
+ - `:html-inline` applies inline `style` attributes to the tags.
+ - `:html-classes` adds semantic `class` attributes to the tags."
+ (:require
+ [clojure.string :as str]
+ [clojure.walk :refer [postwalk]]
+ [lambdaisland.deep-diff2.puget.color :as color]))
+
+(def style-attribute
+ "Map from keywords usable in a color-scheme value to vectors
+ representing css style attributes"
+ {:none nil
+ :bold [:font-weight "bold"]
+ :underline [:text-decoration "underline"]
+ :blink [:text-decoration "blink"]
+ :reverse nil
+ :hidden [:visibility "hidden"]
+ :strike [:text-decoration "line-through"]
+ :black [:color "black"]
+ :red [:color "red"]
+ :green [:color "green"]
+ :yellow [:color "yellow"]
+ :blue [:color "blue"]
+ :magenta [:color "magenta"]
+ :cyan [:color "cyan"]
+ :white [:color "white"]
+ :fg-256 nil
+ :fg-reset nil
+ :bg-black [:background-color "black"]
+ :bg-red [:background-color "red"]
+ :bg-green [:background-color "green"]
+ :bg-yellow [:background-color "yellow"]
+ :bg-blue [:background-color "blue"]
+ :bg-magenta [:background-color "magenta"]
+ :bg-cyan [:background-color "cyan"]
+ :bg-white [:background-color "white"]
+ :bg-256 nil
+ :bg-reset nil})
+
+(defn style
+ "Returns a formatted style attribute for a span given a seq of
+ keywords usable in a :color-scheme value"
+ [codes]
+ (let [attributes (map #(get style-attribute % [:color (name %)]) codes)]
+ (str "style=\""
+ (str/join ";" (map (fn [[k v]] (str (name k) ":" v)) attributes))
+ "\"")))
+
+(defn escape-html-text
+ "Escapes special characters into HTML entities."
+ [text]
+ (str/escape text {\& "&" \< "<" \> ">" \" """}))
+
+(defn escape-html-node
+ "Applies HTML escaping to the node if it is a string. Returns a print
+ document representing the escaped string, or the original node if not."
+ [node]
+ (if (string? node)
+ (let [escaped-text (escape-html-text node)
+ spans (str/split escaped-text #"(?=&)")]
+ (reduce (fn [acc span]
+ (case (first span)
+ nil acc
+ \& (let [semicolon-pos ((fnil inc 0) (str/index-of span \;))
+ escaped (subs span 0 semicolon-pos)
+ span (subs span semicolon-pos)
+ acc (conj acc [:escaped escaped])]
+ (if (seq span)
+ (conj acc span)
+ acc))
+ (conj acc span)))
+ [:span]
+ spans))
+ node))
+
+(defn escape-html-document
+ "Escapes special characters into fipp :span/:escaped nodes"
+ [document]
+ (postwalk escape-html-node document))
+
+(defmethod color/document :html-inline
+ [options element document]
+ (if-let [codes (-> options :color-scheme (get element) seq)]
+ [:span [:pass ""]
+ (escape-html-document document)
+ [:pass ""]]
+ (escape-html-document document)))
+
+(defmethod color/text :html-inline
+ [options element text]
+ (if-let [codes (-> options :color-scheme (get element) seq)]
+ (str "" (escape-html-text text) "")
+ (escape-html-text text)))
+
+(defmethod color/document :html-classes
+ [options element document]
+ [:span [:pass ""]
+ (escape-html-document document)
+ [:pass ""]])
+
+(defmethod color/text :html-classes
+ [options element text]
+ (str "" (escape-html-text text) ""))
diff --git a/src/lambdaisland/deep_diff2/puget/dispatch.cljc b/src/lambdaisland/deep_diff2/puget/dispatch.cljc
new file mode 100644
index 0000000..b02c108
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/puget/dispatch.cljc
@@ -0,0 +1,114 @@
+(ns lambdaisland.deep-diff2.puget.dispatch
+ "Dispatch functions take a `Class` argument and return the looked-up value.
+ This provides similar functionality to Clojure's protocols, but operates over
+ locally-constructed logic rather than using a global dispatch table.
+
+ A simple example is a map from classes to values, which can be used directly
+ as a lookup function."
+ (:require [clojure.string :as str]))
+
+;; ## Logical Dispatch
+(defn chained-lookup
+ "Builds a dispatcher which looks up a type by checking multiple dispatchers
+ in order until a matching entry is found. Takes either a single collection of
+ dispatchers or a variable list of dispatcher arguments. Ignores nil
+ dispatchers in the sequence."
+ ([dispatchers]
+ {:pre [(sequential? dispatchers)]}
+ (let [candidates (remove nil? dispatchers)
+ no-chain-lookup-provided-message "chained-lookup must be provided at least one dispatch function to try."]
+ (when (empty? candidates)
+ (throw (ex-info no-chain-lookup-provided-message
+ {:causes #{:no-chained-lookup-provided}})))
+ (if (= 1 (count candidates))
+ (first candidates)
+ (fn lookup
+ [t]
+ (some #(% t) candidates)))))
+ ([a b & more]
+ (chained-lookup (list* a b more))))
+
+(defn caching-lookup
+ "Builds a dispatcher which caches values returned for each type. This improves
+ performance when the underlying dispatcher may need to perform complex
+ lookup logic to determine the dispatched value."
+ [dispatch]
+ (let [cache (atom {})]
+ (fn lookup
+ [t]
+ (let [memory @cache]
+ (if (contains? memory t)
+ (get memory t)
+ (let [v (dispatch t)]
+ (swap! cache assoc t v)
+ v))))))
+
+;; Space for predicate-lookup. ClojureScript support
+#?(:cljs
+ (defn predicate-lookup
+ "Look up a handler for a value based on a map from predicate to handler"
+ [types]
+ (fn lookup [value]
+ (some (fn [[pred? handler]]
+ (when (pred? value)
+ handler))
+ types))))
+
+;; ## Type Dispatch (Clojure)
+#?(:clj
+ (defn symbolic-lookup
+ "Builds a dispatcher which looks up a type by checking the underlying lookup
+ using the type's _symbolic_ name, rather than the class value itself. This is
+ useful for checking configuration that must be created in situations where the
+ classes themselves may not be loaded yet."
+ [dispatch]
+ (fn lookup
+ [^Class t]
+ (dispatch (symbol (.getName t))))))
+
+#?(:clj
+ (defn- lineage
+ "Returns the ancestry of the given class, starting with the class and
+ excluding the `java.lang.Object` base class."
+ [cls]
+ (take-while #(and (some? %) (not= Object %))
+ (iterate #(when (class? %) (.getSuperclass ^Class %)) cls))))
+
+#?(:clj
+ (defn- find-interfaces
+ "Resolves all of the interfaces implemented by a class, both direct (through
+ class ancestors) and indirect (through other interfaces)."
+ [cls]
+ (let [get-interfaces (fn [^Class c] (.getInterfaces c))
+ direct-interfaces (mapcat get-interfaces (lineage cls))]
+ (loop [queue (vec direct-interfaces)
+ interfaces #{}]
+ (if (empty? queue)
+ interfaces
+ (let [^Class iface (first queue)
+ implemented (get-interfaces iface)]
+ (recur (into (rest queue)
+ (remove interfaces implemented))
+ (conj interfaces iface))))))))
+
+#?(:clj
+ (defn inheritance-lookup
+ "Builds a dispatcher which looks up a type by looking up the type itself,
+ then attempting to look up its ancestor classes, implemented interfaces, and
+ finally `java.lang.Object`."
+ [dispatch]
+ (fn lookup
+ [obj]
+ (let [t (class obj)]
+ (or
+ (some dispatch (lineage t))
+ (let [candidates (remove (comp nil? first)
+ (map (juxt dispatch identity)
+ (find-interfaces t)))
+ wrong-number-of-candidates-message "%d candidates found for interfaces on dispatch type %s: %s"]
+ (case (count candidates)
+ 0 nil
+ 1 (ffirst candidates)
+ (throw (ex-info (format wrong-number-of-candidates-message
+ (count candidates) t (str/join ", " (map second candidates)))))))
+ (dispatch Object))))))
diff --git a/src/lambdaisland/deep_diff2/puget/printer.cljc b/src/lambdaisland/deep_diff2/puget/printer.cljc
new file mode 100644
index 0000000..96146e6
--- /dev/null
+++ b/src/lambdaisland/deep_diff2/puget/printer.cljc
@@ -0,0 +1,756 @@
+(ns lambdaisland.deep-diff2.puget.printer
+ "Enhanced printing functions for rendering Clojure values. The following
+ options are available to control the printer:
+
+ #### General Rendering
+
+ `:width`
+
+ Number of characters to try to wrap pretty-printed forms at.
+
+ `:print-meta`
+
+ If true, metadata will be printed before values. Defaults to the value of
+ `*print-meta*` if unset.
+
+ #### Collection Options
+
+ `:sort-keys`
+
+ Print maps and sets with ordered keys. If true, the pretty printer will sort
+ all unordered collections before printing. If a number, counted collections
+ will be sorted if they are smaller than the given size. Otherwise
+ collections are printed in their natural sort order. Sorted collections are
+ always printed in their natural sort order.
+
+ `:map-delimiter`
+
+ The text placed between key-value pairs in a map.
+
+ `:map-coll-separator`
+
+ The text placed between a map key and a collection value. The keyword :line
+ will cause line breaks if the whole map does not fit on a single line.
+
+ `:namespace-maps`
+
+ Extract common keyword namespaces from maps using the namespace map literal
+ syntax. See `*print-namespace-maps*`.
+
+ `:seq-limit`
+
+ If set to a positive number, then lists will only render at most the first n
+ elements. This can help prevent unintentional realization of infinite lazy
+ sequences.
+
+ #### Color Options
+
+ `:print-color`
+
+ When true, ouptut colored text from print functions.
+
+ `:color-markup`
+
+ :ansi for ANSI color text (the default)
+ :html-inline for inline-styled html
+ :html-classes to use the names of the keys in the :color-scheme map
+ as class names for spans so styling can be specified via CSS.
+
+ `:color-scheme`
+
+ Map of syntax element keywords to color codes.
+
+ #### Type Handling
+
+ `:print-handlers`
+
+ A lookup function which will return a rendering function for a given class
+ type. This will be tried before the built-in type logic. See the
+ `lambdaisland.deep-diff2.puget.dispatch` namespace for some helpful constructors. The returned
+ function should accept the current printer and the value to be rendered
+ returning a format document.
+
+ `:print-fallback`
+
+ Keyword argument specifying how to format unknown values. Puget supports a few
+ different options:
+
+ - `:pretty` renders values with the default colored representation.
+ - `:print` defers to the standard print method by rendering unknown values
+ using `pr-str`.
+ - `:error` will throw an exception when types with no defined handler are
+ encountered.
+ - A function value will be called with the current printer options and the
+ unknown value and is expected to return a formatting document representing
+ it.
+ "
+ (:require [arrangement.core :as order]
+ [clojure.string :as str]
+ [fipp.engine :as fe]
+ [fipp.visit :as fv]
+ [lambdaisland.deep-diff2.puget.color :as color]
+ [lambdaisland.deep-diff2.puget.color.ansi]
+ [lambdaisland.deep-diff2.puget.color.html]
+ [lambdaisland.deep-diff2.puget.dispatch :as dispatch]
+ #?(:cljs [goog.object :as gobj]))
+ (:import #?@(:clj [(java.text SimpleDateFormat)
+ (java.util TimeZone)
+ (java.sql Timestamp)]
+ :cljs [(goog.i18n DateTimeFormat)])))
+
+(defn get-type-name
+ "Get the type of the given object as a string. For Clojure, gets the name of
+ the class of the object. For ClojureScript, gets either the `name` attribute
+ or the protocol name if the `name` attribute doesn't exist."
+ [x]
+ #?(:clj (.getName (class x))
+ :cljs (let [t (type x)
+ n (.-name t)]
+ (if (empty? n)
+ (pr-str t)
+ n))))
+
+(defn get-identity-hashcode
+ "Get the hashcode for a given object o"
+ [o]
+ #?(:clj (System/identityHashCode o)
+ :cljs (hash o)))
+
+(defn to-hex-string
+ "Returns a hex representation of input-string"
+ [input-string]
+ #?(:clj (Integer/toHexString input-string)
+ :cljs (.toString input-string 16)))
+
+;; ## Control Vars
+(def ^:dynamic *options*
+ "Default options to use when constructing new printers."
+ {:width 80
+ :sort-keys 80
+ :map-delimiter ","
+ :map-coll-separator " "
+ :namespace-maps false
+ :print-fallback :pretty
+ :print-color false
+ :color-markup :ansi
+ :color-scheme
+ {;; syntax elements
+ :delimiter [:bold :red]
+ :tag [:red]
+
+ ;; primitive values
+ :nil [:bold :black]
+ :boolean [:green]
+ :number [:cyan]
+ :string [:bold :magenta]
+ :character [:bold :magenta]
+ :keyword [:bold :yellow]
+ :symbol nil
+
+ ;; special types
+ :function-symbol [:bold :blue]
+ :class-delimiter [:blue]
+ :class-name [:bold :blue]}})
+
+(defn merge-options
+ "Merges maps of printer options, taking care to combine the color scheme
+ correctly."
+ [a b]
+ (let [colors (merge (:color-scheme a) (:color-scheme b))]
+ (assoc (merge a b) :color-scheme colors)))
+
+(defmacro with-options
+ "Executes the given expressions with a set of options merged into the current
+ option map."
+ [opts & body]
+ `(binding [*options* (merge-options *options* ~opts)]
+ ~@body))
+
+(defmacro with-color
+ "Executes the given expressions with colored output enabled."
+ [& body]
+ `(with-options {:print-color true}
+ ~@body))
+
+(defn color-text
+ "Produces text colored according to the active color scheme. This is mostly
+ useful to clients which want to produce output which matches data printed by
+ Puget, but which is not directly printed by the library. Note that this
+ function still obeys the `:print-color` option."
+ ([element text]
+ (color-text *options* element text))
+ ([options element text]
+ (color/text options element text)))
+
+;; ## Formatting Methods
+(defn- order-collection
+ "Takes a sequence of entries and checks the mode to determine whether to sort
+ them. Returns an appropriately ordered sequence."
+ [mode coll sort-fn]
+ (if (and (not (sorted? coll))
+ (or (true? mode)
+ (and (number? mode)
+ (counted? coll)
+ (>= mode (count coll)))))
+ (sort-fn coll)
+ (seq coll)))
+
+
+(defn- common-key-ns
+ "Extract a common namespace from the keys in the map. Returns a tuple of the
+ ns string and the stripped map, or nil if the keys are not keywords or there
+ is no sufficiently common namespace."
+ [m]
+ (when (every? (every-pred keyword? namespace) (keys m))
+ (let [nsf (frequencies (map namespace (keys m)))
+ [common n] (apply max-key val nsf)]
+ (when (< (/ (count m) 2) n)
+ [common
+ (into (empty m)
+ (map (fn strip-common
+ [[k v :as e]]
+ (if (= common (namespace k))
+ [(keyword (name k)) v]
+ e)))
+ m)]))))
+
+(defn format-unknown
+ "Renders common syntax doc for an unknown representation of a value."
+ ([printer value]
+ (format-unknown printer value (str value)))
+ ([printer value repr]
+ (format-unknown printer value (get-type-name value) repr))
+ ([printer value tag repr]
+ (let [sys-id (to-hex-string (get-identity-hashcode value))]
+ [:span
+ (color/document printer :class-delimiter "#<")
+ (color/document printer :class-name tag)
+ (color/document printer :class-delimiter "@")
+ sys-id
+ (when (not= repr (str tag "@" sys-id))
+ (list " " repr))
+ (color/document printer :class-delimiter ">")])))
+
+(defn format-doc*
+ "Formats a document without considering metadata."
+ [printer value]
+ (let [lookup (:print-handlers printer)
+ handler (and lookup (lookup value))]
+ (if handler
+ (handler printer value)
+ (fv/visit* printer value))))
+
+(defn format-doc
+ "Recursively renders a print document for the given value."
+ [printer value]
+ (if-let [metadata (meta value)]
+ (fv/visit-meta printer metadata value)
+ (format-doc* printer value)))
+
+;; ## Type Handlers
+(defn pr-handler
+ "Print handler which renders the value with `pr-str`."
+ [printer value]
+ (pr-str value))
+
+(defn unknown-handler
+ "Print handler which renders the value using the printer's unknown type logic."
+ [printer value]
+ (fv/visit-unknown printer value))
+
+(defn tagged-handler
+ "Generates a print handler function which renders a tagged-literal with the
+ given tag and a value produced by calling the function."
+ [tag value-fn]
+ (when-not (symbol? tag)
+ (throw (ex-info (str "Cannot create tagged handler with non-symbol tag "
+ (pr-str tag))
+ {:tag tag, :value-fn value-fn})))
+ (when-not (ifn? value-fn)
+ (throw (ex-info (str "Cannot create tagged handler for " tag
+ " with non-function value transform")
+ {:tag tag, :value-fn value-fn})))
+ (fn handler
+ [printer value]
+ (format-doc printer (tagged-literal tag (value-fn value)))))
+
+(def inst-pattern "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00")
+
+#?(:cljs
+ (defn utc-date [date]
+ (js/Date.
+ (.getUTCFullYear date)
+ (.getUTCMonth date)
+ (.getUTCDate date)
+ (.getUTCHours date)
+ (.getUTCMinutes date)
+ (.getUTCSeconds date)
+ (.getUTCMilliseconds date))))
+
+#?(:clj
+ (defn utc-timestamp-format ^SimpleDateFormat []
+ (doto (SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss")
+ (.setTimeZone (TimeZone/getTimeZone "GMT")))))
+
+(def platform-handlers
+ "Map of print handlers for Java/JavaScript types. This supports syntax for regular
+ expressions, dates, UUIDs, and futures."
+ #?(:clj
+ (->
+ {java.lang.Class
+ (fn class-handler
+ [printer value]
+ (format-unknown printer value "Class" (get-type-name value)))
+
+ java.util.concurrent.Future
+ (fn future-handler
+ [printer value]
+ (let [doc (if (future-done? value)
+ (format-doc printer @value)
+ (color/document printer :nil "pending"))]
+ (format-unknown printer value "Future" doc)))
+
+ java.util.UUID
+ (tagged-handler 'uuid str)
+
+ java.util.Date
+ (tagged-handler
+ 'inst
+ #(-> (java.text.SimpleDateFormat. inst-pattern)
+ (doto (.setTimeZone (java.util.TimeZone/getTimeZone "GMT")))
+ (.format ^java.util.Date %)))
+
+ java.sql.Timestamp
+ (tagged-handler
+ 'inst
+ (fn [ts]
+ (str (.format ^SimpleDateFormat (utc-timestamp-format) ts)
+ (format ".%09d-00:00" (.getNanos ^Timestamp ts)))))}
+ #?(:bb identity
+ :clj (assoc java.util.GregorianCalendar
+ (tagged-handler
+ 'inst
+ #(let [formatted (format "%1$tFT%1$tT.%1$tL%1$tz" %)
+ offset-minutes (- (.length formatted) 2)]
+ (str (subs formatted 0 offset-minutes)
+ ":"
+ (subs formatted offset-minutes)))))))
+
+ :cljs
+ {inst?
+ (tagged-handler
+ 'inst
+ #(.format (DateTimeFormat. inst-pattern) (utc-date %)))
+
+ uuid?
+ (tagged-handler 'uuid str)
+
+ object?
+ (tagged-handler
+ 'js
+ (fn [x]
+ ;; non-recursive conversion to map
+ (reduce (fn [m k]
+ (assoc m k (gobj/get x k)))
+ {}
+ (js/Object.keys x))))}))
+
+(def clojure-handlers
+ "Map of print handlers for 'primary' Clojure types. These should take
+ precedence over the handlers in `clojure-interface-handlers`."
+ {#?(:clj clojure.lang.Atom
+ :cljs #(implements? IAtom %))
+ (fn atom-handler
+ [printer value]
+ (format-unknown printer value "Atom" (format-doc printer @value)))
+ #?(:clj clojure.lang.Delay
+ :cljs #(implements? Delay %))
+ (fn delay-handler
+ [printer value]
+ (let [doc (if (realized? value)
+ (format-doc printer @value)
+ (color/document printer :nil "pending"))]
+ (format-unknown printer value "Delay" doc)))
+ #?(:clj clojure.lang.ISeq
+ :cljs seq?)
+ (fn iseq-handler
+ [printer value]
+ (fv/visit-seq printer value))})
+
+(def clojure-interface-handlers
+ "Fallback print handlers for other Clojure interfaces."
+ {#?(:clj clojure.lang.IPending
+ :cljs #(implements? IPending %))
+ (fn pending-handler
+ [printer value]
+ (let [doc (if (realized? value)
+ (format-doc printer @value)
+ (color/document printer :nil "pending"))]
+ (format-unknown printer value doc)))
+ #?(:clj clojure.lang.Fn
+ :cljs fn?)
+ (fn fn-handler
+ [printer value]
+ (let [doc (let [[vname & tail] (-> (get-type-name value)
+ (str/replace-first "$" "/")
+ (str/split #"\$"))]
+ (if (seq tail)
+ (str vname "["
+ (->> tail
+ (map #(first (str/split % #"__")))
+ (str/join "/"))
+ "]")
+ vname))]
+ (format-unknown printer value "Fn" doc)))})
+
+(def common-handlers
+ "Print handler dispatch combining Java and Clojure handlers with inheritance
+ lookups. Provides a similar experience as the standard Clojure
+ pretty-printer."
+ #?(:clj (dispatch/chained-lookup
+ (dispatch/inheritance-lookup platform-handlers)
+ (dispatch/inheritance-lookup clojure-handlers)
+ (dispatch/inheritance-lookup clojure-interface-handlers))
+ :cljs (dispatch/chained-lookup
+ (dispatch/predicate-lookup platform-handlers)
+ (dispatch/predicate-lookup clojure-handlers)
+ (dispatch/predicate-lookup clojure-interface-handlers))))
+
+
+;; ## Canonical Printer Implementation
+(defrecord CanonicalPrinter [print-handlers]
+ fv/IVisitor
+
+ ;; Primitive Types
+ (visit-nil
+ [this]
+ "nil")
+
+ (visit-boolean
+ [this value]
+ (str value))
+
+ (visit-number
+ [this value]
+ (pr-str value))
+
+ (visit-character
+ [this value]
+ (pr-str value))
+
+ (visit-string
+ [this value]
+ (pr-str value))
+
+ (visit-keyword
+ [this value]
+ (str value))
+
+ (visit-symbol
+ [this value]
+ (str value))
+
+ ;; Collection Types
+ (visit-seq
+ [this value]
+ (if (seq value)
+ (let [entries (map (partial format-doc this) value)]
+ [:group "(" [:align (interpose " " entries)] ")"])
+ "()"))
+
+ (visit-vector
+ [this value]
+ (if (seq value)
+ (let [entries (map (partial format-doc this) value)]
+ [:group "[" [:align (interpose " " entries)] "]"])
+ "[]"))
+
+ (visit-set
+ [this value]
+ (if (seq value)
+ (let [entries (map (partial format-doc this)
+ (sort order/rank value))]
+ [:group "#{" [:align (interpose " " entries)] "}"])
+ "#{}"))
+
+ (visit-map
+ [this value]
+ (if (seq value)
+ (let [entries (map #(vector :span (format-doc this (key %))
+ " " (format-doc this (val %)))
+ (sort-by first order/rank value))]
+ [:group "{" [:align (interpose " " entries)] "}"])
+ "{}"))
+
+ ;; Clojure Types
+ (visit-meta
+ [this metadata value]
+ ;; Metadata is not printed for canonical rendering.
+ (format-doc* this value))
+
+ (visit-var
+ [this value]
+ ;; Defer to unknown, cover with handler.
+ (fv/visit-unknown this value))
+
+ (visit-pattern
+ [this value]
+ ;; Defer to unknown, cover with handler.
+ (fv/visit-unknown this value))
+
+ (visit-record
+ [this value]
+ ;; Defer to unknown, cover with handler.
+ (fv/visit-unknown this value))
+
+ ;; Special Types
+ (visit-tagged
+ [this value]
+ [:span (str "#" (:tag value)) " " (format-doc this (:form value))])
+
+ (visit-unknown
+ [this value]
+ (let [not-defined-representation-message (str "No defined representation for "
+ (get-type-name value)
+ ": "
+ (pr-str value))]
+ (throw (ex-info not-defined-representation-message
+ {:causes #{:undefined-representation}})))))
+
+(defn canonical-printer
+ "Constructs a new canonical printer with the given handler dispatch."
+ ([]
+ (canonical-printer nil))
+ ([handlers]
+ (assoc (CanonicalPrinter. handlers)
+ :width 0)))
+
+;; Remove automatic constructor function.
+#?(:clj (ns-unmap *ns* '->CanonicalPrinter))
+
+;; ## Pretty Printer Implementation
+(defrecord PrettyPrinter
+
+ [width
+ print-meta
+ sort-keys
+ map-delimiter
+ map-coll-separator
+ namespace-maps
+ seq-limit
+ print-color
+ color-markup
+ color-scheme
+ print-handlers
+ print-fallback]
+
+ fv/IVisitor
+
+ ;; Primitive Types
+ (visit-nil
+ [this]
+ (color/document this :nil "nil"))
+
+ (visit-boolean
+ [this value]
+ (color/document this :boolean (str value)))
+
+ (visit-number
+ [this value]
+ (color/document this :number (pr-str value)))
+
+ (visit-character
+ [this value]
+ (color/document this :character (pr-str value)))
+
+ (visit-string
+ [this value]
+ (color/document this :string (pr-str value)))
+
+ (visit-keyword
+ [this value]
+ (color/document this :keyword (str value)))
+
+ (visit-symbol
+ [this value]
+ (color/document this :symbol (str value)))
+
+ ;; Collection Types
+ (visit-seq
+ [this value]
+ (if (seq value)
+ (let [[values trimmed?]
+ (if (and seq-limit (pos? seq-limit))
+ (let [head (take seq-limit value)]
+ [head (<= seq-limit (count head))])
+ [(seq value) false])
+ elements
+ (cond-> (if (symbol? (first values))
+ (cons (color/document this :function-symbol (str (first values)))
+ (map (partial format-doc this) (rest values)))
+ (map (partial format-doc this) values))
+ trimmed? (concat [(color/document this :nil "...")]))]
+ [:group
+ (color/document this :delimiter "(")
+ [:align (interpose :line elements)]
+ (color/document this :delimiter ")")])
+ (color/document this :delimiter "()")))
+
+ (visit-vector
+ [this value]
+ (if (seq value)
+ [:group
+ (color/document this :delimiter "[")
+ [:align (interpose :line (map (partial format-doc this) value))]
+ (color/document this :delimiter "]")]
+ (color/document this :delimiter "[]")))
+
+ (visit-set
+ [this value]
+ (if (seq value)
+ (let [entries (order-collection sort-keys value (partial sort order/rank))]
+ [:group
+ (color/document this :delimiter "#{")
+ [:align (interpose :line (map (partial format-doc this) entries))]
+ (color/document this :delimiter "}")])
+ (color/document this :delimiter "#{}")))
+
+ (visit-map
+ [this value]
+ (if (seq value)
+ (let [[common-ns stripped] (when namespace-maps (common-key-ns value))
+ kvs (order-collection sort-keys
+ (or stripped value)
+ (partial sort-by first order/rank))
+ entries (map (fn [[k v]]
+ [:span
+ (format-doc this k)
+ (if (coll? v)
+ map-coll-separator
+ " ")
+ (format-doc this v)])
+ kvs)
+ map-doc [:group
+ (color/document this :delimiter "{")
+ [:align (interpose [:span map-delimiter :line] entries)]
+ (color/document this :delimiter "}")]]
+ (if common-ns
+ [:group (color/document this :tag (str "#:" common-ns)) :line map-doc]
+ map-doc))
+ (color/document this :delimiter "{}")))
+
+ ;; Clojure Types
+ (visit-meta
+ [this metadata value]
+ (if print-meta
+ [:align
+ [:span (color/document this :delimiter "^") (format-doc this metadata)]
+ :line (format-doc* this value)]
+ (format-doc* this value)))
+
+ (visit-var
+ [this value]
+ [:span
+ (color/document this :delimiter "#'")
+ (color/document this :symbol (subs (str value) 2))])
+
+ (visit-pattern
+ [this value]
+ [:span
+ (color/document this :delimiter "#")
+ (color/document this :string (str \" value \"))])
+
+ (visit-record
+ [this value]
+ (fv/visit-tagged
+ this
+ (tagged-literal (symbol (get-type-name value))
+ (into {} value))))
+
+ ;; Special Types
+ (visit-tagged
+ [this value]
+ (let [{:keys [tag form]} value]
+ [:group
+ (color/document this :tag (str "#" (:tag value)))
+ (if (coll? form) :line " ")
+ (format-doc this (:form value))]))
+
+ (visit-unknown
+ [this value]
+ (case print-fallback
+ :pretty
+ (format-unknown this value)
+
+ :print
+ [:span (pr-str value)]
+
+ :error
+ (throw (ex-info (str "No defined representation for " (get-type-name value) ": " (pr-str value))
+ {:causes #{:undefined-representation}}))
+ (if (ifn? print-fallback)
+ (print-fallback this value)
+ (throw (ex-info (str "Unsupported value for print-fallback: " (pr-str print-fallback))
+ {:causes #{:unsupported-value}}))))))
+
+(defn pretty-printer
+ "Constructs a new printer from the given configuration."
+ [opts]
+ (->> [{:print-meta *print-meta*
+ :print-handlers common-handlers}
+ *options*
+ opts]
+ (reduce merge-options)
+ (map->PrettyPrinter)))
+
+;; Remove automatic constructor function.
+#?(:clj (ns-unmap *ns* '->PrettyPrinter))
+
+;; ## Printing Functions
+(defn render-out
+ "Prints a value using the given printer."
+ ([printer value]
+ (render-out printer value nil))
+ ([printer value opts]
+ (binding [*print-meta* false]
+ (fe/pprint-document
+ (format-doc printer value)
+ (merge {:width (:width printer)}
+ opts)))))
+
+(defn render-str
+ "Renders a value to a string using the given printer."
+ ^String
+ [printer value]
+ (str/trim-newline
+ (with-out-str
+ (render-out printer value))))
+
+(defn pprint
+ "Pretty-prints a value to *out*. Options may be passed to override the
+ default *options* map."
+ ([value]
+ (pprint value nil))
+ ([value opts]
+ (render-out (pretty-printer opts) value opts)))
+
+(defn pprint-str
+ "Pretty-print a value to a string."
+ ([value]
+ (pprint-str value nil))
+ ([value opts]
+ (render-str (pretty-printer opts) value)))
+
+(defn cprint
+ "Like pprint, but turns on colored output."
+ ([value]
+ (cprint value nil))
+ ([value opts]
+ (pprint value (assoc opts :print-color true))))
+
+(defn cprint-str
+ "Pretty-prints a value to a colored string."
+ ([value]
+ (cprint-str value nil))
+ ([value opts]
+ (pprint-str value (assoc opts :print-color true))))
diff --git a/test/lambdaisland/deep_diff/printer_test.clj b/test/lambdaisland/deep_diff/printer_test.clj
deleted file mode 100644
index e442ce3..0000000
--- a/test/lambdaisland/deep_diff/printer_test.clj
+++ /dev/null
@@ -1,40 +0,0 @@
-(ns lambdaisland.deep-diff.printer-test
- (:require [clojure.test :refer :all]
- [lambdaisland.deep-diff.diff :as diff]
- [lambdaisland.deep-diff.printer :as printer])
- (:import (java.sql Timestamp)
- (java.util Date
- GregorianCalendar
- TimeZone)))
-
-(defn- printed
- [diff]
- (let [printer (printer/puget-printer {})]
- (with-out-str (-> diff
- (printer/format-doc printer)
- (printer/print-doc printer)))))
-
-(defn- calendar
- [date]
- (doto (GregorianCalendar. (TimeZone/getTimeZone "GMT"))
- (.setTime date)))
-
-(deftest print-doc-test
- (testing "date"
- (is (= "\u001B[31m-#inst \"2019-04-09T14:57:46.128-00:00\"\u001B[0m \u001B[32m+#inst \"2019-04-10T14:57:46.128-00:00\"\u001B[0m\n"
- (printed (diff/diff #inst "2019-04-09T14:57:46.128-00:00"
- #inst "2019-04-10T14:57:46.128-00:00")))))
-
- (testing "timestamp"
- (is (= "\u001B[31m-#inst \"1970-01-01T00:00:00.000000000-00:00\"\u001B[0m \u001B[32m+#inst \"1970-01-01T00:00:01.000000101-00:00\"\u001B[0m\n"
- (printed (diff/diff (Timestamp. 0)
- (doto (Timestamp. 1000) (.setNanos 101)))))))
-
- (testing "calendar"
- (is (= "\u001B[31m-#inst \"1970-01-01T00:00:00.000+00:00\"\u001B[0m \u001B[32m+#inst \"1970-01-01T00:00:01.001+00:00\"\u001B[0m\n"
- (printed (diff/diff (calendar (Date. 0)) (calendar (Date. 1001)))))))
-
- (testing "uuid"
- (is (= "\u001B[31m-#uuid \"e41b325a-ce9d-4fdd-b51d-280d9c91314d\"\u001B[0m \u001B[32m+#uuid \"0400be9a-619f-4c6a-a735-6245e4955995\"\u001B[0m\n"
- (printed (diff/diff #uuid "e41b325a-ce9d-4fdd-b51d-280d9c91314d"
- #uuid "0400be9a-619f-4c6a-a735-6245e4955995"))))))
diff --git a/test/lambdaisland/deep_diff/diff_test.clj b/test/lambdaisland/deep_diff2/diff_test.cljc
similarity index 72%
rename from test/lambdaisland/deep_diff/diff_test.clj
rename to test/lambdaisland/deep_diff2/diff_test.cljc
index aa7213c..4e0cff2 100644
--- a/test/lambdaisland/deep_diff/diff_test.clj
+++ b/test/lambdaisland/deep_diff2/diff_test.cljc
@@ -1,20 +1,14 @@
-(ns lambdaisland.deep-diff.diff-test
- (:require [clojure.test :refer :all]
- [clojure.test.check :as tc]
- [clojure.test.check.clojure-test :refer [defspec]]
- [clojure.test.check.generators :as gen]
- [clojure.test.check.properties :as prop]
- [lambdaisland.deep-diff.diff :as diff]))
-
-(doseq [v [#'diff/diff-seq
- #'diff/diff-seq-replacements
- #'diff/diff-seq-insertions
- #'diff/diff-seq-deletions
- #'diff/replacements
- #'diff/del+ins]]
- (alter-meta! v dissoc :private))
+(ns lambdaisland.deep-diff2.diff-test
+ (:require
+ [clojure.test :refer [deftest testing is are]]
+ [clojure.test.check :as tc]
+ [clojure.test.check.clojure-test :refer [defspec]]
+ [clojure.test.check.generators :as gen]
+ [clojure.test.check.properties :as prop]
+ [lambdaisland.deep-diff2.diff-impl :as diff]))
(defrecord ARecord [])
+(defrecord BRecord [])
(deftest diff-test
(testing "diffing atoms"
@@ -39,6 +33,9 @@
(is (= []
(diff/diff [] [])))
+ (is (= {:meta true}
+ (meta (diff/diff ^:meta [] ^:meta []))))
+
(is (= [1 2 3]
(diff/diff (into-array [1 2 3]) [1 2 3])))
@@ -76,6 +73,9 @@
(is (= #{:a}
(diff/diff #{:a} #{:a})))
+ (is (= {:meta true}
+ (meta (diff/diff ^:meta #{} ^:meta #{}))))
+
(is (= #{(diff/->Insertion :a)}
(diff/diff #{} #{:a})))
@@ -88,6 +88,9 @@
(testing "maps"
(is (= {} (diff/diff {} {})))
+ (is (= {:meta true}
+ (meta (diff/diff ^:meta {} ^:meta {}))))
+
(is (= {:a (diff/->Mismatch 1 2)}
(diff/diff {:a 1} {:a 2})))
@@ -100,11 +103,30 @@
(is (= {:a [1 (diff/->Deletion 2) 3]}
(diff/diff {:a [1 2 3]} {:a [1 3]}))))
+ (testing "map key order doesn't impact diff result"
+ (is (= {:name (diff/->Mismatch "Alyysa P Hacker" "Alyssa P Hacker"), :age 40}
+
+ (diff/diff (array-map :name "Alyysa P Hacker" :age 40)
+ (array-map :age 40 :name "Alyssa P Hacker"))
+
+ (diff/diff (array-map :age 40 :name "Alyysa P Hacker")
+ (array-map :age 40 :name "Alyssa P Hacker")))))
+
(testing "records"
- (is (= {:a (diff/->Mismatch 1 2)}
+ (is (= (map->ARecord {:a (diff/->Mismatch 1 2)})
(diff/diff (map->ARecord {:a 1}) (map->ARecord {:a 2}))))
- (is (= {(diff/->Insertion :a) 1}
- (diff/diff (map->ARecord {}) (map->ARecord {:a 1}))))))
+ (is (= (map->ARecord {(diff/->Insertion :a) 1})
+ (diff/diff (map->ARecord {}) (map->ARecord {:a 1}))))
+ (is (= (map->ARecord {(diff/->Deletion :a) 1})
+ (diff/diff (map->ARecord {:a 1})
+ (map->ARecord {}))))
+ (is (= (diff/->Mismatch (map->ARecord {:a 1}) (map->BRecord {:a 1}))
+ (diff/diff (map->ARecord {:a 1})
+ (map->BRecord {:a 1}))))
+ (is (= (diff/->Mismatch {:a 1} (map->ARecord {:a 1}))
+ (diff/diff {:a 1} (map->ARecord {:a 1}))))
+ (is (= (diff/->Mismatch (map->ARecord {:a 1}) {:a 1})
+ (diff/diff (map->ARecord {:a 1}) {:a 1})))))
(is (= [{:x (diff/->Mismatch 1 2)}]
(diff/diff [{:x 1}] [{:x 2}])))
@@ -204,12 +226,30 @@
(is (= [#{0 1} {-1 [[]]}]
(diff/del+ins [0 0] [[]]))))
+;; (not= ##NaN ##NaN), which messes up test results
+;; https://stackoverflow.com/questions/16983955/check-for-nan-in-clojurescript
+(defn NaN? [node]
+;; Need to confirm that it's a Double first.
+ #?(:clj (and (instance? Double node) (Double/isNaN node))
+ :cljs
+ (and (= (.call js/toString node) (str "[object Number]"))
+ (js/eval (str node " != +" node )))))
+
+(def gen-any-except-NaN (gen/recursive-gen
+ gen/container-type
+ (gen/such-that (complement NaN?) gen/simple-type)))
+
(defspec round-trip-diff 100
- (prop/for-all [x gen/any
- y gen/any]
- (let [diff (diff/diff x y)]
- (= [x y] [(diff/left-undiff diff) (diff/right-undiff diff)]))))
+ (prop/for-all
+ [x gen-any-except-NaN
+ y gen-any-except-NaN]
+ (let [diff (diff/diff x y)]
+ (= [x y] [(diff/left-undiff diff) (diff/right-undiff diff)]))))
+(defspec diff-same-is-same 100
+ (prop/for-all
+ [x gen-any-except-NaN]
+ (= x (diff/diff x x))))
(deftest diff-seq-test
(is (= [(diff/->Insertion 1) 2 (diff/->Insertion 3)]
@@ -265,18 +305,15 @@
(diff/diff-seq [:a :b :c] [:a :c :d]))))
-
-
-
(comment
(use 'kaocha.repl)
(run)
- (defmethod clojure.core/print-method lambdaisland.deep-diff.diff.Insertion [v writer]
+ (defmethod clojure.core/print-method lambdaisland.deep-diff2.diff.Insertion [v writer]
(.write writer (pr-str `(diff/->Insertion ~(:+ v)))))
- (defmethod clojure.core/print-method lambdaisland.deep-diff.diff.Deletion [v writer]
+ (defmethod clojure.core/print-method lambdaisland.deep-diff2.diff.Deletion [v writer]
(.write writer (pr-str `(diff/->Deletion ~(:- v)))))
- (defmethod clojure.core/print-method lambdaisland.deep-diff.diff.Mismatch [v writer]
+ (defmethod clojure.core/print-method lambdaisland.deep-diff2.diff.Mismatch [v writer]
(.write writer (pr-str `(diff/->Mismatch ~(:- v) ~(:+ v))))))
diff --git a/test/lambdaisland/deep_diff2/minimise_test.cljc b/test/lambdaisland/deep_diff2/minimise_test.cljc
new file mode 100644
index 0000000..a113317
--- /dev/null
+++ b/test/lambdaisland/deep_diff2/minimise_test.cljc
@@ -0,0 +1,84 @@
+(ns lambdaisland.deep-diff2.minimise-test
+ (:require
+ [clojure.test :refer [deftest testing is are]]
+ [clojure.test.check.clojure-test :refer [defspec]]
+ [clojure.test.check.generators :as gen]
+ [clojure.test.check.properties :as prop]
+ [lambdaisland.deep-diff2 :as ddiff]
+ [lambdaisland.deep-diff2.diff-impl :as diff]
+ [lambdaisland.deep-diff2.diff-test :as diff-test]))
+
+(deftest basic-strip-test
+ (testing "diff without minimise"
+ (let [x {:a 1 :b 2 :d {:e 1} :g [:e [:k 14 :g 15]]}
+ y {:a 1 :c 3 :d {:e 15} :g [:e [:k 14 :g 15]]}]
+ (is (= (ddiff/diff x y)
+ {:a 1
+ (diff/->Deletion :b) 2
+ :d {:e (diff/->Mismatch 1 15)}
+ :g [:e [:k 14 :g 15]]
+ (diff/->Insertion :c) 3}))))
+ (testing "diff with minimise"
+ (let [x {:a 1 :b 2 :d {:e 1} :g [:e [:k 14 :g 15]]}
+ y {:a 1 :c 3 :d {:e 15} :g [:e [:k 14 :g 15]]}]
+ (is (= (ddiff/minimise (ddiff/diff x y))
+ {(diff/->Deletion :b) 2
+ :d {:e (diff/->Mismatch 1 15)}
+ (diff/->Insertion :c) 3})))))
+
+(deftest minimise-on-diff-test
+ (testing "diffing atoms"
+ (testing "when different"
+ (is (= (ddiff/minimise
+ (ddiff/diff :a :b))
+ (diff/->Mismatch :a :b))))
+
+ (testing "when equal"
+ (is (= (ddiff/minimise
+ (ddiff/diff :a :a))
+ nil))))
+
+ (testing "diffing collections"
+ (testing "when different collection types"
+ (is (= (ddiff/minimise
+ (ddiff/diff [:a :b] #{:a :b}))
+ (diff/->Mismatch [:a :b] #{:a :b}))))
+
+ (testing "when equal with clojure set"
+ (is (= (ddiff/minimise
+ (ddiff/diff #{:a :b} #{:a :b}))
+ #{})))
+
+ (testing "when different with clojure set"
+ (is (= (ddiff/minimise
+ (ddiff/diff #{:a :b :c} #{:a :b :d}))
+ #{(diff/->Insertion :d) (diff/->Deletion :c)})))
+
+ (testing "when equal with clojure vector"
+ (is (= (ddiff/minimise
+ (ddiff/diff [:a :b] [:a :b]))
+ [])))
+
+ (testing "when equal with clojure hashmap"
+ (is (= (ddiff/minimise
+ (ddiff/diff {:a 1} {:a 1}))
+ {})))
+
+ (testing "when equal with clojure nesting vector"
+ (is (= (ddiff/minimise
+ (ddiff/diff [:a [:b :c :d]] [:a [:b :c :d]]))
+ [])))
+
+ (testing "inserting a new map"
+ (is
+ (= (ddiff/minimise (ddiff/diff {} {:foo {:a 1}}))
+ {(diff/->Insertion :foo) {:a 1}})))))
+
+;; "diff itself and minimise yields empty"
+(defspec diff-itself 100
+ (prop/for-all
+ [x diff-test/gen-any-except-NaN]
+ (if (coll? x)
+ (= (ddiff/minimise (ddiff/diff x x))
+ (empty x))
+ (nil? (ddiff/minimise (ddiff/diff x x))))))
diff --git a/test/lambdaisland/deep_diff2/printer_test.cljc b/test/lambdaisland/deep_diff2/printer_test.cljc
new file mode 100644
index 0000000..b99f749
--- /dev/null
+++ b/test/lambdaisland/deep_diff2/printer_test.cljc
@@ -0,0 +1,48 @@
+(ns lambdaisland.deep-diff2.printer-test
+ (:require [clojure.test :refer [deftest testing is are]]
+ [lambdaisland.deep-diff2.diff-impl :as diff]
+ [lambdaisland.deep-diff2.printer-impl :as printer])
+ #?(:clj
+ (:import (java.sql Timestamp)
+ (java.util Date
+ TimeZone))))
+
+#?(:bb nil ;; GregorianCalender not included in favor of java.time
+ :clj (import '[java.util GregorianCalendar]))
+
+(defn- printed
+ [diff]
+ (let [printer (printer/puget-printer {})]
+ (with-out-str (-> diff
+ (printer/format-doc printer)
+ (printer/print-doc printer)))))
+#?(:bb nil
+ :clj
+ (defn- calendar
+ [date]
+ (doto (GregorianCalendar. (TimeZone/getTimeZone "GMT"))
+ (.setTime date))))
+
+(deftest print-doc-test
+ (testing "date"
+ (is (= "\u001B[31m-#inst \"2019-04-09T14:57:46.128-00:00\"\u001B[0m \u001B[32m+#inst \"2019-04-10T14:57:46.128-00:00\"\u001B[0m\n"
+ (printed (diff/diff #inst "2019-04-09T14:57:46.128-00:00"
+ #inst "2019-04-10T14:57:46.128-00:00")))))
+
+ #?(:bb nil ;; bb TimeStamp constructor not included as of 1.0.166
+ :clj
+ (testing "timestamp"
+ (is (= "\u001B[31m-#inst \"1970-01-01T00:00:00.000000000-00:00\"\u001B[0m \u001B[32m+#inst \"1970-01-01T00:00:01.000000101-00:00\"\u001B[0m\n"
+ (printed (diff/diff (Timestamp. 0)
+ (doto (Timestamp. 1000) (.setNanos 101))))))))
+
+ #?(:bb nil
+ :clj
+ (testing "calendar"
+ (is (= "\u001B[31m-#inst \"1970-01-01T00:00:00.000+00:00\"\u001B[0m \u001B[32m+#inst \"1970-01-01T00:00:01.001+00:00\"\u001B[0m\n"
+ (printed (diff/diff (calendar (Date. 0)) (calendar (Date. 1001))))))))
+
+ (testing "uuid"
+ (is (= "\u001B[31m-#uuid \"e41b325a-ce9d-4fdd-b51d-280d9c91314d\"\u001B[0m \u001B[32m+#uuid \"0400be9a-619f-4c6a-a735-6245e4955995\"\u001B[0m\n"
+ (printed (diff/diff #uuid "e41b325a-ce9d-4fdd-b51d-280d9c91314d"
+ #uuid "0400be9a-619f-4c6a-a735-6245e4955995"))))))
diff --git a/test/lambdaisland/deep_diff2/puget_test.cljc b/test/lambdaisland/deep_diff2/puget_test.cljc
new file mode 100644
index 0000000..01dac48
--- /dev/null
+++ b/test/lambdaisland/deep_diff2/puget_test.cljc
@@ -0,0 +1,18 @@
+(ns lambdaisland.deep-diff2.puget-test
+ (:require [clojure.test :refer [deftest testing is]]
+ [lambdaisland.deep-diff2.puget.color.html :as sut]))
+
+(deftest puget-html-test
+ (testing "properly escape html"
+ (let [input ["
"]
+ expected-result [[:span
+ [:escaped "<"] "ul id=someList" [:escaped ">"]
+ [:escaped "<"] "li class=red" [:escaped ">"]
+ "Item 1"
+ [:escaped "<"] "/li" [:escaped ">"]
+ [:escaped "<"] "li" [:escaped ">"]
+ "Item 2"
+ [:escaped "<"] "/li" [:escaped ">"]
+ [:escaped "<"] "/ul" [:escaped ">"]]]]
+ (is (= expected-result (sut/escape-html-document input))))))
+
diff --git a/test/lambdaisland/deep_diff2/runner.clj b/test/lambdaisland/deep_diff2/runner.clj
new file mode 100644
index 0000000..f323d7e
--- /dev/null
+++ b/test/lambdaisland/deep_diff2/runner.clj
@@ -0,0 +1,14 @@
+(ns lambdaisland.deep-diff2.runner
+ "Test runner for babashka, until kaocha works with bb :)"
+ (:require [clojure.test :as t]))
+
+(defn run-tests [_]
+ (let [test-nss '[lambdaisland.deep-diff2.diff-test
+ lambdaisland.deep-diff2.printer-test
+ lambdaisland.deep-diff2.puget-test]]
+ (doseq [test-ns test-nss]
+ (require test-ns))
+ (let [{:keys [fail error]}
+ (apply t/run-tests test-nss)]
+ (when (and fail error (pos? (+ fail error)))
+ (throw (ex-info "Tests failed" {:babashka/exit 1}))))))
diff --git a/test/lambdaisland/deep_diff2_test.cljc b/test/lambdaisland/deep_diff2_test.cljc
new file mode 100644
index 0000000..15cd703
--- /dev/null
+++ b/test/lambdaisland/deep_diff2_test.cljc
@@ -0,0 +1,19 @@
+(ns lambdaisland.deep-diff2-test
+ "Smoke tests of the top level API."
+ (:require [lambdaisland.deep-diff2 :as ddiff]
+ [lambdaisland.deep-diff2.diff-impl :as diff-impl]
+ [clojure.test :refer [is are deftest testing]]
+ [clojure.string :as str]))
+
+(deftest diff-test
+ (is (= [{:foo (diff-impl/->Mismatch 1 2)}]
+ (ddiff/diff [{:foo 1}] [{:foo 2}]))))
+
+(deftest printer-test
+ (is (instance? lambdaisland.deep_diff2.puget.printer.PrettyPrinter
+ (ddiff/printer))))
+
+(deftest pretty-print-test
+ (is (= "\u001B[1;31m[\u001B[0m\u001B[1;31m{\u001B[0m\u001B[1;33m:foo\u001B[0m \u001B[31m-1\u001B[0m \u001B[32m+2\u001B[0m\u001B[1;31m}\u001B[0m\u001B[1;31m]\u001B[0m\n"
+ (with-out-str
+ (ddiff/pretty-print (ddiff/diff [{:foo 1}] [{:foo 2}]))))))
diff --git a/tests.edn b/tests.edn
index d5e8da4..edd4abc 100644
--- a/tests.edn
+++ b/tests.edn
@@ -1,4 +1,6 @@
#kaocha/v1
-{:tests [{:id :unit
- :test-paths ["test"]
- :source-paths ["src"]}]}
+{:tests [{:id :clj}
+ {:id :cljs
+ :type :kaocha.type/cljs}]
+ :kaocha/bindings {kaocha.stacktrace/*stacktrace-filters* []}
+ }