New function flatten-tree

Co-authored-by: Basil L. Contovounesios <contovob@tcd.ie>

* doc/lispref/lists.texi: Document `flatten-tree'.

* lisp/progmodes/js.el (js--maybe-join):
* lisp/printing.el (pr-switches):
* lisp/lpr.el (lpr-print-region):
* lisp/gnus/nnimap.el (nnimap-find-wanted-parts):
* lisp/gnus/message.el (message-talkative-question):
* lisp/gnus/gnus-sum.el (gnus-remove-thread)
(gnus-thread-highest-number, gnus-thread-latest-date):
* lisp/eshell/esh-util.el (eshell-flatten-and-stringify):
* lisp/eshell/esh-opt.el (eshell-eval-using-options):
* lisp/eshell/esh-ext.el (eshell-external-command):
* lisp/eshell/em-xtra.el (eshell/expr):
* lisp/eshell/em-unix.el (eshell/rm, eshell-mvcpln-template)
(eshell/cat, eshell/make, eshell-poor-mans-grep, eshell-grep)
(eshell/du, eshell/time, eshell/diff, eshell/locate):
* lisp/eshell/em-tramp.el (eshell/su, eshell/sudo):
* lisp/eshell/em-term.el (eshell-exec-visual):
* lisp/eshell/em-dirs.el (eshell-dirs-substitute-cd, eshell/cd):
* lisp/eshell/em-basic.el (eshell/printnl):
Use new flatten-tree.

* lisp/progmodes/js.el (js--flatten-list):
* lisp/lpr.el (lpr-flatten-list):
* lisp/gnus/message.el (message-flatten-list):
* lisp/eshell/esh-util.el (eshell-flatten-list):
Obsolete in favor of Emacs-wide `flatten-tree'.

