[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH] Add shell-quasiquote.
From: |
Taylan Ulrich Bayırlı/Kammer |
Subject: |
Re: [PATCH] Add shell-quasiquote. |
Date: |
Mon, 19 Oct 2015 14:35:26 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux) |
address@hidden (Taylan Ulrich "Bayırlı/Kammer") writes:
> This is for ELPA.
>
> [...]
Here's a version with slightly improved documentation and a clearer
reasoning for why shell-quote-argument is avoided.
Eli refused to clarify the safety guarantees of shell-quote-argument in
bug#21702, and after all it has significantly different semantics anyway
(it doesn't quote shell keywords like 'if'), so I will not be using
shell-quote-argument.
>From 276e3adc61b2f083b0348fd231a97feaa7017e36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Taylan=20Ulrich=20Bay=C4=B1rl=C4=B1/Kammer?=
<address@hidden>
Date: Sat, 17 Oct 2015 18:32:22 +0200
Subject: [PATCH 1/3] Add shell-quasiquote.
---
packages/shell-quasiquote/shell-quasiquote.el | 151 ++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
create mode 100644 packages/shell-quasiquote/shell-quasiquote.el
diff --git a/packages/shell-quasiquote/shell-quasiquote.el
b/packages/shell-quasiquote/shell-quasiquote.el
new file mode 100644
index 0000000..1f18862
--- /dev/null
+++ b/packages/shell-quasiquote/shell-quasiquote.el
@@ -0,0 +1,151 @@
+;;; shell-quasiquote.el --- Turn s-expressions into shell command strings.
+
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+
+;; Author: Taylan Ulrich Bayırlı/Kammer <address@hidden>
+;; Keywords: extensions, unix
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; "Shell quasiquote" -- turn s-expressions into POSIX shell command strings.
+;;
+;; Shells other than POSIX sh are not supported.
+;;
+;; Quoting is automatic and safe against injection.
+;;
+;; (let ((file1 "file one")
+;; (file2 "file two"))
+;; (shqq (cp -r ,file1 ,file2 "My Files")))
+;; => "cp -r 'file one' 'file two' 'My Files'"
+;;
+;; You can splice many arguments into place with ,@foo.
+;;
+;; (let ((files (list "file one" "file two")))
+;; (shqq (cp -r ,@files "My Files")))
+;; => "cp -r 'file one' 'file two' 'My Files'"
+;;
+;; Note that the quoting disables a variety of shell expansions like ~/foo,
+;; $ENV_VAR, and e.g. {x..y} in GNU Bash.
+;;
+;; You can use ,,foo to escape the quoting.
+;;
+;; (let ((files "file1 file2"))
+;; (shqq (cp -r ,,files "My Files")))
+;; => "cp -r file1 file2 'My Files'"
+;;
+;; And ,,@foo to splice and escape quoting.
+;;
+;; (let* ((arglist '("-x 'foo bar' -y baz"))
+;; (arglist (append arglist '("-z 'qux fux'"))))
+;; (shqq (command ,,@arglist)))
+;; => "command -x 'foo bar' -y baz -z 'qux fux'"
+;;
+;; Neat, eh?
+
+
+;;; Code:
+
+;;; We don't use `shell-quote-argument' because it doesn't provide any safety
+;;; guarantees, and this quotes shell keywords as well.
+(defun shqq--quote-string (string)
+ (concat "'" (replace-regexp-in-string "'" "'\\\\''" string) "'"))
+
+(defun shqq--atom-to-string (atom)
+ (cond
+ ((symbolp atom) (symbol-name atom))
+ ((stringp atom) atom)
+ ((numberp atom) (number-to-string atom))
+ (t (error "Bad shqq atom: %S" atom))))
+
+(defun shqq--quote-atom (atom)
+ (shqq--quote-string (shqq--atom-to-string atom)))
+
+(defun shqq--match-comma (form)
+ "Matches FORM against ,foo i.e. (\, foo) and returns foo.
+Returns nil if FORM didn't match. You can't disambiguate between
+FORM matching ,nil and not matching."
+ (if (and (consp form)
+ (eq '\, (car form))
+ (consp (cdr form))
+ (null (cddr form)))
+ (cadr form)))
+
+(defun shqq--match-comma2 (form)
+ "Matches FORM against ,,foo i.e. (\, (\, foo)) and returns foo.
+Returns nil if FORM didn't match. You can't disambiguate between
+FORM matching ,,nil and not matching."
+ (if (and (consp form)
+ (eq '\, (car form))
+ (consp (cdr form))
+ (null (cddr form)))
+ (shqq--match-comma (cadr form))))
+
+
+(defmacro shqq (parts)
+ "First, PARTS is turned into a list of strings. For this,
+every element of PARTS must be one of:
+
+- a symbol, evaluating to its name,
+
+- a string, evaluating to itself,
+
+- a number, evaluating to its decimal representation,
+
+- \",expr\", where EXPR must evaluate to an atom that will be
+ interpreted according to the previous rules,
+
+- \",@list-expr\", where LIST-EXPR must evaluate to a list whose
+ elements will each be interpreted like the EXPR in an \",EXPR\"
+ form, and spliced into the list of strings,
+
+- \",,expr\", where EXPR is interpreted like in \",expr\",
+
+- or \",,@expr\", where EXPR is interpreted like in \",@expr\".
+
+In the resulting list of strings, all elements except the ones
+resulting from \",,expr\" and \",,@expr\" forms are quoted for
+shell grammar.
+
+Finally, the resulting list of strings is concatenated with
+separating spaces."
+ (let ((parts
+ (mapcar
+ (lambda (part)
+ (cond
+ ((atom part) (shqq--quote-atom part))
+ ;; We use the match-comma helpers because pcase can't match ,foo.
+ (t (pcase part
+ ;; ,,foo i.e. (, (, foo))
+ ((pred shqq--match-comma2)
+ (shqq--match-comma2 part))
+ ;; ,,@foo i.e. (, (,@ foo))
+ ((and (pred shqq--match-comma)
+ (let `,@,form (shqq--match-comma part)))
+ `(mapconcat #'identity ,form " "))
+ ;; ,foo
+ ;; Insert redundant 'and x' to work around debbugs#18554.
+ ((and x (pred shqq--match-comma))
+ `(shqq--quote-atom ,(shqq--match-comma part)))
+ ;; ,@foo
+ (`,@,form
+ `(mapconcat #'shqq--quote-atom ,form " "))
+ (_
+ (error "Bad shqq part: %S" part))))))
+ parts)))
+ `(mapconcat #'identity (list ,@parts) " ")))
+
+(provide 'shell-quasiquote)
+;;; shell-quasiquote.el ends here
--
2.5.0
- Re: [PATCH] Add shell-quasiquote., (continued)
Re: [PATCH] Add shell-quasiquote., Random832, 2015/10/17
Re: [PATCH] Add shell-quasiquote., Artur Malabarba, 2015/10/17
Re: [PATCH] Add shell-quasiquote.,
Taylan Ulrich Bayırlı/Kammer <=
- Re: [PATCH] Add shell-quasiquote., David Kastrup, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Taylan Ulrich Bayırlı/Kammer, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Random832, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Taylan Ulrich Bayırlı/Kammer, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Paul Eggert, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Taylan Ulrich Bayırlı/Kammer, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Paul Eggert, 2015/10/19
- Re: [PATCH] Add shell-quasiquote., Taylan Ulrich Bayırlı/Kammer, 2015/10/20
- Re: [PATCH] Add shell-quasiquote., Nicolas Richard, 2015/10/20
- Re: [PATCH] Add shell-quasiquote., Dmitry Gutov, 2015/10/20