Git Product home page Git Product logo

cli's Introduction

Go CLI Library GoDoc

cli is a library for implementing command-line interfaces in Go. cli is the library that powers the CLI for Packer, Consul, Vault, Terraform, Nomad, and more.

Features

  • Easy sub-command based CLIs: cli foo, cli bar, etc.

  • Support for nested subcommands such as cli foo bar.

  • Optional support for default subcommands so cli does something other than error.

  • Support for shell autocompletion of subcommands, flags, and arguments with callbacks in Go. You don't need to write any shell code.

  • Automatic help generation for listing subcommands.

  • Automatic help flag recognition of -h, --help, etc.

  • Automatic version flag recognition of -v, --version.

  • Helpers for interacting with the terminal, such as outputting information, asking for input, etc. These are optional, you can always interact with the terminal however you choose.

  • Use of Go interfaces/types makes augmenting various parts of the library a piece of cake.

Example

Below is a simple example of creating and running a CLI

package main

import (
	"log"
	"os"

	"github.com/mitchellh/cli"
)

func main() {
	c := cli.NewCLI("app", "1.0.0")
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"foo": fooCommandFactory,
		"bar": barCommandFactory,
	}

	exitStatus, err := c.Run()
	if err != nil {
		log.Println(err)
	}

	os.Exit(exitStatus)
}

cli's People

Contributors

acomagu avatar andrii-zakurenyi avatar arbourd avatar chuyeow avatar dnephin avatar fatih avatar fgtatsuro avatar gobwas avatar jefferai avatar jen20 avatar kmoe avatar magiconair avatar mitchellh avatar moorara avatar phinze avatar pims avatar radeksimko avatar rdeusser avatar ryanuber avatar santosh653 avatar sarahhodne avatar sean- avatar sethvargo avatar sks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cli's Issues

Tab/backspace handling

When I run this program...

package main

import (
    "github.com/mitchellh/cli"
    "os"
)

func main() {
    ui := cli.BasicUi{os.Stdin, os.Stdout}
    ui.Ask(" >")
}

and press "tab" it seems to jump to the right 8 spaces, but pressing backspace causes it to delete only one of those spaces.

Additionally, if I type "asdf" and then press "tab" twice, pressing backspace several times will delete the tabs, the "Ask" prompt message (" >", in my case), but leave the "asdf" in its place. There seems to be a lot of abhorrent behavior around this.

I use terminator from the default repos in Ubuntu 10.04.

Consider changing `Ui` to `UI`

I know it's your style and it's not that big of a deal, but I started using cli in one of my projects and I'd like to keep the naming convention consistent throughout the code. Also... it's NewCLI not NewCli in your code. Unless Ui means something in Japanese...

https://code.google.com/p/go-wiki/wiki/CodeReviewComments#Initialisms

Initialisms

Words in names that are initialisms or acronyms (e.g. URL or NATO) have a consistent case. For example, URL should appear as URL or url (as in urlPony, or URLPony), never as Url. Here's an example: ServeHTTP not ServeHttp.

This rule also applies to ID when it is short for "identifier," so write appID instead of appId.

Error building

$ go version
go version go1.4.2 solaris/amd64
$ go build github.com/mitchellh/cli 
# github.com/mitchellh/cli
src/github.com/mitchellh/cli/ui.go:79: undefined: terminal.IsTerminal
src/github.com/mitchellh/cli/ui.go:81: undefined: terminal.ReadPassword

is there support for interactive mode?

if I run the cli in interactive mode, I should be able to run the sub commands without having to type the cli name again. example:

$ mycli
> foo
> bar

Versus cobra cli

Could you explain the differences with cobra cli. What are pros to choose this CLI over the cobra and flags?

https://github.com/spf13/cobra
https://gobyexample.com/command-line-flags

I am using your homdir library as well and it is very handy. I will try this library as well and perhaps it would be interesting to add a paragraph regarding the differences. I will report my findings and I could submit a PR and I am also curious what is your point of view regarding the differences.

Generate help text from command options

I have the following help text:

func (c *cloudformationStopInstancesCommand) Help() string {

	helpText := `
Usage: cloudformation stop-instances [options]
  Stops all instances in the given cloudformation stack
Options:
  -stackname=stackname      The name of the cloudformation stack
  -region=region            The AWS region
 `
	return strings.TrimSpace(helpText)

}

