3
\$\begingroup\$

I've written a reimplementation of the redis-benchmark in Go and part of that is parsing options from the command line. I evaluated several libraries but nothing I found seem to do what I had in mind.

Here is the code:

// ParseArguments parses a string array and returns a populated Options struct func ParseArguments(arguments []string) Options { options := defaultOptions args := arguments[1:] var errOptions Options for i := 0; i < len(args); i++ { if args[i] == "--help" || args[i] == "-h" { return Options{ShowHelp: true, HelpText: helpText} } else if args[i] == "--host" || args[i] == "-H" { i++ if i >= len(args) { return buildHelp("Error: Incorrect parameters specified") } options.Host = args[i] } else if args[i] == "--requests" || args[i] == "-n" { options.Requests, errOptions = parseNumber(args, &i) if errOptions.ShowHelp { return errOptions } } else if args[i] == "--clients" || args[i] == "-c" { options.Connections, errOptions = parseNumber(args, &i) if errOptions.ShowHelp { return errOptions } } else if args[i] == "--tests" || args[i] == "-t" { i++ if i >= len(args) { return buildHelp("Error: Incorrect parameters specified") } options.Tests = strings.Split(args[i], ",") for i := range options.Tests { options.Tests[i] = strings.ToUpper(options.Tests[i]) } } else if args[i] == "--port" || args[i] == "-p" { options.Port, errOptions = parseNumber(args, &i) if errOptions.ShowHelp { return errOptions } } else { return buildHelp(fmt.Sprintf("Error: Invalid parameter: %v", args[i])) } } return options } 

(For more context, the rest of the file is on github).

When gocyclo is run over the code the function gets a complexity of 20. I'm interested in suggestions that could reduce this to below the threshold of 10.

\$\endgroup\$
2
  • \$\begingroup\$Uhm... Why exactly do you not want to use flag from the standard library?\$\endgroup\$
    – Ainar-G
    CommentedOct 4, 2017 at 23:42
  • \$\begingroup\$I was looking for something that would just take the string arguments and return a struct with typed fields. Will look at it again.\$\endgroup\$CommentedOct 5, 2017 at 9:04

2 Answers 2

5
\$\begingroup\$

You should definitely rely on a library to achieve this. The flag package from standard library is nice, but go-flags is even better

With this library, the equivalent code is:

package main import ( "fmt" "os" "github.com/jessevdk/go-flags" ) type options struct { Help bool `short:"h" long:"help" description:"show help message"` Requests int `short:"n" long:"requests" default:"1"` Clients int `short:"c" long:"clients" default:"1"` Tests []string `short:"t" long:"tests" env-delim:","` Port int `short:"p" long:"port" default:"9000"` } func main() { var opts options p := flags.NewParser(&opts, flags.Default&^flags.HelpFlag) _, err := p.Parse() if err != nil { fmt.Printf("fail to parse args: %v", err) os.Exit(1) } if opts.Help { p.WriteHelp(os.Stdout) os.Exit(0) } fmt.Printf("tests: %v\n", opts) } 

All errors (type error, required flag missing ect...) are handle directly by the library

Now the complexity of the function is 3

\$\endgroup\$
    0
    \$\begingroup\$

    You can do all this just fine with the standard library. The only tricky bit is the CSV format for --tests (consider using []string directly, and let the caller specify multiple -t/--tests arguments). I had to guess the Options type, but it should be obvious how to change it for, say, uints.

    package main import ( "flag" "fmt" "strings" ) type Options struct { Requests int Connections int Tests []string Port int } func main() { options := ParseArguments([]string{"-c", "10", "--requests", "20", "-t", "foo,bar"}) fmt.Printf("%+v\n", options) // {Requests:20 Connections:10 Tests:[FOO BAR] Port:0} } func ParseArguments(arguments []string) Options { var defaultOptions Options options := defaultOptions fs := flag.NewFlagSet("main", flag.ExitOnError) fs.IntVar(&options.Requests, "requests", defaultOptions.Requests, "requests description") fs.IntVar(&options.Requests, "n", defaultOptions.Requests, "requests description") fs.IntVar(&options.Connections, "clients", defaultOptions.Connections, "clients description") fs.IntVar(&options.Connections, "c", defaultOptions.Connections, "clients description") fs.IntVar(&options.Port, "port", defaultOptions.Port, "port description") fs.IntVar(&options.Port, "p", defaultOptions.Port, "port description") tests := csv{} fs.Var(&tests, "tests", "tests description") fs.Var(&tests, "t", "tests description") fs.Parse(arguments) options.Tests = tests return options } type csv []string func (vs *csv) String() string { return strings.Join(*vs, ",") } func (vs *csv) Set(arg string) error { values := strings.Split(arg, ",") for i := range values { values[i] = strings.ToUpper(values[i]) } *vs = values // or append(), if repeated args are okay return nil } 

    https://play.golang.org/p/bqNgCt63QL

    \$\endgroup\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.