package command import ( "encoding/json" "strings" ) // ParseArgs converts a slice of raw arguments into structured ParsedArgs. // Supports: positional args, key=value pairs, and quoted values like key="hello world". // Pure function — no side effects. func ParseArgs(args []string) ParsedArgs { p := ParsedArgs{ Named: make(map[string]string), Raw: args, } // First, rejoin args to handle quoted values that were split by Fields(). joined := strings.Join(args, " ") tokens := tokenize(joined) for _, tok := range tokens { if idx := strings.IndexByte(tok, '='); idx > 0 { key := tok[:idx] val := tok[idx+1:] // Strip surrounding quotes from value val = stripQuotes(val) p.Named[key] = val } else { p.Positional = append(p.Positional, tok) } } return p } // ArgsToJSON converts a named args map to a JSON string for tools.Registry.Execute. // Pure function. func ArgsToJSON(named map[string]string) string { if len(named) == 0 { return "" } m := make(map[string]any, len(named)) for k, v := range named { m[k] = v } b, _ := json.Marshal(m) return string(b) } // tokenize splits a string respecting quoted values. // e.g. `host=server1 command="uptime -a"` → ["host=server1", `command="uptime -a"`] func tokenize(s string) []string { var tokens []string var current strings.Builder inQuote := false quoteChar := byte(0) for i := 0; i < len(s); i++ { ch := s[i] switch { case !inQuote && (ch == '"' || ch == '\''): inQuote = true quoteChar = ch current.WriteByte(ch) case inQuote && ch == quoteChar: inQuote = false current.WriteByte(ch) case !inQuote && ch == ' ': if current.Len() > 0 { tokens = append(tokens, current.String()) current.Reset() } default: current.WriteByte(ch) } } if current.Len() > 0 { tokens = append(tokens, current.String()) } return tokens } // stripQuotes removes surrounding double or single quotes from a string. func stripQuotes(s string) string { if len(s) >= 2 { if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') { return s[1 : len(s)-1] } } return s }