aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/dev-fkeys.el23
-rw-r--r--modules/system-lib.el18
-rw-r--r--tests/test-system-lib-shell-quote-argument-readable.el55
3 files changed, 79 insertions, 17 deletions
diff --git a/modules/dev-fkeys.el b/modules/dev-fkeys.el
index c9a5fc13..170e70b9 100644
--- a/modules/dev-fkeys.el
+++ b/modules/dev-fkeys.el
@@ -41,6 +41,7 @@
;;; Code:
(require 'cl-lib)
+(require 'system-lib)
(declare-function projectile-compile-project "projectile" (arg))
(declare-function projectile-run-project "projectile" (arg))
@@ -334,18 +335,6 @@ languages fall back to the basename without extension."
;; ---------- F6 test-runner command builder ----------
-(defconst cj/--f6-shell-safe-argument-regexp "\\`[[:alnum:]_./=+@%:,^-]+\\'"
- "Regexp matching shell arguments safe to interpolate unchanged.")
-
-(defun cj/--f6-shell-quote-argument (argument)
- "Quote ARGUMENT for shell command interpolation when needed.
-Simple file paths and test regexes are returned unchanged so existing
-F6 command strings stay readable. Arguments containing whitespace or
-shell-significant characters are escaped with `shell-quote-argument'."
- (if (string-match-p cj/--f6-shell-safe-argument-regexp argument)
- argument
- (shell-quote-argument argument)))
-
(defun cj/--f6-test-runner-cmd-for (language is-test-file rel-path stem rel-dir)
"Return shell command to run tests for the given primitives, or nil.
LANGUAGE is the language symbol; IS-TEST-FILE is non-nil when the file
@@ -364,20 +353,20 @@ TypeScript / JavaScript and unknown languages return nil."
;; The project Makefile prepends `tests/' to FILE, so pass the
;; basename only — passing the rel-path produces `tests/tests/...'.
(format "make test-file FILE=%s"
- (cj/--f6-shell-quote-argument
+ (cj/shell-quote-argument-readable
(file-name-nondirectory rel-path)))
(format "make test-name TEST=%s"
- (cj/--f6-shell-quote-argument
+ (cj/shell-quote-argument-readable
(format "^test-%s-" stem)))))
('python
(if is-test-file
- (format "pytest %s" (cj/--f6-shell-quote-argument rel-path))
+ (format "pytest %s" (cj/shell-quote-argument-readable rel-path))
(format "pytest %s"
- (cj/--f6-shell-quote-argument
+ (cj/shell-quote-argument-readable
(format "tests/test_%s.py" stem)))))
('go
(format "go test %s"
- (cj/--f6-shell-quote-argument
+ (cj/shell-quote-argument-readable
(if (string-empty-p rel-dir)
"./"
(format "./%s" rel-dir)))))
diff --git a/modules/system-lib.el b/modules/system-lib.el
index f932353f..dc1f8316 100644
--- a/modules/system-lib.el
+++ b/modules/system-lib.el
@@ -36,6 +36,24 @@ keep working."
:warning)
nil)))
+(defconst cj/shell-safe-argument-regexp "\\`[[:alnum:]_./=+@%:,^-]+\\'"
+ "Regexp matching shell arguments safe to interpolate unchanged.
+Members of this character set survive shell parsing without quoting,
+so a command line containing only these characters in each argument
+remains both safe and readable.")
+
+(defun cj/shell-quote-argument-readable (argument)
+ "Quote ARGUMENT for shell command interpolation when needed.
+
+When ARGUMENT consists only of characters in `cj/shell-safe-argument-regexp'
+it is returned unchanged so the surrounding command stays human-readable
+(useful for compile/test command lines you'll inspect in *compilation*).
+Otherwise falls back to `shell-quote-argument' so the result is safe to
+interpolate."
+ (if (string-match-p cj/shell-safe-argument-regexp argument)
+ argument
+ (shell-quote-argument argument)))
+
(defun cj/log-silently (format-string &rest args)
"Append formatted message (FORMAT-STRING with ARGS) to *Messages* buffer.
This does so without echoing in the minibuffer."
diff --git a/tests/test-system-lib-shell-quote-argument-readable.el b/tests/test-system-lib-shell-quote-argument-readable.el
new file mode 100644
index 00000000..1a0c7227
--- /dev/null
+++ b/tests/test-system-lib-shell-quote-argument-readable.el
@@ -0,0 +1,55 @@
+;;; test-system-lib-shell-quote-argument-readable.el --- Tests for cj/shell-quote-argument-readable -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; `cj/shell-quote-argument-readable' is the readable-quote helper.
+;; When ARGUMENT consists only of characters safe in a shell string,
+;; return it unchanged so the surrounding command stays human-readable
+;; (compile / test command lines, log inspection). When it contains
+;; whitespace or shell metacharacters, fall back to
+;; `shell-quote-argument' so the result is safe to interpolate.
+
+;;; Code:
+
+(require 'ert)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'system-lib)
+
+(ert-deftest test-cj-shell-quote-argument-readable-simple-path-unchanged ()
+ "Normal: an alphanumeric path is returned unchanged."
+ (should (equal (cj/shell-quote-argument-readable "tests/test-foo.el")
+ "tests/test-foo.el")))
+
+(ert-deftest test-cj-shell-quote-argument-readable-test-regex-unchanged ()
+ "Normal: a typical test-name regex with safe punctuation is unchanged."
+ (should (equal (cj/shell-quote-argument-readable "^test-dev-fkeys-")
+ "^test-dev-fkeys-")))
+
+(ert-deftest test-cj-shell-quote-argument-readable-flags-unchanged ()
+ "Normal: arguments with `=', `+', `:' read as-is (FLAG=value, addr:port)."
+ (should (equal (cj/shell-quote-argument-readable "FILE=tests/test-foo.el")
+ "FILE=tests/test-foo.el"))
+ (should (equal (cj/shell-quote-argument-readable "host:1234")
+ "host:1234")))
+
+(ert-deftest test-cj-shell-quote-argument-readable-spaces-quoted ()
+ "Boundary: an argument containing spaces falls back to shell-quote-argument."
+ (let ((quoted (cj/shell-quote-argument-readable "path/with space.el")))
+ ;; shell-quote-argument either backslash-escapes or wraps in single
+ ;; quotes; either way the raw input cannot survive verbatim.
+ (should-not (equal quoted "path/with space.el"))
+ (should (string-match-p "space" quoted))))
+
+(ert-deftest test-cj-shell-quote-argument-readable-shell-metachars-quoted ()
+ "Boundary: arguments with `$', `;', `&', backticks, `*' are quoted."
+ (dolist (arg '("$HOME" "a;b" "foo&bar" "back`tick`" "glob*"))
+ (let ((quoted (cj/shell-quote-argument-readable arg)))
+ (should-not (equal quoted arg)))))
+
+(ert-deftest test-cj-shell-quote-argument-readable-empty-string-quoted ()
+ "Boundary: empty string is unsafe in a command line and is quoted."
+ (let ((quoted (cj/shell-quote-argument-readable "")))
+ (should-not (equal quoted ""))))
+
+(provide 'test-system-lib-shell-quote-argument-readable)
+;;; test-system-lib-shell-quote-argument-readable.el ends here