diff options
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | projectile.el | 56 | ||||
| -rw-r--r-- | test/projectile-test.el | 62 |
3 files changed, 75 insertions, 47 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 40a5c62..e4e33a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### Changes + +* `projectile-parse-dirconfig-file' now returns a `projectile-dirconfig' struct (with `keep', `ignore', and `ensure' slots) instead of a positional 3-tuple. External callers should use the accessors (`projectile-dirconfig-keep' etc.) rather than `car'/`cadr'/`caddr'. + ### New features * Warn once per session when `projectile-indexing-method' is `alien' but the project has a non-empty `.projectile' file, so users notice their dirconfig rules are being bypassed. Controlled by the new `projectile-warn-when-dirconfig-is-ignored' option. diff --git a/projectile.el b/projectile.el index 7f2b754..dd6fce0 100644 --- a/projectile.el +++ b/projectile.el @@ -1516,8 +1516,10 @@ If PROJECT is not specified acts on the current project." ;;; Project indexing (defun projectile-get-project-directories (project-dir) "Get the list of PROJECT-DIR directories that are of interest to the user." - (mapcar (lambda (subdir) (concat project-dir subdir)) - (or (car (projectile-parse-dirconfig-file)) '("")))) + (let* ((cfg (projectile-parse-dirconfig-file)) + (keep (and cfg (projectile-dirconfig-keep cfg)))) + (mapcar (lambda (subdir) (concat project-dir subdir)) + (or keep '(""))))) (defun projectile--directory-p (directory) "Checks if DIRECTORY is a string designating a valid directory." @@ -2101,13 +2103,23 @@ Unignored files are not included." Unignored directories are not included." (seq-filter 'file-directory-p (projectile-project-ignored))) +(defun projectile--dirconfig-ignore () + "Return the IGNORE entries from the project's dirconfig, or nil." + (when-let* ((cfg (projectile-parse-dirconfig-file))) + (projectile-dirconfig-ignore cfg))) + +(defun projectile--dirconfig-ensure () + "Return the ENSURE entries from the project's dirconfig, or nil." + (when-let* ((cfg (projectile-parse-dirconfig-file))) + (projectile-dirconfig-ensure cfg))) + (defun projectile-paths-to-ignore () "Return a list of ignored project paths." - (projectile-normalise-paths (cadr (projectile-parse-dirconfig-file)))) + (projectile-normalise-paths (projectile--dirconfig-ignore))) (defun projectile-patterns-to-ignore () "Return a list of relative file patterns." - (projectile-normalise-patterns (cadr (projectile-parse-dirconfig-file)))) + (projectile-normalise-patterns (projectile--dirconfig-ignore))) (defun projectile-project-ignored () "Return list of project ignored files/directories. @@ -2151,7 +2163,7 @@ Unignored files/directories are not included." (defun projectile-paths-to-ensure () "Return a list of unignored project paths." - (projectile-normalise-paths (caddr (projectile-parse-dirconfig-file)))) + (projectile-normalise-paths (projectile--dirconfig-ensure))) (defun projectile-files-to-ensure () (let ((default-directory (projectile-project-root))) @@ -2160,7 +2172,7 @@ Unignored files/directories are not included." (defun projectile-patterns-to-ensure () "Return a list of relative file patterns." - (projectile-normalise-patterns (caddr (projectile-parse-dirconfig-file)))) + (projectile-normalise-patterns (projectile--dirconfig-ensure))) (defun projectile-filtering-patterns () (cons (projectile-patterns-to-ignore) @@ -2176,6 +2188,15 @@ Unignored files/directories are not included." "Return the absolute path to the project's dirconfig file." (expand-file-name projectile-dirconfig-file (projectile-project-root))) +(cl-defstruct projectile-dirconfig + "Parsed contents of a project's dirconfig file. +KEEP is the list of subdirectories to restrict the project to (as +returned with a trailing slash). IGNORE and ENSURE are the lists +of files or directories to ignore and to forcibly include, +respectively. All slots default to nil, which represents \"no +file present or no entries of this kind\"." + (keep nil) (ignore nil) (ensure nil)) + (defun projectile--warn-glob-in-keep-entry (entry dirconfig) "Warn that ENTRY in DIRCONFIG looks like a glob pattern after a `+'. The `+' prefix is for subdirectories only; the parser silently coerces @@ -2190,7 +2211,7 @@ or move the pattern to a `-'/`!' rule." (defun projectile--parse-dirconfig-file-uncached () "Parse the dirconfig file without caching. -Returns a list of (KEEP IGNORE ENSURE) or nil if the file doesn't exist." +Return a `projectile-dirconfig' or nil if the file doesn't exist." (let (keep ignore ensure (dirconfig (projectile-dirconfig-file))) (when (projectile-file-exists-p dirconfig) (with-temp-buffer @@ -2216,20 +2237,19 @@ Returns a list of (KEEP IGNORE ENSURE) or nil if the file doesn't exist." (dolist (entry trimmed-keep) (when (string-match-p "[*?[]" entry) (projectile--warn-glob-in-keep-entry entry dirconfig))) - (list (mapcar #'file-name-as-directory trimmed-keep) - (mapcar #'string-trim - (delete "" (reverse ignore))) - (mapcar #'string-trim - (delete "" (reverse ensure)))))))) + (make-projectile-dirconfig + :keep (mapcar #'file-name-as-directory trimmed-keep) + :ignore (mapcar #'string-trim (delete "" (reverse ignore))) + :ensure (mapcar #'string-trim (delete "" (reverse ensure)))))))) (defun projectile-parse-dirconfig-file () - "Parse project ignore file and return directories to ignore and keep. + "Parse project ignore file and return its rules. -The return value is a list of three elements: the car is the list -of directories to keep, the cadr is the list of files or -directories to ignore, and the caddr is the list of files or -directories to ensure (i.e. forcibly include even when otherwise -ignored). +The return value is a `projectile-dirconfig' struct with three +slots: KEEP (subdirectories to restrict the project to), IGNORE +(files or directories to skip), and ENSURE (files or directories +to forcibly include even when otherwise ignored). When the file +does not exist, the return value is nil. Lines are dispatched on their first non-whitespace character: diff --git a/test/projectile-test.el b/test/projectile-test.el index 7e1e2a1..30ddaa3 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -534,22 +534,23 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (spy-on 'insert-file-contents :and-call-fake (lambda (filename) (save-excursion (insert "\n-exclude\n+include\n#may-be-a-comment\nno-prefix\n left-wspace\nright-wspace\t\n")))) - (expect (projectile-parse-dirconfig-file) :to-equal '(("include/") - ("exclude" - "#may-be-a-comment" - "no-prefix" - "left-wspace" - "right-wspace") - nil)) + (expect (projectile-parse-dirconfig-file) + :to-equal (make-projectile-dirconfig + :keep '("include/") + :ignore '("exclude" + "#may-be-a-comment" + "no-prefix" + "left-wspace" + "right-wspace"))) ;; same test - but with comment lines enabled using prefix '#' (let ((projectile-dirconfig-comment-prefix ?#)) - (expect (projectile-parse-dirconfig-file) :to-equal '(("include/") - ("exclude" - "no-prefix" - "left-wspace" - "right-wspace") - nil))) - ) + (expect (projectile-parse-dirconfig-file) + :to-equal (make-projectile-dirconfig + :keep '("include/") + :ignore '("exclude" + "no-prefix" + "left-wspace" + "right-wspace"))))) (it "skips leading whitespace before dispatching on the prefix" (spy-on 'file-exists-p :and-return-value t) (spy-on 'insert-file-contents :and-call-fake @@ -560,9 +561,10 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. " !indented-ensure\n" " no-prefix-indented\n")))) (expect (projectile-parse-dirconfig-file) - :to-equal '(("indented-include/") - ("indented-exclude" "no-prefix-indented") - ("indented-ensure")))) + :to-equal (make-projectile-dirconfig + :keep '("indented-include/") + :ignore '("indented-exclude" "no-prefix-indented") + :ensure '("indented-ensure")))) (it "treats indented comment-prefix lines as comments" (spy-on 'file-exists-p :and-return-value t) (spy-on 'insert-file-contents :and-call-fake @@ -572,7 +574,7 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. "-keep-this\n")))) (let ((projectile-dirconfig-comment-prefix ?#)) (expect (projectile-parse-dirconfig-file) - :to-equal '(nil ("keep-this") nil)))) + :to-equal (make-projectile-dirconfig :ignore '("keep-this"))))) (it "warns when a + keep entry contains glob metacharacters" (spy-on 'file-exists-p :and-return-value t) (spy-on 'insert-file-contents :and-call-fake @@ -613,9 +615,10 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. "stale-pattern\n")) (spy-on 'projectile-project-root :and-return-value root) (expect (projectile-parse-dirconfig-file) - :to-equal '(("/src/") - ("/build" "stale-pattern") - ("/build/keepme"))))))) + :to-equal (make-projectile-dirconfig + :keep '("/src/") + :ignore '("/build" "stale-pattern") + :ensure '("/build/keepme"))))))) (it "round-trips non-ASCII paths through the parser" (projectile-test-with-sandbox (projectile-test-with-files @@ -627,9 +630,9 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. "+/プロジェクト\n")) (spy-on 'projectile-project-root :and-return-value root) (expect (projectile-parse-dirconfig-file) - :to-equal '(("/プロジェクト/") - ("héllo/wörld") - nil)))))) + :to-equal (make-projectile-dirconfig + :keep '("/プロジェクト/") + :ignore '("héllo/wörld"))))))) (it "tolerates a trailing line without a final newline" (projectile-test-with-sandbox (projectile-test-with-files @@ -638,7 +641,7 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (with-temp-file (expand-file-name ".projectile" root) (insert "-foo\n-bar")) (spy-on 'projectile-project-root :and-return-value root) - (expect (cadr (projectile-parse-dirconfig-file)) + (expect (projectile-dirconfig-ignore (projectile-parse-dirconfig-file)) :to-equal '("foo" "bar"))))))) (describe "dirconfig cache" @@ -667,14 +670,14 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (dirconfig (expand-file-name ".projectile" root))) (with-temp-file dirconfig (insert "-foo\n")) (spy-on 'projectile-project-root :and-return-value root) - (expect (cadr (projectile-parse-dirconfig-file)) + (expect (projectile-dirconfig-ignore (projectile-parse-dirconfig-file)) :to-equal '("foo")) ;; Force a distinct mtime — file-attribute-modification-time has ;; second-level resolution on some filesystems. (set-file-times dirconfig (time-add (current-time) 5)) (with-temp-file dirconfig (insert "-bar\n")) (set-file-times dirconfig (time-add (current-time) 5)) - (expect (cadr (projectile-parse-dirconfig-file)) + (expect (projectile-dirconfig-ignore (projectile-parse-dirconfig-file)) :to-equal '("bar")))))) (it "returns nil and does not cache when the dirconfig file is absent" (projectile-test-with-sandbox @@ -766,11 +769,12 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (describe "projectile-get-project-directories" (it "gets the list of project directories" (spy-on 'projectile-project-root :and-return-value "/my/root/") - (spy-on 'projectile-parse-dirconfig-file :and-return-value '(nil)) + (spy-on 'projectile-parse-dirconfig-file :and-return-value nil) (expect (projectile-get-project-directories "/my/root") :to-equal '("/my/root"))) (it "gets the list of project directories with dirs to keep" (spy-on 'projectile-project-root :and-return-value "/my/root/") - (spy-on 'projectile-parse-dirconfig-file :and-return-value '(("foo" "bar/baz"))) + (spy-on 'projectile-parse-dirconfig-file + :and-return-value (make-projectile-dirconfig :keep '("foo" "bar/baz"))) (expect (projectile-get-project-directories "/my/root/") :to-equal '("/my/root/foo" "/my/root/bar/baz")))) (describe "projectile-dir-files" |
