diff --git a/elisp/runfiles/runfiles-test.el b/elisp/runfiles/runfiles-test.el index 050e0b74..19e83154 100644 --- a/elisp/runfiles/runfiles-test.el +++ b/elisp/runfiles/runfiles-test.el @@ -56,8 +56,17 @@ "phst_rules_elisp/elisp/runfiles/test-manifest")) (runfiles (elisp/runfiles/make :manifest manifest :directory "/invalid/"))) - (should (equal (elisp/runfiles/rlocation "testäα𝐴🐈'.txt" runfiles) - "/:/runfiles/testäα𝐴🐈'.txt")))) + (pcase-dolist (`(,source ,target) + '(("testäα𝐴🐈'.txt" + "/:/runfiles/testäα𝐴🐈'.txt") + ("target-with-space" + "/:/runfiles/with space\\and backslash") + ("target-with-newline" + "/:/runfiles/with\nnewline\\and backslash") + ("source with space,\nnewline,\\and backslash" + "/:/runfiles/with space,\nnewline,\\and backslash"))) + (ert-info (source :prefix "Source: ") + (should (equal (elisp/runfiles/rlocation source runfiles) target)))))) (ert-deftest elisp/runfiles/make/empty-file () (let* ((manifest (elisp/runfiles/rlocation diff --git a/elisp/runfiles/runfiles.el b/elisp/runfiles/runfiles.el index 5e06f162..6e59f28f 100644 --- a/elisp/runfiles/runfiles.el +++ b/elisp/runfiles/runfiles.el @@ -501,13 +501,31 @@ Return an object of type ‘elisp/runfiles/runfiles--manifest’." ;; Perform the same parsing as ;; https://github.com/bazelbuild/bazel/blob/6.4.0/tools/cpp/runfiles/runfiles_src.cc#L241. (while (not (eobp)) - (pcase (buffer-substring-no-properties (point) (line-end-position)) - ((rx bos (let key (+ (not (any ?\n ?\s)))) - ?\s (let value (* nonl)) eos) - ;; Runfiles are always local, so quote them unconditionally. - (puthash key (if (string-empty-p value) :empty (concat "/:" value)) - manifest)) - (other (signal 'elisp/runfiles/syntax-error (list filename other)))) + (let ((line (buffer-substring-no-properties + (point) (line-end-position))) + (escaped (eql (following-char) ?\s))) + (cl-flet* ((syntax-error () + (signal 'elisp/runfiles/syntax-error + (list filename line))) + (unescape (string &rest other) + (let ((pairs `(,@other ("\\n" . "\n") ("\\b" . "\\")))) + (replace-regexp-in-string + (rx ?\\ (? anychar)) + (lambda (seq) + (or (cdr (assoc seq pairs)) (syntax-error))) + string :fixedcase :literal)))) + (pcase (if escaped (substring-no-properties line 1) line) + ((rx bos (let key (+ (not (any ?\n ?\s)))) + ?\s (let value (* nonl)) eos) + (when escaped + (cl-callf unescape key '("\\s" . " ")) + (cl-callf unescape value)) + (puthash key + ;; Runfiles are always local, so quote them + ;; unconditionally. + (if (string-empty-p value) :empty (concat "/:" value)) + manifest)) + (_ (syntax-error))))) (forward-line))) (elisp/runfiles/manifest--make filename manifest))) diff --git a/elisp/runfiles/test-manifest b/elisp/runfiles/test-manifest index 6915b999..26b1f400 100644 --- a/elisp/runfiles/test-manifest +++ b/elisp/runfiles/test-manifest @@ -1,3 +1,6 @@ __init__.py foo/bar runfiles/foo/bar testäα𝐴🐈'.txt /runfiles/testäα𝐴🐈'.txt +target-with-space /runfiles/with space\and backslash + target-with-newline /runfiles/with\nnewline\band backslash + source\swith\sspace,\nnewline,\band\sbackslash /runfiles/with space,\nnewline,\band backslash