Skip to content

Commit

Permalink
fix: parse arguments with spaces
Browse files Browse the repository at this point in the history
  • Loading branch information
b00f committed Jan 8, 2025
1 parent 3a3a973 commit 6b5b7dd
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 29 deletions.
88 changes: 66 additions & 22 deletions internal/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (be *BotEngine) ParseAndExecute(
var cmds []string
var args map[string]string

cmds, args, err := parseCommand(input)
cmds, args, err := parseInput(input)
if err != nil {
return command.CommandResult{
Message: err.Error(),
Expand All @@ -180,35 +180,79 @@ func (be *BotEngine) ParseAndExecute(
return be.executeCommand(appID, callerID, cmds, args)
}

// parseCommand parses the input string into commands and arguments.
func parseCommandInput(cmdInput string) []string {
cmds := make([]string, 0)

tokens := strings.Split(cmdInput, " ")
for _, token := range tokens {
token = strings.TrimSpace(token)

if token != "" {
cmds = append(cmds, token)
}
}

return cmds
}

func parseArgumentInput(argInput string) (map[string]string, error) {
args := make(map[string]string)

tokens := strings.Split(argInput, "--")
for _, token := range tokens {
token = strings.TrimSpace(token)

if token != "" {
parts := strings.SplitN(token, "=", 2)
key := strings.TrimSpace(parts[0])

if key == "" {
return nil, fmt.Errorf("invalid argument format: %s", argInput)
}

if len(parts) == 1 {
// Boolean argument
args[key] = "true"
} else {
value := strings.TrimSpace(parts[1])
value = strings.Trim(value, "\"'")
value = strings.TrimSpace(value)

args[key] = value
}
}
}

return args, nil
}

// parseInput parses the input string into commands and arguments.
// The input string should be in the following format:
// `command1 command2 --arg1=val1 --arg2=val2`
// It returns an error if parsing fails.
func parseCommand(input string) ([]string, map[string]string, error) {
if strings.TrimSpace(input) == "" {
func parseInput(input string) ([]string, map[string]string, error) {
// normalize input
input = strings.ReplaceAll(input, "\t", " ")
input = strings.TrimSpace(input)
if input == "" {
return nil, nil, errors.New("input string cannot be empty")
}

// Split input by spaces while preserving argument values
parts := strings.Fields(input)
argIndex := strings.Index(input, "--")

// Prepare results
cmds := make([]string, 0)
args := make(map[string]string)
var cmdInput, argInput string
if argIndex != -1 {
cmdInput = input[:argIndex]
argInput = input[argIndex:]
} else {
cmdInput = input
argInput = ""
}

// Iterate over parts to separate commands and arguments
for _, part := range parts {
if strings.HasPrefix(part, "--") {
// Argument: split on '='
argParts := strings.SplitN(part, "=", 2)
key := strings.TrimPrefix(argParts[0], "--")
if len(argParts) != 2 || strings.TrimSpace(key) == "" || strings.TrimSpace(argParts[1]) == "" {
return nil, nil, fmt.Errorf("invalid argument format: %s", part)
}
args[key] = argParts[1]
} else {
cmds = append(cmds, part)
}
cmds := parseCommandInput(cmdInput)
args, err := parseArgumentInput(argInput)
if err != nil {
return nil, nil, err
}

return cmds, args, nil
Expand Down
63 changes: 56 additions & 7 deletions internal/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,55 @@ func TestParseCommand(t *testing.T) {
wantArgs: map[string]string{"arg1": "val1", "arg2": "val2"},
wantErr: nil,
},
{
name: "arguments with quotation",
input: "command1 --arg1='val1' --arg2=\"val2\"",
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": "val1", "arg2": "val2"},
wantErr: nil,
},
{
name: "arguments with quotation inside value",
input: "command1 --arg1='val ' 1' --arg2=\"val \" 2\"",
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": "val ' 1", "arg2": "val \" 2"},
wantErr: nil,
},
{
name: "arguments with quotation and spaces",
input: "command1 --arg1='val 1' --arg2=\"val 2\" --arg3=val3",
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": "val 1", "arg2": "val 2", "arg3": "val3"},
wantErr: nil,
},
{
name: "arguments with = inside value",
input: "command1 --arg1='val=1'",
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": "val=1"},
wantErr: nil,
},
{
name: "extra spaces",
input: "command1 command2 --arg1=' val 1' --arg2=\"val 2 \"",
wantCmds: []string{"command1", "command2"},
wantArgs: map[string]string{"arg1": "val 1", "arg2": "val 2"},
wantErr: nil,
},
{
name: "with tabs",
input: "command1 --arg1='val 1 ' --arg2=\" val 2\"",
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": "val 1", "arg2": "val 2"},
wantErr: nil,
},
{
name: "argument with empty value",
input: "command1 --arg1=\"\"",
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": ""},
wantErr: nil,
},
{
name: "input with no arguments",
input: "command1 command2",
Expand All @@ -47,9 +96,9 @@ func TestParseCommand(t *testing.T) {
{
name: "invalid argument format (missing =)",
input: "command1 --arg1",
wantCmds: nil,
wantArgs: nil,
wantErr: fmt.Errorf("invalid argument format: --arg1"),
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": "true"},
wantErr: nil,
},
{
name: "invalid argument format (empty key)",
Expand All @@ -61,9 +110,9 @@ func TestParseCommand(t *testing.T) {
{
name: "invalid argument format (empty value)",
input: "command1 --arg1=",
wantCmds: nil,
wantArgs: nil,
wantErr: fmt.Errorf("invalid argument format: --arg1="),
wantCmds: []string{"command1"},
wantArgs: map[string]string{"arg1": ""},
wantErr: nil,
},
{
name: "empty input",
Expand All @@ -76,7 +125,7 @@ func TestParseCommand(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCmds, gotArgs, gotErr := parseCommand(tt.input)
gotCmds, gotArgs, gotErr := parseInput(tt.input)

// Compare commands
assert.Equal(t, tt.wantCmds, gotCmds, "commands mismatch: got %v, want %v", gotCmds, tt.wantCmds)
Expand Down

0 comments on commit 6b5b7dd

Please sign in to comment.