Amit Saha

Practical Go


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

sub-commands. Then, you will see how you can enforce predictable behavior in your command-line applications using contexts. Finally, you will learn how to combine contexts and handling operating system signals in your application. Let's jump in.

      Sub-commands are a way to split the functionality of your command-line application into logically independent commands having their own options and arguments. You have a top-level command—your application—and then you have a set of sub-commands, each having its own options and arguments. For example, the Go toolchain is distributed as a single application, go, which is the top-level command. As a Go developer, you will interact with its various functionalities via dedicated sub-commands such as build, fmt, and test .

      Consider the main() function of an application with two sub-commands, – cmd-a and cmd-b :

      func main() { var err error if len(os.Args) < 2 { printUsage(os.Stdout) os.Exit(1) } switch os.Args[1] { case "cmd-a": err = handleCmdA(os.Stdout, os.Args[2:]) case "cmd-b": err = handleCmdB(os.Stdout, os.Args[2:]) default: printUsage(os.Stdout) } if err != nil { fmt.Println(err) } os.Exit(1) }

      The os.Args slice contains the command-line arguments that invoke the application. We will handle three input cases:

      1 If the second argument is cmd-a, the handleCmdA() function is called.

      2 If the second argument is cmd-b, the handleCmdB() function is called.

      3 If the application is called without any sub-commands, or neither of those listed in case 1 or case 2 above, the printUsage() function is called to print a help message and exit.

      The above function looks very similar to the parseArgs() function that you had implemented earlier as part of the greeter application in Chapter 1. It creates a new FlagSet object, performs a setup of the various options, and parses the specific slice of arguments. The handleCmdB() function would perform its own setup for the cmd-b sub-command.

      The printUsage() function is defined as follows:

      func printUsage(w io.Writer) { fmt.Fprintf(w, "Usage: %s [cmd-a|cmd-b] -h\n", os.Args[0]) handleCmdA(w, []string{"-h"}) handleCmdB(w, []string{"-h"}) }

      We first print a line of usage message for the application by means of the fmt.Fprintf() function and then invoke the individual sub-command handler functions with -h as the sole element in a slice of arguments. This results in those sub-commands displaying their own help messages.

      // chap2/sub-cmd-example/main.go package main import ( "flag" "fmt" "io" "os" ) // TODO Insert handleCmdaA() implementation as earlier func handleCmdB(w io.Writer, args []string) error { var v string fs := flag.NewFlagSet("cmd-b", flag.ContinueOnError) fs.SetOutput(w) fs.StringVar(&v, "verb", "argument-value", "Argument 1") err := fs.Parse(args) if err != nil { return err } fmt.Fprintf(w, "Executing command B") return nil } // TODO Insert printUsage() implementation as earlier func main() { var err error if len(os.Args) < 2 { printUsage(os.Stdout) os.Exit(1) } switch os.Args[1] { case "cmd-a": err = handleCmdA(os.Stdout, os.Args[2:]) case "cmd-b": err = handleCmdB(os.Stdout, os.Args[2:]) default: printUsage(os.Stdout) } if err != nil { fmt.Fprintln(os.Stdout, err) os.Exit(1) } }

      Create a new directory chap2/sub-cmd-example/, and initialize a module inside it:

      Next, save Listing 2.1 as a file main.go within it. Build and run the application without any arguments:

      $ go build -o application $ ./application Usage: ./application [cmd-a|cmd-b] -h Usage of cmd-a: -verb string Argument 1 (default "argument-value") Usage of cmd-b: -verb string Argument 1 (default "argument-value")

      Try executing any of the sub-commands:

      $ ./application cmd-a Executing command A $ ./application cmd-b Executing command B

      You have now seen an example of how you can implement your command-line application with sub-commands by creating multiple FlagSet objects. Each sub-command is constructed like a stand-alone command-line application. Thus, implementing sub-commands is a great way to separate unrelated functionalities of your application. For example, the go build sub-command provides all of the build-related functionality and the go test sub-command provides all of the testing-related functionality for a Go project.

      Let's continue this exploration by discussing a strategy to make this scalable.

      An Architecture for Sub-command-Driven Applications

      Next, you lay down the foundation of a generic command-line network client, which you will build upon in later chapters. We will call this program mync (short for my network client). For now, you will ignore the implementation of the sub-commands and come back to it in later chapters when you fill in the implementation.

Schematic illustration of the main package implements the root command. A sub-command is implemented in its own package.