aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--projectile.el64
-rw-r--r--test/projectile-test.el31
2 files changed, 69 insertions, 26 deletions
diff --git a/projectile.el b/projectile.el
index dd6fce0..7217397 100644
--- a/projectile.el
+++ b/projectile.el
@@ -2209,38 +2209,50 @@ or move the pattern to a `-'/`!' rule."
dirconfig entry)
:warning))
+(defun projectile--dirconfig-classify-line (line)
+ "Classify LINE from a dirconfig file.
+Return a cons (BUCKET . VALUE) where BUCKET is one of `:keep',
+`:ignore', `:ensure', or `:comment'. Return nil for a blank line.
+Leading whitespace is skipped before dispatch so an accidental space
+or tab before the prefix does not change classification."
+ (let* ((trimmed (string-trim-left line))
+ (first-char (and (> (length trimmed) 0) (aref trimmed 0))))
+ (cond
+ ((null first-char) nil)
+ ((and projectile-dirconfig-comment-prefix
+ (eql first-char projectile-dirconfig-comment-prefix))
+ (cons :comment nil))
+ ((eql first-char ?+) (cons :keep (string-trim (substring trimmed 1))))
+ ((eql first-char ?-) (cons :ignore (string-trim (substring trimmed 1))))
+ ((eql first-char ?!) (cons :ensure (string-trim (substring trimmed 1))))
+ (t (cons :ignore (string-trim trimmed))))))
+
+(defun projectile--parse-dirconfig-string (text)
+ "Parse TEXT (a dirconfig file's contents) into a `projectile-dirconfig'."
+ (let (keep ignore ensure)
+ (dolist (line (split-string text "\n"))
+ (pcase (projectile--dirconfig-classify-line line)
+ (`(:keep . ,v) (unless (string-empty-p v) (push v keep)))
+ (`(:ignore . ,v) (unless (string-empty-p v) (push v ignore)))
+ (`(:ensure . ,v) (unless (string-empty-p v) (push v ensure)))))
+ (make-projectile-dirconfig
+ :keep (mapcar #'file-name-as-directory (nreverse keep))
+ :ignore (nreverse ignore)
+ :ensure (nreverse ensure))))
+
(defun projectile--parse-dirconfig-file-uncached ()
"Parse the dirconfig file without caching.
Return a `projectile-dirconfig' or nil if the file doesn't exist."
- (let (keep ignore ensure (dirconfig (projectile-dirconfig-file)))
+ (let ((dirconfig (projectile-dirconfig-file)))
(when (projectile-file-exists-p dirconfig)
- (with-temp-buffer
- (insert-file-contents dirconfig)
- (while (not (eobp))
- ;; Skip leading whitespace so prefix dispatch isn't defeated by
- ;; an accidental space or tab before the +/-/! marker or the
- ;; configured comment character.
- (skip-chars-forward " \t")
- (pcase (char-after)
- ;; ignore comment lines if prefix char has been set
- ((pred (lambda (leading-char)
- (and projectile-dirconfig-comment-prefix
- (eql leading-char
- projectile-dirconfig-comment-prefix))))
- nil)
- (?+ (push (buffer-substring (1+ (point)) (line-end-position)) keep))
- (?- (push (buffer-substring (1+ (point)) (line-end-position)) ignore))
- (?! (push (buffer-substring (1+ (point)) (line-end-position)) ensure))
- (_ (push (buffer-substring (point) (line-end-position)) ignore)))
- (forward-line)))
- (let ((trimmed-keep (mapcar #'string-trim (delete "" (reverse keep)))))
- (dolist (entry trimmed-keep)
+ (let ((cfg (projectile--parse-dirconfig-string
+ (with-temp-buffer
+ (insert-file-contents dirconfig)
+ (buffer-string)))))
+ (dolist (entry (projectile-dirconfig-keep cfg))
(when (string-match-p "[*?[]" entry)
(projectile--warn-glob-in-keep-entry entry dirconfig)))
- (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))))))))
+ cfg))))
(defun projectile-parse-dirconfig-file ()
"Parse project ignore file and return its rules.
diff --git a/test/projectile-test.el b/test/projectile-test.el
index 30ddaa3..fc6116d 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -527,6 +527,37 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'.
(let ((projectile-globally-unignored-files '("path/unignored-file")))
(expect (projectile-add-unignored nil nil '("file")) :to-equal '("file" "path/unignored-file")))))))
+(describe "projectile--dirconfig-classify-line"
+ (it "returns nil for blank or whitespace-only lines"
+ (expect (projectile--dirconfig-classify-line "") :to-be nil)
+ (expect (projectile--dirconfig-classify-line " ") :to-be nil)
+ (expect (projectile--dirconfig-classify-line "\t") :to-be nil))
+ (it "classifies prefix dispatches"
+ (expect (projectile--dirconfig-classify-line "+/src")
+ :to-equal '(:keep . "/src"))
+ (expect (projectile--dirconfig-classify-line "-/build")
+ :to-equal '(:ignore . "/build"))
+ (expect (projectile--dirconfig-classify-line "!/build/keepme")
+ :to-equal '(:ensure . "/build/keepme")))
+ (it "treats prefix-less lines as ignore for backward compatibility"
+ (expect (projectile--dirconfig-classify-line "stale-pattern")
+ :to-equal '(:ignore . "stale-pattern")))
+ (it "skips leading whitespace before dispatch"
+ (expect (projectile--dirconfig-classify-line " -indented")
+ :to-equal '(:ignore . "indented"))
+ (expect (projectile--dirconfig-classify-line "\t+keep")
+ :to-equal '(:keep . "keep")))
+ (it "honors the comment prefix when configured"
+ (let ((projectile-dirconfig-comment-prefix ?#))
+ (expect (projectile--dirconfig-classify-line "# a comment")
+ :to-equal '(:comment))
+ (expect (projectile--dirconfig-classify-line " # indented comment")
+ :to-equal '(:comment)))
+ ;; Without a comment prefix, # is just a regular character.
+ (let ((projectile-dirconfig-comment-prefix nil))
+ (expect (projectile--dirconfig-classify-line "#may-be-a-comment")
+ :to-equal '(:ignore . "#may-be-a-comment")))))
+
(describe "projectile-parse-dirconfig-file"
(it "parses dirconfig and returns directories to ignore and keep"
(spy-on 'file-exists-p :and-return-value t)