add ShlexSplit() as an alternative to shlex.Split()

This commit is contained in:
koba1t
2025-07-12 07:24:16 +09:00
parent 042a2cf177
commit b6128950c9
2 changed files with 68 additions and 22 deletions

View File

@@ -5,15 +5,55 @@ package execplugin
import (
"fmt"
shlex "github.com/carapace-sh/carapace-shlex"
"strings"
"unicode"
)
// ShlexSplit splits a string into a slice of strings using shell-style rules for quoting and commenting
// Similar to Python's shlex.split with comments enabled
func ShlexSplit(s string) ([]string, error) {
// return shlexSplit(s)
tokens, err := shlex.Split(s)
if err != nil {
return nil, fmt.Errorf("shlex split error: %w", err)
}
return tokens.Strings(), nil
return shlexSplit(s)
}
func shlexSplit(s string) ([]string, error) {
result := []string{}
var current strings.Builder
var quote rune
var escaped bool
for _, r := range s {
switch {
case escaped:
current.WriteRune(r)
escaped = false
case r == '\\' && quote != '\'':
escaped = true
case (r == '\'' || r == '"') && quote == 0:
quote = r
case r == quote:
quote = 0
case r == '#' && quote == 0:
// Comment starts, ignore the rest of the line
if current.Len() > 0 {
result = append(result, current.String())
}
return result, nil
case quote == 0 && unicode.IsSpace(r):
if current.Len() > 0 {
result = append(result, current.String())
current.Reset()
}
default:
current.WriteRune(r)
}
}
if quote != 0 {
return nil, fmt.Errorf("unclosed quote in string")
}
if current.Len() > 0 {
result = append(result, current.String())
}
return result, nil
}

View File

@@ -103,13 +103,13 @@ func TestShlexSplit(t *testing.T) {
{
name: "empty string",
input: ``,
expected: []string{""},
expected: []string{},
wantErr: false,
},
{
name: "multiple spaces",
input: ` multiple spaces `,
expected: []string{"multiple", "spaces", ""},
input: ` multiple spaces `,
expected: []string{"multiple", "spaces"},
wantErr: false,
},
{
@@ -118,36 +118,42 @@ func TestShlexSplit(t *testing.T) {
expected: []string{"echo", "Hello, W#orld!", "${USER}"},
wantErr: false,
},
{
name: "comment only",
input: `# this line is just a comment`,
expected: []string{},
wantErr: false,
},
// may cause an error in shlex at python3
{
name: "unclosed double quote",
input: `"unclosed quote`,
expected: []string{"unclosed quote"},
wantErr: false,
expected: nil,
wantErr: true,
},
{
name: "unclosed single quote",
input: `'unclosed quote`,
expected: []string{"unclosed quote"},
wantErr: false,
expected: nil,
wantErr: true,
},
{
name: "mixed unclosed quotes",
input: `"mixed 'quotes`,
expected: []string{"mixed 'quotes"},
wantErr: false,
expected: nil,
wantErr: true,
},
{
name: "single quote closed with double quote",
input: `"hello world'`,
expected: []string{"hello world'"},
wantErr: false,
expected: nil,
wantErr: true,
},
{
name: "double quote closed with single quote",
input: `'hello world"`,
expected: []string{"hello world\""},
wantErr: false,
expected: nil,
wantErr: true,
},
}
@@ -165,7 +171,7 @@ func TestShlexSplit(t *testing.T) {
return
}
if assert.NoError(t, err, "FAIL: Unexpected error for input %q", tc.input) {
if assert.NoError(t, err, "FAIL: Unexpected error %q for input %q", err, tc.input) {
// check if the result matches the expected output
assert.Equal(t, tc.expected, result,
"FAIL: Result mismatch,Input %q, Expected %q, Got: %q\n",