* lisp/subr.el (flatten-list): Alias to `flatten-tree' for
discoverability.

* lisp/subr.el (flatten-tree): New defun.

* test/lisp/subr-tests.el (subr-tests-flatten-tree): New test.
This commit is contained in:
Alex Branham 2018-12-17 12:15:09 +01:00 committed by Michael Albinus
parent 09a6cc4778
commit 36b05dc842
19 changed files with 96 additions and 72 deletions

View file

@ -667,6 +667,18 @@ non-@code{nil}, it copies vectors too (and operates recursively on
their elements).
@end defun
@defun flatten-tree tree
Take @var{tree} and "flatten" it.
This always returns a list containing all the terminal nodes, or
leaves, of @var{tree}. Dotted pairs are flattened as well, and nil
elements are removed.
@end defun
@example
(flatten-tree '(1 (2 . 3) nil (4 5 (6)) 7))
@result{}(1 2 3 4 5 6 7)
@end example
@defun number-sequence from &optional to separation
This returns a list of numbers starting with @var{from} and
incrementing by @var{separation}, and ending at or just before

View file

@ -1359,6 +1359,12 @@ are implemented in C using the Jansson library.
** New function 'ring-resize'.
'ring-resize' can be used to grow or shrink a ring.
+++
** New function 'flatten-tree'.
'flatten-list' is provided as an alias. These functions take a tree
and 'flatten' it such that the result is a list of all the terminal
nodes.
** Mailcap
---

View file

@ -118,7 +118,7 @@ or `eshell-printn' for display."
(defun eshell/printnl (&rest args)
"Print out each of the arguments, separated by newlines."
(let ((elems (eshell-flatten-list args)))
(let ((elems (flatten-tree args)))
(while elems
(eshell-printn (eshell-echo (list (car elems))))
(setq elems (cdr elems)))))

View file

@ -259,7 +259,7 @@ Thus, this does not include the current directory.")
(if (> (length args) 1)
(error "%s: command not found" (car args))
(throw 'eshell-replace-command
(eshell-parse-command "cd" (eshell-flatten-list args)))))
(eshell-parse-command "cd" (flatten-tree args)))))
(defun eshell-parse-user-reference ()
"An argument beginning with ~ is a filename to be expanded."
@ -353,7 +353,7 @@ in the minibuffer:
(defun eshell/cd (&rest args) ; all but first ignored
"Alias to extend the behavior of `cd'."
(setq args (eshell-flatten-list args))
(setq args (flatten-tree args))
(let ((path (car args))
(subpath (car (cdr args)))
(case-fold-search (eshell-under-windows-p))

View file

@ -175,7 +175,7 @@ allowed."
(let* (eshell-interpreter-alist
(interp (eshell-find-interpreter (car args) (cdr args)))
(program (car interp))
(args (eshell-flatten-list
(args (flatten-tree
(eshell-stringify-list (append (cdr interp)
(cdr args)))))
(term-buf

View file

@ -62,7 +62,7 @@
"Alias \"su\" to call TRAMP.
Uses the system su through TRAMP's su method."
(setq args (eshell-stringify-list (eshell-flatten-list args)))
(setq args (eshell-stringify-list (flatten-tree args)))
(let ((orig-args (copy-tree args)))
(eshell-eval-using-options
"su" args
@ -100,7 +100,7 @@ Become another USER during a login session.")
"Alias \"sudo\" to call Tramp.
Uses the system sudo through TRAMP's sudo method."
(setq args (eshell-stringify-list (eshell-flatten-list args)))
(setq args (eshell-stringify-list (flatten-tree args)))
(let ((orig-args (copy-tree args)))
(eshell-eval-using-options
"sudo" args

View file

@ -231,7 +231,7 @@ Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
This is implemented to call either `delete-file', `kill-buffer',
`kill-process', or `unintern', depending on the nature of the
argument."
(setq args (eshell-flatten-list args))
(setq args (flatten-tree args))
(eshell-eval-using-options
"rm" args
'((?h "help" nil nil "show this usage screen")
@ -481,7 +481,7 @@ Remove the DIRECTORY(ies), if they are empty.")
(error "%s: missing destination file or directory" ,command))
(if (= len 1)
(nconc args '(".")))
(setq args (eshell-stringify-list (eshell-flatten-list args)))
(setq args (eshell-stringify-list (flatten-tree args)))
(if (and ,(not (equal command "ln"))
(string-match eshell-tar-regexp (car (last args)))
(or (> (length args) 2)
@ -606,7 +606,7 @@ with `--symbolic'. When creating hard links, each TARGET must exist.")
"Implementation of cat in Lisp.
If in a pipeline, or the file is not a regular file, directory or
symlink, then revert to the system's definition of cat."
(setq args (eshell-stringify-list (eshell-flatten-list args)))
(setq args (eshell-stringify-list (flatten-tree args)))
(if (or eshell-in-pipeline-p
(catch 'special
(dolist (arg args)
@ -670,7 +670,7 @@ Fallback to standard make when called synchronously."
(compile (concat "make " (eshell-flatten-and-stringify args))))
(throw 'eshell-replace-command
(eshell-parse-command "*make" (eshell-stringify-list
(eshell-flatten-list args))))))
(flatten-tree args))))))
(put 'eshell/make 'eshell-no-numeric-conversions t)
@ -705,7 +705,7 @@ available..."
(erase-buffer)
(occur-mode)
(let ((files (eshell-stringify-list
(eshell-flatten-list (cdr args))))
(flatten-tree (cdr args))))
(inhibit-redisplay t)
string)
(when (car args)
@ -750,11 +750,11 @@ external command."
(throw 'eshell-replace-command
(eshell-parse-command (concat "*" command)
(eshell-stringify-list
(eshell-flatten-list args))))
(flatten-tree args))))
(let* ((args (mapconcat 'identity
(mapcar 'shell-quote-argument
(eshell-stringify-list
(eshell-flatten-list args)))
(flatten-tree args)))
" "))
(cmd (progn
(set-text-properties 0 (length args)
@ -876,7 +876,7 @@ external command."
(defun eshell/du (&rest args)
"Implementation of \"du\" in Lisp, passing ARGS."
(setq args (if args
(eshell-stringify-list (eshell-flatten-list args))
(eshell-stringify-list (flatten-tree args))
'(".")))
(let ((ext-du (eshell-search-path "du")))
(if (and ext-du
@ -976,7 +976,7 @@ Show wall-clock time elapsed during execution of COMMAND.")
(eshell-parse-command (car time-args)
;;; https://lists.gnu.org/r/bug-gnu-emacs/2007-08/msg00205.html
(eshell-stringify-list
(eshell-flatten-list (cdr time-args))))))))
(flatten-tree (cdr time-args))))))))
(defun eshell/whoami (&rest _args)
"Make \"whoami\" Tramp aware."
@ -1000,7 +1000,7 @@ Show wall-clock time elapsed during execution of COMMAND.")
(defun eshell/diff (&rest args)
"Alias \"diff\" to call Emacs `diff' function."
(let ((orig-args (eshell-stringify-list (eshell-flatten-list args))))
(let ((orig-args (eshell-stringify-list (flatten-tree args))))
(if (or eshell-plain-diff-behavior
(not (and (eshell-interactive-output-p)
(not eshell-in-pipeline-p)
@ -1056,7 +1056,7 @@ Show wall-clock time elapsed during execution of COMMAND.")
(string-match "^-" (car args))))
(throw 'eshell-replace-command
(eshell-parse-command "*locate" (eshell-stringify-list
(eshell-flatten-list args))))
(flatten-tree args))))
(save-selected-window
(let ((locate-history-list (list (car args))))
(locate-with-filter (car args) (cadr args))))))

View file

@ -51,7 +51,7 @@ naturally accessible within Emacs."
"Implementation of expr, using the calc package."
(if (not (fboundp 'calc-eval))
(throw 'eshell-replace-command
(eshell-parse-command "*expr" (eshell-flatten-list args)))
(eshell-parse-command "*expr" (flatten-tree args)))
;; to fool the byte-compiler...
(let ((func 'calc-eval))
(funcall func (eshell-flatten-and-stringify args)))))

View file

@ -222,7 +222,7 @@ causing the user to wonder if anything's really going on..."
(defun eshell-external-command (command args)
"Insert output from an external COMMAND, using ARGS."
(setq args (eshell-stringify-list (eshell-flatten-list args)))
(setq args (eshell-stringify-list (flatten-tree args)))
(let ((interp (eshell-find-interpreter
command
args

View file

@ -77,7 +77,7 @@ arguments, some do not. The recognized :KEYWORDS are:
arguments.
:preserve-args
If present, do not pass MACRO-ARGS through `eshell-flatten-list'
If present, do not pass MACRO-ARGS through `flatten-tree'
and `eshell-stringify-list'.
:parse-leading-options-only
@ -106,7 +106,7 @@ let-bound variable `args'."
,(if (memq ':preserve-args (cadr options))
macro-args
(list 'eshell-stringify-list
(list 'eshell-flatten-list macro-args))))
(list 'flatten-tree macro-args))))
(processed-args (eshell--do-opts ,name ,options temp-args))
,@(delete-dups
(delq nil (mapcar (lambda (opt)

View file

@ -285,15 +285,7 @@ Prepend remote identification of `default-directory', if any."
,@forms)
(setq list-iter (cdr list-iter)))))
(defun eshell-flatten-list (args)
"Flatten any lists within ARGS, so that there are no sublists."
(let ((new-list (list t)))
(dolist (a args)
(if (and (listp a)
(listp (cdr a)))
(nconc new-list (eshell-flatten-list a))
(nconc new-list (list a))))
(cdr new-list)))
(define-obsolete-function-alias 'eshell-flatten-list #'flatten-tree "27.1")
(defun eshell-uniquify-list (l)
"Remove occurring multiples in L. You probably want to sort first."
@ -330,7 +322,7 @@ Prepend remote identification of `default-directory', if any."
(defsubst eshell-flatten-and-stringify (&rest args)
"Flatten and stringify all of the ARGS into a single string."
(mapconcat 'eshell-stringify (eshell-flatten-list args) " "))
(mapconcat 'eshell-stringify (flatten-tree args) " "))
(defsubst eshell-directory-files (regexp &optional directory)
"Return a list of files in the given DIRECTORY matching REGEXP."

View file

@ -4773,7 +4773,7 @@ If LINE, insert the rebuilt thread starting on line LINE."
(let (headers thread last-id)
;; First go up in this thread until we find the root.
(setq last-id (gnus-root-id id)
headers (message-flatten-list (gnus-id-to-thread last-id)))
headers (flatten-tree (gnus-id-to-thread last-id)))
;; We have now found the real root of this thread. It might have
;; been gathered into some loose thread, so we have to search
;; through the threads to find the thread we wanted.
@ -5069,7 +5069,7 @@ Unscored articles will be counted as having a score of zero."
"Return the highest article number in THREAD."
(apply 'max (mapcar (lambda (header)
(mail-header-number header))
(message-flatten-list thread))))
(flatten-tree thread))))
(defun gnus-article-sort-by-most-recent-date (h1 h2)
"Sort articles by number."
@ -5087,9 +5087,9 @@ Unscored articles will be counted as having a score of zero."
"Return the highest article date in THREAD."
(apply 'max
(mapcar (lambda (header) (float-time
(gnus-date-get-time
(mail-header-date header))))
(message-flatten-list thread))))
(gnus-date-get-time
(mail-header-date header))))
(flatten-tree thread))))
(defun gnus-thread-total-score-1 (root)
;; This function find the total score of the thread below ROOT.

View file

@ -8051,7 +8051,7 @@ regular text mode tabbing command."
If SHOW is non-nil, the arguments TEXT... are displayed in a temp buffer.
The following arguments may contain lists of values."
(if (and show
(setq text (message-flatten-list text)))
(setq text (flatten-tree text)))
(save-window-excursion
(with-output-to-temp-buffer " *MESSAGE information message*"
(with-current-buffer " *MESSAGE information message*"
@ -8061,15 +8061,7 @@ The following arguments may contain lists of values."
(funcall ask question))
(funcall ask question)))
(defun message-flatten-list (list)
"Return a new, flat list that contains all elements of LIST.
\(message-flatten-list \\='(1 (2 3 (4 5 (6))) 7))
=> (1 2 3 4 5 6 7)"
(cond ((consp list)
(apply 'append (mapcar 'message-flatten-list list)))
(list
(list list))))
(define-obsolete-function-alias 'message-flatten-list #'flatten-tree "27.1")
(defun message-generate-new-buffer-clone-locals (name &optional varstr)
"Create and return a buffer with name based on NAME using `generate-new-buffer'.

View file

@ -804,7 +804,7 @@ textual parts.")
(insert "\n--" boundary "--\n")))
(defun nnimap-find-wanted-parts (structure)
(message-flatten-list (nnimap-find-wanted-parts-1 structure "")))
(flatten-tree (nnimap-find-wanted-parts-1 structure "")))
(defun nnimap-find-wanted-parts-1 (structure prefix)
(let ((num 1)

View file

@ -258,7 +258,7 @@ for further customization of the printer command."
(defun lpr-print-region (start end switches name)
(let ((buf (current-buffer))
(nswitches (lpr-flatten-list
(nswitches (flatten-tree
(mapcar #'lpr-eval-switch ; Dynamic evaluation
switches)))
(switch-string (if switches
@ -336,23 +336,7 @@ The characters tab, linefeed, space, return and formfeed are not affected."
((consp arg) (apply (car arg) (cdr arg)))
(t nil)))
;; `lpr-flatten-list' is defined here (copied from "message.el" and
;; enhanced to handle dotted pairs as well) until we can get some
;; sensible autoloads, or `flatten-list' gets put somewhere decent.
;; (lpr-flatten-list '((a . b) c (d . e) (f g h) i . j))
;; => (a b c d e f g h i j)
(defun lpr-flatten-list (&rest list)
(lpr-flatten-list-1 list))
(defun lpr-flatten-list-1 (list)
(cond
((null list) nil)
((consp list)
(append (lpr-flatten-list-1 (car list))
(lpr-flatten-list-1 (cdr list))))
(t (list list))))
(define-obsolete-function-alias 'lpr-flatten-list #'flatten-tree "27.1")
(provide 'lpr)

View file

@ -5672,7 +5672,7 @@ If menu binding was not done, calls `pr-menu-bind'."
(defun pr-switches (switches mess)
(or (listp switches)
(error "%S should have a list of strings" mess))
(lpr-flatten-list ; dynamic evaluation
(flatten-tree ; dynamic evaluation
(mapcar #'lpr-eval-switch switches)))

View file

@ -623,11 +623,7 @@ then the \".\"s will be lined up:
"Parse state at `js--last-parse-pos'.")
(make-variable-buffer-local 'js--state-at-last-parse-pos)
(defun js--flatten-list (list)
(cl-loop for item in list
nconc (cond ((consp item)
(js--flatten-list item))
(item (list item)))))
(define-obsolete-function-alias 'js--flatten-list #'flatten-tree "27.1")
(defun js--maybe-join (prefix separator suffix &rest list)
"Helper function for `js--update-quick-match-re'.
@ -636,7 +632,7 @@ elements, separated by SEPARATOR, prefixed by PREFIX, and ended
with SUFFIX as with `concat'. Otherwise, if LIST is empty, return
nil. If any element in LIST is itself a list, flatten that
element."
(setq list (js--flatten-list list))
(setq list (flatten-tree list))
(when list
(concat prefix (mapconcat #'identity list separator) suffix)))

View file

@ -5448,5 +5448,30 @@ This function is called from lisp/Makefile and leim/Makefile."
(setq file (concat (substring file 1 2) ":" (substring file 2))))
file)
(defun flatten-tree (tree)
"Take TREE and \"flatten\" it.
This always returns a list containing all the terminal nodes, or
\"leaves\", of TREE. Dotted pairs are flattened as well, and nil
elements are removed.
\(flatten-tree \\='(1 (2 . 3) nil (4 5 (6)) 7))
=> (1 2 3 4 5 6 7)
TREE can be anything that can be made into a list. For each
element in TREE, if it is a cons cell return its car
recursively. Otherwise return the element."
(let (elems)
(setq tree (list tree))
(while (let ((elem (pop tree)))
(cond ((consp elem)
(setq tree (cons (car elem) (cons (cdr elem) tree))))
(elem
(push elem elems)))
tree))
(nreverse elems)))
;; Technically, `flatten-list' is a misnomer, but we provide it here
;; for discoverability:
(defalias 'flatten-list 'flatten-tree)
;;; subr.el ends here

View file

@ -372,5 +372,22 @@ See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=19350."
(shell-quote-argument "%ca%")))
"without-caret %ca%"))))
(ert-deftest subr-tests-flatten-tree ()
"Test `flatten-tree' behavior."
(should (equal (flatten-tree '(1 (2 . 3) nil (4 5 (6)) 7))
'(1 2 3 4 5 6 7)))
(should (equal (flatten-tree '((1 . 2)))
'(1 2)))
(should (equal (flatten-tree '(1 nil 2))
'(1 2)))
(should (equal (flatten-tree 42)
'(42)))
(should (equal (flatten-tree t)
'(t)))
(should (equal (flatten-tree nil)
nil))
(should (equal (flatten-tree '(1 ("foo" "bar") 2))
'(1 "foo" "bar" 2))))
(provide 'subr-tests)
;;; subr-tests.el ends here