diff --git a/api/internal/plugins/execplugin/shlex.go b/api/internal/plugins/execplugin/shlex.go index 9e7c5740f..aadfbeeb8 100644 --- a/api/internal/plugins/execplugin/shlex.go +++ b/api/internal/plugins/execplugin/shlex.go @@ -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 } diff --git a/api/internal/plugins/execplugin/shlex_test.go b/api/internal/plugins/execplugin/shlex_test.go index b8276426c..fea6f5708 100644 --- a/api/internal/plugins/execplugin/shlex_test.go +++ b/api/internal/plugins/execplugin/shlex_test.go @@ -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",