but I was wondering if it possible to generate the Options section from the options usage params?

stackname := cmdFlags.String("stackname", "", "The name of the cloudformation stack")
region := cmdFlags.String("region", "", "The AWS region")

Right now I am duplicating the usage strings. I feel like I'm doing something wrong here..

Subcommand parsing: "fooasdf -o=foo" is incorrectly identified as the subcommand "foo"

Consider a command with a single subcommand foo.

Running the command with arguments fooasdf -o=bar returns a usage error, return code 127.

However, running the command with arguments fooasdf -o=foo does not return an error, and instead sets the subcommand to foo. This is incorrect - this example should exit 127 just as above.

This problem was seen in hashicorp/terraform#30870, in which terraform planasdf -out tfplan, which should error, instead behaves as if the user had run terraform plan -out tfplan.

The problem seems to be that the subcommand matching logic at https://github.com/mitchellh/cli/blob/master/cli.go#L704 is too liberal, and identifies any string both prefixed and suffixed by a subcommand as that subcommand.

please tag and version this project

Hello,

Can you please tag and version this project?

I am the Debian Maintainer for cli and versioning would help Debian keep up with development.

Nested Subcommand Help

The generated subcommands should only show immediate child when listing subcommands:

nomad operator
Usage: nomad operator <subcommand> [options]

  Provides cluster-level tools for Nomad operators, such as interacting with
  the Raft subsystem. NOTE: Use this command with extreme caution, as improper
  use could lead to a Nomad outage and even loss of data.

  Run nomad operator <subcommand> with no arguments for help on that subcommand.

Subcommands:

    raft                Provides access to the Raft subsystem
    raft list-peers     List peers from the Raft quorum
    raft remove-peer    Remove peers from the Raft quorum

Expected behavior would be to just show raft

Allow for passing around context with cli

In my app I'm parsing a config file into a struct in main. It would be great if I could set that as context on either the cli object so I can access it in my commands. What would you think of adding something like that?

How to allow default command to accept arguments like `--flag arg`?

I'm writing a tool that needs to accept these:

mytool
mytool -boolean_flag
mytool -string_flag arg

I'm using mitchellh/cli to implement a variety of subcommands and have a default command that handles the the first two cases above but in the third case it complains about "Invalid flags before the subcommand. ...".

I was hopeful that @yegle's changes in #20 would help, but it seems like it will have the same problem.

This change to cli.go gives me the behavior that I'm looking for. I'm unsure of any potential downsides.

