Practical Go. Amit Saha
Чтение книги онлайн.

Читать онлайн книгу Practical Go - Amit Saha страница 17

Название: Practical Go

Автор: Amit Saha

Издательство: John Wiley & Sons Limited

Жанр: Программы

Серия:

isbn: 9781119773832

isbn:

СКАЧАТЬ which prints the name you entered a specified number of times. Usage of %s: ` fmt.Fprintf(w, usageString, fs.Name()) fmt.Fprintln(w) fs.PrintDefaults() }

      Once we set the Usage attribute of the FlagSet object to a custom function, it is called whenever there is an error parsing the specified options. Note that the preceding function is defined as an anonymous function so that it can access the specified writer object, w, to display the custom usage message. Inside the function, we access the name of the FlagSet using the Name() method. Then we print a new line and call the PrintDefaults() method, which prints the various options that have been defined along with their type and default values. The updated parseArgs() function is as follows:

      func parseArgs(w io.Writer, args []string) (config, error) { c := config{} fs := flag.NewFlagSet("greeter", flag.ContinueOnError) fs.SetOutput(w) fs.Usage = func() { var usageString = ` A greeter application which prints the name you entered a specified number of times. Usage of %s: <options> [name]` fmt.Fprintf(w, usageString, fs.Name()) fmt.Fprintln(w) fmt.Fprintln(w, "Options: ") fs.PrintDefaults() } fs.IntVar(&c.numTimes, "n", 0, "Number of times to greet") err := fs.Parse(args) if err != nil { return c, err } if fs.NArg()> 1 { return c, errInvalidPosArgSpecified } if fs.NArg() == 1 { c.name = fs.Arg(0) } return c, nil }

      Next, you will implement the final improvement. The greeter program will now allow specifying the name via a positional argument as well. If one is not specified, you will ask for the name interactively.

      Accept Name via a Positional Argument

      First, update the config struct to have a name field of type string as follows:

      type config struct { numTimes int name string }

      Then the greetUser() function will be updated to the following:

      func greetUser(c config, w io.Writer) { msg := fmt.Sprintf("Nice to meet you %s\n", c.name) for i := 0; i < c.numTimes; i++ { fmt.Fprintf(w, msg) } }

      Next, we update the custom error value as follows:

      var errInvalidPosArgSpecified = errors.New("More than one positional argument specified")

      We update the parseArgs() function now to look for a positional argument and, if one is found, set the name attribute of the config object appropriately:

       if fs.NArg()> 1 { return c, errInvalidPosArgSpecified } if fs.NArg() == 1 { c.name = fs.Arg(0) }

      The runCmd() function is updated so that it only asks the user to input the name interactively if not specified, or if an empty string was specified:

      Create a new directory, chap1/flag-improvements/, and initialize a module inside it:

      $ mkdir -p chap1/flag-improvements $ cd chap1/flag-improvements $ go mod init github.com/username/flag-improvements

      Next, save Listing 1.7 as main.go. Build it as follows:

      $ go build -o application

      Run the built application code with -help, and you will see the custom usage message:

      $ ./application -help A greeter application which prints the name you entered a specified number of times. Usage of greeter: <options> [name] Options: -n int Number of times to greet

      Now let's specify a name as a positional argument:

      $ ./application -n 1 "Jane Doe" Nice to meet you Jane Doe

      Next let's specify a bad input—a string as value to the -n option:

      Two points are worth noting here:

       The error is displayed only once now instead of being displayed twice.

       Our custom usage is displayed instead of the default.

      Try a few input combinations before moving on to updating the unit tests.

      We are going to finish off the chapter by updating the unit tests for the functions that we modified. Consider the parseArgs() function first. We will define a new anonymous struct for the test cases:

      tests := []struct { args []string config output string err error }{..} The fields are as follows:

       args: A slice of strings that contains the command-line arguments to parse.

       config: An embedded field representing the expected config object value.

       output: A string that will store the expected standard output.

       err: An error value that will store the expected error.

      Next, we define a slice of test cases representing the various test cases. The first one is as follows:

      The preceding test cases test the behavior when the program is run with the -h argument. In other words, it prints the usage message. СКАЧАТЬ