diff --git a/pkg/execute/executor.go b/pkg/execute/executor.go index 1deac40a1..523f93f88 100644 --- a/pkg/execute/executor.go +++ b/pkg/execute/executor.go @@ -88,12 +88,12 @@ func (e *DefaultExecutor) Execute(ctx context.Context) interactive.CoreMessage { flags, err := ParseFlags(expandedRawCmd) if err != nil { - e.log.Errorf("while parsing command flags %q: %s", expandedRawCmd, err.Error()) + e.log.WithError(err).WithField("msg", expandedRawCmd).Error("Failed to parse user message") return interactive.CoreMessage{ Description: header(cmdCtx), Message: api.Message{ BaseBody: api.Body{ - Plaintext: err.Error(), + Plaintext: cantParseCmd, }, }, } diff --git a/pkg/execute/params.go b/pkg/execute/params.go index 71ce3dd1d..0db2e0b01 100644 --- a/pkg/execute/params.go +++ b/pkg/execute/params.go @@ -1,7 +1,6 @@ package execute import ( - "errors" "fmt" "regexp" "strings" @@ -31,6 +30,8 @@ type Flags struct { // ParseFlags parses raw cmd and removes optional params with flags. func ParseFlags(cmd string) (Flags, error) { + cmd = ensureEvenSingleQuotes(cmd) + cmd, clusterName, err := extractParam(cmd, "cluster-name") if err != nil { return Flags{}, err @@ -54,7 +55,7 @@ func ParseFlags(cmd string) (Flags, error) { tokenized, err := shellwords.Parse(cmd) if err != nil { - return Flags{}, errors.New(cantParseCmd) + return Flags{}, err } return Flags{ CleanCmd: cmd, @@ -110,6 +111,21 @@ func extractParam(cmd, flagName string) (string, string, error) { return cmd, withParam, nil } +// ensureEvenSingleQuotes ensures that single quotes are even. It is required, e.g., for AI plugins to work. +// Otherwise, the command won't be parsed correctly (removing cluster name flags, etc.) and will result in an error. +// This is only a workaround for now. Ultimately, we should find a better and more generic way for extracting +// parameters. Additionally, we should delegate command tokenizing to the plugin. +func ensureEvenSingleQuotes(cmd string) string { + for _, k := range []string{`‘`, `'`, `’`} { + no := strings.Count(cmd, k) + if no%2 == 0 { + continue + } + cmd = strings.Replace(cmd, k, k+k, 1) + } + return cmd +} + func extractBoolParam(cmd, flagName string) (string, bool, error) { flag := fmt.Sprintf("--%s", flagName) var isSet bool diff --git a/pkg/execute/params_test.go b/pkg/execute/params_test.go index 7e7aadc91..2359885f8 100644 --- a/pkg/execute/params_test.go +++ b/pkg/execute/params_test.go @@ -92,6 +92,20 @@ func TestRemoveBotkubeRelatedFlags(t *testing.T) { ClusterName: "api", Filter: "=./Users/botkube/somefile.txt [info]", }, + { + Name: "Handle even number of single quotes with text filter and cluster name extraction", + Input: `@botkube ai I'm not sure if it's what's best for us, but let's give it a try. --cluster-name='api' --filter="=./Users/botkube/somefile.txt"`, + Cmd: `@botkube ai I'm not sure if it's what's best for us, but let's give it a try. `, + ClusterName: "api", + Filter: "=./Users/botkube/somefile.txt", + }, + { + Name: "Handle odd number of single quotes with text filter and cluster name extraction", + Input: `@botkube ai are there any failing pods? It's what's been keeping me energized lately --cluster-name='api' --filter="=./Users/botkube/somefile.txt"`, + Cmd: `@botkube ai are there any failing pods? It's what's been keeping me energized lately `, + ClusterName: "api", + Filter: "=./Users/botkube/somefile.txt", + }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) {