Practical Go. Amit Saha

Читать онлайн книгу.

Practical Go - Amit Saha


Скачать книгу
the program and interactively by typing it in. First you will implement a greeter command-line application that will ask the user to specify their name and the number of times they want to be greeted. The name will be input by the user when asked, and the number of times will be specified as an argument when executing the application. The program will then display a custom message the specified number of times. Once you have written the complete application, a sample execution will appear as follows:

      $ ./application 6 Your name please? Press the Enter key when done. Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool Nice to meet you Joe Cool

      First, let's look at the function asking a user to input their name:

      func getName(r io.Reader, w io.Writer) (string, error) { msg := "Your name please? Press the Enter key when done.\n" fmt.Fprintf(w, msg) scanner := bufio.NewScanner(r) scanner.Scan() if err := scanner.Err(); err != nil { return "", err } name := scanner.Text() if len(name) == 0 { return "", errors.New("You didn't enter your name") } return name, nil }

      The getName() function accepts two arguments. The first argument, r, is a variable whose value satisfies the Reader interface defined in the io package. An example of such a variable is Stdin, as defined in the os package. It represents the standard input for the program—usually the terminal session in which you are executing the program.

      The second argument, w, is a variable whose value satisfies the Writer interface, as defined in the io package. An example of such a variable is the Stdout variable, as defined in the os package. It represents the standard output for the application—usually the terminal session in which you are executing the program.

      You may be wondering why we do not refer to the Stdin and Stdout variables from the os package directly. The reason is that doing so will make our function very unfriendly when we want to write unit tests for it. We will not be able to specify a customized input to the application, nor will we be able to verify the application's output. Hence, we inject the writer and the reader into the function so that we have control over what the reader, r, and writer, w, values refer to.

      The getName() function returns two values: one of type string and the other of type error. If the user's input name was read successfully, the name is returned along with a nil error. However, if there was an error, an empty string and the error is returned.

      The next key function is parseArgs(). It takes as input a slice of strings and returns two values: one of type config and a second of type error :

      type config struct { numTimes int printUsage bool } func parseArgs(args []string) (config, error) { var numTimes int var err error c := config{} if len(args) != 1 { return c, errors.New("Invalid number of arguments") } if args[0] == "-h" || args[0] == "--help" { c.printUsage = true return c, nil } numTimes, err = strconv.Atoi(args[0]) if err != nil { return c, err } c.numTimes = numTimes return c, nil }

      Command-line arguments supplied to a program are available via the Args slice defined in the os package. The first element of the slice is the name of the program itself, and the slice os.Args[1:] contains the arguments that your program may care about. This is the slice of strings with which parseArgs() is called. The function first checks to see if the number of command-line arguments is not equal to 1, and if so, it returns an empty config object and an error using the following snippet:

      if len(args) != 1 { return c, errors.New("Invalid number of arguments") }

      If only one argument is specified, and it is -h or -help, the printUsage field is specified to true and the object, c, and a nil error are returned using the following snippet:

      if args[0] == "-h" || args[0] == "-help" { c.printUsage = true return c, nil }

      Finally, the argument specified is assumed to be the number of times to print the greeting, and the Atoi() function from the strconv package is used to convert the argument—a string—to its integer equivalent:

      numTimes, err = strconv.Atoi(args[0]) if err != nil { return c, err }

      If the Atoi() function returns a non-nil error value, it is returned; else numTimes is set to the converted integer:

      c.numTimes = numTimes

      So far, we have seen how you can read the input from the user and read command-line arguments. The next step is to ensure that the input is logically valid; in other words, whether or not it makes sense for the application. For example, if the user specified 0 for the number of times to print the greeting, it is a logically incorrect value. The validateArgs() function performs this validation:

      If the value of the numTimes field is not greater than 0, an error is returned by the validateArgs() function.

      After processing and validating the command- line arguments, the application invokes the runCmd() function to perform the relevant action based on the value in the config object, c :

      func runCmd(r io.Reader, w io.Writer, c config) error { if c.printUsage { printUsage(w) return nil } name, err := getName(r, w) if err != nil { return err } greetUser(c, name, w) return nil }

      If the field printUsage is set to true ( -help or -h specified by the user), the printUsage() function is called and a nil error is returned. Otherwise, the getName() function is called to ask the user to input their name.

      If getName() returned a non-nil error, it is returned. Else, the greetUser() function is called. The greetUser() function displays a greeting to the user based on the configuration supplied:

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


Скачать книгу