aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Cassou2020-08-09 14:48:22 +0200
committerLars Ingebrigtsen2020-08-09 14:48:22 +0200
commit8e82baf5a730ff542118ddba5b76afdc1db643f6 (patch)
tree6870659b38a168709c2f98f571c35a6451ecb64f
parentd586bae501a3d6ec8e6a8088d05b0abfa541dece (diff)
downloademacs-8e82baf5a730ff542118ddba5b76afdc1db643f6.tar.gz
emacs-8e82baf5a730ff542118ddba5b76afdc1db643f6.zip
Add the new library hierarchy.el
* lisp/emacs-lisp/hierarchy.el: New file.
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/emacs-lisp/hierarchy.el579
-rw-r--r--test/lisp/emacs-lisp/hierarchy-tests.el556
3 files changed, 1139 insertions, 0 deletions
diff --git a/etc/NEWS b/etc/NEWS
index b983b290d72..8118272070e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -737,6 +737,10 @@ The recentf files are no longer backed up.
737 737
738** Miscellaneous 738** Miscellaneous
739 739
740*** The new library hierarchy.el has been added.
741It's a library to create, query, navigate and display hierarchy
742structures.
743
740--- 744---
741*** The width of the buffer-name column in 'list-buffers' is now dynamic. 745*** The width of the buffer-name column in 'list-buffers' is now dynamic.
742The width now depends of the width of the window, but will never be 746The width now depends of the width of the window, but will never be
diff --git a/lisp/emacs-lisp/hierarchy.el b/lisp/emacs-lisp/hierarchy.el
new file mode 100644
index 00000000000..8cef029c4cf
--- /dev/null
+++ b/lisp/emacs-lisp/hierarchy.el
@@ -0,0 +1,579 @@
1;;; hierarchy.el --- Library to create and display hierarchy structures -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2020 Free Software Foundation, Inc.
4
5;; Author: Damien Cassou <damien@cassou.me>
6;; Maintainer: emacs-devel@gnu.org
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; Library to create, query, navigate and display hierarchy structures.
26
27;; Creation: After having created a hierarchy with `hierarchy-new',
28;; populate it by calling `hierarchy-add-tree' or
29;; `hierarchy-add-trees'. You can then optionally sort its element
30;; with `hierarchy-sort'.
31
32;; Querying: You can learn more about your hierarchy by using
33;; functions such as `hierarchy-roots', `hierarchy-has-item',
34;; `hierarchy-length', `hierarchy-parent', `hierarchy-descendant-p'.
35
36;; Navigation: When your hierarchy is ready, you can use
37;; `hierarchy-map-item', `hierarchy-map', and `map-tree' to apply
38;; functions to elements of the hierarchy.
39
40;; Display: You can display a hierarchy as a tabulated list using
41;; `hierarchy-tabulated-display' and as an expandable/foldable tree
42;; using `hierarchy-convert-to-tree-widget'. The
43;; `hierarchy-labelfn-*' functions will help you display each item of
44;; the hierarchy the way you want it.
45
46;;; Limitation:
47
48;; - Current implementation uses #'equal to find and distinguish
49;; elements. Support for user-provided equality definition is
50;; desired but not yet implemented;
51;;
52;; - nil can't be added to a hierarchy;
53;;
54;; - the hierarchy is computed eagerly.
55
56;;; Code:
57
58(require 'seq)
59(require 'map)
60(require 'subr-x)
61(require 'cl-lib)
62
63
64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
65;; Helpers
66;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
67
68(cl-defstruct (hierarchy
69 (:constructor hierarchy--make)
70 (:conc-name hierarchy--))
71 (roots (list)) ; list of the hierarchy roots (no parent)
72 (parents (make-hash-table :test 'equal)) ; map an item to its parent
73 (children (make-hash-table :test 'equal)) ; map an item to its childre
74 ;; cache containing the set of all items in the hierarchy
75 (seen-items (make-hash-table :test 'equal))) ; map an item to t
76
77(defun hierarchy--seen-items-add (hierarchy item)
78 "In HIERARCHY, add ITEM to seen items."
79 (map-put! (hierarchy--seen-items hierarchy) item t))
80
81(defun hierarchy--compute-roots (hierarchy)
82 "Search roots of HIERARCHY and return them."
83 (cl-set-difference
84 (map-keys (hierarchy--seen-items hierarchy))
85 (map-keys (hierarchy--parents hierarchy))
86 :test #'equal))
87
88(defun hierarchy--sort-roots (hierarchy sortfn)
89 "Compute, sort and store the roots of HIERARCHY.
90
91SORTFN is a function taking two items of the hierarchy as parameter and
92returning non-nil if the first parameter is lower than the second."
93 (setf (hierarchy--roots hierarchy)
94 (sort (hierarchy--compute-roots hierarchy)
95 sortfn)))
96
97(defun hierarchy--add-relation (hierarchy item parent acceptfn)
98 "In HIERARCHY, add ITEM as child of PARENT.
99
100ACCEPTFN is a function returning non-nil if its parameter (any object)
101should be an item of the hierarchy."
102 (let* ((existing-parent (hierarchy-parent hierarchy item))
103 (has-parent-p (funcall acceptfn existing-parent)))
104 (cond
105 ((and has-parent-p (not (equal existing-parent parent)))
106 (error "An item (%s) can only have one parent: '%s' vs '%s'"
107 item existing-parent parent))
108 ((not has-parent-p)
109 (let ((existing-children (map-elt (hierarchy--children hierarchy)
110 parent (list))))
111 (map-put! (hierarchy--children hierarchy)
112 parent (append existing-children (list item))))
113 (map-put! (hierarchy--parents hierarchy) item parent)))))
114
115(defun hierarchy--set-equal (list1 list2 &rest cl-keys)
116 "Return non-nil if LIST1 and LIST2 have same elements.
117
118I.e., if every element of LIST1 also appears in LIST2 and if
119every element of LIST2 also appears in LIST1.
120
121CL-KEYS are key-value pairs just like in `cl-subsetp'. Supported
122keys are :key and :test."
123 (and (apply 'cl-subsetp list1 list2 cl-keys)
124 (apply 'cl-subsetp list2 list1 cl-keys)))
125
126
127;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
128;; Creation
129;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
130
131(defun hierarchy-new ()
132 "Create a hierarchy and return it."
133 (hierarchy--make))
134
135(defun hierarchy-add-tree (hierarchy item parentfn &optional childrenfn acceptfn)
136 "In HIERARCHY, add ITEM.
137
138PARENTFN is either nil or a function defining the child-to-parent
139relationship: this function takes an item as parameter and should return
140the parent of this item in the hierarchy. If the item has no parent in the
141hierarchy (i.e., it should be a root), the function should return an object
142not accepted by acceptfn (i.e., nil for the default value of acceptfn).
143
144CHILDRENFN is either nil or a function defining the parent-to-children
145relationship: this function takes an item as parameter and should return a
146list of children of this item in the hierarchy.
147
148If both PARENTFN and CHILDRENFN are non-nil, the results of PARENTFN and
149CHILDRENFN are expected to be coherent with each other.
150
151ACCEPTFN is a function returning non-nil if its parameter (any object)
152should be an item of the hierarchy. By default, ACCEPTFN returns non-nil
153if its parameter is non-nil."
154 (unless (hierarchy-has-item hierarchy item)
155 (let ((acceptfn (or acceptfn #'identity)))
156 (hierarchy--seen-items-add hierarchy item)
157 (let ((parent (and parentfn (funcall parentfn item))))
158 (when (funcall acceptfn parent)
159 (hierarchy--add-relation hierarchy item parent acceptfn)
160 (hierarchy-add-tree hierarchy parent parentfn childrenfn)))
161 (let ((children (and childrenfn (funcall childrenfn item))))
162 (mapc (lambda (child)
163 (when (funcall acceptfn child)
164 (hierarchy--add-relation hierarchy child item acceptfn)
165 (hierarchy-add-tree hierarchy child parentfn childrenfn)))
166 children)))))
167
168(defun hierarchy-add-trees (hierarchy items parentfn &optional childrenfn acceptfn)
169 "Call `hierarchy-add-tree' on HIERARCHY and each element of ITEMS.
170
171PARENTFN, CHILDRENFN and ACCEPTFN have the same meaning as in `hierarchy-add'."
172 (seq-map (lambda (item)
173 (hierarchy-add-tree hierarchy item parentfn childrenfn acceptfn))
174 items))
175
176(defun hierarchy-add-list (hierarchy list &optional wrap childrenfn)
177 "Add to HIERARCHY the sub-lists in LIST.
178
179If WRAP is non-nil, allow duplicate items in LIST by wraping each
180item in a cons (id . item). The root's id is 1.
181
182CHILDRENFN is a function (defaults to `cdr') taking LIST as a
183parameter which should return LIST's children (a list). Each
184child is (recursively) passed as a parameter to CHILDRENFN to get
185its own children. Because of this parameter, LIST can be
186anything, not necessarily a list."
187 (let* ((childrenfn (or childrenfn #'cdr))
188 (id 0)
189 (wrapfn (lambda (item)
190 (if wrap
191 (cons (setq id (1+ id)) item)
192 item)))
193 (unwrapfn (if wrap #'cdr #'identity)))
194 (hierarchy-add-tree
195 hierarchy (funcall wrapfn list) nil
196 (lambda (item)
197 (mapcar wrapfn (funcall childrenfn
198 (funcall unwrapfn item)))))
199 hierarchy))
200
201(defun hierarchy-from-list (list &optional wrap childrenfn)
202 "Create and return a hierarchy built from LIST.
203
204This function passes LIST, WRAP and CHILDRENFN unchanged to
205`hierarchy-add-list'."
206 (hierarchy-add-list (hierarchy-new) list wrap childrenfn))
207
208(defun hierarchy-sort (hierarchy &optional sortfn)
209 "Modify HIERARCHY so that its roots and item's children are sorted.
210
211SORTFN is a function taking two items of the hierarchy as parameter and
212returning non-nil if the first parameter is lower than the second. By
213default, SORTFN is `string-lessp'."
214 (let ((sortfn (or sortfn #'string-lessp)))
215 (hierarchy--sort-roots hierarchy sortfn)
216 (mapc (lambda (parent)
217 (setf
218 (map-elt (hierarchy--children hierarchy) parent)
219 (sort (map-elt (hierarchy--children hierarchy) parent) sortfn)))
220 (map-keys (hierarchy--children hierarchy)))))
221
222(defun hierarchy-extract-tree (hierarchy item)
223 "Return a copy of HIERARCHY with ITEM's descendants and parents."
224 (if (not (hierarchy-has-item hierarchy item))
225 nil
226 (let ((tree (hierarchy-new)))
227 (hierarchy-add-tree tree item
228 (lambda (each) (hierarchy-parent hierarchy each))
229 (lambda (each)
230 (when (or (equal each item)
231 (hierarchy-descendant-p hierarchy each item))
232 (hierarchy-children hierarchy each))))
233 tree)))
234
235(defun hierarchy-copy (hierarchy)
236 "Return a copy of HIERARCHY.
237
238Items in HIERARCHY are shared, but structure is not."
239 (hierarchy-map-hierarchy (lambda (item _) (identity item)) hierarchy))
240
241
242;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
243;; Querying
244;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
245
246(defun hierarchy-items (hierarchy)
247 "Return a list of all items of HIERARCHY."
248 (map-keys (hierarchy--seen-items hierarchy)))
249
250(defun hierarchy-has-item (hierarchy item)
251 "Return t if HIERARCHY includes ITEM."
252 (map-contains-key (hierarchy--seen-items hierarchy) item))
253
254(defun hierarchy-empty-p (hierarchy)
255 "Return t if HIERARCHY is empty."
256 (= 0 (hierarchy-length hierarchy)))
257
258(defun hierarchy-length (hierarchy)
259 "Return the number of items in HIERARCHY."
260 (hash-table-count (hierarchy--seen-items hierarchy)))
261
262(defun hierarchy-has-root (hierarchy item)
263 "Return t if one of HIERARCHY's roots is ITEM.
264
265A root is an item with no parent."
266 (seq-contains-p (hierarchy-roots hierarchy) item))
267
268(defun hierarchy-roots (hierarchy)
269 "Return all roots of HIERARCHY.
270
271A root is an item with no parent."
272 (let ((roots (hierarchy--roots hierarchy)))
273 (or roots
274 (hierarchy--compute-roots hierarchy))))
275
276(defun hierarchy-leafs (hierarchy &optional node)
277 "Return all leafs of HIERARCHY.
278
279A leaf is an item with no child.
280
281If NODE is an item of HIERARCHY, only return leafs under NODE."
282 (let ((leafs (cl-set-difference
283 (map-keys (hierarchy--seen-items hierarchy))
284 (map-keys (hierarchy--children hierarchy)))))
285 (if (hierarchy-has-item hierarchy node)
286 (seq-filter (lambda (item)
287 (hierarchy-descendant-p hierarchy item node))
288 leafs)
289 leafs)))
290
291(defun hierarchy-parent (hierarchy item)
292 "In HIERARCHY, return parent of ITEM."
293 (map-elt (hierarchy--parents hierarchy) item))
294
295(defun hierarchy-children (hierarchy parent)
296 "In HIERARCHY, return children of PARENT."
297 (map-elt (hierarchy--children hierarchy) parent (list)))
298
299(defun hierarchy-child-p (hierarchy item1 item2)
300 "In HIERARCHY, return non-nil if and only if ITEM1 is a child of ITEM2."
301 (equal (hierarchy-parent hierarchy item1) item2))
302
303(defun hierarchy-descendant-p (hierarchy item1 item2)
304 "In HIERARCHY, return non-nil if and only if ITEM1 is a descendant of ITEM2.
305
306ITEM1 is a descendant of ITEM2 if and only if both are items of HIERARCHY
307and either:
308
309- ITEM1 is child of ITEM2, or
310- ITEM1's parent is a descendant of ITEM2."
311 (and
312 (hierarchy-has-item hierarchy item1)
313 (hierarchy-has-item hierarchy item2)
314 (or
315 (hierarchy-child-p hierarchy item1 item2)
316 (hierarchy-descendant-p
317 hierarchy (hierarchy-parent hierarchy item1) item2))))
318
319(defun hierarchy-equal (hierarchy1 hierarchy2)
320 "Return t if HIERARCHY1 and HIERARCHY2 are equal.
321
322Two equal hierarchies share the same items and the same
323relationships among them."
324 (and (hierarchy-p hierarchy1)
325 (hierarchy-p hierarchy2)
326 (= (hierarchy-length hierarchy1) (hierarchy-length hierarchy2))
327 ;; parents are the same
328 (seq-every-p (lambda (child)
329 (equal (hierarchy-parent hierarchy1 child)
330 (hierarchy-parent hierarchy2 child)))
331 (map-keys (hierarchy--parents hierarchy1)))
332 ;; children are the same
333 (seq-every-p (lambda (parent)
334 (hierarchy--set-equal
335 (hierarchy-children hierarchy1 parent)
336 (hierarchy-children hierarchy2 parent)
337 :test #'equal))
338 (map-keys (hierarchy--children hierarchy1)))))
339
340
341;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
342;; Navigation
343;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
344
345(defun hierarchy-map-item (func item hierarchy &optional indent)
346 "Return the result of applying FUNC to ITEM and its descendants in HIERARCHY.
347
348This function navigates the tree top-down: FUNCTION is first called on item
349and then on each of its children. Results are concatenated in a list.
350
351INDENT is a number (default 0) representing the indentation of ITEM in
352HIERARCHY. FUNC should take 2 argument: the item and its indentation
353level."
354 (let ((indent (or indent 0)))
355 (cons
356 (funcall func item indent)
357 (seq-mapcat (lambda (child) (hierarchy-map-item func child
358 hierarchy (1+ indent)))
359 (hierarchy-children hierarchy item)))))
360
361(defun hierarchy-map (func hierarchy &optional indent)
362 "Return the result of applying FUNC to each element of HIERARCHY.
363
364This function navigates the tree top-down: FUNCTION is first called on each
365root. To do so, it calls `hierarchy-map-item' on each root
366sequentially. Results are concatenated in a list.
367
368FUNC should take 2 arguments: the item and its indentation level.
369
370INDENT is a number (default 0) representing the indentation of HIERARCHY's
371roots."
372 (let ((indent (or indent 0)))
373 (seq-mapcat (lambda (root) (hierarchy-map-item func root hierarchy indent))
374 (hierarchy-roots hierarchy))))
375
376(defun hierarchy-map-tree (function hierarchy &optional item indent)
377 "Apply FUNCTION on each item of HIERARCHY under ITEM.
378
379This function navigates the tree bottom-up: FUNCTION is first called on
380leafs and the result is passed as parameter when calling FUNCTION on
381parents.
382
383FUNCTION should take 3 parameters: the current item, its indentation
384level (a number), and a list representing the result of applying
385`hierarchy-map-tree' to each child of the item.
386
387INDENT is 0 by default and is passed as second parameter to FUNCTION.
388INDENT is incremented by 1 at each level of the tree.
389
390This function returns the result of applying FUNCTION to ITEM (the first
391root if nil)."
392 (let ((item (or item (car (hierarchy-roots hierarchy))))
393 (indent (or indent 0)))
394 (funcall function item indent
395 (mapcar (lambda (child)
396 (hierarchy-map-tree function hierarchy
397 child (1+ indent)))
398 (hierarchy-children hierarchy item)))))
399
400(defun hierarchy-map-hierarchy (function hierarchy)
401 "Apply FUNCTION to each item of HIERARCHY in a new hierarchy.
402
403FUNCTION should take 2 parameters, the current item and its
404indentation level (a number), and should return an item to be
405added to the new hierarchy."
406 (let* ((items (make-hash-table :test #'equal))
407 (transform (lambda (item) (map-elt items item))))
408 ;; Make 'items', a table mapping original items to their
409 ;; transformation
410 (hierarchy-map (lambda (item indent)
411 (map-put! items item (funcall function item indent)))
412 hierarchy)
413 (hierarchy--make
414 :roots (mapcar transform (hierarchy-roots hierarchy))
415 :parents (let ((result (make-hash-table :test #'equal)))
416 (map-apply (lambda (child parent)
417 (map-put! result
418 (funcall transform child)
419 (funcall transform parent)))
420 (hierarchy--parents hierarchy))
421 result)
422 :children (let ((result (make-hash-table :test #'equal)))
423 (map-apply (lambda (parent children)
424 (map-put! result
425 (funcall transform parent)
426 (seq-map transform children)))
427 (hierarchy--children hierarchy))
428 result)
429 :seen-items (let ((result (make-hash-table :test #'equal)))
430 (map-apply (lambda (item v)
431 (map-put! result
432 (funcall transform item)
433 v))
434 (hierarchy--seen-items hierarchy))
435 result))))
436
437
438;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
439;; Display
440;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
441
442(defun hierarchy-labelfn-indent (labelfn &optional indent-string)
443 "Return a function rendering LABELFN indented with INDENT-STRING.
444
445INDENT-STRING defaults to a 2-space string. Indentation is
446multiplied by the depth of the displayed item."
447 (let ((indent-string (or indent-string " ")))
448 (lambda (item indent)
449 (dotimes (_ indent) (insert indent-string))
450 (funcall labelfn item indent))))
451
452(defun hierarchy-labelfn-button (labelfn actionfn)
453 "Return a function rendering LABELFN in a button.
454
455Clicking the button triggers ACTIONFN. ACTIONFN is a function
456taking an item of HIERARCHY and an indentation value (a number)
457as input. This function is called when an item is clicked. The
458return value of ACTIONFN is ignored."
459 (lambda (item indent)
460 (let ((start (point)))
461 (funcall labelfn item indent)
462 (make-text-button start (point)
463 'action (lambda (_) (funcall actionfn item indent))))))
464
465(defun hierarchy-labelfn-button-if (labelfn buttonp actionfn)
466 "Return a function rendering LABELFN as a button if BUTTONP.
467
468Pass LABELFN and ACTIONFN to `hierarchy-labelfn-button' if
469BUTTONP is non-nil. Otherwise, render LABELFN without making it
470a button.
471
472BUTTONP is a function taking an item of HIERARCHY and an
473indentation value (a number) as input."
474 (lambda (item indent)
475 (if (funcall buttonp item indent)
476 (funcall (hierarchy-labelfn-button labelfn actionfn) item indent)
477 (funcall labelfn item indent))))
478
479(defun hierarchy-labelfn-to-string (labelfn item indent)
480 "Execute LABELFN on ITEM and INDENT. Return result as a string."
481 (with-temp-buffer
482 (funcall labelfn item indent)
483 (buffer-substring (point-min) (point-max))))
484
485(defun hierarchy-print (hierarchy &optional to-string)
486 "Insert HIERARCHY in current buffer as plain text.
487
488Use TO-STRING to convert each element to a string. TO-STRING is
489a function taking an item of HIERARCHY as input and returning a
490string. If nil, TO-STRING defaults to a call to `format' with \"%s\"."
491 (let ((to-string (or to-string (lambda (item) (format "%s" item)))))
492 (hierarchy-map
493 (hierarchy-labelfn-indent (lambda (item _)
494 (insert (funcall to-string item) "\n")))
495 hierarchy)))
496
497(defun hierarchy-to-string (hierarchy &optional to-string)
498 "Return a string representing HIERARCHY.
499
500TO-STRING is passed unchanged to `hierarchy-print'."
501 (with-temp-buffer
502 (hierarchy-print hierarchy to-string)
503 (buffer-substring (point-min) (point-max))))
504
505(defun hierarchy-tabulated-imenu-action (_item-name position)
506 "Move to ITEM-NAME at POSITION in current buffer."
507 (goto-char position)
508 (back-to-indentation))
509
510(define-derived-mode hierarchy-tabulated-mode tabulated-list-mode "Hierarchy tabulated"
511 "Major mode to display a hierarchy as a tabulated list."
512 (setq-local imenu-generic-expression
513 ;; debbugs: 26457 - Cannot pass a function to
514 ;; imenu-generic-expression. Add
515 ;; `hierarchy-tabulated-imenu-action' to the end of the
516 ;; list when bug is fixed
517 '(("Item" "^[[:space:]]+\\(?1:.+\\)$" 1))))
518
519(defun hierarchy-tabulated-display (hierarchy labelfn &optional buffer)
520 "Display HIERARCHY as a tabulated list in `hierarchy-tabulated-mode'.
521
522LABELFN is a function taking an item of HIERARCHY and an indentation
523level (a number) as input and inserting a string to be displayed in the
524table.
525
526The tabulated list is displayed in BUFFER, or a newly created buffer if
527nil. The buffer is returned."
528 (let ((buffer (or buffer (generate-new-buffer "hierarchy-tabulated"))))
529 (with-current-buffer buffer
530 (hierarchy-tabulated-mode)
531 (setq tabulated-list-format
532 (vector '("Item name" 0 nil)))
533 (setq tabulated-list-entries
534 (hierarchy-map (lambda (item indent)
535 (list item (vector (hierarchy-labelfn-to-string
536 labelfn item indent))))
537 hierarchy))
538 (tabulated-list-init-header)
539 (tabulated-list-print))
540 buffer))
541
542(declare-function widget-convert "wid-edit")
543(defun hierarchy-convert-to-tree-widget (hierarchy labelfn)
544 "Return a tree-widget for HIERARCHY.
545
546LABELFN is a function taking an item of HIERARCHY and an indentation
547value (a number) as parameter and inserting a string to be displayed as a
548node label."
549 (require 'wid-edit)
550 (require 'tree-widget)
551 (hierarchy-map-tree (lambda (item indent children)
552 (widget-convert
553 'tree-widget
554 :tag (hierarchy-labelfn-to-string labelfn item indent)
555 :args children))
556 hierarchy))
557
558(defun hierarchy-tree-display (hierarchy labelfn &optional buffer)
559 "Display HIERARCHY as a tree widget in a new buffer.
560
561HIERARCHY and LABELFN are passed unchanged to
562`hierarchy-convert-to-tree-widget'.
563
564The tree widget is displayed in BUFFER, or a newly created buffer if
565nil. The buffer is returned."
566 (let ((buffer (or buffer (generate-new-buffer "*hierarchy-tree*")))
567 (tree-widget (hierarchy-convert-to-tree-widget hierarchy labelfn)))
568 (with-current-buffer buffer
569 (setq-local buffer-read-only t)
570 (let ((inhibit-read-only t))
571 (erase-buffer)
572 (widget-create tree-widget)
573 (goto-char (point-min))
574 (special-mode)))
575 buffer))
576
577(provide 'hierarchy)
578
579;;; hierarchy.el ends here
diff --git a/test/lisp/emacs-lisp/hierarchy-tests.el b/test/lisp/emacs-lisp/hierarchy-tests.el
new file mode 100644
index 00000000000..23cfc79d848
--- /dev/null
+++ b/test/lisp/emacs-lisp/hierarchy-tests.el
@@ -0,0 +1,556 @@
1;;; hierarchy-tests.el --- Tests for hierarchy.el
2
3;; Copyright (C) 2017-2019 Damien Cassou
4
5;; Author: Damien Cassou <damien@cassou.me>
6;; Maintainer: emacs-devel@gnu.org
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; Tests for hierarchy.el
26
27;;; Code:
28
29(require 'ert)
30(require 'hierarchy)
31
32(defun hierarchy-animals ()
33 "Create a sorted animal hierarchy."
34 (let ((parentfn (lambda (item) (cl-case item
35 (dove 'bird)
36 (pigeon 'bird)
37 (bird 'animal)
38 (dolphin 'animal)
39 (cow 'animal))))
40 (hierarchy (hierarchy-new)))
41 (hierarchy-add-tree hierarchy 'dove parentfn)
42 (hierarchy-add-tree hierarchy 'pigeon parentfn)
43 (hierarchy-add-tree hierarchy 'dolphin parentfn)
44 (hierarchy-add-tree hierarchy 'cow parentfn)
45 (hierarchy-sort hierarchy)
46 hierarchy))
47
48(ert-deftest hierarchy-add-one-root ()
49 (let ((parentfn (lambda (_) nil))
50 (hierarchy (hierarchy-new)))
51 (hierarchy-add-tree hierarchy 'animal parentfn)
52 (should (equal (hierarchy-roots hierarchy) '(animal)))))
53
54(ert-deftest hierarchy-add-one-item-with-parent ()
55 (let ((parentfn (lambda (item)
56 (cl-case item
57 (bird 'animal))))
58 (hierarchy (hierarchy-new)))
59 (hierarchy-add-tree hierarchy 'bird parentfn)
60 (should (equal (hierarchy-roots hierarchy) '(animal)))
61 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))))
62
63(ert-deftest hierarchy-add-one-item-with-parent-and-grand-parent ()
64 (let ((parentfn (lambda (item)
65 (cl-case item
66 (dove 'bird)
67 (bird 'animal))))
68 (hierarchy (hierarchy-new)))
69 (hierarchy-add-tree hierarchy 'dove parentfn)
70 (should (equal (hierarchy-roots hierarchy) '(animal)))
71 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))
72 (should (equal (hierarchy-children hierarchy 'bird) '(dove)))))
73
74(ert-deftest hierarchy-add-same-root-twice ()
75 (let ((parentfn (lambda (_) nil))
76 (hierarchy (hierarchy-new)))
77 (hierarchy-add-tree hierarchy 'animal parentfn)
78 (hierarchy-add-tree hierarchy 'animal parentfn)
79 (should (equal (hierarchy-roots hierarchy) '(animal)))))
80
81(ert-deftest hierarchy-add-same-child-twice ()
82 (let ((parentfn (lambda (item)
83 (cl-case item
84 (bird 'animal))))
85 (hierarchy (hierarchy-new)))
86 (hierarchy-add-tree hierarchy 'bird parentfn)
87 (hierarchy-add-tree hierarchy 'bird parentfn)
88 (should (equal (hierarchy-roots hierarchy) '(animal)))
89 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))))
90
91(ert-deftest hierarchy-add-item-and-its-parent ()
92 (let ((parentfn (lambda (item)
93 (cl-case item
94 (bird 'animal))))
95 (hierarchy (hierarchy-new)))
96 (hierarchy-add-tree hierarchy 'bird parentfn)
97 (hierarchy-add-tree hierarchy 'animal parentfn)
98 (should (equal (hierarchy-roots hierarchy) '(animal)))
99 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))))
100
101(ert-deftest hierarchy-add-item-and-its-child ()
102 (let ((parentfn (lambda (item)
103 (cl-case item
104 (bird 'animal))))
105 (hierarchy (hierarchy-new)))
106 (hierarchy-add-tree hierarchy 'animal parentfn)
107 (hierarchy-add-tree hierarchy 'bird parentfn)
108 (should (equal (hierarchy-roots hierarchy) '(animal)))
109 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))))
110
111(ert-deftest hierarchy-add-two-items-sharing-parent ()
112 (let ((parentfn (lambda (item)
113 (cl-case item
114 (dove 'bird)
115 (pigeon 'bird))))
116 (hierarchy (hierarchy-new)))
117 (hierarchy-add-tree hierarchy 'dove parentfn)
118 (hierarchy-add-tree hierarchy 'pigeon parentfn)
119 (should (equal (hierarchy-roots hierarchy) '(bird)))
120 (should (equal (hierarchy-children hierarchy 'bird) '(dove pigeon)))))
121
122(ert-deftest hierarchy-add-two-hierarchies ()
123 (let ((parentfn (lambda (item)
124 (cl-case item
125 (dove 'bird)
126 (circle 'shape))))
127 (hierarchy (hierarchy-new)))
128 (hierarchy-add-tree hierarchy 'dove parentfn)
129 (hierarchy-add-tree hierarchy 'circle parentfn)
130 (should (equal (hierarchy-roots hierarchy) '(bird shape)))
131 (should (equal (hierarchy-children hierarchy 'bird) '(dove)))
132 (should (equal (hierarchy-children hierarchy 'shape) '(circle)))))
133
134(ert-deftest hierarchy-add-with-childrenfn ()
135 (let ((childrenfn (lambda (item)
136 (cl-case item
137 (animal '(bird))
138 (bird '(dove pigeon)))))
139 (hierarchy (hierarchy-new)))
140 (hierarchy-add-tree hierarchy 'animal nil childrenfn)
141 (should (equal (hierarchy-roots hierarchy) '(animal)))
142 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))
143 (should (equal (hierarchy-children hierarchy 'bird) '(dove pigeon)))))
144
145(ert-deftest hierarchy-add-with-parentfn-and-childrenfn ()
146 (let ((parentfn (lambda (item)
147 (cl-case item
148 (bird 'animal)
149 (animal 'life-form))))
150 (childrenfn (lambda (item)
151 (cl-case item
152 (bird '(dove pigeon))
153 (pigeon '(ashy-wood-pigeon)))))
154 (hierarchy (hierarchy-new)))
155 (hierarchy-add-tree hierarchy 'bird parentfn childrenfn)
156 (should (equal (hierarchy-roots hierarchy) '(life-form)))
157 (should (equal (hierarchy-children hierarchy 'life-form) '(animal)))
158 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))
159 (should (equal (hierarchy-children hierarchy 'bird) '(dove pigeon)))
160 (should (equal (hierarchy-children hierarchy 'pigeon) '(ashy-wood-pigeon)))))
161
162(ert-deftest hierarchy-add-twice-with-parentfn-and-childrenfn ()
163 (let* ((parentfn (lambda (item)
164 (cl-case item
165 (dove 'bird)
166 (bird 'animal))))
167 (childrenfn (lambda (item)
168 (cl-case item
169 (animal '(bird))
170 (bird '(dove)))))
171 (hierarchy (hierarchy-new)))
172 (hierarchy-add-tree hierarchy 'bird parentfn childrenfn)
173 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))
174 (should (equal (hierarchy-children hierarchy 'bird) '(dove)))))
175
176(ert-deftest hierarchy-add-trees ()
177 (let ((parentfn (lambda (item)
178 (cl-case item
179 (dove 'bird)
180 (pigeon 'bird)
181 (bird 'animal))))
182 (hierarchy (hierarchy-new)))
183 (hierarchy-add-trees hierarchy '(dove pigeon) parentfn)
184 (should (equal (hierarchy-roots hierarchy) '(animal)))
185 (should (equal (hierarchy-children hierarchy 'animal) '(bird)))
186 (should (equal (hierarchy-children hierarchy 'bird) '(dove pigeon)))))
187
188(ert-deftest hierarchy-from-list ()
189 (let ((hierarchy (hierarchy-from-list
190 '(animal (bird (dove)
191 (pigeon))
192 (cow)
193 (dolphin)))))
194 (hierarchy-sort hierarchy (lambda (item1 item2)
195 (string< (car item1)
196 (car item2))))
197 (should (equal (hierarchy-to-string hierarchy (lambda (item) (symbol-name (car item))))
198 "animal\n bird\n dove\n pigeon\n cow\n dolphin\n"))))
199
200(ert-deftest hierarchy-from-list-with-duplicates ()
201 (let ((hierarchy (hierarchy-from-list
202 '(a (b) (b))
203 t)))
204 (hierarchy-sort hierarchy (lambda (item1 item2)
205 ;; sort by ID
206 (< (car item1) (car item2))))
207 (should (equal (hierarchy-length hierarchy) 3))
208 (should (equal (hierarchy-to-string
209 hierarchy
210 (lambda (item)
211 (format "%s(%s)"
212 (cadr item)
213 (car item))))
214 "a(1)\n b(2)\n b(3)\n"))))
215
216(ert-deftest hierarchy-from-list-with-childrenfn ()
217 (let ((hierarchy (hierarchy-from-list
218 "abc"
219 nil
220 (lambda (item)
221 (when (string= item "abc")
222 (split-string item "" t))))))
223 (hierarchy-sort hierarchy (lambda (item1 item2) (string< item1 item2)))
224 (should (equal (hierarchy-length hierarchy) 4))
225 (should (equal (hierarchy-to-string hierarchy)
226 "abc\n a\n b\n c\n"))))
227
228(ert-deftest hierarchy-add-relation-check-error-when-different-parent ()
229 (let ((parentfn (lambda (item)
230 (cl-case item
231 (bird 'animal))))
232 (hierarchy (hierarchy-new)))
233 (hierarchy-add-tree hierarchy 'bird parentfn)
234 (should-error
235 (hierarchy--add-relation hierarchy 'bird 'cow #'identity))))
236
237(ert-deftest hierarchy-empty-p-return-non-nil-for-empty ()
238 (should (hierarchy-empty-p (hierarchy-new))))
239
240(ert-deftest hierarchy-empty-p-return-nil-for-non-empty ()
241 (should-not (hierarchy-empty-p (hierarchy-animals))))
242
243(ert-deftest hierarchy-length-of-empty-is-0 ()
244 (should (equal (hierarchy-length (hierarchy-new)) 0)))
245
246(ert-deftest hierarchy-length-of-non-empty-counts-items ()
247 (let ((parentfn (lambda (item)
248 (cl-case item
249 (bird 'animal)
250 (dove 'bird)
251 (pigeon 'bird))))
252 (hierarchy (hierarchy-new)))
253 (hierarchy-add-tree hierarchy 'dove parentfn)
254 (hierarchy-add-tree hierarchy 'pigeon parentfn)
255 (should (equal (hierarchy-length hierarchy) 4))))
256
257(ert-deftest hierarchy-has-root ()
258 (let ((parentfn (lambda (item)
259 (cl-case item
260 (bird 'animal)
261 (dove 'bird)
262 (pigeon 'bird))))
263 (hierarchy (hierarchy-new)))
264 (should-not (hierarchy-has-root hierarchy 'animal))
265 (should-not (hierarchy-has-root hierarchy 'bird))
266 (hierarchy-add-tree hierarchy 'dove parentfn)
267 (hierarchy-add-tree hierarchy 'pigeon parentfn)
268 (should (hierarchy-has-root hierarchy 'animal))
269 (should-not (hierarchy-has-root hierarchy 'bird))))
270
271(ert-deftest hierarchy-leafs ()
272 (let ((animals (hierarchy-animals)))
273 (should (equal (hierarchy-leafs animals)
274 '(dove pigeon dolphin cow)))))
275
276(ert-deftest hierarchy-leafs-includes-lonely-roots ()
277 (let ((parentfn (lambda (item) nil))
278 (hierarchy (hierarchy-new)))
279 (hierarchy-add-tree hierarchy 'foo parentfn)
280 (should (equal (hierarchy-leafs hierarchy)
281 '(foo)))))
282
283(ert-deftest hierarchy-leafs-of-node ()
284 (let ((animals (hierarchy-animals)))
285 (should (equal (hierarchy-leafs animals 'cow) '()))
286 (should (equal (hierarchy-leafs animals 'animal) '(dove pigeon dolphin cow)))
287 (should (equal (hierarchy-leafs animals 'bird) '(dove pigeon)))
288 (should (equal (hierarchy-leafs animals 'dove) '()))))
289
290(ert-deftest hierarchy-child-p ()
291 (let ((animals (hierarchy-animals)))
292 (should (hierarchy-child-p animals 'dove 'bird))
293 (should (hierarchy-child-p animals 'bird 'animal))
294 (should (hierarchy-child-p animals 'cow 'animal))
295 (should-not (hierarchy-child-p animals 'cow 'bird))
296 (should-not (hierarchy-child-p animals 'bird 'cow))
297 (should-not (hierarchy-child-p animals 'animal 'dove))
298 (should-not (hierarchy-child-p animals 'animal 'bird))))
299
300(ert-deftest hierarchy-descendant ()
301 (let ((animals (hierarchy-animals)))
302 (should (hierarchy-descendant-p animals 'dove 'animal))
303 (should (hierarchy-descendant-p animals 'dove 'bird))
304 (should (hierarchy-descendant-p animals 'bird 'animal))
305 (should (hierarchy-descendant-p animals 'cow 'animal))
306 (should-not (hierarchy-descendant-p animals 'cow 'bird))
307 (should-not (hierarchy-descendant-p animals 'bird 'cow))
308 (should-not (hierarchy-descendant-p animals 'animal 'dove))
309 (should-not (hierarchy-descendant-p animals 'animal 'bird))))
310
311(ert-deftest hierarchy-descendant-if-not-same ()
312 (let ((animals (hierarchy-animals)))
313 (should-not (hierarchy-descendant-p animals 'cow 'cow))
314 (should-not (hierarchy-descendant-p animals 'dove 'dove))
315 (should-not (hierarchy-descendant-p animals 'bird 'bird))
316 (should-not (hierarchy-descendant-p animals 'animal 'animal))))
317
318;; keywords supported: :test :key
319(ert-deftest hierarchy--set-equal ()
320 (should (hierarchy--set-equal '(1 2 3) '(1 2 3)))
321 (should (hierarchy--set-equal '(1 2 3) '(3 2 1)))
322 (should (hierarchy--set-equal '(3 2 1) '(1 2 3)))
323 (should-not (hierarchy--set-equal '(2 3) '(3 2 1)))
324 (should-not (hierarchy--set-equal '(1 2 3) '(2 3)))
325 (should-not (hierarchy--set-equal '("1" "2") '("2" "1") :test #'eq))
326 (should (hierarchy--set-equal '("1" "2") '("2" "1") :test #'equal))
327 (should-not (hierarchy--set-equal '(1 2) '(-1 -2)))
328 (should (hierarchy--set-equal '(1 2) '(-1 -2) :key #'abs))
329 (should-not (hierarchy--set-equal '(("1" 1) ("2" 1)) '(("1" 2) ("2" 2))))
330 (should-not (hierarchy--set-equal '(("1" 1) ("2" 1)) '(("1" 2) ("2" 2)) :key #'car))
331 (should-not (hierarchy--set-equal '(("1" 1) ("2" 1)) '(("1" 2) ("2" 2)) :test #'equal))
332 (should (hierarchy--set-equal '(("1" 1) ("2" 1)) '(("1" 2) ("2" 2)) :key #'car :test #'equal)))
333
334(ert-deftest hierarchy-equal-returns-true-for-same-hierarchy ()
335 (let ((animals (hierarchy-animals)))
336 (should (hierarchy-equal animals animals))
337 (should (hierarchy-equal (hierarchy-animals) animals))))
338
339(ert-deftest hierarchy-equal-returns-true-for-hierarchy-copies ()
340 (let ((animals (hierarchy-animals)))
341 (should (hierarchy-equal animals (hierarchy-copy animals)))))
342
343(ert-deftest hierarchy-map-item-on-leaf ()
344 (let* ((animals (hierarchy-animals))
345 (result (hierarchy-map-item (lambda (item indent) (cons item indent))
346 'cow
347 animals)))
348 (should (equal result '((cow . 0))))))
349
350(ert-deftest hierarchy-map-item-on-leaf-with-indent ()
351 (let* ((animals (hierarchy-animals))
352 (result (hierarchy-map-item (lambda (item indent) (cons item indent))
353 'cow
354 animals
355 2)))
356 (should (equal result '((cow . 2))))))
357
358(ert-deftest hierarchy-map-item-on-parent ()
359 (let* ((animals (hierarchy-animals))
360 (result (hierarchy-map-item (lambda (item indent) (cons item indent))
361 'bird
362 animals)))
363 (should (equal result '((bird . 0) (dove . 1) (pigeon . 1))))))
364
365(ert-deftest hierarchy-map-item-on-grand-parent ()
366 (let* ((animals (hierarchy-animals))
367 (result (hierarchy-map-item (lambda (item indent) (cons item indent))
368 'animal
369 animals)))
370 (should (equal result '((animal . 0) (bird . 1) (dove . 2) (pigeon . 2)
371 (cow . 1) (dolphin . 1))))))
372
373(ert-deftest hierarchy-map-conses ()
374 (let* ((animals (hierarchy-animals))
375 (result (hierarchy-map (lambda (item indent)
376 (cons item indent))
377 animals)))
378 (should (equal result '((animal . 0)
379 (bird . 1)
380 (dove . 2)
381 (pigeon . 2)
382 (cow . 1)
383 (dolphin . 1))))))
384
385(ert-deftest hierarchy-map-tree ()
386 (let ((animals (hierarchy-animals)))
387 (should (equal (hierarchy-map-tree (lambda (item indent children)
388 (list item indent children))
389 animals)
390 '(animal
391 0
392 ((bird 1 ((dove 2 nil) (pigeon 2 nil)))
393 (cow 1 nil)
394 (dolphin 1 nil)))))))
395
396(ert-deftest hierarchy-map-hierarchy-keeps-hierarchy ()
397 (let* ((animals (hierarchy-animals))
398 (result (hierarchy-map-hierarchy (lambda (item _) (identity item))
399 animals)))
400 (should (hierarchy-equal animals result))))
401
402(ert-deftest hierarchy-map-applies-function ()
403 (let* ((animals (hierarchy-animals))
404 (parentfn (lambda (item)
405 (cond
406 ((equal item "bird") "animal")
407 ((equal item "dove") "bird")
408 ((equal item "pigeon") "bird")
409 ((equal item "cow") "animal")
410 ((equal item "dolphin") "animal"))))
411 (expected (hierarchy-new)))
412 (hierarchy-add-tree expected "dove" parentfn)
413 (hierarchy-add-tree expected "pigeon" parentfn)
414 (hierarchy-add-tree expected "cow" parentfn)
415 (hierarchy-add-tree expected "dolphin" parentfn)
416 (should (hierarchy-equal
417 (hierarchy-map-hierarchy (lambda (item _) (symbol-name item)) animals)
418 expected))))
419
420(ert-deftest hierarchy-extract-tree ()
421 (let* ((animals (hierarchy-animals))
422 (birds (hierarchy-extract-tree animals 'bird)))
423 (hierarchy-sort birds)
424 (should (equal (hierarchy-roots birds) '(animal)))
425 (should (equal (hierarchy-children birds 'animal) '(bird)))
426 (should (equal (hierarchy-children birds 'bird) '(dove pigeon)))))
427
428(ert-deftest hierarchy-extract-tree-nil-if-not-in-hierarchy ()
429 (let* ((animals (hierarchy-animals)))
430 (should-not (hierarchy-extract-tree animals 'foobar))))
431
432(ert-deftest hierarchy-items-of-empty-hierarchy-is-empty ()
433 (should (seq-empty-p (hierarchy-items (hierarchy-new)))))
434
435(ert-deftest hierarchy-items-returns-sequence-of-same-length ()
436 (let* ((animals (hierarchy-animals))
437 (result (hierarchy-items animals)))
438 (should (= (seq-length result) (hierarchy-length animals)))))
439
440(ert-deftest hierarchy-items-return-all-elements-of-hierarchy ()
441 (let* ((animals (hierarchy-animals))
442 (result (hierarchy-items animals)))
443 (should (equal (seq-sort #'string< result) '(animal bird cow dolphin dove pigeon)))))
444
445(ert-deftest hierarchy-labelfn-indent-no-indent-if-0 ()
446 (let* ((labelfn-base (lambda (_item _indent) (insert "foo")))
447 (labelfn (hierarchy-labelfn-indent labelfn-base)))
448 (should (equal
449 (with-temp-buffer
450 (funcall labelfn "bar" 0)
451 (buffer-substring (point-min) (point-max)))
452 "foo"))))
453
454(ert-deftest hierarchy-labelfn-indent-three-times-if-3 ()
455 (let* ((labelfn-base (lambda (_item _indent) (insert "foo")))
456 (labelfn (hierarchy-labelfn-indent labelfn-base)))
457 (should (equal
458 (with-temp-buffer
459 (funcall labelfn "bar" 3)
460 (buffer-substring (point-min) (point-max)))
461 " foo"))))
462
463(ert-deftest hierarchy-labelfn-indent-default-indent-string ()
464 (let* ((labelfn-base (lambda (_item _indent) (insert "foo")))
465 (labelfn (hierarchy-labelfn-indent labelfn-base)))
466 (should (equal
467 (with-temp-buffer
468 (funcall labelfn "bar" 1)
469 (buffer-substring (point-min) (point-max)))
470 " foo"))))
471
472(ert-deftest hierarchy-labelfn-indent-custom-indent-string ()
473 (let* ((labelfn-base (lambda (_item _indent) (insert "foo")))
474 (labelfn (hierarchy-labelfn-indent labelfn-base "###"))
475 (content (with-temp-buffer
476 (funcall labelfn "bar" 1)
477 (buffer-substring (point-min) (point-max)))))
478 (should (equal content "###foo"))))
479
480(ert-deftest hierarchy-labelfn-button-propertize ()
481 (let* ((labelfn-base (lambda (_item _indent) (insert "foo")))
482 (actionfn #'identity)
483 (labelfn (hierarchy-labelfn-button labelfn-base actionfn))
484 (properties (with-temp-buffer
485 (funcall labelfn "bar" 1)
486 (text-properties-at 1))))
487 (should (equal (car properties) 'action))))
488
489(ert-deftest hierarchy-labelfn-button-execute-labelfn ()
490 (let* ((labelfn-base (lambda (_item _indent) (insert "foo")))
491 (actionfn #'identity)
492 (labelfn (hierarchy-labelfn-button labelfn-base actionfn))
493 (content (with-temp-buffer
494 (funcall labelfn "bar" 1)
495 (buffer-substring-no-properties (point-min) (point-max)))))
496 (should (equal content "foo"))))
497
498(ert-deftest hierarchy-labelfn-button-if-does-not-button-unless-condition ()
499 (let ((labelfn-base (lambda (_item _indent) (insert "foo")))
500 (spy-count 0)
501 (condition (lambda (_item _indent) nil)))
502 (cl-letf (((symbol-function 'hierarchy-labelfn-button) (lambda (_labelfn _actionfn) (lambda (_item _indent) (cl-incf spy-count)))))
503 (funcall (hierarchy-labelfn-button-if labelfn-base condition #'identity) nil nil)
504 (should (equal spy-count 0)))))
505
506(ert-deftest hierarchy-labelfn-button-if-does-button-when-condition ()
507 (let ((labelfn-base (lambda (_item _indent) (insert "foo")))
508 (spy-count 0)
509 (condition (lambda (_item _indent) t)))
510 (cl-letf (((symbol-function 'hierarchy-labelfn-button) (lambda (_labelfn _actionfn) (lambda (_item _indent) (cl-incf spy-count)))))
511 (funcall (hierarchy-labelfn-button-if labelfn-base condition #'identity) nil nil)
512 (should (equal spy-count 1)))))
513
514(ert-deftest hierarchy-labelfn-to-string ()
515 (let ((labelfn (lambda (item _indent) (insert item))))
516 (should (equal (hierarchy-labelfn-to-string labelfn "foo" 1) "foo"))))
517
518(ert-deftest hierarchy-print ()
519 (let* ((animals (hierarchy-animals))
520 (result (with-temp-buffer
521 (hierarchy-print animals)
522 (buffer-substring-no-properties (point-min) (point-max)))))
523 (should (equal result "animal\n bird\n dove\n pigeon\n cow\n dolphin\n"))))
524
525(ert-deftest hierarchy-to-string ()
526 (let* ((animals (hierarchy-animals))
527 (result (hierarchy-to-string animals)))
528 (should (equal result "animal\n bird\n dove\n pigeon\n cow\n dolphin\n"))))
529
530(ert-deftest hierarchy-tabulated-display ()
531 (let* ((animals (hierarchy-animals))
532 (labelfn (lambda (item _indent) (insert (symbol-name item))))
533 (contents (with-temp-buffer
534 (hierarchy-tabulated-display animals labelfn (current-buffer))
535 (buffer-substring-no-properties (point-min) (point-max)))))
536 (should (equal contents "animal\nbird\ndove\npigeon\ncow\ndolphin\n"))))
537
538(ert-deftest hierarchy-sort-non-root-nodes ()
539 (let* ((animals (hierarchy-animals)))
540 (should (equal (hierarchy-roots animals) '(animal)))
541 (should (equal (hierarchy-children animals 'animal) '(bird cow dolphin)))
542 (should (equal (hierarchy-children animals 'bird) '(dove pigeon)))))
543
544(ert-deftest hierarchy-sort-roots ()
545 (let* ((organisms (hierarchy-new))
546 (parentfn (lambda (item)
547 (cl-case item
548 (oak 'plant)
549 (bird 'animal)))))
550 (hierarchy-add-tree organisms 'oak parentfn)
551 (hierarchy-add-tree organisms 'bird parentfn)
552 (hierarchy-sort organisms)
553 (should (equal (hierarchy-roots organisms) '(animal plant)))))
554
555(provide 'hierarchy-tests)
556;;; hierarchy-tests.el ends here