Upstream twpayne/chezmoi state of the touched files before 41a618c046
This commit is contained in:
1 parent
4e184024be
commit
740f357900
2 files changed
+127
No files matched your search
@@ -0,0 +1,67 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nonShellLiteralRx is a regular expression that matches anything that is not a
|
||||||
|
// shell literal.
|
||||||
|
var nonShellLiteralRx = regexp.MustCompile(`[^+\-./0-9=A-Z_a-z]`)
|
||||||
|
|
||||||
|
// shellQuote returns s quoted as a shell argument, if necessary.
|
||||||
|
func shellQuote(s string) string {
|
||||||
|
const (
|
||||||
|
backslash = '\\'
|
||||||
|
singleQuote = '\''
|
||||||
|
)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case s == "":
|
||||||
|
return "''"
|
||||||
|
case nonShellLiteralRx.MatchString(s):
|
||||||
|
result := make([]byte, 0, 2+len(s))
|
||||||
|
inSingleQuotes := false
|
||||||
|
for _, b := range []byte(s) {
|
||||||
|
switch b {
|
||||||
|
case backslash:
|
||||||
|
if !inSingleQuotes {
|
||||||
|
result = append(result, singleQuote)
|
||||||
|
inSingleQuotes = true
|
||||||
|
}
|
||||||
|
result = append(result, backslash, backslash)
|
||||||
|
case singleQuote:
|
||||||
|
if inSingleQuotes {
|
||||||
|
result = append(result, singleQuote)
|
||||||
|
inSingleQuotes = false
|
||||||
|
}
|
||||||
|
result = append(result, '\\', singleQuote)
|
||||||
|
default:
|
||||||
|
if !inSingleQuotes {
|
||||||
|
result = append(result, singleQuote)
|
||||||
|
inSingleQuotes = true
|
||||||
|
}
|
||||||
|
result = append(result, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inSingleQuotes {
|
||||||
|
result = append(result, singleQuote)
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
default:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shellQuoteCommand returns a string containing command and args shell quoted.
|
||||||
|
func shellQuoteCommand(command string, args []string) string {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return shellQuote(command)
|
||||||
|
}
|
||||||
|
elems := make([]string, 1+len(args))
|
||||||
|
elems[0] = shellQuote(command)
|
||||||
|
for i, arg := range args {
|
||||||
|
elems[i+1] = shellQuote(arg)
|
||||||
|
}
|
||||||
|
return strings.Join(elems, " ")
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShellQuote(t *testing.T) {
|
||||||
|
tcs := map[string]string{
|
||||||
|
``: `''`,
|
||||||
|
`'`: `\'`,
|
||||||
|
`''`: `\'\'`,
|
||||||
|
`'a'`: `\''a'\'`,
|
||||||
|
`\`: `'\\'`,
|
||||||
|
`\a`: `'\\a'`,
|
||||||
|
`$a`: `'$a'`,
|
||||||
|
`a`: `a`,
|
||||||
|
`a/b`: `a/b`,
|
||||||
|
`a b`: `'a b'`,
|
||||||
|
`--arg`: `--arg`,
|
||||||
|
`--arg=value`: `--arg=value`,
|
||||||
|
`it's`: `'it'\''s'`,
|
||||||
|
`$(echo foo)`: `'$(echo foo)'`,
|
||||||
|
}
|
||||||
|
for _, s := range slices.Sorted(maps.Keys(tcs)) {
|
||||||
|
assert.Equal(t, tcs[s], shellQuote(s), "quoting %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShellQuoteCommand(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
command string
|
||||||
|
args []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
command: "command",
|
||||||
|
expected: "command",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: "command with spaces",
|
||||||
|
expected: "'command with spaces'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: "command",
|
||||||
|
args: []string{"arg1"},
|
||||||
|
expected: "command arg1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: "command",
|
||||||
|
args: []string{"arg1", "arg 2 with spaces"},
|
||||||
|
expected: "command arg1 'arg 2 with spaces'",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
assert.Equal(t, tc.expected, shellQuoteCommand(tc.command, tc.args))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user