branch: elpa/extmap
commit c0dd886e0bf4d6899489583beda6e77ee165449b
Author: Paul Pogonyshev <pogonys...@gmail.com>
Commit: Paul Pogonyshev <pogonys...@gmail.com>

    Add function `extmap-equal-p`.
---
 .github/workflows/test.yml | 36 ++++++-------------
 README.md                  | 24 +++++++++++++
 extmap.el                  | 87 ++++++++++++++++++++++++++++++++++++++++++++--
 test/extmap-test.el        | 64 ++++++++++++++++++++++++++++++++--
 4 files changed, 181 insertions(+), 30 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ea3557682d..3c3c905957 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,38 +2,24 @@ name: CI
 
 on:
   push:
-    paths-ignore:
-      - '**.md'
+    paths-ignore: ['**.md', '**.adoc']
   pull_request:
-    paths-ignore:
-      - '**.md'
+    paths-ignore: ['**.md', '**.adoc']
 
 jobs:
   test:
     runs-on: ubuntu-latest
-    continue-on-error: ${{matrix.allow_failures}}
+    continue-on-error: ${{matrix.emacs_version == 'snapshot'}}
 
     strategy:
       matrix:
-        emacs_version:
-          # Not supported by Eldev; not terribly important.
-          # - 24.1
-          # - 24.2
-          # - 24.3
-          - 24.4
-          - 24.5
-          - 25.1
-          - 25.2
-          - 25.3
-          - 26.1
-          - 26.2
-          - 26.3
-
-        allow_failures: [false]
-
-        include:
-          - emacs_version: snapshot
-            allow_failures: true
+        # Not supported by Eldev; not terribly important.
+        # '24.1', '24.2', '24.3'
+        emacs_version: ['24.4',
+                        '25.1', '25.2', '25.3',
+                        '26.1', '26.2', '26.3',
+                        '27.1', '27.2',
+                        'snapshot']
 
     steps:
     - name: Set up Emacs
@@ -49,5 +35,5 @@ jobs:
 
     - name: Test the project
       run: |
-        eldev -p -dtT -C test --expect 5
+        eldev -p -dtT -C test --expect 10
         eldev -dtT -C compile --warnings-as-errors
diff --git a/README.md b/README.md
index 82dde3678b..50cdfceb8a 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,30 @@ exists.  However, you can tell it to overwrite it instead:
     (extmap-from-alist ... :overwrite t)
 
 
+## Comparing two maps
+
+Sometimes it might be useful to find out what is different between two
+maps, especially when they are two consequent versions of the same
+map.  This is not trivial as map files are binary.
+
+However, starting with 1.2 there is function `extmap-equal-p` for
+this.  By default it gives just a boolean value (`t` or `nil`), but
+optionally you can ask it to explain the differences in human-readable
+way.  Another useful option is to let it ignore all changes associated
+with certain keys.  Example usage would look like this:
+
+    (extmap-equal-p "my-data.extmap.old" "my-data.extmap"
+                    '(version cache) t)
+
+Here we compare the map stored in file `my-data.extmap` with
+(presumably a manually made) old copy in `my-data.extmap.old`.  We
+instruct the function to ignore all changes in associations for keys
+`version` and `cache`, and print information about other discovered
+differences — if any.
+
+The function can be run both from normal Emacs and non-interactively.
+
+
 ## Using a map
 
 Using a map is also easy.  First, you need to initialize it:
diff --git a/extmap.el b/extmap.el
index 1e08476fa0..c1b45880b4 100644
--- a/extmap.el
+++ b/extmap.el
@@ -80,6 +80,7 @@
                                            (offset    u32)))
 
 
+
 (defun extmap-init (filename &rest options)
   "Load metadata of a previously created map from FILENAME.
 
@@ -307,6 +308,7 @@ any time."
       (auto-reload . ,(not (null (cdr (nth 0 extmap))))))))
 
 
