[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master 99e418d 1/3: Add package path-iterator, version 0
From: |
Stephen Leake |
Subject: |
[elpa] master 99e418d 1/3: Add package path-iterator, version 0 |
Date: |
Wed, 16 Jan 2019 16:57:40 -0500 (EST) |
branch: master
commit 99e418d23913c865215ddcde2944906be30f533b
Author: Stephen Leake <address@hidden>
Commit: Stephen Leake <address@hidden>
Add package path-iterator, version 0
---
packages/path-iterator/path-iterator.el | 248 ++++++++++++++++++++++++++++++++
1 file changed, 248 insertions(+)
diff --git a/packages/path-iterator/path-iterator.el
b/packages/path-iterator/path-iterator.el
new file mode 100644
index 0000000..abfdff1
--- /dev/null
+++ b/packages/path-iterator/path-iterator.el
@@ -0,0 +1,248 @@
+;; path-iterator.el --- An iterator for traversing a directory path.
-*-lexical-binding:t-*-
+
+;; Copyright (C) 2015 - 2017 Free Software Foundation, Inc.
+;;
+;; Author: Stephen Leake <address@hidden>
+;; Maintainer: Stephen Leake <address@hidden>
+;; Version: 0
+;; package-requires: ((emacs "25.0"))
+;;
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'cl-generic)
+
+(cl-defstruct
+ (path-iterator
+ (:conc-name path-iter-)
+ (:copier nil)
+ (:constructor nil)
+ (:constructor make-path-iterator
+ (&key
+ user-path-non-recursive
+ user-path-recursive
+ ignore-function
+ &aux
+ (path-non-recursive-init (path-iter-to-truename
user-path-non-recursive))
+ (path-non-recursive path-non-recursive-init)
+ (path-recursive-init (path-iter-to-truename
user-path-recursive))
+ (path-recursive path-recursive-init)
+ (visited nil)
+ (current nil)
+ (state nil))
+ ))
+
+ path-non-recursive-init ;; absolute directory file truenames, no recursion
+ path-recursive-init ;; absolute directory file truenames, recurse into
subdirectories
+
+ path-non-recursive ;; temp storage while iterating
+ path-recursive ;; "
+
+ ignore-function
+ ;; Function called with absolute directory name; return non-nil
+ ;; if it should be ignored.
+
+ visited
+ ;; During first iteration - list of directories already visited.
+ ;; During subsequent iterations - vector of directories to visit
+ ;;
+ ;; We have to populate the visited list during the first iteration
+ ;; in order to avoid visiting a directory twice, so we might as well
+ ;; use it for subsequent iterations.
+
+ current ;; index into `visited' during subsequent iterations
+
+ state ;; one of nil, 'started, 'complete. Allows detecting interrupted
computation.
+ )
+
+(cl-defmethod path-iter-contains-root ((iter path-iterator) root)
+ "Return non-nil if ITER roots contain ROOT."
+ (or (member root (path-iter-path-recursive-init iter))
+ (member root (path-iter-path-non-recursive-init iter))
+ ))
+
+(defun path-iter-to-truename (path)
+ "Convert each existing element of PATH to an absolute directory file
truename,
+return the resulting list. Elements of PATH are either absolute or
+relative to `default-directory'.
+
+If an element of PATH is nil, `default-directory' is used."
+ ;; The nil handling is as defined by the `load-path' doc string.
+ (let (result)
+ (cl-mapc
+ (lambda (name)
+ (let ((absname (if name
+ (expand-file-name name)
+ default-directory)))
+ (when (file-directory-p absname)
+ (push (file-truename absname) result))
+ ))
+ path)
+ (nreverse result)))
+
+(cl-defmethod path-iter-done ((iter path-iterator))
+"Return non-nil if ITER is done."
+ (cond
+ ((listp (path-iter-visited iter))
+ ;; First iteration
+ (and (null (car (path-iter-path-non-recursive iter)))
+ (null (car (path-iter-path-recursive iter)))))
+
+ (t
+ ;; Subsequent iterations
+ (= (1+ (path-iter-current iter)) (length (path-iter-visited iter))))
+ ))
+
+(cl-defmethod path-iter-next ((iter path-iterator))
+ "Return the next directory to visit, or nil if there are no more.
+
+The iterator will first visit all elements of the non-recursive
+path, then all elements of the recursive path, and visit all
+subdirectories of the recursive path for which `ignore-function'
+returns nil, in depth-first order (parent directories are visited
+before their subdirectories; sibling directories are visited
+after subdirectories), but will not visit any directory more than
+once. The order of subdirectories within a directory is given by
+`directory-files'.
+
+`ignore-function' is passed one argument; the directory file
+name. Symlinks in the directory part are resolved, but the
+nondirectory part is the link name if it is a symlink.
+
+The directories returned by `path-iter-next' are absolute
+directory file truenames; they contain forward slashes, do
+not end in a slash, have casing that matches the existing
+directory file name, and resolve simlinks (see `file-truename')."
+ (cond
+ ((and (listp (path-iter-visited iter))
+ (not (null (path-iter-path-non-recursive iter))))
+ ;; First iteration, doing non-recursive path
+ (let ((result (pop (path-iter-path-non-recursive iter))))
+
+ (while (member result (path-iter-visited iter))
+ (setq result (pop (path-iter-path-non-recursive iter))))
+
+ (push result (path-iter-visited iter))
+
+ (unless result
+ (setf (path-iter-state iter) 'complete))
+
+ result))
+
+ ((and (listp (path-iter-visited iter))
+ (not (null (path-iter-path-recursive iter))))
+ ;; First iteration, doing recursive path
+
+ (let ((result (pop (path-iter-path-recursive iter)))
+ subdirs)
+
+ (while (member result (path-iter-visited iter))
+ (setq result (pop (path-iter-path-recursive iter))))
+
+ (push result (path-iter-visited iter))
+
+ ;; Push directories in `result' onto the path, to be visited
+ ;; next. `directory-files' sorts the list.
+ (cl-mapc
+ (lambda (absname)
+ (unless (or (string-equal "." (file-name-nondirectory absname))
+ (string-equal ".." (file-name-nondirectory absname))
+ (not (file-directory-p absname))
+ ;; If `absname' is a symlink, we assume
+ ;; `ignore-function' wants the link name.
+ (and (path-iter-ignore-function iter)
+ (funcall (path-iter-ignore-function iter) absname)))
+ (push (file-truename absname) subdirs))
+ )
+ (directory-files result t))
+
+ (setf (path-iter-path-recursive iter)
+ (append
+ (nreverse subdirs)
+ (path-iter-path-recursive iter)))
+
+ (unless result
+ (setf (path-iter-state iter) 'complete))
+
+ result))
+
+ ((listp (path-iter-visited iter))
+ ;; Both paths empty; first iteration done.
+
+ (setf (path-iter-state iter) 'complete)
+
+ nil)
+
+ (t
+ ;; Subsequent iterations; path-iter-visited changed to a vector
+ (setf (path-iter-current iter) (1+ (path-iter-current iter)))
+
+ (if (< (path-iter-current iter) (length (path-iter-visited iter)))
+ (aref (path-iter-visited iter) (path-iter-current iter))
+ (setf (path-iter-state iter) 'complete)
+ nil))
+ ))
+
+(cl-defmethod path-iter-restart ((iter path-iterator))
+ "Restart ITER.
+Next call to `path-iter-next' will return first directory visited.
+Uses cached path computed during first iteration; see `path-iter-reset'."
+ (if (eq 'started (path-iter-state iter))
+ ;; compute was interrupted (probably by `while-no-input' in icomplete)
+ (path-iter-reset iter)
+
+ (cond
+ ((null (path-iter-visited iter))
+ ;; Not run first time yet
+ (setf (path-iter-state iter) 'started))
+
+ ((listp (path-iter-visited iter))
+ ;; Run once; convert to vector
+ (setf (path-iter-visited iter) (vconcat nil (nreverse (path-iter-visited
iter))))
+ (setf (path-iter-current iter) -1))
+
+ (t
+ ;; Run more than once
+ (setf (path-iter-current iter) -1))
+ )))
+
+(cl-defmethod path-iter-reset ((iter path-iterator))
+ "Reset ITER; recomputes path.
+Next call to `path-iter-next' will return first directory visited."
+ (setf (path-iter-path-non-recursive iter) (path-iter-path-non-recursive-init
iter))
+ (setf (path-iter-path-recursive iter) (path-iter-path-recursive-init iter))
+ (setf (path-iter-visited iter) nil)
+ (setf (path-iter-current iter) nil)
+ (setf (path-iter-state iter) 'started)
+ )
+
+(cl-defmethod path-iter-expand-filename ((iter path-iterator) filename)
+ "Expand FILENAME with ITER.
+Return a list of absolute filenames or nil if none found."
+ (path-iter-restart iter)
+
+ (let (result dir)
+ (while (setq dir (path-iter-next iter))
+ (cl-mapc
+ (lambda (filename)
+ (push (concat (file-name-as-directory dir) filename) result))
+ (file-name-all-completions filename dir)))
+ result))
+
+(provide 'path-iterator)
+;; path-iterator.el ends here