package main import ( "fmt" "os" "strings" "testing" ) type Args struct { Options map[string]string Flags map[string]bool Positionals []string } // ParseArgs parses a compact CLI syntax: // --key=value, --key value, --flag, -abc, and positional arguments. // Parsing stops after "--"; remaining tokens are positionals. func ParseArgs(argv []string) Args { out := Args{ Options: make(map[string]string), Flags: make(map[string]bool), } for i := 0; i < len(argv); i++ { arg := argv[i] if arg == "--" { out.Positionals = append(out.Positionals, argv[i+1:]...) break } if strings.HasPrefix(arg, "--") && len(arg) > 2 { name := arg[2:] if key, val, ok := strings.Cut(name, "="); ok { out.Options[key] = val continue } if i+1 < len(argv) && !strings.HasPrefix(argv[i+1], "-") { out.Options[name] = argv[i+1] i++ } else { out.Flags[name] = true } continue } if strings.HasPrefix(arg, "-") && len(arg) > 1 { for _, ch := range arg[1:] { out.Flags[string(ch)] = true } continue } out.Positionals = append(out.Positionals, arg) } return out } func main() { args := ParseArgs(os.Args[1:]) fmt.Println("options:", args.Options) fmt.Println("flags:", args.Flags) fmt.Println("positionals:", args.Positionals) } func TestParseArgs(t *testing.T) { args := ParseArgs([]string{ "--name=demo", "--count", "3", "-vf", "input.txt", }) if args.Options["name"] != "demo" { t.Fatalf("name = %q", args.Options["name"]) } if args.Options["count"] != "3" { t.Fatalf("count = %q", args.Options["count"]) } if !args.Flags["v"] || !args.Flags["f"] { t.Fatalf("flags = %#v", args.Flags) } if len(args.Positionals) != 1 || args.Positionals[0] != "input.txt" { t.Fatalf("positionals = %#v", args.Positionals) } } func TestParseArgsStopsAfterDoubleDash(t *testing.T) { args := ParseArgs([]string{"--verbose", "--", "--not-a-flag", "-x"}) if !args.Flags["verbose"] { t.Fatalf("flags = %#v", args.Flags) } if len(args.Positionals) != 2 { t.Fatalf("positionals = %#v", args.Positionals) } }