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 "проверка"))