+
 (defun extmap-from-alist (filename data &rest options)
   "Create an externally-stored map from given DATA.
 
@@ -484,8 +486,8 @@ Only available on Emacs 25, as this requires `generator' 
package."
 
 ;; This is like built-in `equal-including-properties', except that
 ;; property values are compared with the same function, not with `eq'.
-;; Slow, but is used only during extmap creation and testing, both of
-;; which are not performance-critical.
+;; Slow, but is used only during extmap creation, testing and diffing,
+;; all of which are not performance-critical.
 (defun extmap--equal-including-properties (a b)
   (cond ((stringp a)
          (and (stringp b)
@@ -543,6 +545,87 @@ Only available on Emacs 25, as this requires `generator' 
package."
 (define-hash-table-test 'extmap--equal-including-properties 
#'extmap--equal-including-properties #'sxhash)
 
 
+
+(defun extmap-equal-p (extmap1 extmap2 &optional keys-to-ignore describe)
+  "Compare two maps.
+Don't count any differences in KEYS-TO-IGNORE (must be a list).
+Return non-nil if the two maps are equal for all other keys.
+
+When optional argument DESCRIBE is set, also print information
+about differences to a new buffer and present it, if there are
+any.  Non-interactively, print this to stdout.  The information
+is in free form meant only for humans.  Presentation can thus be
+improved or otherwise changed in future versions."
+  (when (stringp extmap1)
+    (setq extmap1 (extmap-init extmap1)))
+  (when (stringp extmap2)
+    (setq extmap2 (extmap-init extmap2)))
+  (setq keys-to-ignore (let ((lookup (make-hash-table :test #'eq)))
+                         (dolist (key keys-to-ignore)
+                           (puthash key t lookup))
+                         (remhash nil lookup)
+                         lookup))
+  (catch 'done
+    (with-temp-buffer
+      (let* ((keys1            (sort (extmap-keys extmap1) #'string<))
+             (keys2            (sort (extmap-keys extmap2) #'string<))
+             (scan1            keys1)
+             (scan2            keys2)
+             (only-in-1-lookup (make-hash-table :test #'eq))
+             only-in-1
+             only-in-2)
+        (while (or scan1 scan2)
+          (let ((key1 (car scan1))
+                (key2 (car scan2)))
+            (if (eq key1 key2)
+                (setq scan1 (cdr scan1)
+                      scan2 (cdr scan2))
+              (cond ((gethash key1 keys-to-ignore)
+                     (setq scan1 (cdr scan1)))
+                    ((gethash key2 keys-to-ignore)
+                     (setq scan2 (cdr scan2)))
+                    (t
+                     (unless describe
+                       (throw 'done nil))
+                     (if (and key1 (or (null key2) (string< key1 key2)))
+                         (progn (setq only-in-1 (cons key1 only-in-1)
+                                      scan1     (cdr scan1))
+                                (puthash key1 t only-in-1-lookup))
+                       (setq only-in-2 (cons key2 only-in-2)
+                             scan2     (cdr scan2))))))))
+        (when only-in-1
+          (insert "Only in the first extmap:\n")
+          (dolist (key (nreverse only-in-1))
+            (insert "    " (symbol-name key) "\n")))
+        (when only-in-2
+          (insert "Only in the second extmap:\n")
+          (dolist (key (nreverse only-in-2))
+            (insert "    " (symbol-name key) "\n")))
+        (dolist (key keys1)
+          (unless (or (gethash key keys-to-ignore) (gethash key 
only-in-1-lookup))
+            (let ((value1 (extmap-get extmap1 key))
+                  (value2 (extmap-get extmap2 key)))
+              (unless (extmap--equal-including-properties value1 value2)
+                (unless describe
+                  (throw 'done nil))
+                (insert (symbol-name key) ":\n" (prin1-to-string value1) "\n" 
(prin1-to-string value2) "\n"))))))
+      (if describe
+          (if (= (point) 1)
+              (progn (message "There are no differences")
+                     t)
+            (let ((differences (buffer-string)))
+              (if noninteractive
+                  (princ differences)
+                (let ((buffer (get-buffer-create " *Extmap differences*")))
+                  (set-buffer buffer)
+                  (erase-buffer)
+                  (insert differences)
+                  (goto-char (point-min))
+                  (display-buffer buffer))))
+            nil)
+        t))))
+
+
 (provide 'extmap)
 
 ;;; extmap.el ends here
diff --git a/test/extmap-test.el b/test/extmap-test.el
index 545cd56fa7..b095ace324 100644
--- a/test/extmap-test.el
+++ b/test/extmap-test.el
@@ -29,15 +29,19 @@
   (let ((filename (concat extmap--test-directory (or extmap--test-filename 
"test.extmap"))))
     (apply #'extmap-from-alist filename data :overwrite t options)
     (let ((extmap (extmap-init filename)))
-      (should (equal (extmap--test-sort-keys (mapcar #'car data)) 
(extmap--test-sort-keys (extmap-keys extmap))))
+      (should (equal (sort (mapcar #'car data) #'string<) (sort (extmap-keys 
extmap) #'string<)))
       (dolist (entry data)
         (should (extmap-contains-key extmap (car entry)))
         (should (extmap--equal-including-properties (extmap-get extmap (car 
entry)) (cdr entry)))
         (should (extmap-value-loaded extmap (car entry))))
       extmap)))
 
-(defun extmap--test-sort-keys (keys)
-  (sort keys (lambda (a b) (string< (symbol-name a) (symbol-name b)))))
+(defun extmap--test-compare (data1 data2 &optional keys-to-ignore &rest 
options)
+  (let* ((filename1 (concat extmap--test-directory (or extmap--test-filename 
"test1.extmap")))
+         (filename2 (concat extmap--test-directory (or extmap--test-filename 
"test2.extmap"))))
+    (apply #'extmap-from-alist filename1 data1 :overwrite t options)
+    (apply #'extmap-from-alist filename2 data2 :overwrite t options)
+    (extmap-equal-p filename1 filename2 keys-to-ignore)))
 
 (defun extmap--test-compress-value (value)
   (let ((compressed (extmap--compress-value value (make-hash-table :test 
#'extmap--equal-including-properties))))
@@ -98,6 +102,60 @@
     (should     (eq (nth 0 foo) (nth 1 foo)))))
 
 
+(ert-deftest extmap-equal-p-1 ()
+  (should (extmap--test-compare `((foo  . 1))
+                                `((foo  . 1))))
+  (should (extmap--test-compare `((foo  . 1)
+                                  (bar  . "string")
+                                  (baz  . ,(number-sequence 0 100))
+                                  (spam . "lalala lalala lalala lalala lalala 
lalala lalala lalala lalala lalala lalala"))
+                                `((foo  . 1)
+                                  (bar  . "string")
+                                  (baz  . ,(number-sequence 0 100))
+                                  (spam . "lalala lalala lalala lalala lalala 
lalala lalala lalala lalala lalala lalala")))))
+
+(ert-deftest extmap-equal-p-2 ()
+  (should-not (extmap--test-compare `((foo . 1))
+                                    `((foo . 2))))
+  (should-not (extmap--test-compare `((foo . 1))
+                                    `((foo . 2))
+                                    '(what?)))
+  (should-not (extmap--test-compare `((foo . 1))
+                                    `((bar . 2))))
+  (should-not (extmap--test-compare `((foo . 1))
+                                    `((foo . 1)
+                                      (bar . 2))))
+  (should-not (extmap--test-compare `((foo . 1)
+                                      (bar . 2))
+                                    `((foo . 1)))))
+
+(ert-deftest extmap-equal-p-3 ()
+  (should (extmap--test-compare `((foo . 1))
+                                `((foo . 2))
+                                '(foo)))
+  (should (extmap--test-compare `((foo . 1))
+                                `((bar . 2))
+                                '(foo bar)))
+  (should (extmap--test-compare `((foo . 1))
+                                `((foo . 1)
+                                  (bar . 2))
+                                '(bar)))
+  (should (extmap--test-compare `((foo . 1)
+                                  (bar . 2))
+                                `((foo . 1))
+                                '(bar))))
+
+(ert-deftest extmap-equal-p-nonascii-1 ()
+  (should (extmap--test-compare `((раз    . 1)
+                                  (два    . "два")
+                                  (три    . ,(cons "ноль" (number-sequence 1 
100)))
+                                  (четыре . "В траве сидел кузнечик, // В 
траве сидел кузнечик, // Совсем как огуречик, // Зелененький он был."))
+                                `((раз    . 1)
+                                  (два    . "два")
+                                  (три    . ,(cons "ноль" (number-sequence 1 
100)))
+                                  (четыре . "В траве сидел кузнечик, // В 
траве сидел кузнечик, // Совсем как огуречик, // Зелененький он был.")))))
+
+
 (ert-deftest extmap-plain-string-p ()
   (should (extmap--plain-string-p "foo"))
   (should (extmap--plain-string-p "проверка"))

Reply via email to