Practical Go. Amit Saha
Чтение книги онлайн.
Читать онлайн книгу Practical Go - Amit Saha страница 12
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:
func validateArgs(c config) error { if !(c.numTimes> 0) { return errors.New("Must specify a number greater than 0") } return nil }
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) } }
The complete greeter application is shown in Listing 1.1.
Listing 1.1: A greeter application
// chap1/manual-parse/main.go package main import ( "bufio" "errors" "fmt" "io" "os" "strconv" ) type config struct { numTimes int printUsage bool } var usageString = fmt.Sprintf(`Usage: %s <integer> [-h|--help] A greeter application which prints the name you entered <integer> number of times. `, os.Args[0]) func printUsage(w io.Writer) { fmt.Fprintf(w, usageString) } func validateArgs(c config) error { if !(c.numTimes> 0) { return errors.New("Must specify a number greater than 0") } return nil } // TODO – Insert definition of parseArgs() as earlier // TODO – Insert definition of getName() as earlier // TODO – Insert definition of greetUser() as earlier // TODO – Insert definition of runCmd() as earlier func main() { c, err := parseArgs(os.Args[1:]) if err != nil { fmt.Fprintln(os.Stdout, err) printUsage(os.Stdout) os.Exit(1) } err = validateArgs(c) if err != nil { fmt.Fprintln(os.Stdout, err) printUsage(os.Stdout) os.Exit(1) } err = runCmd(os.Stdin, os.Stdout, c) if err != nil { fmt.Fprintln(os.Stdout, err) os.Exit(1) } }
The main()
function first calls the parseArgs()
function with the slice of the command-line arguments, starting from the second argument. We get back two values from the function: c
, a config object, and err
, an error value. If a non- nil
error is returned, the following steps are performed:
1 Print the error.
2 Print a usage message by calling the printUsage() function, passing in os.Stdout as the writer.
3 Terminate the program execution with exit code 1 by calling the Exit() function from the os package.
If the arguments have been parsed correctly, the validateArgs()
function is called with the config object, c
, that is returned by parseArgs()
.
Finally, if the validateArgs()
function returned a nil
error value, the runCmd()
function is called, passing it a reader, os.Stdin
; a writer, os.Stdout
; and the config object, c
.
Create a new directory, chap1/manual-parse/
, and initialize a module inside it:
$ mkdir -p chap1/manual-parse $ cd chap1/manual-parse $ go mod init github.com/username/manual-parse
Next, save Listing 1.1 to a file called main.go
, and build it:
$ go build -o application
Run the command without specifying any arguments. You will see an error and the following usage message:
$ ./application Invalid number of arguments Usage: ./application <integer> [-h|--help] A greeter application which prints the name you entered <integer> number of times.
In addition, you will also see that the exit code of the program is 1
.
$ echo $? 1
If you are using PowerShell on Windows, you can use echo $LastExitCode
to see the exit code.
This is another notable behavior of command-line applications that you should look to preserve. Any non-successful execution should result in a non-zero exit code upon termination using the Exit()
function defined in the os
package.
Specifying -h
or -help
will print a usage message:
$ ./application -help Usage: ./application <integer> [-h|-help] A greeter application which prints the name you entered <integer> number of times.
Finally, let's see what a successful execution of the program looks like:
$ ./application 5 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
You have manually tested that your application behaves as expected under three different input scenarios:
1 No command-line argument specified.
2 -h or -help is specified as a command-line argument.
3 A greeting is displayed to the user a specified number of times.
Manual testing is error prone and cumbersome, however. Next, you will learn to write automated tests for your application.
Writing Unit Tests
The