Tutorial: Building A CLI Application
Here is a basic scaffold of a CLI application that uses subcommands.
Here, we're building the command foo
that has the subcommand bar
,
which can be invoked like this from tthe commandline: foo bar
.
foo.go:
package main
import(
"github.com/Masterminds/cookoo"
"github.com/Masterminds/cookoo/cli"
"fmt"
"os"
)
func main() {
// Start a cookoo app.
reg, router, cxt := cookoo.Cookoo();
// Put the arguments into the context.
cxt.Put("os.Args", os.Args)
// Create help text
reg.Route("help", "Show application-scoped help.").
Does(cli.ShowHelp, "help").
Using("show").WithDefault(true).
Using("summary").WithDefault("This is the help text.")
// Handle the "bar" subcommand
reg.Route("bar", "Do something").
Does(MyBarCommand, "bar")
// This is the main runner. It proxies to subcommands.
reg.Route("run", "Run the app.").
// Shift off two args: the app name and then the subcommand.
// The subcommand is then put in the context as "subcommand"
Does(cli.ShiftArgs, "subcommand").
Using("n").WithDefault(2).
Does(cookoo.ForwardTo, "sub").
Using("route").From("cxt:subcommand").WithDefault("help").
Using("ignoreRoutes").WithDefault([]string{"subcommand"}).
// This starts the app. If a fatal error occurs, we
// display the error.
e := router.HandleRequest("run", cxt, true)
if e != nil {
fmt.Printf("Error: %s\n", e)
}
}
// This is our command.
func MyBarCommand(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
fmt.Printf("OH HAI")
return nil, nil
}
When we run foo bar
(or go run foo.go bar
), here's what happens:
- main() is run. It creates a Cookoo app, defines the registry, and
then runs
router.HandleRequest("run", ...)
- The "run" route is run. This reads the
os.Args
and sees that it should execute the subcommandbar
. - The "bar" route is run, which executes it's one command:
MyBarCommand
. MyBarCommand
runs, printing "OH HAI" to stdout.
If you were to run foo
or foo help
, then the chain would execute
like this:
- main() runs, and passes to `router.HandleRequest("run"...)
- "run" will execute
ShiftArgs
and thenForwardTo
, which will resolve the subcommand to "help" (which is the default target). - The "help" route will be run, which will print out simple help:
go run foo.go
SUMMARY
This is the help text.
Using Flags
Go provides the flag
package with many utilities for working with
command-line flags. We can use those in Cookoo.
Here's a snippet of code that could be worked into the previous example:
reg.Route("add-user", "Add a new account.").
Does(cli.ParseArgs, "args").
Using("flagset").WithDefault(AddUserFlags()).
Using("args").From("cxt:os.Args").
Does(cli.ShowHelp, "help").
Using("show").From("cxt:h").
Using("summary").WithDefault("Add a new account.").
Using("usage").WithDefault("tool add-user -a NAME -p PASSWORD").
Using("flags").WithDefault(AccountAddFlags()).
// ... do the rest...
With the example above, we can generate our flags like this:
func AddUserFlags() *flag.FlagSet {
flags := flag.NewFlagSet("account", flag.PanicOnError)
flags.Bool("h", false, "Print help text.")
flags.String("a", nil, "Account name.")
flags.String('p', nil, "User's new password.")
return flags
}
The arguments are parsed by cli.ParseArgs
. Each flag is then placed
directly into the context. (See how we access -h
with From(cxt:h)
above?)
In addition to using flags for processing, cli.ShowHelp
can take a
*flag.FlagSet
and automatically generate help text.
While this example shows providing flags for subcommands, higher level
flags can be handled in much the same way. For example, we could use
them in our run
route, too.
Where to from here?
From this starting point, you should be able to assemble your own routes, where each new route represents a subcommand.