mitchellh / cli Goto Github PK
View Code? Open in Web Editor NEWA Go library for implementing command-line interfaces.
License: Mozilla Public License 2.0
A Go library for implementing command-line interfaces.
License: Mozilla Public License 2.0
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.
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.
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
Update to https://github.com/Masterminds/sprig/releases/tag/v3.2.1 or later to remove GHSA-xg2h-wx96-xgxr.
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?
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
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:
CLI.commandNested
is true
./app "foo bar"
)./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.
Line 667 in 65fcae5
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?
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
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
.
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..
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
Even within a subcommand -version
will print the application version. If the flag is given with equals this is avoided!
$ nomad job revert -version 0 e
Nomad v0.6.0-dev
$ nomad job revert -version=0 e
# Works
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?
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?
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.
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?
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.
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.
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?
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.
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.
posener/complete has been abandoned (not updated since 2020) and it clearly has some flaws.
It'd be great if it could be replaced with a solution that tackled the issues presented here:
Cobra's shell completions provide some of the required features. Could something like it be implemented?
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!
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"'
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
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?
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
$ 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
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
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.
Got the following failure with Golang 1.12:
FAIL: TestCLIAutocomplete_subcommandArgs (0.00s)
FAIL: TestCLIAutocomplete_subcommandArgs/RE (0.00s)
cli_test.go:1427: bad prediction: []string(nil)
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.