package main import ( "errors" "fmt" "io" "os" "strconv" "strings" ) type options struct { name string count int verbose bool help bool positional []string } // parseArgs parses long flags, short flags, and positional arguments. func parseArgs(args []string) (options, error) { opts := options{name: "world", count: 1} for i := 0; i < len(args); i++ { arg := args[i] if arg == "--" { opts.positional = append(opts.positional, args[i+1:]...) break } switch { case arg == "-h" || arg == "--help": opts.help = true case arg == "-v" || arg == "--verbose": opts.verbose = true case arg == "-n" || arg == "--name": i++ if i >= len(args) { return options{}, fmt.Errorf("%s requires a value", arg) } opts.name = args[i] case strings.HasPrefix(arg, "--name="): opts.name = strings.TrimPrefix(arg, "--name=") case arg == "-c" || arg == "--count": i++ if i >= len(args) { return options{}, fmt.Errorf("%s requires a value", arg) } count, err := positiveInt(args[i]) if err != nil { return options{}, fmt.Errorf("%s: %w", arg, err) } opts.count = count case strings.HasPrefix(arg, "--count="): count, err := positiveInt(strings.TrimPrefix(arg, "--count=")) if err != nil { return options{}, fmt.Errorf("--count: %w", err) } opts.count = count case strings.HasPrefix(arg, "-"): return options{}, fmt.Errorf("unknown option %q", arg) default: opts.positional = append(opts.positional, arg) } } if opts.name == "" { return options{}, errors.New("name cannot be empty") } return opts, nil } func positiveInt(value string) (int, error) { n, err := strconv.Atoi(value) if err != nil || n < 1 { return 0, fmt.Errorf("expected a positive integer, got %q", value) } return n, nil } func usage(w io.Writer) { fmt.Fprintln(w, `Usage: greet [options] [message...] Options: -n, --name NAME name to greet (default: world) -c, --count N number of greetings (default: 1) -v, --verbose print additional information -h, --help show this help`) } func main() { // A lightweight self-test keeps this example in one source file. if len(os.Args) == 2 && os.Args[1] == "--self-test" { runTests() return } opts, err := parseArgs(os.Args[1:]) if err != nil { fmt.Fprintln(os.Stderr, "error:", err) usage(os.Stderr) os.Exit(2) } if opts.help { usage(os.Stdout) return } message := "Hello" if len(opts.positional) > 0 { message = strings.Join(opts.positional, " ") } if opts.verbose { fmt.Fprintf(os.Stderr, "greeting %q %d time(s)\n", opts.name, opts.count) } for i := 0; i < opts.count; i++ { fmt.Printf("%s, %s!\n", message, opts.name) } } func runTests() { tests := []struct { name string args []string want options }{ { name: "long options", args: []string{"--name=Ada", "--count", "2", "--verbose", "Welcome"}, want: options{name: "Ada", count: 2, verbose: true, positional: []string{"Welcome"}}, }, { name: "double dash", args: []string{"-n", "Lin", "--", "--not-a-flag"}, want: options{name: "Lin", count: 1, positional: []string{"--not-a-flag"}}, }, } for _, test := range tests { got, err := parseArgs(test.args) if err != nil || fmt.Sprintf("%#v", got) != fmt.Sprintf("%#v", test.want) { fmt.Fprintf(os.Stderr, "FAIL %s: got %#v, err=%v; want %#v\n", test.name, got, err, test.want) os.Exit(1) } } if _, err := parseArgs([]string{"--count=0"}); err == nil { fmt.Fprintln(os.Stderr, "FAIL invalid count: expected an error") os.Exit(1) } fmt.Println("all tests passed") }