From b4299090c3e503ba0c49a6086b1a46c218ca45f4 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Thu, 12 Oct 2017 17:59:45 -0700 Subject: Command, Context, command line splitting setup --- cmd/cmd.go | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 22 deletions(-) (limited to 'cmd/cmd.go') diff --git a/cmd/cmd.go b/cmd/cmd.go index 725aaea..945e945 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -27,18 +27,69 @@ // to use for other commands. package cmd -import "os" +import ( + "fmt" + "io" + "os" + "text/template" + "time" -// Command represents a command with many potential top-level commands. This is -// transformed into a cli.Command in Run(). -type Command struct { - Name string - UsageLines []string - SubCmds []*Command - Arguments []*Argument - Flags []Flag - ManPage *ManEntry - Action CommandFunc + "github.com/blang/semver" +) + +// Context represents the state of a running application, and is the only thing +// passed to a CommandFunc. +type Context struct { + Command *Command + Parent *Context + Info *Info + Args []string + flagArgs []string +} + +// FullName returns the space-separated name of the command and all parents. +func (ctx *Context) FullName() string { + if ctx.Parent == nil { + return ctx.Command.Name + } + return fmt.Sprintf("%s %s", ctx.Parent.FullName(), ctx.Command.Name) +} + +// ManPage returns the man page entry for this context. It is either the ManPage +// for the the current command or the closet Parent. +func (ctx *Context) ManPage() *ManPage { + if ctx.Command.ManPage.Section != 0 || ctx.Parent == nil { + return ctx.Command.ManPage + } + return ctx.Parent.ManPage() +} + +// Creates an anonymous template from the text, and runs it with the provided +// Context and writer. Panics if text has a bad format or execution fails. +func (ctx *Context) executeTemplate(w io.Writer, text string) { + tmpl := template.Must(template.New("").Parse(text)) + if err := tmpl.Execute(w, ctx); err != nil { + panic(err) + } +} + +func (ctx *Context) execute() { + fmt.Printf("%+v\n", ctx) + return +} + +// Info is a parsed view of the corresponding global variables. +type Info struct { + Version semver.Version + BuildTime time.Time + Authors []Author + Copyright string +} + +// Author contains the contact information for a contributor. +type Author struct { + Name string + Email string } // Argument represents a parameter passed to a function. It has an optional @@ -48,19 +99,35 @@ type Argument struct { Usage string } -// ManEntry represents an entry in a man page with a name, section, and title. -type ManEntry struct { - Name string - Section int +func (a *Argument) String() string { return fmt.Sprintf("<%s>", a.ArgName) } + +// ManPage a man page with a title and section. +type ManPage struct { Title string + Section int } -// CommandFunc contains the implementation of a command. The provided args have -// the flags and leading command names removed. If a normal error is returned, -// it is printed out (with an optional explanation) and exits with FailureCode. -// If a usage error is returned, it is printed out with the command's usage and -// exits with UsageFailureCode. Returning nil causes an exit with success. -type CommandFunc func(args []string) error +// CommandFunc contains the implementation of a command. If a normal error is +// returned, the error will be printed out (with an optional explanation) and +// Run will exit with FailureCode. If a usage error is returned, the error and +// the commnd's usage are printed out and Run will exit with UsageFailureCode. +// Returning nil causes Run to return. +type CommandFunc func(ctx *Context) error + +// Command represents a command with many potential top-level commands. This is +// transformed into a cli.Command in Run(). +type Command struct { + Name string + Title string + UsageLines []string + SubCommands []*Command + InheritArguments bool + Arguments []*Argument + InheritFlags bool + Flags []Flag + ManPage *ManPage + Action CommandFunc +} // Run executes the command with os.Args, equivalent to c.RunArgs(os.Args). func (c *Command) Run() { @@ -71,5 +138,40 @@ func (c *Command) Run() { // empty, args[0]'s basename is used instead. If the command fails, this method // will not return. func (c *Command) RunArgs(args []string) { - // TODO(joerichey): Implement conversion to cli.Command + binaryName, args := args[0], args[1:] + if c.Name == "" { + c.Name = binaryName + } + + // Create our initial context by sorting the args and parsing the tags. + ctx := &Context{ + Command: c, + Info: parseInfo(), + } + ctx.Args, ctx.flagArgs = sortArgs(args) + + ctx.execute() +} + +// Divide the arguments into flag arguments (those starting with "-") and normal +// arguments. If "--" appears in the list, it will classified as a normal +// argument as well as all arguments following it. Also removes empty args. +func sortArgs(args []string) (normalArgs, flagArgs []string) { + var arg string + for len(args) > 0 { + arg, args = args[0], args[1:] + if arg == "" { + continue + } + if arg == "--" { + normalArgs = append(normalArgs, arg) + normalArgs = append(normalArgs, args...) + return + } else if arg[0] == '-' { + flagArgs = append(flagArgs, arg) + } else { + normalArgs = append(normalArgs, arg) + } + } + return } -- cgit v1.2.3