diff --git a/cli.go b/cli.go
index bb676e3..dd039c2 100644
--- a/cli.go
+++ b/cli.go
@@ -174,9 +174,17 @@ func (c *CLI) processArgs() {

        // If we never found a subcommand and support a default command, then
        // switch to using that.
-       if c.subcommand == "" {
+       // If we found a subcommand that doesn't exist and there's a default
+       // command, perhaps what we though was a subcommand is really an
+       // argument to the default command.  Give the default command a shot
+       // at it.
+       if c.Commands[c.subcommand] == nil || c.subcommand == "" {
                if _, ok := c.Commands[""]; ok {
                        args := c.topFlags
+                       if c.Commands[c.subcommand] == nil {
+                               args = append(args, c.subcommand)
+                               c.subcommand = ""
+                       }
                        args = append(args, c.subcommandArgs...)
                        c.topFlags = nil
                        c.subcommandArgs = args

I realize that I've mixed metaphors a bit, but I'm unsure when/why indexing into a map and checking for a zero value is better than using the two value assignment. Stylistic feedback welcome!

default subcommand throws stack overflow

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x57eb76, 0xe)
        /usr/local/go/src/runtime/panic.go:619 +0x81
runtime.newstack()
        /usr/local/go/src/runtime/stack.go:1054 +0x71f
runtime.morestack()
        /usr/local/go/src/runtime/asm_amd64.s:480 +0x89

goroutine 1 [running]:
strings.Index(0x0, 0x0, 0x57cd70, 0x1, 0x0)
        /usr/local/go/src/strings/strings_amd64.go:27 +0x3c9 fp=0xc440100318 sp=0xc440100310 pc=0x4c7f39
github.com/mitchellh/cli.(*CLI).initAutocompleteSub.func1(0x0, 0x0, 0x552100, 0x586c58, 0x8)
        /home/ayu/go/src/github.com/mitchellh/cli/cli.go:431 +0xaa fp=0xc4401003c0 sp=0xc440100318 pc=0x52713a
github.com/armon/go-radix.recursiveWalk(0xc4200742a0, 0xc4401004b8, 0x0)
        /home/ayu/go/src/github.com/armon/go-radix/radix.go:522 +0xcf fp=0xc440100410 sp=0xc4401003c0 pc=0x518b7f
github.com/armon/go-radix.(*Tree).WalkPrefix(0xc420010320, 0x0, 0x0, 0xc4401004b8)

Reproducible Code

https://gist.github.com/ayunyan/67e1580b97df859a725a99e109ece5eb

Go Version

go version go1.9.2 freebsd/amd64
go version go1.10 linux/amd64

BasicUi.Ask functionality throws a panic

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x20 pc=0xec913]

goroutine 8 [running]:
panic(0x371ac0, 0xc8200120c0)
    /usr/local/go/src/runtime/panic.go:464 +0x3e6
bufio.(*Reader).fill(0xc82002cee8)
    /usr/local/go/src/bufio/bufio.go:97 +0x1e3
bufio.(*Reader).ReadSlice(0xc82003fed8, 0x1380a, 0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/bufio/bufio.go:328 +0x21a
bufio.(*Reader).ReadBytes(0xc82003fed8, 0x2ddb0a, 0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/bufio/bufio.go:406 +0xa9
bufio.(*Reader).ReadString(0xc82003fed8, 0xc82002ce0a, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/bufio/bufio.go:446 +0x4d
github.com/mitchellh/cli.(*BasicUi).ask.func1(0xc820068500, 0xc82000ef00, 0xc820068540, 0xc8200685a0)
    /Users/thirumarant/workspace/src/github.com/mitchellh/cli/ui.go:85 +0x27d
created by github.com/mitchellh/cli.(*BasicUi).ask
    /Users/thirumarant/workspace/src/github.com/mitchellh/cli/ui.go:93 +0x345

Using the Ask function keeps throwing a panic

Cursor navigation via arrow keys, home/end, and ctrl+tab

Given this program:

package main

import (
    "github.com/mitchellh/cli"
    "os"
)

func main() {
    ui := cli.BasicUi{os.Stdin, os.Stdout}
    ui.Ask(" >")
}

When I type a character and then press the left arrow key, the command line is populated with ^[[D, instead of moving the cursor to the beginning of the command line. Similarly, it seems the other arrow keys, the home/end keys, and ctrl+tab are similarly unhandled.

These are common cursor navigation mechanisms which users expect command line apps to support.

panic if a subcommand is wrapped in double quotes and contains spaces

This issue was originally reported as hashicorp/terraform#16642, debugging for a while, it turns out to be a bug of mitchellh/cli.

Panic will occur under the following conditions:

  • A CLI.commandNested is true
  • The subcommand is wrapped in double quotes and contains spaces ( ./app "foo bar" )
  • If there is no double quote, it is a valid subcommand ( ./app foo bar )

Minimum reproduction code as follows:

package main

import (
	"log"
	"os"

	"github.com/mitchellh/cli"
)

type FooBar struct{}

func (f *FooBar) Help() string {
	return "app foo bar"
}

func (f *FooBar) Run(args []string) int {
	log.Println("Foo Bar!")
	return 0
}

func (f *FooBar) Synopsis() string {
	return "Print \"Foo Bar!\""
}

func main() {
	c := cli.NewCLI("app", "1.0.0")
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"foo bar": func() (cli.Command, error) {
			return &FooBar{}, nil
		},
	}

	exitStatus, err := c.Run()
	if err != nil {
		log.Println(err)
	}

	os.Exit(exitStatus)
}
$ go run main.go foo bar
2017/11/26 22:13:50 Foo Bar!

$ go run main.go "foo bar"
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
github.com/mitchellh/cli.(*CLI).processArgs(0xc420090000)
        /Users/m-morita/src/github.com/mitchellh/cli/cli.go:667 +0xb8c
github.com/mitchellh/cli.(*CLI).init(0xc420090000)
        /Users/m-morita/src/github.com/mitchellh/cli/cli.go:382 +0x3b9
github.com/mitchellh/cli.(*CLI).(github.com/mitchellh/cli.init)-fm()
        /Users/m-morita/src/github.com/mitchellh/cli/cli.go:158 +0x2a
sync.(*Once).Do(0xc4200900b8, 0xc42004def8)
        /usr/local/Cellar/go/1.9.1/libexec/src/sync/once.go:44 +0xbe
github.com/mitchellh/cli.(*CLI).Run(0xc420090000, 0xc420074270, 0x11858dc, 0x7)
        /Users/m-morita/src/github.com/mitchellh/cli/cli.go:171 +0x6d
main.main()
        /Users/m-morita/work/tmp/20171124/main.go:34 +0x13f

Panic occurs in the following lines.

cli/cli.go

Line 667 in 65fcae5

c.subcommandArgs = c.Args[i+1:]

If wrapping in double quotes then len(c.Args) = 1 , but the args matches c.commandTree.LongestPrefix (searchKey), so the index i is added by the number of spaces, resulting in out-of-range access.

I think this is an edge case, but it is not good to panic by user input.
How should we fix it?

Add flags dynamically

I want to dynamically add more flags to the application.

Imagine that I have a flag that is --extra-features. I want to add more flags dynamically if this one is present. How can I do something like that?

Thank you.

panic: runtime error: index out of range in ui_writer.go

Running consul v0.6 I encountered a panic:

panic: runtime error: index out of range


goroutine 1 [running]:
github.com/mitchellh/cli.(*UiWriter).Write(0xc82015d640, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
        /golang/workspace/src/github.com/mitchellh/cli/ui_writer.go:12 +0xf0
github.com/hashicorp/consul/command/agent.(*GatedWriter).Write(0xc8200ea5f0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
        /golang/workspace/src/github.com/hashicorp/consul/command/agent/gated_writer.go:36 +0x112
github.com/hashicorp/consul/command/agent.(*GatedWriter).Flush(0xc8200ea5f0)
        /golang/workspace/src/github.com/hashicorp/consul/command/agent/gated_writer.go:26 +0x107
github.com/hashicorp/consul/command/agent.(*Command).Run(0xc82013e000, 0xc82000a140, 0x10, 0x10, 0x0)
        /golang/workspace/src/github.com/hashicorp/consul/command/agent/command.go:715 +0x23ba
github.com/mitchellh/cli.(*CLI).Run(0xc82013c000, 0xc82013c000, 0x0, 0x0)
        /golang/workspace/src/github.com/mitchellh/cli/cli.go:112 +0x655
main.realMain(0xc820000180)
        /golang/workspace/src/github.com/hashicorp/consul/main.go:40 +0x214
main.main()
        /golang/workspace/src/github.com/hashicorp/consul/main.go:12 +0x18

.....
     (lots of tracebacks)
.....

Indeed, UiWriter::Write makes an optimistic assumption about p length in:

func (w *UiWriter) Write(p []byte) (n int, err error) {
    n = len(p)
    if p[n-1] == '\n' {
        p = p[:n-1]
    }

    w.Ui.Info(string(p))
    return n, nil
}

I do not have a core.

Mark commands (and flags) as hidden

There isn't currently a way to mark a subcommand as "hidden". It's possible to filter help output to exclude it using a HelpFunc, but the subcommand still appears in the autocompletion. There should either be a way to customize the list of commands which are auto-completed or (better), a first-class mechanism for marking a command as hidden.

There are a few use cases here, the most prominent being backwards compatibility. I may want to introduce a new command to do something, but I want to have the old command around with a warning for a few releases.

Another use case involves hidden "internal-only" commands. Sometimes you want some flags or commands that are for internal use only. Again, there's currently no way to achieve this nicely.

/cc @dadgar

How to write shell completion function in Go

For example the following is a zsh completion function for command foo:

#compdef _foo foo
compadd first second third fourth

So, if we type foo <tab> in the zsh terminal, it suggests first second third fourth.

How can I do this using cli?

Ui.Error, Info, Output should take variadic arguments

Instead of Ui.Error(string) it should be Ui.Error(string, interface{}...). This should then call fmt.Fprintf(w, string, ...v).

Almost all of my calls to Ui.(Error|Info|Output) end up including a call to fmt.Sprintf.

Compatibility with standard library `flag` package

I feel like I've missed something obvious - but not finding the answer anywhere in documentation or code. Or, if I did, I didn't understand it.

Is this library incompatible with the standard library flag package?

I've defined some flags, but they don't seem to get parsed when a command is specified. It doesn't matter if the flags are specified before or after the command. I have a program that's expecting reference to a config file via a flag, the program just exits early if one isn't specified. For example:

$ program --conf conf.yml  #executes, but just prints out list of available commands, as none was specified
$ program --conf conf.yml status  # executes, but doesn't call the `status` command, just prints list of available commands
$ program status --conf conf.yml  #immediately exits because it didn't get the `conf` flag

`app --help` with default command invokes default's help, no obvious way to get app's help

If I have a default command (e.g. default) installed for my app then

app --help

is the same as

app default --help

but I'd prefer that it generate the output that would be generated if there was no default comand.

My best solution is to stash/close over a copy of the cli

type DefaultInventoryCommand struct {
    // ... other things might go here
    Host string
    Ui   cli.Ui
    cli  cli.CLI
}

and then use it in the default command's Help() method, like this

func (c *DefaultInventoryCommand) Help() string {
    return c.cli.HelpFunc(c.cli.Commands) + "\n"
}

but I feel like I might be doing something outside of the API. Is this safe/reasonable? Is there a better alternative?

Package semver tag releases?

Why not? Useful if using gom or another versioned dependency manager in Go

If the API changes without retrocompatibility (however seems like not too much), how I can as package consumer avoid interface coupling troubles?

Subcommand parsing logic stops at first flag

Noticed this behavior on an internal tool, essentially, if you have flags defined for a command, then execute a subcommand of that command with a flag before the subcommand, then it identifies the command as the subcommand.

For example, if we take the test cases @ https://github.com/mitchellh/cli/blob/master/cli_test.go#L1459-L1489 and change it to:

func TestCLISubcommand_nested(t *testing.T) {
	testCases := []struct {
		args       []string
		subcommand string
	}{
		{[]string{"bar"}, "bar"},
		{[]string{"foo", "-h"}, "foo"},
		{[]string{"-h", "bar"}, "bar"},
		{[]string{"foo", "bar", "-h"}, "foo bar"},
		{[]string{"foo", "bar", "baz", "-h"}, "foo bar baz"},
		{[]string{"foo", "bar", "-h", "baz"}, "foo bar"}, // expected match would be `foo bar baz`
		{[]string{"-h", "foo", "bar"}, "foo bar"},
	}

	for _, testCase := range testCases {
		cli := &CLI{
			Args: testCase.args,
			Commands: map[string]CommandFactory{
				"foo bar": func() (Command, error) {
					return new(MockCommand), nil
				},
				"foo bar baz": func() (Command, error) {
					return new(MockCommand), nil
				},
			},
		}
		result := cli.Subcommand()

		if result != testCase.subcommand {
			t.Errorf("Expected %#v, got %#v. Args: %#v",
				testCase.subcommand, result, testCase.args)
		}
	}
}

I guess the question is, with other tools like cobra which handle flags anywhere within the arguments, does it make sense to change this library's behavior?

Sporadic test failures in "TestCLIRun_printCommandHelpSubcommands"

With Golang 1.6 TestCLIRun_printCommandHelpSubcommands sometimes fail as follows:

--- FAIL: TestCLIRun_printCommandHelpSubcommands (0.00s) 
        cli_test.go:344: bad: []string{"-h", "foo"} 

                '"donuts\n\nSubcommands:\n\n    longer    hi!\n    bar       hi!\n\n"' 

                '"donuts\n\nSubcommands:\n\n    bar       hi!\n    longer    hi!\n\n"' 

Flags showing up before subcommands

Filing this for hashicorp/terraform#1773

Current behavior: Except for help/version, flags before a subcommand are silently ignored.

Possible solutions:

(A) Flags before subcommand cause error
(B) Flags before subcommand are passed in as subcommand args

Unsure what the better behavior. Originally I thought (A) but then I swung to (B) after staring at it a bit.

Thoughts?

Root command

I tried to find somewhere in the code, issues and docs (readme) but I couldn't find how I can create a root command. Cobra for example I can create a root command whenever I call my program it would run the root command by default. Using CLI if I run without specifying a command it returns exit status 127.

Add "subcommand chooser"

The cli.CLI struct should add a SubcommandChooser, which is a func that return a cli.Command.

The reason would be when the specified subcommand doesn't exist (see also pull reqeust #10) the behaviour can be controlled by the user.

Also I can implement something similar to busybox's behaviour that by creating a link foo to my binary, I can invoke the foo subcommand directly.

If this idea looks good to you I can send a PR and implement it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.