spf13 / cobra

A Commander for modern Go CLI interactions
https://cobra.dev
Apache License 2.0
37.79k stars 2.83k forks source link

Cobra Command Fails to remove flag when runs in a Interactive mode #1419

Open debankur1 opened 3 years ago

debankur1 commented 3 years ago

When cobra command is executed via API e.g. rootCmd.Execute() in an interactive mode, it keeps the flags that were persisted from the previous command here are the code snippet and test results.

package main

import ( "bufio" "fmt" "github.com/spf13/cobra" "os" "strings" )

func main() { for { reader := bufio.NewReader(os.Stdin) input, , := reader.ReadLine() args := strings.Split(string(input), " ") rootCmd.SetArgs(args) rootCmd.Execute() fmt.Println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")

}

}

var rootCmd = &cobra.Command{ Use: "cli", Short: "test app to test iShell like usage in cobra",

} var testCmd = &cobra.Command{ Use: "tcmd", Short: "test cmd for cobra command", Run: func(cmd *cobra.Command, args []string) { fmt.Println("Hello World") }, } func init() { rootCmd.AddCommand(testCmd) }

tcmd Hello World &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& tcmd -h test cmd for cobra command

Usage: cli tcmd [flags]

Flags: -h, --help help for tcmd &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& tcmd test cmd for cobra command

Usage: cli tcmd [flags]

Flags: -h, --help help for tcmd &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

As it can be clearly seen that after running the command with a flag -h or --help next time it uses the same flag , instead running the entire command.

Same thing I have testes with iShell

func main(){ shell := ishell.New() shell.Println("Sample Interactive Shell") shell.AddCmd(&ishell.Cmd{ Name: "test", Help: "Test Command Help", Func: func(c *ishell.Context) { rootCmd.SetArgs(c.RawArgs) rootCmd.Execute() }, }) shell.Run() } Have also tried the cmd.RestFlags() it didn't work. Any help is really appreciated.

marckhouzam commented 3 years ago

You have to use the flag.Changed field to reset the flag. Here is an example: https://github.com/spf13/cobra/blob/4590150168e93f4b017c6e33469e26590ba839df/completions_test.go#L185

I hope this helps.

debankur1 commented 3 years ago

You have to use the flag.Changed field to reset the flag. Here is an example:

https://github.com/spf13/cobra/blob/4590150168e93f4b017c6e33469e26590ba839df/completions_test.go#L185

I hope this helps.

thanks for the quick response, I believe when we execute rootCmd.Execute() then cobra should do it automatically because if I need to set nonPersistentFlag.Changed = false, then I have to do for all the commands and their subcommands

debankur1 commented 3 years ago

@marckhouzam I tried

var testCmd = &cobra.Command{ Use: "tcmd", Short: "test cmd for cobra command", Run: func(cmd cobra.Command, args []string) { fmt.Println("Hello World") }, PostRun: func(cmd cobra.Command, args []string) { helpFlag := cmd.Flags().Lookup("help") helpFlag.Changed = false }, }

marckhouzam commented 3 years ago

I think we need to take a step back and look at why you would want to do this @debankur1.

The uses of Cobra that I've seen do a single rootCmd.Execute and then exit, so at each time all the flags start fresh.

What is your scenario that would require you to reset the flags manually?

debankur1 commented 3 years ago

@marckhouzam The scenario is simple, let us consider user want to use Interactive Shell (https://github.com/abiosoft/ishell)

func main(){ shell := ishell.New() shell.Println("Sample Interactive Shell") shell.AddCmd(&ishell.Cmd{ Name: "test", Help: "Test Command Help", Func: func(c *ishell.Context) { rootCmd.SetArgs(c.RawArgs) rootCmd.Execute() }, }) shell.Run() }

and user wants to run the cobra command from the interactive shell i.e. rootCmd.Execute() , but the problem is rootCmd.Execute() holds a reference to the memory once it is executed that is not correct. This is a pretty valid case where users may like to use the power of Cobra in an interactive way. Maybe post rootCmd.Execute() it should initialize once again so that all the flags are reset or it should behave as if a user is running a new command.

debankur1 commented 3 years ago

@marckhouzam I have found a workaround to reset the flag (in my case it is help) cmd.Flags().Lookup("help").Value.Set("false"), but the bug remains still valid because flag reset must happen automatically in

func (c Command) ExecuteC() (cmd Command, err error){ }

github-actions[bot] commented 3 years ago

This issue is being marked as stale due to a long period of inactivity

brunomiranda-hotmart commented 3 years ago

@marckhouzam I stumbled on the same problem, in my case is in testing. I created a small example to show the problem.

main.go:

package main

import (
    "github.com/spf13/cobra"
)

var (
    cmd            *cobra.Command
    shouldContinue bool
)

func init() {
    cmd = &cobra.Command{
        Use: "debug",
        Run: func(cmd *cobra.Command, args []string) {
            cmd.Print("Step 1")
            if shouldContinue {
                cmd.Print("Step 2")
                return
            }
        },
    }
    cmd.Flags().BoolVar(&shouldContinue, "continue", false, "test")
}

func main() {
    cmd.Execute()
}

main_test.go:

package main

import (
    "strings"
    "testing"

    "github.com/g14a/metana/pkg"
    "github.com/stretchr/testify/assert"
)

func TestFull(t *testing.T) {
    test := []struct {
        args   []string
        output string
    }{
        {
            args:   []string{"--continue"},
            output: strings.Join([]string{"Step 1", "Step 2"}, ""),
        },
        {
            args:   []string{},
            output: strings.Join([]string{"Step 1"}, ""),
        },
    }

    for i, tt := range test {
        _, out, _ := pkg.ExecuteCommandC(cmd, tt.args...)
        t.Log(i, out, tt.args)

        assert.NotEmpty(t, out)
        assert.Equal(t, tt.output, out)
    }
}

PS.: I'm aware that changing the order in this test case would fix the problem, however in complex commands that might no be possible.

HaRo87 commented 1 year ago

I'am facing the same issue in my tests. 😢

brombach commented 1 year ago

I've run into the same thing in my tests. I believe the issue is caused by the way Cobra uses global variables. I've tried rootCmd.ResetCommands() and rootCmd.ResetFlags(), but the issue is that flags are persisted in each subcommand, so each subcommand must be reset between. I found a similar issue in Github's CLI package:

https://github.com/cli/cli/issues/759

which has the workaround:

https://github.com/cli/cli/blob/c0c28622bd62b273b32838dfdfa7d5ffc739eeeb/command/pr_test.go#L55-L67

The alternative is to restructure each command into a factory and don't use the magic init() and global variables, as the associated PR does.

https://github.com/cli/cli/pull/1500/files#diff-f043ea6e74e4ab1e778461b05a9f79245ba31ca1ce58def813d0e919ed2f35b4R40-R82