210 lines
4.7 KiB
Go
210 lines
4.7 KiB
Go
package axscript
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func Tokenize(cmdline string) []string {
|
|
var tokens []string
|
|
var token strings.Builder
|
|
inQuotes := false
|
|
runes := []rune(cmdline)
|
|
length := len(runes)
|
|
|
|
for i := 0; i < length; {
|
|
c := runes[i]
|
|
|
|
if c == ' ' && !inQuotes {
|
|
if token.Len() > 0 {
|
|
tokens = append(tokens, token.String())
|
|
token.Reset()
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if c == '"' {
|
|
inQuotes = !inQuotes
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if c == '\\' {
|
|
numBS := 0
|
|
for i < length && runes[i] == '\\' {
|
|
numBS++
|
|
i++
|
|
}
|
|
if i < length && runes[i] == '"' {
|
|
for j := 0; j < numBS/2; j++ {
|
|
token.WriteRune('\\')
|
|
}
|
|
if numBS%2 == 0 {
|
|
inQuotes = !inQuotes
|
|
} else {
|
|
token.WriteRune('"')
|
|
}
|
|
i++
|
|
} else {
|
|
for j := 0; j < numBS; j++ {
|
|
token.WriteRune('\\')
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
token.WriteRune(c)
|
|
i++
|
|
}
|
|
|
|
if token.Len() > 0 {
|
|
tokens = append(tokens, token.String())
|
|
}
|
|
|
|
return tokens
|
|
}
|
|
|
|
func ParseCommand(cmdline string, resolved *ResolvedCommand) (*ParsedCommand, error) {
|
|
tokens := Tokenize(cmdline)
|
|
if len(tokens) == 0 {
|
|
return nil, fmt.Errorf("empty command line")
|
|
}
|
|
|
|
result := &ParsedCommand{
|
|
Args: make(map[string]interface{}),
|
|
}
|
|
|
|
commandName := tokens[0]
|
|
result.CommandName = commandName
|
|
result.Args["command"] = commandName
|
|
|
|
var cmdDef *CommandDef
|
|
argTokens := tokens[1:]
|
|
|
|
if resolved.Subcommand != nil {
|
|
if len(tokens) < 2 {
|
|
return nil, fmt.Errorf("subcommand required for '%s'", commandName)
|
|
}
|
|
result.SubcommandName = tokens[1]
|
|
result.Args["subcommand"] = tokens[1]
|
|
cmdDef = resolved.Subcommand
|
|
argTokens = tokens[2:]
|
|
} else {
|
|
cmdDef = resolved.Command
|
|
}
|
|
|
|
parsedArgsMap := make(map[string]string)
|
|
var lastKey string
|
|
|
|
for i := 0; i < len(argTokens); i++ {
|
|
arg := argTokens[i]
|
|
isWideArgs := true
|
|
|
|
for _, commandArg := range cmdDef.Args {
|
|
if commandArg.Flag {
|
|
if commandArg.Type == ArgTypeBool && commandArg.Mark == arg {
|
|
parsedArgsMap[commandArg.Mark] = "true"
|
|
lastKey = commandArg.Mark
|
|
isWideArgs = false
|
|
break
|
|
} else if commandArg.Mark == arg && i+1 < len(argTokens) {
|
|
i++
|
|
parsedArgsMap[commandArg.Name] = argTokens[i]
|
|
lastKey = commandArg.Name
|
|
isWideArgs = false
|
|
break
|
|
}
|
|
} else if _, exists := parsedArgsMap[commandArg.Name]; !exists {
|
|
parsedArgsMap[commandArg.Name] = arg
|
|
lastKey = commandArg.Name
|
|
isWideArgs = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if isWideArgs && lastKey != "" {
|
|
var wide strings.Builder
|
|
for j := i; j < len(argTokens); j++ {
|
|
wide.WriteString(" ")
|
|
wide.WriteString(argTokens[j])
|
|
}
|
|
parsedArgsMap[lastKey] += wide.String()
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, commandArg := range cmdDef.Args {
|
|
nameKey := commandArg.Name
|
|
if commandArg.Type == ArgTypeBool {
|
|
nameKey = commandArg.Mark
|
|
}
|
|
|
|
if val, exists := parsedArgsMap[nameKey]; exists {
|
|
switch commandArg.Type {
|
|
case ArgTypeString:
|
|
result.Args[commandArg.Name] = val
|
|
case ArgTypeInt:
|
|
intVal, err := strconv.Atoi(strings.TrimSpace(val))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid integer value for argument '%s': %s", commandArg.Name, val)
|
|
}
|
|
result.Args[commandArg.Name] = intVal
|
|
case ArgTypeBool:
|
|
result.Args[commandArg.Mark] = val == "true"
|
|
case ArgTypeFile:
|
|
result.FileArgs = append(result.FileArgs, FileArgInfo{
|
|
ArgName: commandArg.Name,
|
|
OriginalPath: val,
|
|
Required: commandArg.Required,
|
|
})
|
|
result.Args[commandArg.Name] = nil
|
|
}
|
|
} else if commandArg.Required {
|
|
if commandArg.DefaultValue == nil && !commandArg.DefaultUsed {
|
|
return nil, fmt.Errorf("missing required argument: %s", commandArg.Name)
|
|
}
|
|
switch commandArg.Type {
|
|
case ArgTypeString:
|
|
if s, ok := commandArg.DefaultValue.(string); ok {
|
|
result.Args[commandArg.Name] = s
|
|
} else {
|
|
return nil, fmt.Errorf("missing required argument: %s", commandArg.Name)
|
|
}
|
|
case ArgTypeInt:
|
|
switch v := commandArg.DefaultValue.(type) {
|
|
case int:
|
|
result.Args[commandArg.Name] = v
|
|
case int64:
|
|
result.Args[commandArg.Name] = int(v)
|
|
case float64:
|
|
result.Args[commandArg.Name] = int(v)
|
|
default:
|
|
return nil, fmt.Errorf("missing required argument: %s", commandArg.Name)
|
|
}
|
|
case ArgTypeBool:
|
|
if b, ok := commandArg.DefaultValue.(bool); ok {
|
|
result.Args[commandArg.Mark] = b
|
|
} else {
|
|
return nil, fmt.Errorf("missing required argument: %s", commandArg.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
msg := cmdDef.Message
|
|
if msg != "" {
|
|
for k, v := range parsedArgsMap {
|
|
param := "<" + k + ">"
|
|
if strings.Contains(msg, param) {
|
|
msg = strings.ReplaceAll(msg, param, v)
|
|
}
|
|
}
|
|
result.Message = msg
|
|
result.Args["message"] = msg
|
|
}
|
|
|
|
return result, nil
|
|
}
|