From a759a44d2dbedb0d1ea2126dd349505934adb306 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Mon, 15 May 2023 21:33:29 +0100 Subject: [PATCH] Treat PowerShell quoting as a special case of`shell-quote-args' - `cider-inject-jack-in-dependencies' requires an additional arg - introduces `cider--shell-quote-argument' to support PowerShell arg quoting - `cider--powershell-encode-command' now only encodes but does not quotes args (this is the job of `cider--shell-quote-argument' now). Tests rebased to - Account for additional command arg in jack in commands - Account for the fact that cider--powershell-encode-command only encodes but not quotes any more - quoting command for dependencies is os/command specific. - Run an example jack-in command test for powershell. --- cider.el | 59 +++++++++------ test/cider-tests.el | 102 +++++++++++++------------- test/integration/integration-tests.el | 2 +- 3 files changed, 87 insertions(+), 76 deletions(-) diff --git a/cider.el b/cider.el index bb39bd3b2..9caa26a3a 100644 --- a/cider.el +++ b/cider.el @@ -744,9 +744,25 @@ removed, LEIN-PLUGINS, LEIN-MIDDLEWARES and finally PARAMS." "Removes the duplicates in DEPS." (cl-delete-duplicates deps :test 'equal)) +(defun cider--jack-in-cmd-powershell-p (command) + "Returns whether COMMAND is PowerShell." + (or (string-equal command "powershell") + (string-equal command "pwsh"))) + +(defun cider--shell-quote-argument (argument &optional command) + "Quotes ARGUMENT like `shell-quote-argument', suitable for use with COMMAND. + +Uses `shell-quote-argument' to quote the ARGUMENT, unless COMMAND is given +and refers to PowerShell, in which case it uses (some limited) PowerShell +rules to quote it." + (if (cider--jack-in-cmd-powershell-p command) + ;; please add more PowerShell quoting rules as necessary. + (format "'%s'" (replace-regexp-in-string "\"" "\"\"" argument)) + (shell-quote-argument argument))) + (defun cider--powershell-encode-command (cmd-params) "Base64 encode the powershell command and jack-in CMD-PARAMS for clojure-cli." - (let* ((quoted-params (replace-regexp-in-string "\"" "\"\"" cmd-params)) + (let* ((quoted-params cmd-params) (command (format "clojure %s" quoted-params)) (utf-16le-command (encode-coding-string command 'utf-16le))) (format "-encodedCommand %s" (base64-encode-string utf-16le-command t)))) @@ -754,7 +770,7 @@ removed, LEIN-PLUGINS, LEIN-MIDDLEWARES and finally PARAMS." (defun cider-clojure-cli-jack-in-dependencies (global-options params dependencies command) "Create Clojure tools.deps jack-in dependencies. Does so by concatenating DEPENDENCIES, PARAMS and GLOBAL-OPTIONS into a -suitable `clojure` invocation and quoting it based on COMMAND if necessary. +suitable `clojure` invocation and quoting suitable for COMMAND invocation. The main is placed in an inline alias :cider/nrepl so that if your aliases contain any mains, the cider/nrepl one will be the one used." (let* ((all-deps (thread-last @@ -778,25 +794,19 @@ contain any mains, the cider/nrepl one will be the one used." (cider-jack-in-normalized-nrepl-middlewares) ",")) (main-opts (format "\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[%s]\"" middleware)) - (deps (format "%s-Sdeps '{:deps {%s} :aliases {:cider/nrepl {:main-opts [%s]}}}' -M%s:cider/nrepl%s" - ;; TODO: global-options are deprecated and should be removed in CIDER 2.0 - (if global-options (format "%s " global-options) "") - (string-join all-deps " ") - main-opts - (if cider-clojure-cli-aliases - ;; remove exec-opts flags -A -M -T or -X from cider-clojure-cli-aliases - ;; concatenated with :cider/nrepl to ensure :cider/nrepl comes last - (format "%s" (replace-regexp-in-string "^-\\(A\\|M\\|T\\|X\\)" "" cider-clojure-cli-aliases)) - "") - (if params (format " %s" params) ""))) - (quoted (if (eq system-type 'windows-nt) - (if (or (string-equal command "powershell") - (string-equal command "pwsh")) - (cider--powershell-encode-command deps) - (thread-last (replace-regexp-in-string "\"" "\"\"" deps) - (replace-regexp-in-string "'" "\""))) - deps))) - quoted)) + (deps (format "{:deps {%s} :aliases {:cider/nrepl {:main-opts [%s]}}}" + (string-join all-deps " ") main-opts)) + (deps-quoted (cider--shell-quote-argument deps command))) + (format "%s-Sdeps %s -M%s:cider/nrepl%s" + ;; TODO: global-options are deprecated and should be removed in CIDER 2.0 + (if global-options (format "%s " global-options) "") + deps-quoted + (if cider-clojure-cli-aliases + ;; remove exec-opts flags -A -M -T or -X from cider-clojure-cli-aliases + ;; concatenated with :cider/nrepl to ensure :cider/nrepl comes last + (format "%s" (replace-regexp-in-string "^-\\(A\\|M\\|T\\|X\\)" "" cider-clojure-cli-aliases)) + "") + (if params (format " %s" params) "")))) (defun cider-shadow-cljs-jack-in-dependencies (global-opts params dependencies) "Create shadow-cljs jack-in deps. @@ -832,7 +842,7 @@ See also `cider-jack-in-auto-inject-clojure'." "Return GLOBAL-OPTS and PARAMS with injected REPL dependencies. These are set in `cider-jack-in-dependencies', `cider-jack-in-lein-plugins' and `cider-jack-in-nrepl-middlewares' are injected from the CLI according -to the used PROJECT-TYPE and COMMAND. Eliminates the need for hacking +to the used PROJECT-TYPE and COMMAND. Eliminates the need for hacking profiles.clj or the boot script for supporting CIDER with its nREPL middleware and dependencies." (pcase project-type @@ -1565,7 +1575,10 @@ PARAMS is a plist with the following keys (non-exhaustive list) (eq cider-allow-jack-in-without-project 'warn) (or params-project-type (y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? ")))) - (let ((cmd (format "%s %s" command-resolved cmd-params))) + (let ((cmd (format "%s %s" command-resolved + (if (cider--jack-in-cmd-powershell-p command) + (cider--powershell-encode-command cmd-params) + cmd-params)))) (plist-put params :jack-in-cmd (if (or cider-edit-jack-in-command (plist-get params :edit-jack-in-command)) (read-string "jack-in command: " cmd 'cider--jack-in-cmd-history) diff --git a/test/cider-tests.el b/test/cider-tests.el index ba02e80ca..dabe9e488 100644 --- a/test/cider-tests.el +++ b/test/cider-tests.el @@ -148,7 +148,7 @@ (setq-local cider-enrich-classpath t)) (it "can inject dependencies in a lein project" - (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein) + (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein "lein") :to-equal (concat "update-in :dependencies conj " (shell-quote-argument "[nrepl/nrepl \"0.9.0\"]") " -- update-in :plugins conj " @@ -160,7 +160,7 @@ (it "can inject dependencies in a lein project with an exclusion" (setq-local cider-jack-in-dependencies-exclusions '(("nrepl/nrepl" ("org.clojure/clojure")))) - (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein) + (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein "lein") :to-equal (concat "update-in :dependencies conj " (shell-quote-argument "[nrepl/nrepl \"0.9.0\" :exclusions [org.clojure/clojure]]") @@ -173,7 +173,7 @@ (it "can inject dependencies in a lein project with multiple exclusions" (setq-local cider-jack-in-dependencies-exclusions '(("nrepl/nrepl" ("org.clojure/clojure" "foo.bar/baz")))) - (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein) + (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein "lein") :to-equal (concat "update-in :dependencies conj " (shell-quote-argument "[nrepl/nrepl \"0.9.0\" :exclusions [org.clojure/clojure foo.bar/baz]]") " -- update-in :plugins conj " @@ -184,7 +184,7 @@ " -- repl :headless"))) (it "can inject dependencies in a boot project" - (expect (cider-inject-jack-in-dependencies "" "repl -s wait" 'boot) + (expect (cider-inject-jack-in-dependencies "" "repl -s wait" 'boot "boot") :to-equal (concat "-i \"(require 'cider.tasks)\"" " -d " @@ -197,7 +197,7 @@ " repl -s wait"))) (it "can inject dependencies in a gradle project" - (expect (cider-inject-jack-in-dependencies "--no-daemon" ":clojureRepl" 'gradle) + (expect (cider-inject-jack-in-dependencies "--no-daemon" ":clojureRepl" 'gradle "grandle") :to-equal (concat "--no-daemon " (shell-quote-argument "-Pdev.clojurephant.jack-in.nrepl=nrepl:nrepl:0.9.0,cider:cider-nrepl:0.28.5") " :clojureRepl " @@ -210,7 +210,7 @@ (setq-local cider-jack-in-nrepl-middlewares '("refactor-nrepl.middleware/wrap-refactor" "cider.nrepl/cider-middleware")) (setq-local cider-jack-in-dependencies-exclusions '())) (it "can inject dependencies in a lein project" - (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein) + (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein "lein") :to-equal (concat "update-in :dependencies conj " (shell-quote-argument "[nrepl/nrepl \"0.9.0\"]") " -- update-in :plugins conj " @@ -224,7 +224,7 @@ (it "can inject dependencies in a boot project" (setq-local cider-jack-in-dependencies '(("refactor-nrepl" "2.0.0"))) - (expect (cider-inject-jack-in-dependencies "" "repl -s wait" 'boot) + (expect (cider-inject-jack-in-dependencies "" "repl -s wait" 'boot "boot") :to-equal (concat "-i \"(require 'cider.tasks)\"" " -d " (shell-quote-argument "nrepl/nrepl:0.9.0") @@ -247,7 +247,7 @@ (setq-local cider-jack-in-nrepl-middlewares '("cider.nrepl/cider-middleware")) (setq-local cider-jack-in-dependencies-exclusions '())) (it "can concat in a lein project" - (expect (cider-inject-jack-in-dependencies "-o -U" "repl :headless" 'lein) + (expect (cider-inject-jack-in-dependencies "-o -U" "repl :headless" 'lein "lein") :to-equal (concat "-o -U update-in :dependencies conj " (shell-quote-argument "[nrepl/nrepl \"0.9.0\"]") " -- update-in :plugins conj " @@ -257,7 +257,7 @@ " -- update-in :middleware conj cider.enrich-classpath/middleware" " -- repl :headless"))) (it "can concat in a boot project" - (expect (cider-inject-jack-in-dependencies "-C -o" "repl -s wait" 'boot) + (expect (cider-inject-jack-in-dependencies "-C -o" "repl -s wait" 'boot "boot") :to-equal (concat "-C -o -i \"(require 'cider.tasks)\"" " -d " (shell-quote-argument "nrepl/nrepl:0.9.0") @@ -268,7 +268,7 @@ (shell-quote-argument "cider.nrepl/cider-middleware") " repl -s wait"))) (it "can concat in a gradle project" - (expect (cider-inject-jack-in-dependencies "--no-daemon" ":clojureRepl" 'gradle) + (expect (cider-inject-jack-in-dependencies "--no-daemon" ":clojureRepl" 'gradle "grandle") :to-equal (concat "--no-daemon " (shell-quote-argument "-Pdev.clojurephant.jack-in.nrepl=nrepl:nrepl:0.9.0,cider:cider-nrepl:0.28.5") " :clojureRepl " @@ -326,7 +326,7 @@ (setq-local cider-jack-in-dependencies-exclusions '()) (setq-local cider-enrich-classpath t)) (it "uses them in a lein project" - (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein) + (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein "lein") :to-equal (concat "update-in :dependencies conj " (shell-quote-argument "[nrepl/nrepl \"0.9.0\"]") " -- update-in :plugins conj " @@ -345,7 +345,7 @@ (setq-local cider-jack-in-dependencies '(("refactor-nrepl" "2.0.0"))) (setq-local cider-jack-in-dependencies-exclusions '())) (it "uses them in a boot project" - (expect (cider-inject-jack-in-dependencies "" "repl -s wait" 'boot) + (expect (cider-inject-jack-in-dependencies "" "repl -s wait" 'boot "boot") :to-equal (concat "-i \"(require 'cider.tasks)\"" " -d " (shell-quote-argument "nrepl/nrepl:0.9.0") @@ -414,8 +414,8 @@ (it "escapes double quotes by repeating them" (expect (cider--powershell-encode-command "\"cmd-params\"") :to-equal (concat "-encodedCommand " - ;; Eval to reproduce reference string below: (base64-encode-string (encode-coding-string "clojure \"\"cmd-params\"\"" 'utf-16le) t) - "YwBsAG8AagB1AHIAZQAgACIAIgBjAG0AZAAtAHAAYQByAGEAbQBzACIAIgA=")))) + ;; Eval to reproduce reference string below: (base64-encode-string (encode-coding-string "clojure "\"cmd-params\""" 'utf-16le) t) + "YwBsAG8AagB1AHIAZQAgACIAYwBtAGQALQBwAGEAcgBhAG0AcwAiAA==")))) (describe "cider--update-jack-in-cmd" (describe "when 'clojure-cli project type and \"powershell\" command" @@ -430,16 +430,15 @@ (spy-on 'cider-jack-in-params :and-return-value "\"cmd-params\"") (expect (plist-get (cider--update-jack-in-cmd nil) :jack-in-cmd) :to-equal (concat "resolved-powershell -encodedCommand " - ;; Eval to reproduce reference string below: (base64-encode-string (encode-coding-string "clojure \"\"cmd-params\"\"" 'utf-16le) t) - "YwBsAG8AagB1AHIAZQAgACIAIgBjAG0AZAAtAHAAYQByAGEAbQBzACIAIgA=")))) + ;; Eval to reproduce reference string below: (base64-encode-string (encode-coding-string "clojure "\"cmd-params"\"" 'utf-16le) t) + "YwBsAG8AagB1AHIAZQAgACIAYwBtAGQALQBwAGEAcgBhAG0AcwAiAA==")))) (describe "when 'clojure-cli project type" (it "uses main opts in an alias to prevent other mains from winning" (setq-local cider-jack-in-dependencies nil) (setq-local cider-jack-in-nrepl-middlewares '("cider.nrepl/cider-middleware")) - (let ((expected (string-join '("clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} " - "cider/cider-nrepl {:mvn/version \"0.28.5\"}} " - ":aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\"" - " \"[cider.nrepl/cider-middleware]\"]}}}' -M:cider/nrepl") + (let ((expected (string-join `("clojure -Sdeps " + ,(shell-quote-argument "{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} cider/cider-nrepl {:mvn/version \"0.28.5\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") + " -M:cider/nrepl") ""))) (setq-local cider-allow-jack-in-without-project t) (setq-local cider-clojure-cli-command "clojure") @@ -451,10 +450,9 @@ :to-equal expected))) (it "allows specifying custom aliases with `cider-clojure-cli-aliases`" - (let ((expected (string-join '("clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} " - "cider/cider-nrepl {:mvn/version \"0.28.5\"}} " - ":aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\"" - " \"[cider.nrepl/cider-middleware]\"]}}}' -M:dev:test:cider/nrepl") + (let ((expected (string-join `("clojure -Sdeps " + ,(shell-quote-argument "{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} cider/cider-nrepl {:mvn/version \"0.28.5\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") + " -M:dev:test:cider/nrepl") ""))) (setq-local cider-jack-in-dependencies nil) (setq-local cider-clojure-cli-aliases "-A:dev:test") @@ -465,56 +463,56 @@ (spy-on 'cider-jack-in-resolve-command :and-return-value "clojure") (expect (plist-get (cider--update-jack-in-cmd nil) :jack-in-cmd) :to-equal expected))) - (it "should remove duplicates, yielding the same result" - (let ((expected (string-join '("-Sdeps '{:deps {cider/cider-nrepl {:mvn/version \"0.28.5\"} " - "nrepl/nrepl {:mvn/version \"0.9.0\"}} " - ":aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\"" - " \"[cider.nrepl/cider-middleware]\"]}}}' -M:dev:test:cider/nrepl") - ""))) - (expect (cider-clojure-cli-jack-in-dependencies nil nil '(("nrepl/nrepl" "0.9.0") - ("nrepl/nrepl" "0.9.0"))) - :to-equal expected))) + + (dolist (command '("clojure" "powershell")) + (it (format "should remove duplicates, yielding the same result (for %S command invocation)" command) + ;; repeat the same test for PowerShell too + (let ((expected (string-join `("-Sdeps " + ,(cider--shell-quote-argument "{:deps {cider/cider-nrepl {:mvn/version \"0.28.5\"} nrepl/nrepl {:mvn/version \"0.9.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}" + command) + " -M:dev:test:cider/nrepl") + ""))) + (expect (cider-clojure-cli-jack-in-dependencies nil nil '(("nrepl/nrepl" "0.9.0") + ("nrepl/nrepl" "0.9.0")) + command) + :to-equal expected)))) (it "handles aliases correctly" - (let ((expected (string-join '("-Sdeps '{:deps {cider/cider-nrepl {:mvn/version \"0.28.5\"} " - "nrepl/nrepl {:mvn/version \"0.9.0\"}} " - ":aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\"" - " \"[cider.nrepl/cider-middleware]\"]}}}' -M:test:cider/nrepl") + (let ((expected (string-join `("-Sdeps " + ,(shell-quote-argument "{:deps {cider/cider-nrepl {:mvn/version \"0.28.5\"} nrepl/nrepl {:mvn/version \"0.9.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") + " -M:test:cider/nrepl") "")) (deps '(("nrepl/nrepl" "0.9.0")))) (let ((cider-clojure-cli-aliases ":test")) - (expect (cider-clojure-cli-jack-in-dependencies nil nil deps) + (expect (cider-clojure-cli-jack-in-dependencies nil nil deps "clojure") :to-equal expected)) (describe "should strip out leading exec opts -A -M -T -X" (let ((cider-clojure-cli-aliases "-A:test")) - (expect (cider-clojure-cli-jack-in-dependencies nil nil deps) + (expect (cider-clojure-cli-jack-in-dependencies nil nil deps "clojure") :to-equal expected)) (let ((cider-clojure-cli-aliases "-M:test")) - (expect (cider-clojure-cli-jack-in-dependencies nil nil deps) + (expect (cider-clojure-cli-jack-in-dependencies nil nil deps "clojure") :to-equal expected)) (let ((cider-clojure-cli-aliases "-T:test")) - (expect (cider-clojure-cli-jack-in-dependencies nil nil deps) + (expect (cider-clojure-cli-jack-in-dependencies nil nil deps "clojure") :to-equal expected)) (let ((cider-clojure-cli-aliases "-T:test")) - (expect (cider-clojure-cli-jack-in-dependencies nil nil deps) + (expect (cider-clojure-cli-jack-in-dependencies nil nil deps "clojure") :to-equal expected))))) (it "allows for global options" - (let ((expected (string-join '("-J-Djdk.attach.allowAttachSelf -Sdeps '{:deps {cider/cider-nrepl {:mvn/version \"0.28.5\"} " - "nrepl/nrepl {:mvn/version \"0.9.0\"}} " - ":aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\"" - " \"[cider.nrepl/cider-middleware]\"]}}}' -M:test:cider/nrepl") + (let ((expected (string-join `("-J-Djdk.attach.allowAttachSelf -Sdeps " + ,(shell-quote-argument "{:deps {cider/cider-nrepl {:mvn/version \"0.28.5\"} nrepl/nrepl {:mvn/version \"0.9.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") + " -M:test:cider/nrepl") "")) (deps '(("nrepl/nrepl" "0.9.0")))) (let ((cider-clojure-cli-aliases ":test")) - (expect (cider-clojure-cli-jack-in-dependencies "-J-Djdk.attach.allowAttachSelf" nil deps) + (expect (cider-clojure-cli-jack-in-dependencies "-J-Djdk.attach.allowAttachSelf" nil deps "clojure") :to-equal expected)))) (it "allows to specify git coordinate as cider-jack-in-dependency" (setq-local cider-jack-in-dependencies '(("org.clojure/tools.deps" (("git/sha" . "6ae2b6f71773de7549d7f22759e8b09fec27f0d9") ("git/url" . "https://github.com/clojure/tools.deps/"))))) - (let ((expected (string-join '("clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} " - "cider/cider-nrepl {:mvn/version \"0.28.5\"} " - "org.clojure/tools.deps { :git/sha \"6ae2b6f71773de7549d7f22759e8b09fec27f0d9\" :git/url \"https://github.com/clojure/tools.deps/\" }} " - ":aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" " - "\"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}' -M:cider/nrepl") + (let ((expected (string-join `("clojure -Sdeps " + ,(shell-quote-argument "{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} cider/cider-nrepl {:mvn/version \"0.28.5\"} org.clojure/tools.deps { :git/sha \"6ae2b6f71773de7549d7f22759e8b09fec27f0d9\" :git/url \"https://github.com/clojure/tools.deps/\" }} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") + " -M:cider/nrepl") ""))) (setq-local cider-allow-jack-in-without-project t) (setq-local cider-clojure-cli-command "clojure") diff --git a/test/integration/integration-tests.el b/test/integration/integration-tests.el index fe3c853a7..6ed8eef6a 100644 --- a/test/integration/integration-tests.el +++ b/test/integration/integration-tests.el @@ -181,7 +181,7 @@ If CLI-COMMAND is nil, then use the default." (cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5) (expect (member (process-status nrepl-proc) '(exit signal)))))))))) - (it "to clojure tools cli" + (it "to clojure tools cli (default)" (jack-in-clojure-cli-test nil)) (when (eq system-type 'windows-nt)