Commit 461473ac authored by ale's avatar ale

Replace gonuts/commander with google/subcommands

Causes a slight reduction in boilerplate, and gets rid of a bunch of
custom logic for command-line parsing that is no longer needed.
parent 7bddc686
Pipeline #5486 passed with stages
in 4 minutes and 59 seconds
This diff is collapsed.
# This is the official list of Go-Commander authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Google Inc
# This is the official list of people who can contribute
# (and typically have contributed) code to the Go-Commander repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Juan Batiz-Benet <juan@benet.ai>
Sebastien Binet <seb.binet@gmail.com>
Yves Junqueira <yves.junqueira@gmail.com>
Copyright (c) 2012 The Go-Commander Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
commander
============
[![Build Status](https://drone.io/github.com/gonuts/commander/status.png)](https://drone.io/github.com/gonuts/commander/latest)
``commander`` is a spin off of [golang](http://golang.org) ``go tool`` infrastructure to provide commands and sub-commands.
A ``commander.Command`` has a ``Subcommands`` field holding ``[]*commander.Command`` subcommands, referenced by name from the command line.
So a ``Command`` can have sub commands.
So you can have, _e.g._:
```sh
$ mycmd action1 [options...]
$ mycmd subcmd1 action1 [options...]
```
Example provided by:
- [hwaf](https://github.com/hwaf/hwaf)
- [examples/my-cmd](examples/my-cmd)
## Documentation
Is available on [godoc](http://godoc.org/github.com/gonuts/commander)
## Installation
Is performed with the usual:
```sh
$ go get github.com/gonuts/commander
```
## Example
See the simple ``my-cmd`` example command for how this all hangs
together [there](http://github.com/gonuts/commander/blob/master/examples/my-cmd/main.go):
```sh
$ my-cmd cmd1
my-cmd-cmd1: hello from cmd1 (quiet=true)
$ my-cmd cmd1 -q
my-cmd-cmd1: hello from cmd1 (quiet=true)
$ my-cmd cmd1 -q=0
my-cmd-cmd1: hello from cmd1 (quiet=false)
$ my-cmd cmd2
my-cmd-cmd2: hello from cmd2 (quiet=true)
$ my-cmd subcmd1 cmd1
my-cmd-subcmd1-cmd1: hello from subcmd1-cmd1 (quiet=true)
$ my-cmd subcmd1 cmd2
my-cmd-subcmd1-cmd2: hello from subcmd1-cmd2 (quiet=true)
$ my-cmd subcmd2 cmd1
my-cmd-subcmd2-cmd1: hello from subcmd2-cmd1 (quiet=true)
$ my-cmd subcmd2 cmd2
my-cmd-subcmd2-cmd2: hello from subcmd2-cmd2 (quiet=true)
$ my-cmd help
Usage:
my-cmd command [arguments]
The commands are:
cmd1 runs cmd1 and exits
cmd2 runs cmd2 and exits
subcmd1 subcmd1 subcommand. does subcmd1 thingies
subcmd2 subcmd2 subcommand. does subcmd2 thingies
Use "my-cmd help [command]" for more information about a command.
Additional help topics:
Use "my-cmd help [topic]" for more information about that topic.
$ my-cmd help subcmd1
Usage:
subcmd1 command [arguments]
The commands are:
cmd1 runs cmd1 and exits
cmd2 runs cmd2 and exits
Use "subcmd1 help [command]" for more information about a command.
Additional help topics:
Use "subcmd1 help [topic]" for more information about that topic.
```
## TODO
- automatically generate the bash/zsh/csh autocompletion lists
- automatically generate Readme examples text
- test cases
// Copyright 2012 The Go-Commander Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// Based on the original work by The Go Authors:
// Copyright 2011 The Go Authors. All rights reserved.
// commander helps creating command line programs whose arguments are flags,
// commands and subcommands.
package commander
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"sort"
"strings"
"text/template"
"github.com/gonuts/flag"
)
// UsageSection differentiates between sections in the usage text.
type Listing int
const (
CommandsList = iota
HelpTopicsList
Unlisted
)
// A Command is an implementation of a subcommand.
type Command struct {
// UsageLine is the short usage message.
// The first word in the line is taken to be the command name.
UsageLine string
// Short is the short description line shown in command lists.
Short string
// Long is the long description shown in the 'help <this-command>' output.
Long string
// List reports which list to show this command in Usage and Help.
// Choose between {CommandsList (default), HelpTopicsList, Unlisted}
List Listing
// Run runs the command.
// The args are the arguments after the command name.
Run func(cmd *Command, args []string) error
// Flag is a set of flags specific to this command.
Flag flag.FlagSet
// CustomFlags indicates that the command will do its own
// flag parsing.
CustomFlags bool
// Subcommands are dispatched from this command
Subcommands []*Command
// Parent command, nil for root.
Parent *Command
// UsageTemplate formats the usage (short) information displayed to the user
// (leave empty for default)
UsageTemplate string
// HelpTemplate formats the help (long) information displayed to the user
// (leave empty for default)
HelpTemplate string
// Stdout and Stderr by default are os.Stdout and os.Stderr, but you can
// point them at any io.Writer
Stdout io.Writer
Stderr io.Writer
}
// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
name := c.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
// Usage prints the usage details to the standard error output.
func (c *Command) Usage() {
c.usage()
}
// FlagOptions returns the flag's options as a string
func (c *Command) FlagOptions() string {
var buf bytes.Buffer
c.Flag.SetOutput(&buf)
c.Flag.PrintDefaults()
str := string(buf.Bytes())
if len(str) > 0 {
return fmt.Sprintf("\nOptions:\n%s", str)
}
return ""
}
// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
return c.Run != nil
}
// Type to allow us to use sort.Sort on a slice of Commands
type CommandSlice []*Command
func (c CommandSlice) Len() int {
return len(c)
}
func (c CommandSlice) Less(i, j int) bool {
return c[i].Name() < c[j].Name()
}
func (c CommandSlice) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
// Sort the commands
func (c *Command) SortCommands() {
sort.Sort(CommandSlice(c.Subcommands))
}
// Init the command
func (c *Command) init() {
if c.Parent != nil {
return // already initialized.
}
// setup strings
if len(c.UsageLine) < 1 {
c.UsageLine = Defaults.UsageLine
}
if len(c.UsageTemplate) < 1 {
c.UsageTemplate = Defaults.UsageTemplate
}
if len(c.HelpTemplate) < 1 {
c.HelpTemplate = Defaults.HelpTemplate
}
if c.Stderr == nil {
c.Stderr = os.Stderr
}
if c.Stdout == nil {
c.Stdout = os.Stdout
}
// init subcommands
for _, cmd := range c.Subcommands {
cmd.init()
}
// init hierarchy...
for _, cmd := range c.Subcommands {
cmd.Parent = c
}
}
// Dispatch executes the command using the provided arguments.
// If a subcommand exists matching the first argument, it is dispatched.
// Otherwise, the command's Run function is called.
func (c *Command) Dispatch(args []string) error {
if c == nil {
return fmt.Errorf("Called Run() on a nil Command")
}
// Ensure command is initialized.
c.init()
// First, try a sub-command
if len(args) > 0 {
for _, cmd := range c.Subcommands {
n := cmd.Name()
if n == args[0] {
return cmd.Dispatch(args[1:])
}
}
// help is builtin (but after, to allow overriding)
if args[0] == "help" {
return c.help(args[1:])
}
// then, try out an external binary (git-style)
bin, err := exec.LookPath(c.FullName() + "-" + args[0])
if err == nil {
cmd := exec.Command(bin, args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = c.Stdout
cmd.Stderr = c.Stderr
return cmd.Run()
}
}
// then, try running this command
if c.Runnable() {
if !c.CustomFlags {
var err = error(nil)
c.Flag.Usage = func() {
c.Usage()
err = fmt.Errorf("Failed to parse flags.")
}
c.Flag.Parse(args)
if err != nil {
return err
}
args = c.Flag.Args()
}
return c.Run(c, args)
}
// TODO: try an alias
//...
// Last, print usage
if err := c.usage(); err != nil {
return err
}
return nil
}
func (c *Command) usage() error {
c.SortCommands()
err := tmpl(c.Stderr, c.UsageTemplate, c)
if err != nil {
fmt.Println(err)
}
return err
}
// help implements the 'help' command.
func (c *Command) help(args []string) error {
// help exactly for this command?
if len(args) == 0 {
if len(c.Long) > 0 {
return tmpl(c.Stdout, c.HelpTemplate, c)
} else {
return c.usage()
}
}
arg := args[0]
// is this help for a subcommand?
for _, cmd := range c.Subcommands {
n := cmd.Name()
// strip out "<parent>-"" name
if strings.HasPrefix(n, c.Name()+"-") {
n = n[len(c.Name()+"-"):]
}
if n == arg {
return cmd.help(args[1:])
}
}
return fmt.Errorf("Unknown help topic %#q. Run '%v help'.\n", arg, c.Name())
}
func (c *Command) MaxLen() (res int) {
res = 0
for _, cmd := range c.Subcommands {
i := len(cmd.Name())
if i > res {
res = i
}
}
return
}
// ColFormat returns the column header size format for printing in the template
func (c *Command) ColFormat() string {
sz := c.MaxLen()
if sz < 11 {
sz = 11
}
return fmt.Sprintf("%%-%ds", sz)
}
// FullName returns the full name of the command, prefixed with parent commands
func (c *Command) FullName() string {
n := c.Name()
if c.Parent != nil {
n = c.Parent.FullName() + "-" + n
}
return n
}
// FullSpacedName returns the full name of the command, with ' ' instead of '-'
func (c *Command) FullSpacedName() string {
n := c.Name()
if c.Parent != nil {
n = c.Parent.FullSpacedName() + " " + n
}
return n
}
func (c *Command) SubcommandList(list Listing) []*Command {
var cmds []*Command
for _, cmd := range c.Subcommands {
if cmd.List == list {
cmds = append(cmds, cmd)
}
}
return cmds
}
var Defaults = Command{
UsageTemplate: `{{if .Runnable}}Usage: {{if .Parent}}{{.Parent.FullSpacedName}}{{end}} {{.UsageLine}}
{{end}}{{.FullSpacedName}} - {{.Short}}
{{if commandList}}Commands:
{{range commandList}}
{{.Name | printf (colfmt)}} {{.Short}}{{end}}
Use "{{.Name}} help <command>" for more information about a command.
{{end}}{{.FlagOptions}}{{if helpList}}
Additional help topics:
{{range helpList}}
{{.Name | printf (colfmt)}} {{.Short}}{{end}}
Use "{{.Name}} help <topic>" for more information about that topic.
{{end}}`,
HelpTemplate: `{{if .Runnable}}Usage: {{if .Parent}}{{.Parent.FullSpacedName}}{{end}} {{.UsageLine}}
{{end}}{{.Long | trim}}
{{.FlagOptions}}
`,
}
// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) error {
t := template.New("top")
t.Funcs(template.FuncMap{
"trim": strings.TrimSpace,
"colfmt": func() string { return data.(*Command).ColFormat() },
"commandList": func() []*Command { return data.(*Command).SubcommandList(CommandsList) },
"helpList": func() []*Command { return data.(*Command).SubcommandList(HelpTopicsList) },
})
template.Must(t.Parse(text))
return t.Execute(w, data)
}
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
flag
=======
[![Build Status](https://drone.io/github.com/gonuts/flag/status.png)](https://drone.io/github.com/gonuts/flag/latest)
A fork of the official "flag" package but with the flag.Value interface extended to provide a ``Get() interface{}`` method.
This diff is collapsed.
Want to contribute? Great! First, read this page (including the small print at the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement]
(https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
### Code reviews
All submissions, including submissions by project members, require review. We
use Github pull requests for this purpose.
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the
[Software Grant and Corporate Contributor License Agreement]
(https://cla.developers.google.com/about/google-corporate).
This diff is collapsed.
# subcommands #
[![GoDoc](https://godoc.org/github.com/google/subcommands?status.svg)](https://godoc.org/github.com/google/subcommands)
Subcommands is a Go package that implements a simple way for a single command to
have many subcommands, each of which takes arguments and so forth.
This is not an official Google product.
## Usage ##
Set up a 'print' subcommand:
```go
import (
"context"
"flag"
"fmt"
"os"
"strings"
"github.com/google/subcommands"
)
type printCmd struct {
capitalize bool
}
func (*printCmd) Name() string { return "print" }
func (*printCmd) Synopsis() string { return "Print args to stdout." }
func (*printCmd) Usage() string {
return `print [-capitalize] <some text>:
Print args to stdout.
`
}
func (p *printCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.capitalize, "capitalize", false, "capitalize output")
}
func (p *printCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
for _, arg := range f.Args() {
if p.capitalize {
arg = strings.ToUpper(arg)
}
fmt.Printf("%s ", arg)
}
fmt.Println()
return subcommands.ExitSuccess
}
```
Register using the default Commander, also use some built in subcommands,
finally run Execute using ExitStatus as the exit code:
```go
func main() {
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&printCmd{}, "")
flag.Parse()
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}
```
module github.com/google/subcommands
This diff is collapsed.
......@@ -128,18 +128,6 @@
"revision": "e91709a02e0e8ff8b86b7aa913fdc9ae9498e825",
"revisionTime": "2019-04-09T05:09:43Z"
},
{
"checksumSHA1": "fqOeX6hfVZtiqrpyFf3QHyXfwFI=",
"path": "github.com/gonuts/commander",
"revision": "f8ba4e959ca914268227c3ebbd7f6bf0bb35541a",
"revisionTime": "2014-02-05T14:10:49Z"
},
{
"checksumSHA1": "EtSsEEeO8SdIjdyBdHdidmEwNvM=",
"path": "github.com/gonuts/flag",
"revision": "741a6cbd37a30dedc93f817e7de6aaf0ca38a493",
"revisionTime": "2013-05-24T08:13:38Z"
},
{
"checksumSHA1": "nTzIVdyrnFQIDJWGkA9kNoT/Gj4=",
"origin": "go.etcd.io/etcd/vendor/github.com/google/btree",
......@@ -147,6 +135,12 @@
"revision": "a621d807f061e1dd635033a8d6bc261461429e27",
"revisionTime": "2019-04-01T20:57:24Z"
},
{
"checksumSHA1": "Y4eZD9qtvN9LQ3+whqrANsDd5X8=",
"path": "github.com/google/subcommands",
"revision": "24aea2b9b9c12400919203843c92ef808c7ad560",
"revisionTime": "2019-09-04T16:18:56Z"
},
{
"checksumSHA1": "C+zzeVyMEN61Nq58sk4/VzhhyI4=",
"origin": "go.etcd.io/etcd/vendor/github.com/google/uuid",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment