Recursively generate golang cobra --help text? - go

If I have a cobra-managed golang application, I can invoke mycommand --help to see the top level help and list of commands, mycommand cmd1 --help to see the same for the first command, etc.
Is there a way using the cobra library to recursively print all the commands, flags, and help text in one pass?
https://github.com/spf13/cobra

I was able to hack something up. This is just a simple recursive function that filters out some noise by command name (e.g I skip over autogenerated help and bash completion commands)
var dumpAllHelp = "dump-all-help"
var recHelpCmd = &cobra.Command{
Use: dumpAllHelp,
Short: "dump all help texts",
Long: "dump all help texts",
Run: func(_ *cobra.Command, _ []string) {
dumpHelp(rootCmd, true)
},
}
func dumpHelp(c *cobra.Command, root bool) {
if !root {
fmt.Println("")
fmt.Println("========================================================")
fmt.Println("")
}
c.Help()
for _, child := range c.Commands() {
if child.Hidden || child.Name() == "completion" || child.Name() == "help" || child.Name() == dumpAllHelp {
continue
}
dumpHelp(child, false)
}
}
func init() {
rootCmd.AddCommand(recHelpCmd)
}

Related

ValidArgsFunction dynamic autocomplete not working with Golang Cobra cli program

I'm trying to get autocomplete working with Cobra but nothing happens after pressing the tab key - on something that I think should autocomplete. Am I missing something?
var HelloCmd = &cobra.Command{
Use: "hello <name>",
Short: "Say hello to someone",
Long: `Say hello to someone`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(args)
fmt.Println("hello " + args[0])
cmd.Help()
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return []string{"steve", "john"}, cobra.ShellCompDirectiveNoFileComp
},
}
And then I call go build . followed by ./program hello s[TAB] or ./program hello j[TAB] it suggests nothing. I want to it suggest the names "steve" and "john". If I type ./program hello [TAB] it suggests the files in the directory.
Pls help I've been ripping my hairs out all morning to fix this!
you need to generate autocomplete script and add it to your shell profile follow this section:
Creating your own completion command
and see [cmd] completion -h for more help.

How to print from go test to console without using verbose option

I want to print an informative message to console from a test, but not have the verbose test output.
I have tried printing from the test using:
fmt.Println("my message") // STDOUT
fmt.Fprintln(os.Stderr, "my message") // STDERR
t.Log("my message\n") // testing.T log
which all produce the same effect of showing in the console if go test -v is used, but not showing if just go test is used.
However, with go test -v, I also get all the verbose test output like:
=== RUN My_Test
--- PASS: My_Test (0.07s)
go help testflag says:
-v
Verbose output: log all tests as they are run. Also print all
text from Log and Logf calls even if the test succeeds.
but what I want is to not log all tests as they are run, but still print all text from Log and Logf calls even if the test succeeds
Is there a way to print a visible message from within a test, but not see the RUN and PASS messages?
The testing framework "hijacks" the standard output and error streams for obvious reasons. So no matter what, whether writing to those streams appears in the output is controlled by the testing framework, and it provides no means to "customize" it other than showing or hiding all using the -v flag.
What you may do is use the -json testing flag:
-json
Log verbose output and test results in JSON. This presents the
same information as the -v flag in a machine-readable format.
So you get all the output you would otherwise get with -v, but you have a separate JSON object for each line.
Having this test function:
func TestMy_Test(t *testing.T) {
fmt.Println("[custom] my message from fmt.Println")
}
Output of go test -v .
=== RUN TestMy_Test
[custom] my message from fmt.Println
--- PASS: TestMy_Test (0.00s)
PASS
ok github.com/icza/play 0.002s
Output of go test -json .
{"Time":"2022-05-10T09:26:26.712800797+02:00","Action":"run","Package":"github.com/icza/play","Test":"TestMy_Test"}
{"Time":"2022-05-10T09:26:26.71293072+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"=== RUN TestMy_Test\n"}
{"Time":"2022-05-10T09:26:26.712946548+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"[custom] my message from fmt.Println\n"}
{"Time":"2022-05-10T09:26:26.712954637+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"--- PASS: TestMy_Test (0.00s)\n"}
{"Time":"2022-05-10T09:26:26.712958774+02:00","Action":"pass","Package":"github.com/icza/play","Test":"TestMy_Test","Elapsed":0}
{"Time":"2022-05-10T09:26:26.712964812+02:00","Action":"output","Package":"github.com/icza/play","Output":"PASS\n"}
{"Time":"2022-05-10T09:26:26.713170439+02:00","Action":"output","Package":"github.com/icza/play","Output":"ok \tgithub.com/icza/play\t0.002s\n"}
{"Time":"2022-05-10T09:26:26.713573313+02:00","Action":"pass","Package":"github.com/icza/play","Elapsed":0.003}
You can write a simple app that processes and filters these JSON objects. Or you can filter the output as you could filter any other output.
Output of go test -json . |grep '\[custom\]'
{"Time":"2022-05-10T09:28:24.197077681+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"[custom] my message from fmt.Println\n"}
If you also want the "pass" or "fail" messages, run go test -json . |grep '"pass"\|"fail"\|\[custom\]'
{"Time":"2022-05-10T09:29:26.069181336+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"[custom] my message from fmt.Println\n"}
{"Time":"2022-05-10T09:29:26.069189228+02:00","Action":"pass","Package":"github.com/icza/play","Test":"TestMy_Test","Elapsed":0}
{"Time":"2022-05-10T09:29:26.069199239+02:00","Action":"pass","Package":"github.com/icza/play","Elapsed":0}
You can create you own Log function and use it for printing on screen
func Log(args ...interface{}) {
fmt.Fprintln(os.Stdout, args...)
}
You can also make it to print log based on condition passed through flag
var p = flag.Bool("p", false, "Enable Local Logging")
func MyLog(args ...interface{}) {
if *p {
fmt.Fprintln(os.Stdout, args...)
}
}
Example
package main
import (
"fmt"
"testing"
"os"
"flag"
)
var p = flag.Bool("p", false, "Enable Local Logging")
func Log(args ...interface{}) {
if *p {
fmt.Fprintln(os.Stdout, args...)
}
}
func IntMin(a, b int) int {
if a < b {
return a
}
return b
}
func TestIntMinBasic(t *testing.T) {
ans := IntMin(2, -2)
if ans != -2 {
t.Errorf("IntMin(2, -2) = %d; want -2", ans)
}
}
func TestIntMinTableDriven(t *testing.T) {
var tests = []struct {
a, b int
want int
}{
{0, 1, 0},
{1, 0, 0},
{2, -2, -2},
{0, -1, -1},
{-1, 0, -1},
}
Log("Print to Screen")
for _, tt := range tests {
testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
t.Run(testname, func(t *testing.T) {
ans := IntMin(tt.a, tt.b)
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
})
}
}
func BenchmarkIntMin(b *testing.B) {
for i := 0; i < b.N; i++ {
IntMin(1, 2)
}
}
And to pass the flag you can use -args
-args Pass the remainder of the command line (everything after -args) to the test binary, uninterpreted and unchanged. Because this flag consumes the remainder of the command line, the package list (if present) must appear before this flag.
Cmd Example:
go test -args -p
Although this doesn't print during the test, it prints straight after, which is better than not printing at all.
Define an os.File in your test file to write messages to:
var messagesFile *os.File
func messages() *os.File {
if messagesFile == nil {
messagesFile, _ = os.Create("tests.out")
}
return messagesFile
}
os.Create creates a new file if one doesn't exist, otherwise truncates the existing file.
Within your tests, write messages to that file:
messages().WriteString("my message")
Run your tests using go test, then cat the file. In my case, I use make:
test:
go test .
#cat tests.out
Output looks like:
ok <path to tests>
my message

Having some issues recursively printing a complete list of a tree-like structure

I'm having trouble creating a structure for a CLI tool that will have n commands. Each command can have n subcommands, and each subcommand can have n more subcommands.
My issue is that, in Go, I'm struggling to figure out a way to create a recursive function to output the name of each command, along with each n subcommands + every n subcommands for that subcommand, in a complete list.
For example, I'm looking to get the following output:
1. command1
2. command2
3. command3
4. command3 subcommand1
5. command3 subcommand1 subcommand1
6. command3 subcommand2
Here is my code:
package main
import (
"fmt"
)
type command struct {
name string
parent *command
subcommands []*command
}
func getLastCommand(c command) command {
for _, s := range c.subcommands {
if len(s.subcommands) == 0 {
return *s
}
return getLastCommand(*s)
}
return c
}
func main() {
cmdBase1 := command{
name: "base1",
}
cmdBase2 := command{
name: "base2",
}
var (
cmdBase3,
cmdBase3Sub1,
cmdBase3Sub1Sub1,
cmdBase3Sub2 command
)
cmdBase3 = command{
name: "base3",
subcommands: []*command{&cmdBase3Sub1, &cmdBase3Sub2},
}
cmdBase3Sub1 = command{
name: "base3:sub1",
parent: &cmdBase3,
subcommands: []*command{&cmdBase3Sub1Sub1},
}
cmdBase3Sub1Sub1 = command{
name: "base3:sub1:sub1",
parent: &cmdBase3Sub1,
}
cmdBase3Sub2 = command{
name: "base3:sub2",
parent: &cmdBase3,
}
// root commands
commands := []command{
cmdBase1,
cmdBase2,
cmdBase3,
}
for _, c := range commands {
last := getLastCommand(c)
fmt.Println(last.name)
}
}
https://play.golang.org/p/HZPRlSghfAY
Here is the current output:
base1
base2
base3:sub1:sub1
My desired output is with the above code is:
base1
base2
base3
base3:sub1
base3:sub1:sub1
base3:sub2
What do I need to change in my code so I can get my above desired output? Is there an algorithm or data structure that I could follow to solve this? I've tried depth-first and binary searching, but I can't seem to mold it to my structure.
A simple and elegant solution would be to "arm" command with a print() method. This could print its name, and range over its subcommands, calling their print() (which do the same):
func (c *command) print() {
fmt.Println(c.name)
for _, sc := range c.subcommands {
sc.print()
}
}
Then printing the commands in main() is just calling their print() method (getLastCommand() is not even needed / used):
for _, c := range commands {
c.print()
}
This will result in your desired output (try it on the Go Playground):
base1
base2
base3
base3:sub1
base3:sub1:sub1
base3:sub2
Note that of course print() doesn't have to be a method, it may be a regular function too, in which case it could look like this:
func print(c *command) {
fmt.Println(c.name)
for _, sc := range c.subcommands {
print(sc)
}
}
And the loop in main():
for _, c := range commands {
print(&c)
}
Result is the same, try this one on the Go Playground.
I would also suggest to be consistent. If you decide to use pointer to command, do it everywhere (e.g. the commands slice in your main() stores non-pointers, that's why its element's address had to be taken to pass to print()).

GO: Run cli command with wrong args

I use cobra to create CLI command tool.
everything is looking OK except the error handling
what I want that if command was sent by mistake (wrong args or wrong input) return std.err instead of std.out
to simplify the secnario I've created this which demonstrate my use-case
package main
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
RootCmd = &cobra.Command{
Use: "myApp",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("ROOT verbose = %d, args = %v\n", args)
},
}
provideCmd = &cobra.Command{
Use: "provide",
Run: nil,
}
appCmd = &cobra.Command{
Use: "apps",
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
if name != "myapp" {
err := errors.New("app name doesnt exist")
return err
}
return nil
},
SilenceUsage: true,
}
)
func init() {
// Add the application command to app command
provideCmd.AddCommand(appCmd)
// add provide command to root command
RootCmd.AddCommand(provideCmd)
}
func main() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
Now if I compile the binary and run exec.Command against the binary everything is working as expected. but if I want to test the error scenario like mycli provide apps apps1
I want to see that returned in std.err and not at std.out
When I execute mycli provide apps myapp everything should be OK
but if I run mycli provide apps myapp2 I want to get std.err and not std.out , which is not the case here ...what am I missing here ?
https://play.golang.org/p/B00z4eZ7Sj-
Your sample already prints the error both to stdout and stderr.
By default the cobra package prints any errors it encounters to stderr, unless you specifically change that.
So running
./main provide apps something 2> ./stderr.txt creates a text file with the following content (this is what cobra writes to stderr without your intervention):
Error: app name doesnt exist
And running ./main provide apps something > ./stdout.txt - creates a text file with the following content (you printed that yourself with fmt.Println(err), the second line from the bottom in your code):
app name doesnt exist
Which means default behaviour prints errors both to stdout and stderr.
As Devin has advised you, changing the last line to os.Stderr.WriteString(err) or
fmt.Fprintln(os.Stderr, err) (the one I would use) will make your project to print everything to stderr only, which means printing errors twice:
Error: app name doesnt exist
app name doesnt exist
It might be useful to know that cobra allows you some control of error printing behaviour. For example, you can tell a cobra command which stream to print to:
command.SetOutput(os.Stdout) // Defaults to os.Stderr
you could also prevent printing of errors:
command.SilenceErrors = true
or prevent printing of usage text:
command.SilenceUsage = true

golang flag stops parsing after the first non-option

i am building a little cli tool that boots my app in development or production.
the way i want it to work is like this:
app run --dev or app run --prod
Atm it doest parses the flags after my command but only before my command. So this works
app --dev run or app --prod run
Any idee how to fix it this so i can use it after my command? here is my code
func main() {
//flag.Usage := usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
Usage()
os.Exit(0)
}
if *dev {
os.Setenv("ENV", "development")
}
if *prod {
os.Setenv("ENV", "production")
}
switch {
// Run
case args[0] == "run" && len(args) == 1:
os.Setenv("port", *port)
log.Printf("Booting in %s", os.Getenv("ENV"))
Run()
// Help
case args[0] == "help" && len(args) == 1:
Usage()
}
}
Traditionally, the UNIX option parser getopt() stops parsing after the first non-option. The glibc altered this behavior to support options in arbitrary positions, a controversial decision. The flag package implements the traditional behavior.
One thing you could do is permuting the arguments array before parsing the flags. That's what the glibc does:
func permutateArgs(args []string) int {
args = args[1:]
optind := 0
for i := range args {
if args[i][0] == '-' {
tmp := args[i]
args[i] = args[optind]
args[optind] = tmp
optind++
}
}
return optind + 1
}
This code permutates args such that options are in front, leaving the program name untouched. permutateArgs returns the index of the first non-option after permutation. Use this code like this:
optind := permutateArgs(os.Args)
flags.Parse()
// process non-options
for i := range os.Args[optind:] {
// ...
}
This is simply the way the flag package deals with arguments. Argument handling is always somewhat convention driven, and the flag package definitely does not follow the gnu-style that many are accustomed to.
One way is to sort the options before the commands:
// example argument sorting using sort.Stable
type ArgSlice []string
func (p ArgSlice) Len() int { return len(p) }
func (p ArgSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p ArgSlice) Less(i, j int) bool {
if len(p[i]) == 0 {
return false
}
if len(p[j]) == 0 {
return true
}
return p[i][0] == '-' && p[j][0] != '-'
}
func main() {
args := []string{"cmd", "-a", "arg", "-b", "-c"}
sort.Stable(ArgSlice(args))
// if sorting os.Args, make sure to omit the first argument
// sort.Stable(ArgSlice(os.Args[1:]))
fmt.Println(args)
}
Many packages use separate FlagSets for subcommands, which provides nice separation, but this would not work when there are possibly different flags between subcommands. The only solution there is to duplicate the flags across each level.
However, the best answer is still to follow the conventions used by the flag package, and not try to fight it.

Resources