diff --git a/active_help.go b/active_help.go index b3e2dadf..4a24814e 100644 --- a/active_help.go +++ b/active_help.go @@ -33,8 +33,15 @@ const ( // to the user. // The array parameter should be the array that will contain the completions. // This function can be called multiple times before and/or after completions are added to -// the array. Each time this function is called with the same array, the new +// the array. Each time this function is called with the same array, the new // ActiveHelp line will be shown below the previous ones when completion is triggered. +// +// Parameters: +// - compArray: The array that will contain the completions. +// - activeHelpStr: The string to be added as ActiveHelp. +// +// Returns: +// - The updated array with the new ActiveHelp line appended. func AppendActiveHelp(compArray []Completion, activeHelpStr string) []Completion { return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr)) } @@ -44,6 +51,12 @@ func AppendActiveHelp(compArray []Completion, activeHelpStr string) []Completion // case, with all non-ASCII-alphanumeric characters replaced by `_`. // It will always return "0" if the global environment variable COBRA_ACTIVE_HELP // is set to "0". +// +// Parameters: +// - cmd: The root command whose ActiveHelp configuration is being retrieved. +// +// Returns: +// - A string representing the value of the ActiveHelp configuration. func GetActiveHelpConfig(cmd *Command) string { activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar) if activeHelpCfg != activeHelpGlobalDisable { @@ -52,9 +65,9 @@ func GetActiveHelpConfig(cmd *Command) string { return activeHelpCfg } -// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment -// variable. It has the format _ACTIVE_HELP where is the name of the -// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`. +// ActiveHelpEnvVar returns the name of the program-specific ActiveHelp environment variable. +// It follows the format _ACTIVE_HELP where is the upper-case version of the root command's name, +// with all non-ASCII-alphanumeric characters replaced by '_'. func activeHelpEnvVar(name string) string { return configEnvVar(name, activeHelpEnvVarSuffix) } diff --git a/active_help_test.go b/active_help_test.go index 2d624794..d2fe55c7 100644 --- a/active_help_test.go +++ b/active_help_test.go @@ -26,6 +26,7 @@ const ( activeHelpMessage2 = "This is the rest of the activeHelp message" ) +// TestActiveHelpAlone tests that the active help function can be added to both root and child commands. func TestActiveHelpAlone(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -81,6 +82,7 @@ func TestActiveHelpAlone(t *testing.T) { } } +// TestActiveHelpWithComps tests the functionality of appending active help to command completions. func TestActiveHelpWithComps(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -166,6 +168,7 @@ func TestActiveHelpWithComps(t *testing.T) { } } +// TestMultiActiveHelp tests the functionality of adding multiple activeHelp messages to a command. func TestMultiActiveHelp(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -228,6 +231,7 @@ func TestMultiActiveHelp(t *testing.T) { } } +// TestActiveHelpForFlag tests the functionality of adding multiple activeHelp messages to a flag completion function. func TestActiveHelpForFlag(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -263,6 +267,7 @@ func TestActiveHelpForFlag(t *testing.T) { } } +// TestConfigActiveHelp tests the functionality of setting and retrieving active help configurations for a command. func TestConfigActiveHelp(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -316,6 +321,7 @@ func TestConfigActiveHelp(t *testing.T) { } } +// TestDisableActiveHelp tests the disabling of active help functionality. func TestDisableActiveHelp(t *testing.T) { rootCmd := &Command{ Use: "root", diff --git a/args.go b/args.go index ed1e70ce..07fa15b0 100644 --- a/args.go +++ b/args.go @@ -21,10 +21,19 @@ import ( type PositionalArgs func(cmd *Command, args []string) error -// legacyArgs validation has the following behaviour: -// - root commands with no subcommands can take arbitrary arguments -// - root commands with subcommands will do subcommand validity checking -// - subcommands will always accept arbitrary arguments +// legacyArgs validates the arguments for a given command based on whether it has subcommands or not. +// +// Behavior: +// - Root commands without subcommands can take arbitrary arguments. +// - Root commands with subcommands perform subcommand-specific validity checks. +// - Subcommands always accept arbitrary arguments. +// +// Parameters: +// - cmd: A pointer to the Command struct representing the command being checked. +// - args: A slice of strings representing the arguments passed to the command. +// +// Returns: +// - error: If the arguments are invalid for the given command, an error is returned. Otherwise, nil. func legacyArgs(cmd *Command, args []string) error { // no subcommand, always take args if !cmd.HasSubCommands() { @@ -38,7 +47,11 @@ func legacyArgs(cmd *Command, args []string) error { return nil } -// NoArgs returns an error if any args are included. +// NoArgs returns an error if any arguments are provided. +// It checks whether the given slice of arguments `args` is empty. +// If `args` contains elements, it indicates that unexpected arguments were provided, +// and thus returns a formatted error indicating the unknown command and its path. +// If no arguments are present, it returns nil, indicating successful validation. func NoArgs(cmd *Command, args []string) error { if len(args) > 0 { return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()) @@ -46,8 +59,9 @@ func NoArgs(cmd *Command, args []string) error { return nil } -// OnlyValidArgs returns an error if there are any positional args that are not in -// the `ValidArgs` field of `Command` +// OnlyValidArgs checks if the provided arguments are valid based on the `ValidArgs` field of the given command. +// It returns an error if any argument is not a valid option. If no validation is required, it returns nil. +// The ValidArgs field can contain descriptions for each valid argument, which should be ignored during validation. func OnlyValidArgs(cmd *Command, args []string) error { if len(cmd.ValidArgs) > 0 { // Remove any description that may be included in ValidArgs. @@ -65,12 +79,13 @@ func OnlyValidArgs(cmd *Command, args []string) error { return nil } -// ArbitraryArgs never returns an error. +// ArbitraryArgs executes a command with arbitrary arguments and does not return an error. func ArbitraryArgs(cmd *Command, args []string) error { return nil } -// MinimumNArgs returns an error if there is not at least N args. +// MinimumNArgs returns a PositionalArgs function that checks if there are at least N arguments provided. +// If fewer than N arguments are given, it returns an error with a message indicating the required number of arguments and the actual count received. func MinimumNArgs(n int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) < n { @@ -80,7 +95,8 @@ func MinimumNArgs(n int) PositionalArgs { } } -// MaximumNArgs returns an error if there are more than N args. +// MaximumNArgs returns a PositionalArgs function that ensures no more than N arguments are provided. +// If the number of arguments exceeds N, it returns an error indicating the maximum allowed arguments and the actual count received. func MaximumNArgs(n int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) > n { @@ -90,7 +106,9 @@ func MaximumNArgs(n int) PositionalArgs { } } -// ExactArgs returns an error if there are not exactly n args. +// ExactArgs returns a PositionalArgs function that checks if the command has exactly `n` arguments. +// If the number of arguments is not exactly `n`, it returns an error with a descriptive message. +// Otherwise, it returns nil indicating no error. func ExactArgs(n int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) != n { @@ -101,6 +119,9 @@ func ExactArgs(n int) PositionalArgs { } // RangeArgs returns an error if the number of args is not within the expected range. +// It takes two integers, min and max, representing the minimum and maximum number of arguments allowed. +// The function returns a PositionalArgs closure that can be used as a validator for command arguments. +// If the number of arguments does not fall within the specified range (inclusive), it returns an error with details about the expected and received argument count. func RangeArgs(min int, max int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) < min || len(args) > max { @@ -110,7 +131,10 @@ func RangeArgs(min int, max int) PositionalArgs { } } -// MatchAll allows combining several PositionalArgs to work in concert. +// MatchAll combines multiple PositionalArgs to work as a single PositionalArg. +// It applies each provided PositionalArg in sequence to the command and arguments. +// If any of the PositionalArgs return an error, that error is returned immediately. +// If all PositionalArgs are successfully applied, nil is returned. func MatchAll(pargs ...PositionalArgs) PositionalArgs { return func(cmd *Command, args []string) error { for _, parg := range pargs { @@ -123,9 +147,9 @@ func MatchAll(pargs ...PositionalArgs) PositionalArgs { } // ExactValidArgs returns an error if there are not exactly N positional args OR -// there are any positional args that are not in the `ValidArgs` field of `Command` +// there are any positional args that are not in the `ValidArgs` field of `Command`. // -// Deprecated: use MatchAll(ExactArgs(n), OnlyValidArgs) instead +// Deprecated: use MatchAll(ExactArgs(n), OnlyValidArgs) instead. func ExactValidArgs(n int) PositionalArgs { return MatchAll(ExactArgs(n), OnlyValidArgs) } diff --git a/args_test.go b/args_test.go index 90d174cc..fc46e97f 100644 --- a/args_test.go +++ b/args_test.go @@ -20,6 +20,10 @@ import ( "testing" ) +// getCommand returns a Command object configured based on the provided PositionalArgs and a boolean indicating whether to include valid arguments. +// If withValid is true, the returned command will have ValidArgs set to ["one", "two", "three"]. +// The Run field of the Command is initialized to emptyRun, which does nothing when called. +// Args determines what arguments are valid for this command. func getCommand(args PositionalArgs, withValid bool) *Command { c := &Command{ Use: "c", @@ -32,6 +36,12 @@ func getCommand(args PositionalArgs, withValid bool) *Command { return c } +// expectSuccess checks if the provided output and error are as expected. If output is not empty, it logs an error. If there is an error, it fails the test with a fatal error. +// Parameters: +// - output: The string output to check. +// - err: The error to check. +// - t: The testing.T instance used for logging errors and failing tests. +// Returns: no value. func expectSuccess(output string, err error, t *testing.T) { if output != "" { t.Errorf("Unexpected output: %v", output) @@ -41,6 +51,9 @@ func expectSuccess(output string, err error, t *testing.T) { } } +// validOnlyWithInvalidArgs checks if the given error is not nil and has the expected error message. +// It takes an error and a testing.T instance as parameters. If the error is nil, it calls t.Fatal with a message indicating that an error was expected. +// It then compares the actual error message with the expected one and calls t.Errorf if they do not match. func validOnlyWithInvalidArgs(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") @@ -52,6 +65,13 @@ func validOnlyWithInvalidArgs(err error, t *testing.T) { } } +// noArgsWithArgs checks if an error is returned when calling a function with no arguments and the specified argument. +// It asserts that the error message matches the expected value for the given command. +// If no error is returned, it calls t.Fatal with an appropriate message. +// Parameters: +// - err: The error to check. +// - t: A testing.T instance used for assertions. +// - arg: The argument passed to the function that triggered the error. func noArgsWithArgs(err error, t *testing.T, arg string) { if err == nil { t.Fatal("Expected an error") @@ -63,6 +83,14 @@ func noArgsWithArgs(err error, t *testing.T, arg string) { } } +// minimumNArgsWithLessArgs checks if the provided error is due to a function requiring at least two arguments but receiving fewer. +// +// Parameters: +// - err: The error returned by the function. +// - t: A testing.T instance used for assertions. +// +// This function asserts that the error message matches the expected "requires at least 2 arg(s), only received 1". +// If the assertion fails, it calls t.Fatalf with a formatted error message. func minimumNArgsWithLessArgs(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") @@ -74,6 +102,14 @@ func minimumNArgsWithLessArgs(err error, t *testing.T) { } } +// maximumNArgsWithMoreArgs checks if the error is related to accepting more arguments than allowed. +// It expects an error and a testing.T instance. If no error is received, it fails the test with a fatal error. +// It then compares the expected error message with the actual one; if they don't match, it fails the test with a fatal error. +// Parameters: +// - err: The error to be checked. +// - t: The testing.T instance used for assertions and logging. +// Returns: +// - No return value. func maximumNArgsWithMoreArgs(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") @@ -85,6 +121,17 @@ func maximumNArgsWithMoreArgs(err error, t *testing.T) { } } +// exactArgsWithInvalidCount checks if the given error indicates that a function expects exactly two arguments but received three. +// +// Parameters: +// - err: The error to check. +// - t: The testing.T instance used for assertions. +// +// Returns: +// This function does not return any value. +// +// Errors: +// - If err is nil, this function calls t.Fatal with an "Expected an error" message. func exactArgsWithInvalidCount(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") @@ -96,6 +143,9 @@ func exactArgsWithInvalidCount(err error, t *testing.T) { } } +// rangeArgsWithInvalidCount checks if the provided error is related to invalid argument count. +// It expects an error indicating that a function accepts between 2 and 4 arguments, +// but received only one. If the error does not match this expectation or if there is no error, it will call t.Fatal. func rangeArgsWithInvalidCount(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") @@ -109,30 +159,40 @@ func rangeArgsWithInvalidCount(err error, t *testing.T) { // NoArgs +// TestNoArgs tests the behavior of the command when no arguments are provided. It verifies that the command executes successfully without any errors. func TestNoArgs(t *testing.T) { c := getCommand(NoArgs, false) output, err := executeCommand(c) expectSuccess(output, err, t) } +// TestNoArgs_WithArgs tests the command execution when it expects no arguments but receives one. +// It creates a command with NoArgs flag and executes it with an argument "one". +// The function checks if the error returned is as expected for a no-arguments command receiving an argument. func TestNoArgs_WithArgs(t *testing.T) { c := getCommand(NoArgs, false) _, err := executeCommand(c, "one") noArgsWithArgs(err, t, "one") } +// TestNoArgs_WithValid_WithArgs tests the behavior of a command with no arguments when provided with valid and unexpected arguments. +// It checks if the command execution returns an error for an unexpected argument. func TestNoArgs_WithValid_WithArgs(t *testing.T) { c := getCommand(NoArgs, true) _, err := executeCommand(c, "one") noArgsWithArgs(err, t, "one") } +// TestNoArgs_WithValid_WithInvalidArgs tests the NoArgs command with valid and invalid arguments. func TestNoArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(NoArgs, true) _, err := executeCommand(c, "a") noArgsWithArgs(err, t, "a") } +// TestNoArgs_WithValidOnly_WithInvalidArgs tests the execution of a command with both valid and invalid arguments when OnlyValidArgs and NoArgs matchers are used. +// It asserts that executing the command with an argument results in an error as expected. +// The test uses the getCommand function to create a command with specific matchers and then checks if the error returned by executeCommand is valid using validOnlyWithInvalidArgs. func TestNoArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, NoArgs), true) _, err := executeCommand(c, "a") @@ -141,12 +201,15 @@ func TestNoArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { // OnlyValidArgs +// TestOnlyValidArgs tests the command with valid arguments. func TestOnlyValidArgs(t *testing.T) { c := getCommand(OnlyValidArgs, true) output, err := executeCommand(c, "one", "two") expectSuccess(output, err, t) } +// TestOnlyValidArgs_WithInvalidArgs tests the behavior of the command when invalid arguments are provided. +// It verifies that an error is returned when executing the command with one invalid argument. func TestOnlyValidArgs_WithInvalidArgs(t *testing.T) { c := getCommand(OnlyValidArgs, true) _, err := executeCommand(c, "a") @@ -155,24 +218,34 @@ func TestOnlyValidArgs_WithInvalidArgs(t *testing.T) { // ArbitraryArgs +// TestArbitraryArgs tests the execution of a command with arbitrary arguments. +// It creates a command with the ArbitraryArgs flag set and executes it with two arguments "a" and "b". +// It then checks if the command executed successfully by expecting no errors and a successful output. func TestArbitraryArgs(t *testing.T) { c := getCommand(ArbitraryArgs, false) output, err := executeCommand(c, "a", "b") expectSuccess(output, err, t) } +// TestArbitraryArgs_WithValid tests the execution of a command with arbitrary arguments when they are valid. +// It verifies that the command executes successfully and returns expected output without errors. func TestArbitraryArgs_WithValid(t *testing.T) { c := getCommand(ArbitraryArgs, true) output, err := executeCommand(c, "one", "two") expectSuccess(output, err, t) } +// TestArbitraryArgs_WithValid_WithInvalidArgs tests the execution of a command with arbitrary arguments, including both valid and invalid cases. +// It verifies that the command executes successfully with valid arguments and handles errors for invalid arguments correctly. +// The function takes a testing.T instance to run assertions and checks the output and error of the executed command. func TestArbitraryArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(ArbitraryArgs, true) output, err := executeCommand(c, "a") expectSuccess(output, err, t) } +// TestArbitraryArgs_WithValidOnly_WithInvalidArgs tests the command execution when only valid arguments are provided along with arbitrary arguments. +// It expects an error when invalid arguments are present during execution. func TestArbitraryArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, ArbitraryArgs), true) _, err := executeCommand(c, "a") @@ -181,48 +254,61 @@ func TestArbitraryArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { // MinimumNArgs +// TestMinimumNArgs tests the MinimumNArgs command with a minimum of 2 arguments. func TestMinimumNArgs(t *testing.T) { c := getCommand(MinimumNArgs(2), false) output, err := executeCommand(c, "a", "b", "c") expectSuccess(output, err, t) } +// TestMinimumNArgs_WithValid tests the execution of a command with at least two arguments. +// It asserts that the command executes successfully when provided with valid arguments. func TestMinimumNArgs_WithValid(t *testing.T) { c := getCommand(MinimumNArgs(2), true) output, err := executeCommand(c, "one", "three") expectSuccess(output, err, t) } +// TestMinimumNArgs_WithValid checks that the MinimumNArgs command works correctly with valid arguments. +// It tests both a successful execution and an error case when invalid arguments are provided. func TestMinimumNArgs_WithValid__WithInvalidArgs(t *testing.T) { c := getCommand(MinimumNArgs(2), true) output, err := executeCommand(c, "a", "b") expectSuccess(output, err, t) } +// TestMinimumNArgs_WithValidOnly_WithInvalidArgs tests the behavior of the command when it expects a minimum number of arguments (2) and only valid args, but receives invalid ones. +// It validates that the command execution fails with an error indicating invalid arguments. func TestMinimumNArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, MinimumNArgs(2)), true) _, err := executeCommand(c, "a", "b") validOnlyWithInvalidArgs(err, t) } +// TestMinimumNArgs_WithLessArgs tests the MinimumNArgs function when provided with fewer arguments than expected. func TestMinimumNArgs_WithLessArgs(t *testing.T) { c := getCommand(MinimumNArgs(2), false) _, err := executeCommand(c, "a") minimumNArgsWithLessArgs(err, t) } +// TestMinimumNArgs_WithLessArgs_WithValid tests the MinimumNArgs function with fewer arguments than expected. func TestMinimumNArgs_WithLessArgs_WithValid(t *testing.T) { c := getCommand(MinimumNArgs(2), true) _, err := executeCommand(c, "one") minimumNArgsWithLessArgs(err, t) } +// TestMinimumNArgs_WithLessArgs_WithValid_WithInvalidArgs tests the MinimumNArgs validator with fewer arguments than expected. +// It verifies both valid and invalid cases to ensure proper error handling. func TestMinimumNArgs_WithLessArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(MinimumNArgs(2), true) _, err := executeCommand(c, "a") minimumNArgsWithLessArgs(err, t) } +// TestMinimumNArgs_WithLessArgs_WithValidOnly_WithInvalidArgs tests the behavior of a command when executed with less than the minimum required arguments and only valid args are allowed. +// It verifies that the command returns an error indicating invalid argument count when called with insufficient arguments. func TestMinimumNArgs_WithLessArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, MinimumNArgs(2)), true) _, err := executeCommand(c, "a") @@ -231,48 +317,76 @@ func TestMinimumNArgs_WithLessArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { // MaximumNArgs +// TestMaximumNArgs tests the execution of a command with a maximum number of arguments. +// It verifies that the command is executed successfully with up to 3 arguments and fails when more than 3 arguments are provided. func TestMaximumNArgs(t *testing.T) { c := getCommand(MaximumNArgs(3), false) output, err := executeCommand(c, "a", "b") expectSuccess(output, err, t) } +// TestMaximumNArgs_WithValid tests the functionality of the MaximumNArgs function with valid arguments. +// It verifies that the command is executed successfully when provided with two arguments. func TestMaximumNArgs_WithValid(t *testing.T) { c := getCommand(MaximumNArgs(2), true) output, err := executeCommand(c, "one", "three") expectSuccess(output, err, t) } +// TestMaximumNArgs_WithValid_WithInvalidArgs tests the behavior of the MaximumNArgs function with both valid and invalid arguments. +// It ensures that the function correctly handles different sets of input arguments. func TestMaximumNArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(MaximumNArgs(2), true) output, err := executeCommand(c, "a", "b") expectSuccess(output, err, t) } +// TestMaximumNArgs_WithValidOnly_WithInvalidArgs tests the behavior of a command with the MatchAll validator, OnlyValidArgs option, and MaximumNArgs set to 2 when provided with both valid and invalid arguments. It asserts that the command execution results in an error indicating the presence of invalid arguments. +// Parameters: +// - t: A pointer to a testing.T instance for running tests. +// Returns: +// None +// Errors: +// - An error if the command does not return an error when it should, or if the error message does not indicate the presence of invalid arguments. func TestMaximumNArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, MaximumNArgs(2)), true) _, err := executeCommand(c, "a", "b") validOnlyWithInvalidArgs(err, t) } +// TestMaximumNArgs_WithMoreArgs tests the behavior of the MaximumNArgs constraint when provided with more arguments than allowed. It checks if the resulting command, when executed, returns an error indicating too many arguments. + +// getCommand creates and returns a test command with the given argument constraint and allowStdin flag. +// executeCommand executes the given command with the specified arguments and returns the result and any error encountered. +// maximumNArgsWithMoreArgs asserts that the provided error indicates the use of more arguments than allowed for MaximumNArgs constraints. func TestMaximumNArgs_WithMoreArgs(t *testing.T) { c := getCommand(MaximumNArgs(2), false) _, err := executeCommand(c, "a", "b", "c") maximumNArgsWithMoreArgs(err, t) } +// TestMaximumNArgs_WithMoreArgs_WithValid tests the execution of a command with more arguments than allowed by MaximumNArgs. +// It verifies that an error is returned when executing the command with three arguments when only two are permitted. func TestMaximumNArgs_WithMoreArgs_WithValid(t *testing.T) { c := getCommand(MaximumNArgs(2), true) _, err := executeCommand(c, "one", "three", "two") maximumNArgsWithMoreArgs(err, t) } +// TestMaximumNArgs_WithMoreArgs_WithValid_WithInvalidArgs tests the behavior of the MaximumNArgs decorator when called with more arguments than expected. +// It checks both valid and invalid argument scenarios. +// It uses the getCommand function to create a command with the MaximumNArgs decorator applied. +// The executeCommand function is then used to run the command with "a", "b", and "c" as arguments. +// The maximumNArgsWithMoreArgs function asserts the expected error behavior based on the number of arguments. func TestMaximumNArgs_WithMoreArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(MaximumNArgs(2), true) _, err := executeCommand(c, "a", "b", "c") maximumNArgsWithMoreArgs(err, t) } +// TestMaximumNArgs_WithMoreArgs_WithValidOnly_WithInvalidArgs tests the behavior of a command with a maximum number of arguments and only valid arguments. +// It ensures that an error is returned when more than two arguments are provided. +// The test checks if the command correctly identifies invalid arguments when they are present. func TestMaximumNArgs_WithMoreArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, MaximumNArgs(2)), true) _, err := executeCommand(c, "a", "b", "c") @@ -281,48 +395,68 @@ func TestMaximumNArgs_WithMoreArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { // ExactArgs +// TestExactArgs tests the functionality of a command that requires exactly three arguments. +// It sets up a command with ExactArgs set to 3 and executes it with three arguments. +// The output and error are then checked for success using expectSuccess. func TestExactArgs(t *testing.T) { c := getCommand(ExactArgs(3), false) output, err := executeCommand(c, "a", "b", "c") expectSuccess(output, err, t) } +// TestExactArgs_WithValid tests the ExactArgs function with valid arguments. +// It verifies that the command is executed successfully when the correct number of arguments is provided. func TestExactArgs_WithValid(t *testing.T) { c := getCommand(ExactArgs(3), true) output, err := executeCommand(c, "three", "one", "two") expectSuccess(output, err, t) } +// TestExactArgs_WithValid_WithInvalidArgs tests the ExactArgs validator with valid and invalid arguments. +// It creates a command with ExactArgs set to 3, executes it with various argument sets, +// and checks if the execution behaves as expected for both valid and invalid cases. func TestExactArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(ExactArgs(3), true) output, err := executeCommand(c, "three", "a", "two") expectSuccess(output, err, t) } +// TestExactArgs_WithValidOnly_WithInvalidArgs tests the behavior of a command that expects exactly 3 arguments and allows only valid args. +// It executes the command with invalid arguments and checks if the error returned is as expected when using OnlyValidArgs. func TestExactArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, ExactArgs(3)), true) _, err := executeCommand(c, "three", "a", "two") validOnlyWithInvalidArgs(err, t) } +// TestExactArgs_WithInvalidCount tests the behavior of a command that expects exactly two arguments but receives an invalid count. +// It creates a command with ExactArgs(2) validator and executes it with three arguments, expecting an error due to the mismatched argument count. func TestExactArgs_WithInvalidCount(t *testing.T) { c := getCommand(ExactArgs(2), false) _, err := executeCommand(c, "a", "b", "c") exactArgsWithInvalidCount(err, t) } +// TestExactArgs_WithInvalidCount_WithValid tests the execution of a command with an exact number of arguments when provided with an invalid count. +// It expects an error related to the argument count being incorrect. +// The test ensures that the command handling correctly identifies and reports errors for too many arguments. func TestExactArgs_WithInvalidCount_WithValid(t *testing.T) { c := getCommand(ExactArgs(2), true) _, err := executeCommand(c, "three", "one", "two") exactArgsWithInvalidCount(err, t) } +// TestExactArgs_WithInvalidCount_WithValid_WithInvalidArgs tests the behavior of a command with exact arguments when executed with an invalid count and valid arguments. +// It checks if the command returns an error when provided with more than the expected number of arguments. func TestExactArgs_WithInvalidCount_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(ExactArgs(2), true) _, err := executeCommand(c, "three", "a", "two") exactArgsWithInvalidCount(err, t) } +// TestExactArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs tests the behavior of a command with exact argument count and valid-only filter when provided with invalid arguments. +// It expects an error due to the mismatch in the number of arguments passed. +// The test ensures that the OnlyValidArgs filter is applied correctly, allowing only valid arguments through. func TestExactArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, ExactArgs(2)), true) _, err := executeCommand(c, "three", "a", "two") @@ -331,48 +465,70 @@ func TestExactArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs(t *testing.T) // RangeArgs +// TestRangeArgs tests the functionality of RangeArgs with provided arguments. +// It creates a command using RangeArgs and executes it with given input arguments. +// Finally, it checks if the command execution was successful. func TestRangeArgs(t *testing.T) { c := getCommand(RangeArgs(2, 4), false) output, err := executeCommand(c, "a", "b", "c") expectSuccess(output, err, t) } +// TestRangeArgs_WithValid tests the RangeArgs function with valid arguments and ensures that the command execution is successful. It uses a test function to verify the output and error returned by the executeCommand function. The getCommand function is used to create a command based on the provided arguments and the debug flag. func TestRangeArgs_WithValid(t *testing.T) { c := getCommand(RangeArgs(2, 4), true) output, err := executeCommand(c, "three", "one", "two") expectSuccess(output, err, t) } +// TestRangeArgs_WithValid_WithInvalidArgs tests the RangeArgs function with valid and invalid arguments. +// It verifies that the command execution behaves as expected when provided with a range of integers (2 to 4). +// It checks both valid and invalid arguments and ensures successful execution for valid inputs. func TestRangeArgs_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(RangeArgs(2, 4), true) output, err := executeCommand(c, "three", "a", "two") expectSuccess(output, err, t) } +// TestRangeArgs_WithValidOnly_WithInvalidArgs tests the behavior of the command when it expects a range of arguments (2 to 4) and only valid arguments. +// It verifies that an error is returned when the provided arguments do not meet the specified criteria. func TestRangeArgs_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, RangeArgs(2, 4)), true) _, err := executeCommand(c, "three", "a", "two") validOnlyWithInvalidArgs(err, t) } +// TestRangeArgs_WithInvalidCount tests the behavior of the RangeArgs command when provided with an invalid number of arguments. +// It creates a command with an expected range and executes it with an incorrect argument count, expecting an error related to the number of arguments being out of range. +// The test uses a helper function `rangeArgsWithInvalidCount` to verify that the correct error is returned. func TestRangeArgs_WithInvalidCount(t *testing.T) { c := getCommand(RangeArgs(2, 4), false) _, err := executeCommand(c, "a") rangeArgsWithInvalidCount(err, t) } +// TestRangeArgs_WithInvalidCount_WithValid tests the RangeArgs function with an invalid count but valid arguments. +// It creates a command using the getCommand function and executes it with a single argument "two". +// The function then checks if the error returned by executeCommand matches the expected error for invalid count. func TestRangeArgs_WithInvalidCount_WithValid(t *testing.T) { c := getCommand(RangeArgs(2, 4), true) _, err := executeCommand(c, "two") rangeArgsWithInvalidCount(err, t) } +// TestRangeArgs_WithInvalidCount_WithValid_WithInvalidArgs tests the execution of a command with invalid count and valid arguments. +// It checks if the function handles errors correctly when provided with an invalid number of arguments. +// Parameters: +// - t: A pointer to a testing.T object used for test assertions. +// The function does not return any values but asserts on the behavior of the executeCommand function. func TestRangeArgs_WithInvalidCount_WithValid_WithInvalidArgs(t *testing.T) { c := getCommand(RangeArgs(2, 4), true) _, err := executeCommand(c, "a") rangeArgsWithInvalidCount(err, t) } +// TestRangeArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs tests the behavior of a command with range arguments when given an invalid count and valid only option, expecting errors for invalid arguments. +// It uses a test function to verify that executing the command with "a" as an argument results in an error due to the mismatch between expected and actual arguments. func TestRangeArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs(t *testing.T) { c := getCommand(MatchAll(OnlyValidArgs, RangeArgs(2, 4)), true) _, err := executeCommand(c, "a") @@ -381,6 +537,9 @@ func TestRangeArgs_WithInvalidCount_WithValidOnly_WithInvalidArgs(t *testing.T) // Takes(No)Args +// TestRootTakesNoArgs tests that calling the root command with illegal arguments returns an error. +// It creates a root command and a child command, adds the child to the root, and then attempts to execute +// the root command with invalid arguments. It expects an error indicating that the "illegal" command is unknown for "root". func TestRootTakesNoArgs(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -398,6 +557,7 @@ func TestRootTakesNoArgs(t *testing.T) { } } +// TestRootTakesArgs tests if the root command correctly handles arguments. func TestRootTakesArgs(t *testing.T) { rootCmd := &Command{Use: "root", Args: ArbitraryArgs, Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -409,6 +569,9 @@ func TestRootTakesArgs(t *testing.T) { } } +// TestChildTakesNoArgs tests that the command handles unexpected arguments correctly when a subcommand does not accept any arguments. +// +// It verifies that calling "child" with additional arguments results in an error. func TestChildTakesNoArgs(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Args: NoArgs, Run: emptyRun} @@ -426,6 +589,9 @@ func TestChildTakesNoArgs(t *testing.T) { } } +// TestChildTakesArgs tests that a child command with arguments can be executed successfully. +// It creates a root command and adds a child command with arbitrary argument requirements. +// Then, it executes the child command with legal arguments and verifies there is no error. func TestChildTakesArgs(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Args: ArbitraryArgs, Run: emptyRun} @@ -437,6 +603,8 @@ func TestChildTakesArgs(t *testing.T) { } } +// TestMatchAll tests the MatchAll function with various test cases to ensure it correctly validates command arguments. +// It checks that the number of arguments is exactly 3 and each argument is exactly 2 bytes long. func TestMatchAll(t *testing.T) { // Somewhat contrived example check that ensures there are exactly 3 // arguments, and each argument is exactly 2 bytes long. @@ -487,34 +655,40 @@ func TestMatchAll(t *testing.T) { // DEPRECATED +// TestExactValidArgs tests the behavior of a command when provided with exactly three valid arguments. +// It checks if the command executes successfully with the given arguments and validates the output. func TestExactValidArgs(t *testing.T) { c := getCommand(ExactValidArgs(3), true) output, err := executeCommand(c, "three", "one", "two") expectSuccess(output, err, t) } +// TestExactValidArgs_WithInvalidCount tests the ExactValidArgs function with an invalid argument count. +// It asserts that an error is returned when the number of arguments does not match the expected count. func TestExactValidArgs_WithInvalidCount(t *testing.T) { c := getCommand(ExactValidArgs(2), false) _, err := executeCommand(c, "three", "one", "two") exactArgsWithInvalidCount(err, t) } +// TestExactValidArgs_WithInvalidCount_WithInvalidArgs tests the behavior of the ExactValidArgs validator when provided with an invalid count and invalid arguments. +// It creates a command with the ExactValidArgs validator, executes it with three arguments (two valid and one invalid), and checks if the error returned is as expected for invalid argument counts. func TestExactValidArgs_WithInvalidCount_WithInvalidArgs(t *testing.T) { c := getCommand(ExactValidArgs(2), true) _, err := executeCommand(c, "three", "a", "two") exactArgsWithInvalidCount(err, t) } +// TestExactValidArgs_WithInvalidArgs tests the behavior of ExactValidArgs when invoked with invalid arguments. +// It uses a test command and checks if the execution results in an error as expected. func TestExactValidArgs_WithInvalidArgs(t *testing.T) { c := getCommand(ExactValidArgs(2), true) _, err := executeCommand(c, "three", "a") validOnlyWithInvalidArgs(err, t) } -// This test make sure we keep backwards-compatibility with respect -// to the legacyArgs() function. -// It makes sure the root command accepts arguments if it does not have -// sub-commands. +// TestLegacyArgsRootAcceptsArgs ensures that the root command accepts arguments if it does not have sub-commands. +// It verifies backwards-compatibility with respect to the legacyArgs() function. func TestLegacyArgsRootAcceptsArgs(t *testing.T) { rootCmd := &Command{Use: "root", Args: nil, Run: emptyRun} @@ -524,9 +698,7 @@ func TestLegacyArgsRootAcceptsArgs(t *testing.T) { } } -// This test make sure we keep backwards-compatibility with respect -// to the legacyArgs() function. -// It makes sure a sub-command accepts arguments and further sub-commands +// TestLegacyArgsSubcmdAcceptsArgs verifies that a sub-command accepts arguments and further sub-commands while maintaining backwards-compatibility with the legacyArgs() function. It ensures that executing commands like "root child somearg" does not result in errors. func TestLegacyArgsSubcmdAcceptsArgs(t *testing.T) { rootCmd := &Command{Use: "root", Args: nil, Run: emptyRun} childCmd := &Command{Use: "child", Args: nil, Run: emptyRun} diff --git a/bash_completions.go b/bash_completions.go index f4d198cb..511c39d1 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -401,6 +401,13 @@ __%[1]s_handle_word() ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name))) } +// writePostscript writes postscript code to the provided buffer for a given command name. +// +// Parameters: +// - buf: io.StringWriter where the postscript code will be written. +// - name: The name of the command for which the postscript code is being generated. +// +// This function handles the generation of completion script postscript code for a Bash or similar shell. It replaces colons in the command name with double underscores, initializes completion variables and functions, and sets up the completion behavior using the 'complete' command. func writePostscript(buf io.StringWriter, name string) { name = strings.ReplaceAll(name, ":", "__") WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name)) @@ -444,6 +451,16 @@ fi WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n") } +// writeCommands writes the command definitions to a buffer. +// +// It takes an io.StringWriter and a *Command as parameters. The function iterates through +// the commands associated with the given Command instance. If a command is not available and +// is not the help command, it skips that command. For each valid command, it writes its name to the buffer +// in the format 'commands+=()' and calls writeCmdAliases to write any aliases for the command. +// +// The function includes a newline character at the end of the output. +// +// It returns nothing but may log errors through WriteStringAndCheck. func writeCommands(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, " commands=()\n") for _, c := range cmd.Commands() { @@ -456,6 +473,16 @@ func writeCommands(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, "\n") } +// writeFlagHandler writes the flag completion handler for a given command and its annotations. +// +// Parameters: +// buf - an io.StringWriter where the completion handler will be written +// name - the name of the flag +// annotations - a map containing annotations for the flag, including types like BashCompFilenameExt, BashCompCustom, and BashCompSubdirsInDir +// cmd - the command associated with the flag +// +// Returns: +// This function does not return any value. It writes to the buffer provided. func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *Command) { for key, value := range annotations { switch key { @@ -494,6 +521,15 @@ func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][ const cbn = "\")\n" +// writeShortFlag writes a short flag to the provided buffer. +// +// Parameters: +// - buf: The io.StringWriter where the flag will be written. +// - flag: A pointer to the pflag.Flag representing the flag to write. +// - cmd: A pointer to the Command associated with the flag. +// +// It constructs a formatted string based on the flag's shorthand and annotations, then writes it to the buffer using WriteStringAndCheck. +// Additionally, it calls writeFlagHandler to handle any specific logic for the flag. func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { name := flag.Shorthand format := " " @@ -505,6 +541,17 @@ func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) } +// writeFlag writes a flag definition to the buffer in a specific format. +// +// Parameters: +// - buf: The io.StringWriter where the flag definition will be written. +// - flag: The pflag.Flag object representing the flag being written. +// - cmd: The Command object associated with the flag. +// +// Returns: +// None +// +// This function handles the formatting and writing of a single flag, including handling cases for flags without default values and two-word flags. It also calls writeFlagHandler to process any additional annotations or handlers related to the flag. func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { name := flag.Name format := " flags+=(\"--%s" @@ -520,6 +567,14 @@ func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) } +// writeLocalNonPersistentFlag writes the local non-persistent flags to the provided buffer. +// +// Parameters: +// - buf: The io.StringWriter where the flags will be written. +// - flag: A pointer to the pflag.Flag that contains the details of the flag. +// +// This function constructs a string representation of the flag and appends it to the buffer. If the flag has a shorthand, +// it also writes the shorthand version of the flag to the buffer. func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { name := flag.Name format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn @@ -532,7 +587,9 @@ func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { } } -// prepareCustomAnnotationsForFlags setup annotations for go completions for registered flags +// prepareCustomAnnotationsForFlags sets up custom annotations for go completions for registered flags. +// +// It takes a pointer to a Command and iterates over the flagCompletionFunctions map, adding custom annotations to each flag's Annotations field. The custom annotation is a Bash completion command that calls the __*_go_custom_completion function for the specified flag, ensuring that the root command name is correctly set for the prefix of the completion script. func prepareCustomAnnotationsForFlags(cmd *Command) { flagCompletionMutex.RLock() defer flagCompletionMutex.RUnlock() @@ -548,6 +605,9 @@ func prepareCustomAnnotationsForFlags(cmd *Command) { } } +// writeFlags writes the flags for a command to the provided buffer. +// It handles both local and inherited flags, excluding non-completable ones. +// If flag parsing is disabled, it sets the corresponding variable in the buffer. func writeFlags(buf io.StringWriter, cmd *Command) { prepareCustomAnnotationsForFlags(cmd) WriteStringAndCheck(buf, ` flags=() @@ -590,6 +650,12 @@ func writeFlags(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, "\n") } +// writeRequiredFlag appends to the buffer a line that specifies which flags are required for the command. +// It takes an io.StringWriter and a Command as parameters. The function checks each non-inherited flag of the command +// and adds it to the must_have_one_flag array if it is annotated with BashCompOneRequiredFlag. If the flag is not of type bool, +// it appends "=" after the flag name in the array. +// This function does not return any values, but it may write strings to the buffer and handle errors through WriteStringAndCheck. +// It also uses nonCompletableFlag to determine if a flag should be skipped. func writeRequiredFlag(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, " must_have_one_flag=()\n") flags := cmd.NonInheritedFlags() @@ -612,6 +678,13 @@ func writeRequiredFlag(buf io.StringWriter, cmd *Command) { }) } +// writeRequiredNouns writes the required nouns for a command to a buffer. +// +// It takes an io.StringWriter and a pointer to a Command as parameters. The Command should have valid arguments defined in ValidArgs or a function to provide them through ValidArgsFunction. +// +// This function sorts the valid arguments, removes any descriptions following a tab character (not supported by bash completion), and appends each noun to the buffer in the format required for command-line interface validation. +// +// If a ValidArgsFunction is provided, it also sets a flag indicating that a completion function is available. func writeRequiredNouns(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, " must_have_one_noun=()\n") sort.Strings(cmd.ValidArgs) @@ -626,6 +699,14 @@ func writeRequiredNouns(buf io.StringWriter, cmd *Command) { } } +// writeCmdAliases writes the command aliases to the provided buffer. +// +// Parameters: +// - buf: An io.StringWriter where the aliases will be written. +// - cmd: A pointer to a Command struct containing the aliases to be written. +// +// Returns: +// - None func writeCmdAliases(buf io.StringWriter, cmd *Command) { if len(cmd.Aliases) == 0 { return @@ -641,6 +722,15 @@ func writeCmdAliases(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, ` fi`) WriteStringAndCheck(buf, "\n") } +// writeArgAliases writes argument aliases for a command to the buffer and checks for errors. +// +// Parameters: +// - buf: The string writer where the alias information will be written. +// - cmd: The command whose aliases are being written. +// +// This function formats and appends argument aliases of the given command to the buffer, +// ensuring they are sorted alphabetically. It also handles any errors that occur during +// the writing process using WriteStringAndCheck. func writeArgAliases(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, " noun_aliases=()\n") sort.Strings(cmd.ArgAliases) @@ -649,6 +739,11 @@ func writeArgAliases(buf io.StringWriter, cmd *Command) { } } +// gen recursively generates a set of functions to call the provided command and its subcommands. +// +// Parameters: +// - buf: an io.StringWriter to write the generated functions to. +// - cmd: the current command to generate functions for. func gen(buf io.StringWriter, cmd *Command) { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() && c != cmd.helpCommand { @@ -679,7 +774,13 @@ func gen(buf io.StringWriter, cmd *Command) { WriteStringAndCheck(buf, "}\n\n") } -// GenBashCompletion generates bash completion file and writes to the passed writer. +// GenBashCompletion generates a bash completion file and writes it to the provided writer. +// +// Parameters: +// - w: io.Writer - The writer to which the bash completion file will be written. +// +// Returns: +// - error: If an error occurs during the generation or writing process, it is returned. func (c *Command) GenBashCompletion(w io.Writer) error { buf := new(bytes.Buffer) writePreamble(buf, c.Name()) @@ -693,11 +794,18 @@ func (c *Command) GenBashCompletion(w io.Writer) error { return err } +// nonCompletableFlag checks if a flag is hidden or deprecated and returns true if it should not be completed. func nonCompletableFlag(flag *pflag.Flag) bool { return flag.Hidden || len(flag.Deprecated) > 0 } -// GenBashCompletionFile generates bash completion file. +// GenBashCompletionFile generates a bash completion file for the command. +// +// Parameters: +// filename - The path to the output bash completion file. +// +// Returns: +// error - An error if the file could not be created or written, nil otherwise. func (c *Command) GenBashCompletionFile(filename string) error { outFile, err := os.Create(filename) if err != nil { diff --git a/bash_completionsV2.go b/bash_completionsV2.go index d2397aa3..9f51c446 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -21,6 +21,9 @@ import ( "os" ) +// genBashCompletion generates Bash completion script for the command. +// It writes the completion script to the provided writer and includes descriptions if specified. +// Returns an error if the operation fails. func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) genBashComp(buf, c.Name(), includeDesc) @@ -466,7 +469,14 @@ fi activeHelpMarker)) } -// GenBashCompletionFileV2 generates Bash completion version 2. +// GenBashCompletionFileV2 generates a Bash completion file for version 2. +// +// Parameters: +// - filename: The name of the file to be created. +// - includeDesc: A boolean indicating whether to include descriptions in the completion file. +// +// Returns: +// - error: An error if any occurs during file creation or generation. func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) error { outFile, err := os.Create(filename) if err != nil { @@ -477,8 +487,14 @@ func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) err return c.GenBashCompletionV2(outFile, includeDesc) } -// GenBashCompletionV2 generates Bash completion file version 2 -// and writes it to the passed writer. +// GenBashCompletionV2 generates Bash completion file version 2 and writes it to the provided writer. It includes a description of each command if includeDesc is true. +// +// Parameters: +// - w: The writer where the Bash completion file will be written. +// - includeDesc: A boolean indicating whether to include descriptions in the completion file. +// +// Returns: +// - An error if there was an issue generating or writing the completion file. func (c *Command) GenBashCompletionV2(w io.Writer, includeDesc bool) error { return c.genBashCompletion(w, includeDesc) } diff --git a/bash_completionsV2_test.go b/bash_completionsV2_test.go index 88587e29..d26fbc3f 100644 --- a/bash_completionsV2_test.go +++ b/bash_completionsV2_test.go @@ -20,6 +20,8 @@ import ( "testing" ) +// TestBashCompletionV2WithActiveHelp tests the generation of bash completion V2 with active help enabled. +// It creates a command, generates bash completion, and asserts that active help is not being disabled. func TestBashCompletionV2WithActiveHelp(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} diff --git a/bash_completions_test.go b/bash_completions_test.go index 44412577..1d283d58 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -24,18 +24,31 @@ import ( "testing" ) +// checkOmit checks if the 'unexpected' substring is present in 'found'. +// If it is, it logs an error using t.Errorf indicating that the 'unexpected' +// value was found. This function is typically used in tests to ensure that +// certain substrings are omitted from a string being tested. +// +// Parameters: +// - t: A testing.T instance for reporting errors. +// - found: The string to search within. +// - unexpected: The substring that should not be present in 'found'. func checkOmit(t *testing.T, found, unexpected string) { if strings.Contains(found, unexpected) { t.Errorf("Got: %q\nBut should not have!\n", unexpected) } } +// check asserts that the `found` string contains the `expected` substring. +// If not, it logs an error with the expected and actual values. +// This function is typically used in testing to validate substrings within larger strings. func check(t *testing.T, found, expected string) { if !strings.Contains(found, expected) { t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found) } } +// checkNumOccurrences checks if the string `found` contains the substring `expected` exactly `expectedOccurrences` times. If not, it fails the test with an error message indicating the expected and actual occurrences. func checkNumOccurrences(t *testing.T, found, expected string, expectedOccurrences int) { numOccurrences := strings.Count(found, expected) if numOccurrences != expectedOccurrences { @@ -43,6 +56,9 @@ func checkNumOccurrences(t *testing.T, found, expected string, expectedOccurrenc } } +// checkRegex checks if the `found` string matches the `pattern` using regular expressions. +// If an error occurs during the matching process, it logs the error and fails the test. +// It asserts that the `found` string should match the `pattern`, otherwise failing the test with a detailed message. func checkRegex(t *testing.T, found, pattern string) { matched, err := regexp.MatchString(pattern, found) if err != nil { @@ -53,6 +69,10 @@ func checkRegex(t *testing.T, found, pattern string) { } } +// runShellCheck runs shellcheck on the provided string with specific options and error codes. +// It executes shellcheck with the bash syntax, sending the input from the string through stdin. +// The function redirects the standard output and error of the command to os.Stdout and os.Stderr respectively. +// Returns an error if the execution of shellcheck fails or if there's an issue with setting up the command input. func runShellCheck(s string) error { cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", "SC2034", // PREFIX appears unused. Verify it or export it. @@ -80,6 +100,7 @@ const bashCompletionFunc = `__root_custom_func() { } ` +// TestBashCompletions tests the generation of bash completion functions for a root command. func TestBashCompletions(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -226,6 +247,9 @@ func TestBashCompletions(t *testing.T) { } } +// TestBashCompletionHiddenFlag tests that a hidden flag is not included in the Bash completion output. +// It creates a command with a hidden flag and asserts that after generating the Bash completion script, +// the hidden flag is not present in the output. func TestBashCompletionHiddenFlag(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} @@ -242,6 +266,9 @@ func TestBashCompletionHiddenFlag(t *testing.T) { } } +// TestBashCompletionDeprecatedFlag tests the generation of bash completion script for a command with a deprecated flag. +// It ensures that the deprecated flag is not included in the generated completion script. +// The function takes a testing.T pointer as an argument to perform assertions and checks. func TestBashCompletionDeprecatedFlag(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} @@ -258,6 +285,8 @@ func TestBashCompletionDeprecatedFlag(t *testing.T) { } } +// TestBashCompletionTraverseChildren tests the bash completion generation for commands with TraverseChildren set to true. +// It checks that local non-persistent flags are not included in the generated completion script. func TestBashCompletionTraverseChildren(t *testing.T) { c := &Command{Use: "c", Run: emptyRun, TraverseChildren: true} @@ -276,6 +305,13 @@ func TestBashCompletionTraverseChildren(t *testing.T) { checkOmit(t, output, `local_nonpersistent_flags+=("-b")`) } +// TestBashCompletionNoActiveHelp tests the generation of bash completion without active help. +// +// Parameters: +// - t: A testing.T instance for assertions and logging test failures. +// +// This function creates a Command instance, generates bash completion with disabled active help, +// and checks if the output contains the correct environment variable setting to disable active help. func TestBashCompletionNoActiveHelp(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} diff --git a/cobra.go b/cobra.go index d9cd2414..949e7b55 100644 --- a/cobra.go +++ b/cobra.go @@ -80,37 +80,48 @@ You need to open cmd.exe and run it from there. // Works only on Microsoft Windows. var MousetrapDisplayDuration = 5 * time.Second -// AddTemplateFunc adds a template function that's available to Usage and Help -// template generation. +// AddTemplateFunc registers a new template function with the given name, making it available for use in the Usage and Help templates. func AddTemplateFunc(name string, tmplFunc interface{}) { templateFuncs[name] = tmplFunc } // AddTemplateFuncs adds multiple template functions that are available to Usage and -// Help template generation. +// Help template generation. It takes a map of template function names to their implementations +// and merges them into the global template function map, allowing these functions to be used +// in usage and help text templates. func AddTemplateFuncs(tmplFuncs template.FuncMap) { for k, v := range tmplFuncs { templateFuncs[k] = v } } -// OnInitialize sets the passed functions to be run when each command's -// Execute method is called. +// OnInitialize registers functions that will be executed whenever a command's +// Execute method is invoked. These functions are typically used for setup or initialization tasks. func OnInitialize(y ...func()) { initializers = append(initializers, y...) } -// OnFinalize sets the passed functions to be run when each command's -// Execute method is terminated. +// OnFinalize sets the provided functions to be executed when each command's +// Execute method is completed. The functions are called in the order they are provided. func OnFinalize(y ...func()) { finalizers = append(finalizers, y...) } // FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. -// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans, -// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as -// ints and then compared. +// Gt compares two types and returns true if the first type is greater than the second. For arrays, channels, +// maps, and slices, it compares their lengths. Ints are compared directly, while strings are first converted to ints +// before comparison. If either conversion fails (e.g., string is not a valid integer), Gt will return false. +// +// Parameters: +// a - the first value to compare. +// b - the second value to compare. +// +// Returns: +// true if a is greater than b, false otherwise. +// +// Errors: +// None. The function handles invalid string conversions internally and returns false in such cases. func Gt(a interface{}, b interface{}) bool { var left, right int64 av := reflect.ValueOf(a) @@ -141,6 +152,13 @@ func Gt(a interface{}, b interface{}) bool { // FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. // Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic. +// Parameters: +// a - the first value to compare +// b - the second value to compare +// Returns: +// true if a and b are equal, false otherwise +// Panics: +// if a or b is of an unsupported type (array, chan, map, slice) func Eq(a interface{}, b interface{}) bool { av := reflect.ValueOf(a) bv := reflect.ValueOf(b) @@ -156,13 +174,21 @@ func Eq(a interface{}, b interface{}) bool { return false } +// TrimRightSpace returns a copy of the input string with trailing white space removed. func trimRightSpace(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) } // FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. -// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s. +// AppendIfNotPresent appends a string to the end of the slice if it's not already present. +// +// Parameters: +// - s: The slice to append to. +// - stringToAppend: The string to be appended. +// +// Returns: +// The updated slice with the string appended, or the original slice unchanged if the string was already present. func appendIfNotPresent(s, stringToAppend string) string { if strings.Contains(s, stringToAppend) { return s @@ -171,11 +197,19 @@ func appendIfNotPresent(s, stringToAppend string) string { } // rpad adds padding to the right of a string. +// +// Parameters: +// - s: The input string to pad. +// - padding: The number of spaces to add as padding on the right. +// +// Returns: +// - The formatted string with right-padding applied. func rpad(s string, padding int) string { formattedString := fmt.Sprintf("%%-%ds", padding) return fmt.Sprintf(formattedString, s) } +// tmpl returns a new tmplFunc instance with the provided text. func tmpl(text string) *tmplFunc { return &tmplFunc{ tmpl: text, @@ -188,7 +222,14 @@ func tmpl(text string) *tmplFunc { } } -// ld compares two strings and returns the levenshtein distance between them. +// ld calculates the Levenshtein distance between two strings, optionally ignoring case. +// It returns the minimum number of single-character edits required to change one word into the other. +// Parameters: +// s - the first string +// t - the second string +// ignoreCase - if true, the comparison is case-insensitive +// Returns: +// The Levenshtein distance between the two strings. func ld(s, t string, ignoreCase bool) int { if ignoreCase { s = strings.ToLower(s) @@ -222,6 +263,14 @@ func ld(s, t string, ignoreCase bool) int { return d[len(s)][len(t)] } +// stringInSlice checks if a string is present in the given slice of strings. +// +// Parameters: +// - a: The string to search for. +// - list: The slice of strings to search within. +// +// Returns: +// - true if the string is found in the slice, false otherwise. func stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { @@ -231,7 +280,7 @@ func stringInSlice(a string, list []string) bool { return false } -// CheckErr prints the msg with the prefix 'Error:' and exits with error code 1. If the msg is nil, it does nothing. +// CheckErr prints the provided message with the prefix 'Error:' and exits with an error code of 1. If the message is `nil`, it does nothing. func CheckErr(msg interface{}) { if msg != nil { fmt.Fprintln(os.Stderr, "Error:", msg) @@ -239,7 +288,9 @@ func CheckErr(msg interface{}) { } } -// WriteStringAndCheck writes a string into a buffer, and checks if the error is not nil. +// WriteStringAndCheck writes a string into a buffer and checks if the error is not nil. +// It takes an io.StringWriter `b` and a string `s`, writes the string to the writer, +// and calls CheckErr with the resulting error, handling any errors that occur during the write operation. func WriteStringAndCheck(b io.StringWriter, s string) { _, err := b.WriteString(s) CheckErr(err) diff --git a/cobra_test.go b/cobra_test.go index f1c5b0a6..9ac8c006 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -24,12 +24,14 @@ import ( "text/template" ) +// assertNoErr checks if an error is not nil and logs it as an error if so. This function is typically used in tests to ensure that no errors occur during the execution of test cases. If an error is encountered, it calls t.Error() with the error message, causing the test to fail. func assertNoErr(t *testing.T, e error) { if e != nil { t.Error(e) } } +// TestAddTemplateFunctions tests the AddTemplateFunc and AddTemplateFuncs functions. func TestAddTemplateFunctions(t *testing.T) { AddTemplateFunc("t", func() bool { return true }) AddTemplateFuncs(template.FuncMap{ @@ -118,6 +120,16 @@ func TestLevenshteinDistance(t *testing.T) { } } +// TestStringInSlice tests the stringInSlice function with various scenarios to ensure its correctness. +// +// The test cases cover: +// - A case-sensitive match where the target string is in the slice. +// - A case-sensitive match where the target string is not in the slice. +// - A case-insensitive match, which should return false as the function is case-sensitive. +// - An empty slice, expecting false. +// - An empty string, expecting false unless the slice also contains an empty string. +// - An empty string matching another empty string, expecting true. +// - An empty string in an empty slice, expecting false. func TestStringInSlice(t *testing.T) { tests := []struct { name string @@ -182,6 +194,7 @@ func TestStringInSlice(t *testing.T) { } } +// TestRpad tests the rpad function with various test cases to ensure it pads strings correctly. func TestRpad(t *testing.T) { tests := []struct { name string @@ -228,18 +241,10 @@ func TestRpad(t *testing.T) { } } -// TestDeadcodeElimination checks that a simple program using cobra in its -// default configuration is linked taking full advantage of the linker's -// deadcode elimination step. +// TestDeadcodeElimination checks that a simple program using cobra in its default configuration is linked taking full advantage of the linker's deadcode elimination step. // -// If reflect.Value.MethodByName/reflect.Value.Method are reachable the -// linker will not always be able to prove that exported methods are -// unreachable, making deadcode elimination less effective. Using -// text/template and html/template makes reflect.Value.MethodByName -// reachable. -// Since cobra can use text/template templates this test checks that in its -// default configuration that code path can be proven to be unreachable by -// the linker. +// If reflect.Value.MethodByName/reflect.Value.Method are reachable the linker will not always be able to prove that exported methods are unreachable, making deadcode elimination less effective. Using text/template and html/template makes reflect.Value.MethodByName reachable. +// Since cobra can use text/template templates this test checks that in its default configuration that code path can be proven to be unreachable by the linker. // // See also: https://github.com/spf13/cobra/pull/1956 func TestDeadcodeElimination(t *testing.T) { diff --git a/command.go b/command.go index 4794e5eb..37691bef 100644 --- a/command.go +++ b/command.go @@ -259,30 +259,23 @@ type Command struct { SuggestionsMinimumDistance int } -// Context returns underlying command context. If command was executed -// with ExecuteContext or the context was set with SetContext, the -// previously set context will be returned. Otherwise, nil is returned. -// -// Notice that a call to Execute and ExecuteC will replace a nil context of -// a command with a context.Background, so a background context will be -// returned by Context after one of these functions has been called. +// Command represents a single CLI command and its options. func (c *Command) Context() context.Context { return c.ctx } -// SetContext sets context for the command. This context will be overwritten by -// Command.ExecuteContext or Command.ExecuteContextC. +// SetContext sets the context for the command, which can be overridden by subsequent calls to ExecuteContext or ExecuteContextC. This method allows you to control the lifecycle and cancellation of operations associated with the command. func (c *Command) SetContext(ctx context.Context) { c.ctx = ctx } -// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden -// particularly useful when testing. +// SetArgs sets the arguments for the command. By default, it uses `os.Args[1:]`. This method allows overriding the default arguments, which is particularly useful during testing. func (c *Command) SetArgs(a []string) { c.args = a } // SetOutput sets the destination for usage and error messages. +// // If output is nil, os.Stderr is used. // // Deprecated: Use SetOut and/or SetErr instead @@ -303,18 +296,29 @@ func (c *Command) SetErr(newErr io.Writer) { c.errWriter = newErr } -// SetIn sets the source for input data -// If newIn is nil, os.Stdin is used. +// SetIn sets the source for input data. If newIn is nil, os.Stdin is used. func (c *Command) SetIn(newIn io.Reader) { c.inReader = newIn } -// SetUsageFunc sets usage function. Usage can be defined by application. +// SetUsageFunc sets a custom usage function for the Command. This function allows you to define how the command's usage should be displayed, providing flexibility in customization. The provided function receives a pointer to the Command and can return an error if there is an issue with the usage definition. +// Parameters: +// - f: A function that takes a pointer to a Command and returns an error. +// Returns: +// - None +// Errors: +// - Any errors returned by the custom usage function. func (c *Command) SetUsageFunc(f func(*Command) error) { c.usageFunc = f } -// SetUsageTemplate sets usage template. Can be defined by Application. +// SetUsageTemplate sets the custom usage template for the command. If an empty string is provided, it resets to the default template. +// Parameters: +// - s: A string representing the custom usage template. +// Returns: +// - None. +// Errors: +// - None. func (c *Command) SetUsageTemplate(s string) { if s == "" { c.usageTemplate = nil @@ -323,18 +327,18 @@ func (c *Command) SetUsageTemplate(s string) { c.usageTemplate = tmpl(s) } -// SetFlagErrorFunc sets a function to generate an error when flag parsing -// fails. +// SetFlagErrorFunc sets a function to generate an error when flag parsing fails. The provided function `f` is called with the command instance and the error that occurred during flag parsing. This allows customization of error handling for command-line flags in your application. func (c *Command) SetFlagErrorFunc(f func(*Command, error) error) { c.flagErrorFunc = f } -// SetHelpFunc sets help function. Can be defined by Application. +// SetHelpFunc sets a help function for the command. This function can be defined by the application to provide custom help functionality when invoked. The provided function takes a pointer to the Command and a slice of strings representing arguments, and is expected to handle displaying help information accordingly. func (c *Command) SetHelpFunc(f func(*Command, []string)) { c.helpFunc = f } -// SetHelpCommand sets help command. +// SetHelpCommand sets the help command for the receiver. +// It takes a pointer to a Command and assigns it to the receiver's helpCommand field. func (c *Command) SetHelpCommand(cmd *Command) { c.helpCommand = cmd } @@ -349,12 +353,24 @@ func (c *Command) SetHelpCommandGroupID(groupID string) { } // SetCompletionCommandGroupID sets the group id of the completion command. +// +// Parameters: +// - groupID: The group ID to be set for the completion command. +// +// This method updates the root command's completion command group ID. If no completion +// command is defined by the user, this group ID will be used. func (c *Command) SetCompletionCommandGroupID(groupID string) { // completionCommandGroupID is used if no completion command is defined by the user c.Root().completionCommandGroupID = groupID } -// SetHelpTemplate sets help template to be used. Application can use it to set custom template. +// SetHelpTemplate sets the help template to be used by the command. If an empty string is provided, the default template will be used. +// +// Parameters: +// - s: A string representing the custom template for help. An empty string resets to the default template. +// +// Returns: +// None func (c *Command) SetHelpTemplate(s string) { if s == "" { c.helpTemplate = nil @@ -363,7 +379,10 @@ func (c *Command) SetHelpTemplate(s string) { c.helpTemplate = tmpl(s) } -// SetVersionTemplate sets version template to be used. Application can use it to set custom template. +// SetVersionTemplate sets the version template to be used. Application can use it to set custom templates. +// If the provided string is empty, it clears the version template. +// Parameters: +// - s: The custom version template as a string. func (c *Command) SetVersionTemplate(s string) { if s == "" { c.versionTemplate = nil @@ -372,7 +391,7 @@ func (c *Command) SetVersionTemplate(s string) { c.versionTemplate = tmpl(s) } -// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix. +// SetErrPrefix sets the error message prefix for the Command instance. The application can use this method to set a custom prefix for error messages. func (c *Command) SetErrPrefix(s string) { c.errPrefix = s } @@ -389,26 +408,37 @@ func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string } } -// OutOrStdout returns output to stdout. +// OutOrStdout returns an io.Writer that outputs data to stdout, or a custom output if set. func (c *Command) OutOrStdout() io.Writer { return c.getOut(os.Stdout) } -// OutOrStderr returns output to stderr +// OutOrStderr returns the standard error writer for the command. +// If no specific output is set, it defaults to writing to os.Stderr. func (c *Command) OutOrStderr() io.Writer { return c.getOut(os.Stderr) } -// ErrOrStderr returns output to stderr +// ErrOrStderr returns the standard error output writer. +// If the command is not running, it returns os.Stderr. +// It is a helper function for directing errors to the appropriate output. +// +// Returns: +// - io.Writer: The writer to which errors should be written. func (c *Command) ErrOrStderr() io.Writer { return c.getErr(os.Stderr) } -// InOrStdin returns input to stdin +// InOrStdin returns an io.Reader that provides input from the command's standard input if set, or falls back to os.Stdin otherwise. func (c *Command) InOrStdin() io.Reader { return c.getIn(os.Stdin) } +// getOut returns the output writer for the command. If a specific output writer is defined, it returns that; otherwise, it recursively calls the parent command's getOut method until it finds one or reaches the default output writer. +// Parameters: +// - def: The default output writer to use if no specific writer is found. +// Returns: +// - io.Writer: The output writer for the command. func (c *Command) getOut(def io.Writer) io.Writer { if c.outWriter != nil { return c.outWriter @@ -419,6 +449,13 @@ func (c *Command) getOut(def io.Writer) io.Writer { return def } +// getErr returns the error writer for the command, or the default if none is set. It recursively checks parent commands if necessary. +// Parameters: +// - def: The default error writer to use if no specific writer is found. +// Returns: +// - io.Writer: The error writer associated with the command or the provided default. +// Errors: +// - None. func (c *Command) getErr(def io.Writer) io.Writer { if c.errWriter != nil { return c.errWriter @@ -429,6 +466,10 @@ func (c *Command) getErr(def io.Writer) io.Writer { return def } +// GetIn returns the input reader for the command. +// If a specific input reader is set, it returns that reader; otherwise, +// if the command has a parent, it recursively calls the parent's getIn method. +// If there are no parent readers, it returns the default reader provided. func (c *Command) getIn(def io.Reader) io.Reader { if c.inReader != nil { return c.inReader @@ -439,8 +480,8 @@ func (c *Command) getIn(def io.Reader) io.Reader { return def } -// UsageFunc returns either the function set by SetUsageFunc for this command -// or a parent, or it returns a default usage function. +// UsageFunc returns the usage function for the command, starting from this command and moving up to parent commands. +// If no specific usage function is set, it falls back to a default implementation that prints the usage template with persistent flags merged. func (c *Command) UsageFunc() (f func(*Command) error) { if c.usageFunc != nil { return c.usageFunc @@ -459,8 +500,10 @@ func (c *Command) UsageFunc() (f func(*Command) error) { } } -// getUsageTemplateFunc returns the usage template function for the command -// going up the command tree if necessary. +// getUsageTemplateFunc returns the usage template function for the command. If no specific template is set for the command, +// it recursively searches up the command tree until it finds a parent with a template or reaches the root, returning the default template if none found. +// The returned function takes an io.Writer to write to and an interface{} containing data to be used in the template. +// It returns an error if there is an issue executing the template. func (c *Command) getUsageTemplateFunc() func(w io.Writer, data interface{}) error { if c.usageTemplate != nil { return c.usageTemplate.fn @@ -472,15 +515,17 @@ func (c *Command) getUsageTemplateFunc() func(w io.Writer, data interface{}) err return defaultUsageFunc } -// Usage puts out the usage for the command. -// Used when a user provides invalid input. -// Can be defined by user by overriding UsageFunc. +// Usage returns an error indicating the usage of the command. +// It is invoked when a user inputs invalid data. +// This method can be customized by users through overriding UsageFunc. func (c *Command) Usage() error { return c.UsageFunc()(c) } -// HelpFunc returns either the function set by SetHelpFunc for this command -// or a parent, or it returns a function with default help behavior. +// HelpFunc returns the function set by SetHelpFunc for this command or a parent, or it returns a function with default help behavior. If the current command has no help function and does not have a parent, it provides a fallback that merges persistent flags and renders the help template to stdout. The returned function takes two parameters: the command itself and a slice of strings representing arguments, and returns an error if rendering the help fails. + +// HasParent checks if the current command has a parent command. +// Returns true if there is a parent command, false otherwise. func (c *Command) HelpFunc() func(*Command, []string) { if c.helpFunc != nil { return c.helpFunc @@ -500,8 +545,7 @@ func (c *Command) HelpFunc() func(*Command, []string) { } } -// getHelpTemplateFunc returns the help template function for the command -// going up the command tree if necessary. +// GetHelpTemplateFunc returns the help template function for the command, going up the command tree if necessary. If the current command has a help template, it returns that; otherwise, it recursively checks its parent commands until it finds one with a help template or reaches the root, returning the default help function if no template is found. func (c *Command) getHelpTemplateFunc() func(w io.Writer, data interface{}) error { if c.helpTemplate != nil { return c.helpTemplate.fn @@ -514,15 +558,15 @@ func (c *Command) getHelpTemplateFunc() func(w io.Writer, data interface{}) erro return defaultHelpFunc } -// Help puts out the help for the command. -// Used when a user calls help [command]. -// Can be defined by user by overriding HelpFunc. +// Help outputs the help information for the command. +// It is invoked when a user requests help for a specific command using 'help [command]'. +// This method allows for custom implementation by overriding the HelpFunc field of the Command struct. func (c *Command) Help() error { c.HelpFunc()(c, []string{}) return nil } -// UsageString returns usage string. +// UsageString returns the usage string of the command. func (c *Command) UsageString() string { // Storing normal writers tmpOutput := c.outWriter @@ -541,9 +585,8 @@ func (c *Command) UsageString() string { return bb.String() } -// FlagErrorFunc returns either the function set by SetFlagErrorFunc for this -// command or a parent, or it returns a function which returns the original -// error. +// FlagErrorFunc returns the function set by SetFlagErrorFunc for this command or a parent. +// If no function is set, it returns a function that returns the original error. func (c *Command) FlagErrorFunc() (f func(*Command, error) error) { if c.flagErrorFunc != nil { return c.flagErrorFunc @@ -559,7 +602,7 @@ func (c *Command) FlagErrorFunc() (f func(*Command, error) error) { var minUsagePadding = 25 -// UsagePadding return padding for the usage. +// UsagePadding returns the padding required for the command's usage based on its parent command's maximum usage length. If there is no parent or the parent's maximum usage length is less than minUsagePadding, it returns minUsagePadding. Otherwise, it returns the parent's maximum usage length. func (c *Command) UsagePadding() int { if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen { return minUsagePadding @@ -569,7 +612,9 @@ func (c *Command) UsagePadding() int { var minCommandPathPadding = 11 -// CommandPathPadding return padding for the command path. +// CommandPathPadding returns the padding for the command path. +// It checks if the parent command exists and if its maximum command path length is greater than a minimum padding value. +// If true, it returns the maximum command path length; otherwise, it returns the minimum padding value. func (c *Command) CommandPathPadding() int { if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen { return minCommandPathPadding @@ -579,7 +624,9 @@ func (c *Command) CommandPathPadding() int { var minNamePadding = 11 -// NamePadding returns padding for the name. +// NamePadding calculates the padding needed for the command's name. +// It returns the minimum padding if the parent is nil or if the current maximum name length in the parent's commands exceeds minNamePadding. +// Otherwise, it returns the maximum name length of the parent's commands. func (c *Command) NamePadding() int { if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen { return minNamePadding @@ -587,8 +634,7 @@ func (c *Command) NamePadding() int { return c.parent.commandsMaxNameLen } -// UsageTemplate returns usage template for the command. -// This function is kept for backwards-compatibility reasons. +// Command represents a command in the application. func (c *Command) UsageTemplate() string { if c.usageTemplate != nil { return c.usageTemplate.tmpl @@ -600,8 +646,11 @@ func (c *Command) UsageTemplate() string { return defaultUsageTemplate } -// HelpTemplate return help template for the command. -// This function is kept for backwards-compatibility reasons. +// HelpTemplate returns the help template for the command. +// +// This function is kept for backwards-compatibility reasons. If a custom help template is set, +// it returns that template. Otherwise, it recursively calls the parent command's HelpTemplate +// method until it finds one or reaches the root command, returning the default help template if necessary. func (c *Command) HelpTemplate() string { if c.helpTemplate != nil { return c.helpTemplate.tmpl @@ -613,7 +662,7 @@ func (c *Command) HelpTemplate() string { return defaultHelpTemplate } -// VersionTemplate return version template for the command. +// VersionTemplate returns the version template for the command. // This function is kept for backwards-compatibility reasons. func (c *Command) VersionTemplate() string { if c.versionTemplate != nil { @@ -626,8 +675,7 @@ func (c *Command) VersionTemplate() string { return defaultVersionTemplate } -// getVersionTemplateFunc returns the version template function for the command -// going up the command tree if necessary. +// Command represents a command with its own version template function. func (c *Command) getVersionTemplateFunc() func(w io.Writer, data interface{}) error { if c.versionTemplate != nil { return c.versionTemplate.fn @@ -639,7 +687,10 @@ func (c *Command) getVersionTemplateFunc() func(w io.Writer, data interface{}) e return defaultVersionFunc } -// ErrPrefix return error message prefix for the command +// ErrPrefix returns the error message prefix for the command. +// If a custom prefix is set, it returns that. +// Otherwise, if the command has a parent, it recursively calls the parent's ErrPrefix. +// If no custom prefix or parent exists, it returns "Error:". func (c *Command) ErrPrefix() string { if c.errPrefix != "" { return c.errPrefix @@ -651,6 +702,9 @@ func (c *Command) ErrPrefix() string { return "Error:" } +// hasNoOptDefVal checks if the flag with the given name in the provided FlagSet has a non-empty NoOptDefVal. +// It returns true if such a flag exists and its NoOptDefVal is not empty, false otherwise. +// If the flag does not exist, it also returns false. func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { flag := fs.Lookup(name) if flag == nil { @@ -659,6 +713,13 @@ func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { return flag.NoOptDefVal != "" } +// shortHasNoOptDefVal checks if a shorthand flag has a non-empty default value. +// +// It takes two parameters: +// - name: the name of the flag to check. +// - fs: a pointer to the FlagSet containing the flags. +// +// It returns true if the flag exists and its NoOptDefVal is not empty, false otherwise. func shortHasNoOptDefVal(name string, fs *flag.FlagSet) bool { if len(name) == 0 { return false @@ -671,6 +732,15 @@ func shortHasNoOptDefVal(name string, fs *flag.FlagSet) bool { return flag.NoOptDefVal != "" } +// stripFlags processes the input arguments by removing any flags. +// It takes a slice of strings `args` and a pointer to a Command `c`. +// If no arguments are provided, it returns the original args. +// It merges persistent flags from the command. +// The function iterates over the arguments, stripping out flags based on their syntax. +// If "--" is encountered, flag processing stops. +// Flags starting with '--' or '-' without '=' are considered options. +// If a short flag ('-f') requires an argument and one is provided, it removes that argument from the list. +// It appends non-flag arguments to `commands` slice and returns this slice. func stripFlags(args []string, c *Command) []string { if len(args) == 0 { return args @@ -709,9 +779,16 @@ Loop: return commands } -// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like -// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]). -// Special care needs to be taken not to remove a flag value. +// argsMinusFirstX removes only the first occurrence of x from args. If no flags are present in args, it will remove the first non-flag element that matches x. +// +// It handles both long and short options, ensuring that flag values are not removed. If "--" is encountered in args, it stops processing further. +// +// Parameters: +// - args: The slice of strings representing command line arguments. +// - x: The string to remove from the args. +// +// Returns: +// - A new slice of strings with the first occurrence of x removed (if found). func (c *Command) argsMinusFirstX(args []string, x string) []string { if len(args) == 0 { return args @@ -747,13 +824,18 @@ Loop: return args } +// isFlagArg checks if the provided argument is a flag. +// It returns true if the argument starts with "--" (e.g., --flag) +// or starts with "-" followed by a non-dash character (e.g., -f). func isFlagArg(arg string) bool { return ((len(arg) >= 3 && arg[0:2] == "--") || (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) } -// Find the target command given the args and command tree -// Meant to be run on the highest node. Only searches down. +// Find returns the target command given the arguments and command tree. +// It starts from the highest node and searches down. +// If the target command is found, it returns the command and any remaining arguments. +// If no matching command is found, it returns the current node and the original arguments. func (c *Command) Find(args []string) (*Command, []string, error) { var innerfind func(*Command, []string) (*Command, []string) @@ -778,6 +860,11 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return commandFound, a, nil } +// findSuggestions returns a string of suggestions based on the given argument. +// It checks if suggestions are disabled or if the minimum distance is not set, +// and adjusts them accordingly. If there are any suggestions, it formats them into a string +// and appends them to the output. +// Returns an empty string if no suggestions are available or suggestions are disabled. func (c *Command) findSuggestions(arg string) string { if c.DisableSuggestions { return "" @@ -795,6 +882,10 @@ func (c *Command) findSuggestions(arg string) string { return sb.String() } +// findNext searches for the next command based on the given name or alias. +// It returns the first matching command if found, otherwise returns nil. +// If multiple commands match but EnablePrefixMatching is enabled, it returns the first match. +// If no matches are found, it returns nil. func (c *Command) findNext(next string) *Command { matches := make([]*Command, 0) for _, cmd := range c.commands { @@ -816,8 +907,8 @@ func (c *Command) findNext(next string) *Command { return nil } -// Traverse the command tree to find the command, and parse args for -// each parent. +// Traverse parses command flags and arguments by traversing the command tree. +// It returns the final command and any remaining arguments, or an error if parsing fails. func (c *Command) Traverse(args []string) (*Command, []string, error) { flags := []string{} inFlag := false @@ -859,7 +950,12 @@ func (c *Command) Traverse(args []string) (*Command, []string, error) { return c, args, nil } -// SuggestionsFor provides suggestions for the typedName. +// SuggestionsFor returns a list of suggestions for the given typedName based on available commands. +// It considers both levenshtein distance and prefix matching to find relevant suggestions. +// Parameters: +// - typedName: The name that needs suggestions. +// Returns: +// - A slice of strings containing suggested command names. func (c *Command) SuggestionsFor(typedName string) []string { suggestions := []string{} for _, cmd := range c.commands { @@ -880,7 +976,9 @@ func (c *Command) SuggestionsFor(typedName string) []string { return suggestions } -// VisitParents visits all parents of the command and invokes fn on each parent. +// VisitParents traverses all parent commands starting from the current command. +// It invokes the provided function on each parent command and recursively visits their parents. +// If there are no parents, it does nothing. func (c *Command) VisitParents(fn func(*Command)) { if c.HasParent() { fn(c.Parent()) @@ -888,7 +986,7 @@ func (c *Command) VisitParents(fn func(*Command)) { } } -// Root finds root command. +// Root returns the root command of the given command, traversing up the parent chain until it reaches the root. If the command does not have a parent, it returns itself as the root. func (c *Command) Root() *Command { if c.HasParent() { return c.Parent().Root() @@ -896,12 +994,23 @@ func (c *Command) Root() *Command { return c } -// ArgsLenAtDash will return the length of c.Flags().Args at the moment -// when a -- was found during args parsing. +// ArgsLenAtDash returns the number of arguments that were provided before encountering a "--" flag during argument parsing. This can be useful for determining how many positional arguments have been specified up to a certain point in a command's flags. func (c *Command) ArgsLenAtDash() int { return c.Flags().ArgsLenAtDash() } +// execute runs the command with the provided arguments. +// +// It handles deprecated commands, initializes help and version flags, +// parses flags, checks for help or version requests, validates arguments, +// and executes pre-run hooks before running the main command logic. +// It also runs post-run hooks after the main logic completes. +// +// Parameters: +// - a: A slice of strings representing the command line arguments. +// +// Returns: +// - err: An error if any step fails; otherwise, nil. func (c *Command) execute(a []string) (err error) { if c == nil { return fmt.Errorf("called Execute() on a nil Command") @@ -1044,29 +1153,34 @@ func (c *Command) execute(a []string) (err error) { return nil } +// preRun executes the initializers for the command. func (c *Command) preRun() { for _, x := range initializers { x() } } +// postRun is called after a command has been executed. It runs all registered finalizers. func (c *Command) postRun() { for _, x := range finalizers { x() } } -// ExecuteContext is the same as Execute(), but sets the ctx on the command. -// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs -// functions. +// ExecuteContext is the same as Execute(), but sets the ctx on the command. It allows setting a context for the command, which can be retrieved using cmd.Context() inside lifecycle or ValidArgs functions. +// This method ensures that any operations within the command can utilize the provided context for managing timeouts, cancellations, and other asynchronous behaviors. +// Parameters: +// - ctx: The context to set on the command. +// Returns: +// - error: If an error occurs during the execution of the command. func (c *Command) ExecuteContext(ctx context.Context) error { c.ctx = ctx return c.Execute() } -// Execute uses the args (os.Args[1:] by default) -// and run through the command tree finding appropriate matches -// for commands and then corresponding flags. +// Execute runs the command with the provided arguments, using os.Args[1:] by default if no arguments are provided. +// It traverses the command tree to find appropriate matches for commands and their corresponding flags. +// Returns an error if there is an issue during execution. func (c *Command) Execute() error { _, err := c.ExecuteC() return err @@ -1169,6 +1283,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return cmd, err } +// ValidateArgs validates the arguments provided to a command. +// +// If the command's Args field is nil, it calls ArbitraryArgs with the command and arguments. +// Otherwise, it calls the Args function assigned to the command with the command and arguments. +// +// It returns an error if validation fails or if no args are provided for a non-nil Args function. func (c *Command) ValidateArgs(args []string) error { if c.Args == nil { return ArbitraryArgs(c, args) @@ -1176,7 +1296,7 @@ func (c *Command) ValidateArgs(args []string) error { return c.Args(c, args) } -// ValidateRequiredFlags validates all required flags are present and returns an error otherwise +// ValidateRequiredFlags validates all required flags are present and returns an error otherwise. It checks if the DisableFlagParsing option is enabled. If not, it iterates over all flags to find those marked as required but not set. If any required flags are missing, it returns an error listing them; otherwise, it returns nil. func (c *Command) ValidateRequiredFlags() error { if c.DisableFlagParsing { return nil @@ -1200,8 +1320,10 @@ func (c *Command) ValidateRequiredFlags() error { return nil } -// checkCommandGroups checks if a command has been added to a group that does not exists. -// If so, we panic because it indicates a coding error that should be corrected. +// checkCommandGroups checks if a command has been added to a group that does not exist. +// If so, it panics because it indicates a coding error that should be corrected. This function +// is typically used during the initialization or validation phase of the application to ensure +// that all command groups are properly defined and referenced. func (c *Command) checkCommandGroups() { for _, sub := range c.commands { // if Group is not defined let the developer know right away @@ -1213,9 +1335,9 @@ func (c *Command) checkCommandGroups() { } } -// InitDefaultHelpFlag adds default help flag to c. -// It is called automatically by executing the c or by calling help and usage. -// If c already has help flag, it will do nothing. +// InitDefaultHelpFlag adds a default help flag to the command. +// It is automatically called when executing the command or by explicitly calling the help and usage functions. +// If the command already has a help flag, this method does nothing. func (c *Command) InitDefaultHelpFlag() { c.mergePersistentFlags() if c.Flags().Lookup(helpFlagName) == nil { @@ -1231,10 +1353,9 @@ func (c *Command) InitDefaultHelpFlag() { } } -// InitDefaultVersionFlag adds default version flag to c. -// It is called automatically by executing the c. -// If c already has a version flag, it will do nothing. -// If c.Version is empty, it will do nothing. +// InitDefaultVersionFlag adds a default version flag to the command. +// It is called automatically when the command is executed. +// If the command already has a version flag or if the Version field is empty, it does nothing. func (c *Command) InitDefaultVersionFlag() { if c.Version == "" { return @@ -1257,9 +1378,8 @@ func (c *Command) InitDefaultVersionFlag() { } } -// InitDefaultHelpCmd adds default help command to c. -// It is called automatically by executing the c or by calling help and usage. -// If c already has help command or c has no subcommands, it will do nothing. +// InitDefaultHelpCmd initializes the default help command for a Command. +// If the command already has a help command or if it has no subcommands, it does nothing. func (c *Command) InitDefaultHelpCmd() { if !c.HasSubCommands() { return @@ -1313,7 +1433,9 @@ Simply type ` + c.DisplayName() + ` help [path to command] for full details.`, c.AddCommand(c.helpCommand) } -// ResetCommands delete parent, subcommand and help command from c. +// ResetCommands deletes the parent, subcommands, and help command associated with the receiver Command. It also clears any parent flag sets. +// This method is useful for resetting a Command instance to its initial state before reconfiguration. +// The receiver *Command should be non-nil, otherwise, this function will panic. func (c *Command) ResetCommands() { c.parent = nil c.commands = nil @@ -1324,11 +1446,17 @@ func (c *Command) ResetCommands() { // Sorts commands by their names. type commandSorterByName []*Command +// Len returns the number of commands in the sorter. func (c commandSorterByName) Len() int { return len(c) } +// Swap swaps elements i and j in the commandSorterByName slice. It is used to implement the sort.Interface for sorting commands by name. func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +// Less returns true if the name of the command at index i should sort before the command at index j. This is used by sorting algorithms to determine the order of elements in a slice of commands. func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Name() } // Commands returns a sorted slice of child commands. +// +// If EnableCommandSorting is true and the commands are not already sorted, it will sort them by name using a custom sorter. +// The method marks the command list as sorted to avoid redundant sorting in future calls. func (c *Command) Commands() []*Command { // do not sort commands if it already sorted or sorting was disabled if EnableCommandSorting && !c.commandsAreSorted { @@ -1339,6 +1467,10 @@ func (c *Command) Commands() []*Command { } // AddCommand adds one or more commands to this parent command. +// It iterates over the provided commands, checks if any of them is a child of itself (which would cause a panic), +// sets their parent to the current command, updates maximum lengths for usage, command path, and name, +// and appends them to the list of commands. If a global normalization function exists, it applies it to all added commands. +// It also marks the commands as unsorted. func (c *Command) AddCommand(cmds ...*Command) { for i, x := range cmds { if cmds[i] == c { @@ -1367,12 +1499,15 @@ func (c *Command) AddCommand(cmds ...*Command) { } } -// Groups returns a slice of child command groups. +// Groups returns a slice of child command groups. It provides access to all sub-groups associated with the current command. The returned slice is not nil but may be empty if there are no child groups. func (c *Command) Groups() []*Group { return c.commandgroups } -// AllChildCommandsHaveGroup returns if all subcommands are assigned to a group +// AllChildCommandsHaveGroup returns true if all subcommands of the command are assigned to a group. +// It iterates over each subcommand and checks if it is an available command or the help command, and if its GroupID is empty. +// If any subcommand meets these conditions but has an empty GroupID, the function returns false. +// Otherwise, it returns true. func (c *Command) AllChildCommandsHaveGroup() bool { for _, sub := range c.commands { if (sub.IsAvailableCommand() || sub == c.helpCommand) && sub.GroupID == "" { @@ -1382,7 +1517,9 @@ func (c *Command) AllChildCommandsHaveGroup() bool { return true } -// ContainsGroup return if groupID exists in the list of command groups. +// ContainsGroup checks if a given groupID exists within the list of command groups associated with the Command instance. +// It iterates through each command group and compares its ID with the provided groupID. +// Returns true if the groupID is found, otherwise returns false. func (c *Command) ContainsGroup(groupID string) bool { for _, x := range c.commandgroups { if x.ID == groupID { @@ -1392,12 +1529,13 @@ func (c *Command) ContainsGroup(groupID string) bool { return false } -// AddGroup adds one or more command groups to this parent command. +// AddGroup appends one or more command groups to the current command. func (c *Command) AddGroup(groups ...*Group) { c.commandgroups = append(c.commandgroups, groups...) } -// RemoveCommand removes one or more commands from a parent command. +// RemoveCommand removes one or more commands from the receiver command. +// It takes a variadic parameter of commands to remove and updates the receiver's internal state accordingly. func (c *Command) RemoveCommand(cmds ...*Command) { commands := []*Command{} main: @@ -1431,37 +1569,52 @@ main: } } -// Print is a convenience method to Print to the defined output, fallback to Stderr if not set. +// Print is a convenience method to print to the defined output. If no output is set, it falls back to using standard error. +// It takes a variadic number of interface{} arguments and prints them according to the format specified. +// Parameters: +// - i ...interface{}: The data to be printed. +// There are no return values for this method. func (c *Command) Print(i ...interface{}) { fmt.Fprint(c.OutOrStderr(), i...) } -// Println is a convenience method to Println to the defined output, fallback to Stderr if not set. +// Command represents a command with optional output redirection. func (c *Command) Println(i ...interface{}) { c.Print(fmt.Sprintln(i...)) } -// Printf is a convenience method to Printf to the defined output, fallback to Stderr if not set. +// Command represents a command with various options and settings. func (c *Command) Printf(format string, i ...interface{}) { c.Print(fmt.Sprintf(format, i...)) } -// PrintErr is a convenience method to Print to the defined Err output, fallback to Stderr if not set. +// PrintErr prints to the error output of the Command. If the error output is not set, it defaults to printing to standard error. +// It takes a variadic number of interface{} arguments and formats them according to the default format for fmt.Print. func (c *Command) PrintErr(i ...interface{}) { fmt.Fprint(c.ErrOrStderr(), i...) } -// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. +// PrintErrln prints the provided arguments to the error output of the Command. If no error output is set, it defaults to printing to Stderr. +// It accepts a variable number of interface{} arguments, which are formatted and printed similarly to fmt.Println. +// This method provides a convenient way to handle errors by automatically directing them to an appropriate output. func (c *Command) PrintErrln(i ...interface{}) { c.PrintErr(fmt.Sprintln(i...)) } -// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. +// PrintErrf formats according to a format specifier and writes to the command's error output. If no specific error output is set, it defaults to Stderr. +// The function uses Sprintf to format the string before passing it to PrintErr for further handling. +// Parameters: +// - format: A format string in Go's printf style. +// - i: Arguments to be formatted into the string. +// This method does not return any value but may print an error message if an error occurs during output. func (c *Command) PrintErrf(format string, i ...interface{}) { c.PrintErr(fmt.Sprintf(format, i...)) } -// CommandPath returns the full path to this command. +// CommandPath returns the full path to this command, including its parent commands if any. If the command has no parent, it returns the display name of the command. +// It recursively concatenates the names of parent commands with the current command's name and a space in between each name. +// Returns: +// - The full path to this command as a string. func (c *Command) CommandPath() string { if c.HasParent() { return c.Parent().CommandPath() + " " + c.Name() @@ -1469,8 +1622,9 @@ func (c *Command) CommandPath() string { return c.DisplayName() } -// DisplayName returns the name to display in help text. Returns command Name() -// If CommandDisplayNameAnnoation is not set +// DisplayName returns the name to display in help text. +// It checks for the CommandDisplayNameAnnotation in the command's annotations. +// If found, it returns the annotation value; otherwise, it returns the result of calling c.Name(). func (c *Command) DisplayName() string { if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok { return displayName @@ -1478,7 +1632,11 @@ func (c *Command) DisplayName() string { return c.Name() } -// UseLine puts out the full usage for a given command (including parents). +// UseLine returns the full usage for a given command, including parents. It replaces the command name with its display name and appends flags if necessary. +// Parameters: +// - c: The Command instance for which to generate the usage line. +// Returns: +// - A string representing the full usage of the command. func (c *Command) UseLine() string { var useline string use := strings.Replace(c.Use, c.Name(), c.DisplayName(), 1) @@ -1496,8 +1654,7 @@ func (c *Command) UseLine() string { return useline } -// DebugFlags used to determine which flags have been assigned to which commands -// and which persist. +// DebugFlags prints debug information about the command and its flags, including both local and persistent flags. func (c *Command) DebugFlags() { c.Println("DebugFlags called on", c.Name()) var debugflags func(*Command) @@ -1547,7 +1704,13 @@ func (c *Command) Name() string { return name } -// HasAlias determines if a given string is an alias of the command. +// HasAlias checks if the given string is an alias of the command. +// +// Parameters: +// - s: The string to check for being an alias. +// +// Returns: +// - bool: True if the string is an alias, false otherwise. func (c *Command) HasAlias(s string) bool { for _, a := range c.Aliases { if commandNameMatches(a, s) { @@ -1566,8 +1729,13 @@ func (c *Command) CalledAs() string { return "" } -// hasNameOrAliasPrefix returns true if the Name or any of aliases start -// with prefix +// hasNameOrAliasPrefix checks if the command's Name or any of its aliases start with the given prefix. +// +// Parameters: +// - prefix: The string prefix to check against the command's Name and Aliases. +// +// Returns: +// - bool: true if any of the Name or Aliases start with the prefix, false otherwise. func (c *Command) hasNameOrAliasPrefix(prefix string) bool { if strings.HasPrefix(c.Name(), prefix) { c.commandCalledAs.name = c.Name() @@ -1582,28 +1750,28 @@ func (c *Command) hasNameOrAliasPrefix(prefix string) bool { return false } -// NameAndAliases returns a list of the command name and all aliases +// NameAndAliases returns a string containing the command name followed by its aliases, separated by commas. +// It takes no parameters and returns the formatted string. func (c *Command) NameAndAliases() string { return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ") } -// HasExample determines if the command has example. +// HasExample returns true if the command has an example. func (c *Command) HasExample() bool { return len(c.Example) > 0 } -// Runnable determines if the command is itself runnable. +// Runnable checks whether the command can be executed directly. It returns true if either the Run or RunE method of the Command instance is non-nil, indicating that the command is runnable. func (c *Command) Runnable() bool { return c.Run != nil || c.RunE != nil } -// HasSubCommands determines if the command has children commands. +// HasSubCommands returns true if the command has child commands, otherwise false. func (c *Command) HasSubCommands() bool { return len(c.commands) > 0 } -// IsAvailableCommand determines if a command is available as a non-help command -// (this includes all non deprecated/hidden commands). +// IsAvailableCommand determines if a command is available as a non-help command (this includes all non deprecated/hidden commands). It checks if the command has no deprecated or hidden flags, is not the help command of its parent, and either runnable itself or has available subcommands. func (c *Command) IsAvailableCommand() bool { if len(c.Deprecated) != 0 || c.Hidden { return false @@ -1620,10 +1788,7 @@ func (c *Command) IsAvailableCommand() bool { return false } -// IsAdditionalHelpTopicCommand determines if a command is an additional -// help topic command; additional help topic command is determined by the -// fact that it is NOT runnable/hidden/deprecated, and has no sub commands that -// are runnable/hidden/deprecated. +// IsAdditionalHelpTopicCommand determines if a command is an additional help topic command. An additional help topic command is defined as a command that is not runnable, deprecated, hidden, and has no sub-commands that meet these criteria. // Concrete example: https://github.com/spf13/cobra/issues/393#issuecomment-282741924. func (c *Command) IsAdditionalHelpTopicCommand() bool { // if a command is runnable, deprecated, or hidden it is not a 'help' command @@ -1642,9 +1807,9 @@ func (c *Command) IsAdditionalHelpTopicCommand() bool { return true } -// HasHelpSubCommands determines if a command has any available 'help' sub commands -// that need to be shown in the usage/help default template under 'additional help -// topics'. +// HasHelpSubCommands determines if a command has any available 'help' sub commands that need to be shown in the usage/help default template under 'additional help topics'. +// It iterates through all sub-commands of the current command and checks if any of them are marked as additional help topic commands. +// Returns true if at least one such sub-command is found, otherwise returns false. func (c *Command) HasHelpSubCommands() bool { // return true on the first found available 'help' sub command for _, sub := range c.commands { @@ -1657,8 +1822,9 @@ func (c *Command) HasHelpSubCommands() bool { return false } -// HasAvailableSubCommands determines if a command has available sub commands that -// need to be shown in the usage/help default template under 'available commands'. +// HasAvailableSubCommands determines if a command has available subcommands that need to be shown in the usage/help default template under 'available commands'. +// It iterates through the subcommands of the command and returns true if any subcommand is available, considering non-deprecated, non-help, and non-hidden subcommands. +// If there are no subcommands or all subcommands are deprecated, help, or hidden, it returns false. func (c *Command) HasAvailableSubCommands() bool { // return true on the first found available (non deprecated/help/hidden) // sub command @@ -1673,18 +1839,17 @@ func (c *Command) HasAvailableSubCommands() bool { return false } -// HasParent determines if the command is a child command. +// HasParent returns true if the command has a parent command, indicating that it is not a root command. Otherwise, it returns false. func (c *Command) HasParent() bool { return c.parent != nil } -// GlobalNormalizationFunc returns the global normalization function or nil if it doesn't exist. +// GlobalNormalizationFunc returns the global normalization function associated with the command, or nil if none is set. func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName { return c.globNormFunc } -// Flags returns the complete FlagSet that applies -// to this command (local and persistent declared here and by all parents). +// Flags returns the complete FlagSet that applies to this command (local and persistent declared here and by all parents). func (c *Command) Flags() *flag.FlagSet { if c.flags == nil { c.flags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) @@ -1697,8 +1862,8 @@ func (c *Command) Flags() *flag.FlagSet { return c.flags } -// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands. -// This function does not modify the flags of the current command, it's purpose is to return the current state. +// LocalNonPersistentFlags returns a FlagSet containing flags specific to this command that will NOT persist to subcommands. +// This function does not modify the flags of the current command; its purpose is to return the current state. func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { persistentFlags := c.PersistentFlags() @@ -1712,7 +1877,10 @@ func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { } // LocalFlags returns the local FlagSet specifically set in the current command. -// This function does not modify the flags of the current command, it's purpose is to return the current state. +// +// This function does not modify the flags of the current command; its purpose is to return the current state. +// It merges persistent flags, initializes the local flag set if necessary, sets output for errors, and adds flags from both the +// regular flags and persistent flags, ensuring that they are not parent PFlags or shadows a parent PFlag. func (c *Command) LocalFlags() *flag.FlagSet { c.mergePersistentFlags() @@ -1740,7 +1908,7 @@ func (c *Command) LocalFlags() *flag.FlagSet { } // InheritedFlags returns all flags which were inherited from parent commands. -// This function does not modify the flags of the current command, it's purpose is to return the current state. +// This function does not modify the flags of the current command; its purpose is to return the current state. func (c *Command) InheritedFlags() *flag.FlagSet { c.mergePersistentFlags() @@ -1765,13 +1933,13 @@ func (c *Command) InheritedFlags() *flag.FlagSet { return c.iflags } -// NonInheritedFlags returns all flags which were not inherited from parent commands. -// This function does not modify the flags of the current command, it's purpose is to return the current state. +// Command represents a command in the application. func (c *Command) NonInheritedFlags() *flag.FlagSet { return c.LocalFlags() } // PersistentFlags returns the persistent FlagSet specifically set in the current command. +// If the FlagSet has not been initialized, it creates a new one with ContinueOnError as error handling and sets its output to the flagErrorBuf of the command. func (c *Command) PersistentFlags() *flag.FlagSet { if c.pflags == nil { c.pflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) @@ -1783,7 +1951,7 @@ func (c *Command) PersistentFlags() *flag.FlagSet { return c.pflags } -// ResetFlags deletes all flags from command. +// ResetFlags deletes all flags from the command, resetting its internal state. func (c *Command) ResetFlags() { c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf.Reset() @@ -1797,50 +1965,59 @@ func (c *Command) ResetFlags() { c.parentsPflags = nil } -// HasFlags checks if the command contains any flags (local plus persistent from the entire structure). +// HasFlags returns true if the command or any of its parent commands contain any flags. +// It checks both local and persistent flags within the command's flag set. func (c *Command) HasFlags() bool { return c.Flags().HasFlags() } -// HasPersistentFlags checks if the command contains persistent flags. +// HasPersistentFlags returns true if the command contains any persistent flags. func (c *Command) HasPersistentFlags() bool { return c.PersistentFlags().HasFlags() } -// HasLocalFlags checks if the command has flags specifically declared locally. +// HasLocalFlags returns true if the command has flags that are specifically declared locally, false otherwise. func (c *Command) HasLocalFlags() bool { return c.LocalFlags().HasFlags() } -// HasInheritedFlags checks if the command has flags inherited from its parent command. +// HasInheritedFlags returns true if the command has flags that are inherited from its parent command. func (c *Command) HasInheritedFlags() bool { return c.InheritedFlags().HasFlags() } -// HasAvailableFlags checks if the command contains any flags (local plus persistent from the entire -// structure) which are not hidden or deprecated. +// HasAvailableFlags returns true if the command contains any flags that are not hidden or deprecated. func (c *Command) HasAvailableFlags() bool { return c.Flags().HasAvailableFlags() } -// HasAvailablePersistentFlags checks if the command contains persistent flags which are not hidden or deprecated. +// HasAvailablePersistentFlags determines whether the command has any persistent flags that are not marked as hidden or deprecated. Persistent flags are those that are defined in a parent command and can be used by all its subcommands. This method returns true if there is at least one such flag available, otherwise it returns false. func (c *Command) HasAvailablePersistentFlags() bool { return c.PersistentFlags().HasAvailableFlags() } -// HasAvailableLocalFlags checks if the command has flags specifically declared locally which are not hidden -// or deprecated. +// HasAvailableLocalFlags returns true if the command has flags specifically declared locally which are not hidden or deprecated. func (c *Command) HasAvailableLocalFlags() bool { return c.LocalFlags().HasAvailableFlags() } -// HasAvailableInheritedFlags checks if the command has flags inherited from its parent command which are -// not hidden or deprecated. +// HasAvailableInheritedFlags returns true if the command has flags inherited from its parent command that are +// not hidden or deprecated. It checks the inherited flags using the InheritedFlags method and then filters out +// any flags that are marked as hidden or deprecated before determining if there are available flags. func (c *Command) HasAvailableInheritedFlags() bool { return c.InheritedFlags().HasAvailableFlags() } -// Flag climbs up the command tree looking for matching flag. +// Flag searches for a flag with the given name within the command and its persistent flags. +// +// It first looks up the flag in the current command's local flags using `Flags().Lookup`. +// If the flag is not found, it then checks the persistent flags using `persistentFlag`. +// +// Parameters: +// - name: The name of the flag to search for. +// +// Returns: +// - flag: A pointer to the found flag if it exists; otherwise, nil. func (c *Command) Flag(name string) (flag *flag.Flag) { flag = c.Flags().Lookup(name) @@ -1851,7 +2028,16 @@ func (c *Command) Flag(name string) (flag *flag.Flag) { return } -// Recursively find matching persistent flag. +// persistentFlag recursively finds the matching persistent flag. +// +// It first checks if the current command has persistent flags and looks for the flag in them. +// If the flag is not found, it updates the parents' persistent flags and searches again. +// +// Parameters: +// - name: The name of the flag to find. +// +// Returns: +// - flag: A pointer to the matching flag if found; otherwise, nil. func (c *Command) persistentFlag(name string) (flag *flag.Flag) { if c.HasPersistentFlags() { flag = c.PersistentFlags().Lookup(name) @@ -1864,7 +2050,16 @@ func (c *Command) persistentFlag(name string) (flag *flag.Flag) { return } -// ParseFlags parses persistent flag tree and local flags. +// ParseFlags parses persistent flag tree and local flags, merging them into a single set and parsing the provided arguments. +// +// Parameters: +// - args: A slice of strings representing command-line arguments to be parsed. +// +// Returns: +// - error: An error if parsing fails, nil otherwise. +// +// Errors: +// - May return an error from the flag package or custom errors defined in the application. func (c *Command) ParseFlags(args []string) error { if c.DisableFlagParsing { return nil @@ -1888,22 +2083,20 @@ func (c *Command) ParseFlags(args []string) error { return err } -// Parent returns a commands parent command. +// Parent returns the parent command of the current command. If there is no parent, it returns nil. func (c *Command) Parent() *Command { return c.parent } -// mergePersistentFlags merges c.PersistentFlags() to c.Flags() -// and adds missing persistent flags of all parents. +// Command represents a command in the application. func (c *Command) mergePersistentFlags() { c.updateParentsPflags() c.Flags().AddFlagSet(c.PersistentFlags()) c.Flags().AddFlagSet(c.parentsPflags) } -// updateParentsPflags updates c.parentsPflags by adding -// new persistent flags of all parents. -// If c.parentsPflags == nil, it makes new. +// updateParentsPflags updates the flag set of the command and its parents by adding new persistent flags. +// If no parent flags have been initialized, it creates a new flag set with the current command's display name and error output. func (c *Command) updateParentsPflags() { if c.parentsPflags == nil { c.parentsPflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError) @@ -1922,9 +2115,7 @@ func (c *Command) updateParentsPflags() { }) } -// commandNameMatches checks if two command names are equal -// taking into account case sensitivity according to -// EnableCaseInsensitive global configuration. +// commandNameMatches checks if two command names are equal, taking into account case sensitivity according to the EnableCaseInsensitive global configuration. It returns true if the command names match, false otherwise. func commandNameMatches(s string, t string) bool { if EnableCaseInsensitive { return strings.EqualFold(s, t) @@ -1971,6 +2162,16 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e ` // defaultUsageFunc is equivalent to executing defaultUsageTemplate. The two should be changed in sync. +// +// It takes an io.Writer and an interface{} as input, where the interface{} must be a pointer to a Command struct. +// It writes usage information to the io.Writer based on the properties of the Command. +// +// Parameters: +// - w: An io.Writer to write the output to. +// - in: An interface{} that should be a pointer to a Command struct. +// +// Returns: +// - error: If an error occurs during the execution, it returns an error. Otherwise, it returns nil. func defaultUsageFunc(w io.Writer, in interface{}) error { c := in.(*Command) fmt.Fprint(w, "Usage:") @@ -2044,6 +2245,16 @@ var defaultHelpTemplate = `{{with (or .Long .Short)}}{{. | trimTrailingWhitespac {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` // defaultHelpFunc is equivalent to executing defaultHelpTemplate. The two should be changed in sync. +// +// It takes an io.Writer and an interface{} as parameters. The interface{} parameter is expected to be a *Command. +// +// The function retrieves the usage information from the Command, prioritizing Long over Short if both are provided. +// It trims any trailing spaces from the usage text. +// +// If the usage text is not empty, it writes the usage text followed by a newline to the io.Writer. +// If the Command is runnable or has subcommands, it appends the usage string of the Command to the io.Writer. +// +// The function returns nil if successful, or an error if any occurs during execution. func defaultHelpFunc(w io.Writer, in interface{}) error { c := in.(*Command) usage := c.Long @@ -2065,6 +2276,12 @@ var defaultVersionTemplate = `{{with .DisplayName}}{{printf "%s " .}}{{end}}{{pr ` // defaultVersionFunc is equivalent to executing defaultVersionTemplate. The two should be changed in sync. +// It writes the version information of a command to the provided writer. +// Parameters: +// - w: An io.Writer to which the version information will be written. +// - in: An interface containing a pointer to a Command object from which the display name and version are retrieved. +// Returns: +// - error: An error if writing to the writer fails, otherwise nil. func defaultVersionFunc(w io.Writer, in interface{}) error { c := in.(*Command) _, err := fmt.Fprintf(w, "%s version %s\n", c.DisplayName(), c.Version) diff --git a/command_test.go b/command_test.go index 156df9eb..ac327cb5 100644 --- a/command_test.go +++ b/command_test.go @@ -27,13 +27,28 @@ import ( "github.com/spf13/pflag" ) +// emptyRun does nothing and is used as a placeholder or default function. It takes a pointer to a Command struct and a slice of strings as parameters but does not return any value. func emptyRun(*Command, []string) {} +// ExecuteCommand executes a command with the given arguments and returns its output. +// +// Parameters: +// - root: The root command to execute. +// - args: Additional arguments to pass to the command. +// +// Returns: +// - output: The output of the executed command. +// - err: An error if the command execution fails. func executeCommand(root *Command, args ...string) (output string, err error) { _, output, err = executeCommandC(root, args...) return output, err } +// executeCommandWithContext executes the given command in the context of a specific context. +// It sets up the command's output and error buffers, applies the provided arguments, +// and runs the command within the given context. The function returns the combined output +// from stdout and stderr as a string, along with any errors that occur during execution. +// The command is expected to be properly configured beforehand, with its out, err, and args set appropriately. func executeCommandWithContext(ctx context.Context, root *Command, args ...string) (output string, err error) { buf := new(bytes.Buffer) root.SetOut(buf) @@ -45,6 +60,17 @@ func executeCommandWithContext(ctx context.Context, root *Command, args ...strin return buf.String(), err } +// executeCommandC executes the given command with the provided arguments and returns the resulting command object, output, and any error that occurs. +// The function sets the standard output and error to a buffer and captures the output during execution. It then calls ExecuteC on the root command with the specified arguments. +// +// Parameters: +// - root: A pointer to the Command object to be executed. +// - args: A variadic slice of strings representing the arguments to pass to the command. +// +// Returns: +// - c: A pointer to the Command object that was executed. +// - output: A string containing the captured standard output and error from the execution. +// - err: An error if an error occurred during execution, otherwise nil. func executeCommandC(root *Command, args ...string) (c *Command, output string, err error) { buf := new(bytes.Buffer) root.SetOut(buf) @@ -56,6 +82,18 @@ func executeCommandC(root *Command, args ...string) (c *Command, output string, return c, buf.String(), err } +// executeCommandWithContextC executes the given command with the specified context and arguments. +// It captures both the output and any errors produced by the command execution. +// +// Parameters: +// - ctx: The context to use for executing the command, allowing for cancellation or timeouts. +// - root: The root command to be executed. +// - args: Additional arguments to pass to the command. +// +// Returns: +// - c: The command that was executed. +// - output: The captured standard output and error of the command execution. +// - err: Any error encountered during the execution of the command. func executeCommandWithContextC(ctx context.Context, root *Command, args ...string) (c *Command, output string, err error) { buf := new(bytes.Buffer) root.SetOut(buf) @@ -67,16 +105,21 @@ func executeCommandWithContextC(ctx context.Context, root *Command, args ...stri return c, buf.String(), err } +// ResetCommandLineFlagSet resets the command line flag set to a new one with os.Args[0] as the program name and ExitOnError as the error handling policy. This is useful for reinitializing flags in tests or when flags need to be reset between runs. func resetCommandLineFlagSet() { pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) } +// checkStringContains checks if the 'got' string contains the 'expected' substring. +// If not, it logs an error message indicating the mismatch between the expected and actual strings. func checkStringContains(t *testing.T, got, expected string) { if !strings.Contains(got, expected) { t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got) } } +// checkStringOmits checks if the `got` string contains the `expected` substring. +// If it does, the test fails with an error message indicating what was expected and what was got. func checkStringOmits(t *testing.T, got, expected string) { if strings.Contains(got, expected) { t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got) @@ -85,6 +128,11 @@ func checkStringOmits(t *testing.T, got, expected string) { const onetwo = "one two" +// TestSingleCommand tests the execution of a single command within a root command. +// +// It sets up a root command with two subcommands "a" and "b". The root command expects exactly two arguments. +// After executing the command with arguments "one" and "two", it checks if there is no unexpected output, error, +// and if the arguments passed to the root command are as expected. func TestSingleCommand(t *testing.T) { var rootCmdArgs []string rootCmd := &Command{ @@ -110,6 +158,8 @@ func TestSingleCommand(t *testing.T) { } } +// TestChildCommand tests the behavior of the root command with a child command that takes exactly two arguments. +// It verifies that the child command receives the correct arguments and no unexpected output or error is returned. func TestChildCommand(t *testing.T) { var child1CmdArgs []string rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} @@ -135,6 +185,7 @@ func TestChildCommand(t *testing.T) { } } +// TestCallCommandWithoutSubcommands tests the scenario where a command is called without any subcommands. It asserts that there should be no errors when calling such a command. func TestCallCommandWithoutSubcommands(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} _, err := executeCommand(rootCmd) @@ -143,6 +194,9 @@ func TestCallCommandWithoutSubcommands(t *testing.T) { } } +// TestRootExecuteUnknownCommand tests the execution of an unknown command in a root command hierarchy. +// It creates a root command with a child command and attempts to execute an unknown command. +// The function checks if the output contains the expected error message for an unknown command. func TestRootExecuteUnknownCommand(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -156,6 +210,9 @@ func TestRootExecuteUnknownCommand(t *testing.T) { } } +// TestSubcommandExecuteC tests the execution of a subcommand using the Command type. +// It sets up a root command with a child command and asserts that executing the child command +// does not produce any output, no error is returned, and the correct command name is returned. func TestSubcommandExecuteC(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -174,6 +231,7 @@ func TestSubcommandExecuteC(t *testing.T) { } } +// TestExecuteContext tests the execution of commands with a context. func TestExecuteContext(t *testing.T) { ctx := context.TODO() @@ -203,6 +261,7 @@ func TestExecuteContext(t *testing.T) { } } +// TestExecuteContextC tests the ExecuteContext method with a context and verifies that all commands have the correct context. func TestExecuteContextC(t *testing.T) { ctx := context.TODO() @@ -232,6 +291,7 @@ func TestExecuteContextC(t *testing.T) { } } +// TestExecute_NoContext tests the Execute function without a context. func TestExecute_NoContext(t *testing.T) { run := func(cmd *Command, args []string) { if cmd.Context() != context.Background() { @@ -259,6 +319,7 @@ func TestExecute_NoContext(t *testing.T) { } } +// TestRootUnknownCommandSilenced tests that unknown commands are handled correctly when errors and usage are silenced. func TestRootUnknownCommandSilenced(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.SilenceErrors = true @@ -271,6 +332,7 @@ func TestRootUnknownCommandSilenced(t *testing.T) { } } +// TestCommandAlias tests the functionality of adding a command with aliases to a root command. func TestCommandAlias(t *testing.T) { var timesCmdArgs []string rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} @@ -302,6 +364,10 @@ func TestCommandAlias(t *testing.T) { } } +// TestEnablePrefixMatching tests the functionality of enabling prefix matching for commands. +// It checks if the command arguments are correctly captured when prefix matching is enabled. +// The test asserts that there is no unexpected output or error, and that the captured arguments match the expected values. +// After the test, it resets the `EnablePrefixMatching` to its default value. func TestEnablePrefixMatching(t *testing.T) { EnablePrefixMatching = true @@ -331,6 +397,7 @@ func TestEnablePrefixMatching(t *testing.T) { EnablePrefixMatching = defaultPrefixMatching } +// TestAliasPrefixMatching tests the alias prefix matching feature of commands. func TestAliasPrefixMatching(t *testing.T) { EnablePrefixMatching = true @@ -366,9 +433,10 @@ func TestAliasPrefixMatching(t *testing.T) { EnablePrefixMatching = defaultPrefixMatching } -// TestPlugin checks usage as plugin for another command such as kubectl. The -// executable is `kubectl-plugin`, but we run it as `kubectl plugin`. The help -// text should reflect the way we run the command. +// TestPlugin checks the usage of a plugin command with another command like `kubectl`. The +// executable name is `kubectl-plugin`, but it's executed as `kubectl plugin`. The help text should +// reflect how the command is invoked. It verifies that the output contains specific strings indicating +// the correct usage and annotations. func TestPlugin(t *testing.T) { cmd := &Command{ Use: "kubectl-plugin", @@ -390,7 +458,7 @@ func TestPlugin(t *testing.T) { checkStringContains(t, cmdHelp, "version for kubectl plugin") } -// TestPluginWithSubCommands checks usage as plugin with sub commands. +// TestPluginWithSubCommands checks usage as a plugin with subcommands. func TestPluginWithSubCommands(t *testing.T) { rootCmd := &Command{ Use: "kubectl-plugin", @@ -459,9 +527,9 @@ func TestChildSameName(t *testing.T) { } } -// TestGrandChildSameName checks the correct behaviour of cobra in cases, -// when user has a root command and a grand child -// with the same name. +// TestGrandChildSameName checks the correct behavior of cobra in cases where +// a user has a root command and a grandchild with the same name. It ensures that +// the execution is correctly routed to the intended child command. func TestGrandChildSameName(t *testing.T) { var fooCmdArgs []string rootCmd := &Command{Use: "foo", Args: NoArgs, Run: emptyRun} @@ -488,6 +556,7 @@ func TestGrandChildSameName(t *testing.T) { } } +// TestFlagLong tests the handling of long flags in a command. func TestFlagLong(t *testing.T) { var cArgs []string c := &Command{ @@ -525,6 +594,9 @@ func TestFlagLong(t *testing.T) { } } +// TestFlagShort tests the functionality of short flag parsing in a Command. +// It sets up a command with integer and string flags, executes it with specific arguments, +// and verifies that the flags are parsed correctly and no unexpected output or error is returned. func TestFlagShort(t *testing.T) { var cArgs []string c := &Command{ @@ -559,6 +631,7 @@ func TestFlagShort(t *testing.T) { } } +// TestChildFlag tests the functionality of a child command with an integer flag. func TestChildFlag(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -580,6 +653,11 @@ func TestChildFlag(t *testing.T) { } } +// TestChildFlagWithParentLocalFlag tests the behavior of child command flags when parent has a local flag with the same shorthand. +// +// It creates a root command and a child command, adds the child to the root, sets up string and integer flags on both commands, +// executes the command with specific arguments, and verifies that an error is returned due to a flag conflict. Additionally, +// it checks that the integer flag value from the child command is set correctly. func TestChildFlagWithParentLocalFlag(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -601,6 +679,8 @@ func TestChildFlagWithParentLocalFlag(t *testing.T) { } } +// TestFlagInvalidInput tests the behavior of the command when provided with an invalid integer flag value. +// It ensures that the function correctly identifies and returns an error for invalid input. func TestFlagInvalidInput(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.Flags().IntP("intf", "i", -1, "") @@ -613,6 +693,7 @@ func TestFlagInvalidInput(t *testing.T) { checkStringContains(t, err.Error(), "invalid syntax") } +// TestFlagBeforeCommand tests the behavior of flags when specified before the command. func TestFlagBeforeCommand(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -640,6 +721,13 @@ func TestFlagBeforeCommand(t *testing.T) { } } +// TestStripFlags runs a series of tests to verify that the stripFlags function correctly removes flags from an input slice of strings. +// +// It creates a Command instance with persistent and local flags. Each test case provides an input slice of strings representing command-line arguments and expected output after flag removal. +// +// The function iterates over each test case, calls stripFlags with the input, and compares the result to the expected output. If the results do not match, it logs an error indicating the test case number, expected output, and actual output. +// +// This test ensures that the stripFlags function is correctly identifying and removing flags based on their prefixes and names. func TestStripFlags(t *testing.T) { tests := []struct { input []string @@ -713,6 +801,8 @@ func TestStripFlags(t *testing.T) { } } +// TestDisableFlagParsing tests the functionality of disabling flag parsing in a command. +// It creates a new Command with DisableFlagParsing set to true and verifies that the Run function receives all arguments without flag parsing. func TestDisableFlagParsing(t *testing.T) { var cArgs []string c := &Command{ @@ -737,6 +827,10 @@ func TestDisableFlagParsing(t *testing.T) { } } +// TestPersistentFlagsOnSameCommand tests the behavior of persistent flags when used on the same command. +// +// It creates a root command with a persistent integer flag and runs it with specific arguments. +// The function verifies that the flag value is correctly set and that the command arguments are as expected. func TestPersistentFlagsOnSameCommand(t *testing.T) { var rootCmdArgs []string rootCmd := &Command{ @@ -765,8 +859,7 @@ func TestPersistentFlagsOnSameCommand(t *testing.T) { } } -// TestEmptyInputs checks, -// if flags correctly parsed with blank strings in args. +// TestEmptyInputs checks if flags are correctly parsed with blank strings in args. func TestEmptyInputs(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} @@ -786,6 +879,7 @@ func TestEmptyInputs(t *testing.T) { } } +// TestChildFlagShadowsParentPersistentFlag tests if a child command's flags shadow parent persistent flags. func TestChildFlagShadowsParentPersistentFlag(t *testing.T) { parent := &Command{Use: "parent", Run: emptyRun} child := &Command{Use: "child", Run: emptyRun} @@ -815,6 +909,7 @@ func TestChildFlagShadowsParentPersistentFlag(t *testing.T) { } } +// TestPersistentFlagsOnChild tests that persistent flags set on the root command are available to its child commands. func TestPersistentFlagsOnChild(t *testing.T) { var childCmdArgs []string rootCmd := &Command{Use: "root", Run: emptyRun} @@ -850,6 +945,7 @@ func TestPersistentFlagsOnChild(t *testing.T) { } } +// TestRequiredFlags checks that required flags are enforced when running a command. func TestRequiredFlags(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.Flags().String("foo1", "", "") @@ -868,6 +964,7 @@ func TestRequiredFlags(t *testing.T) { } } +// TestPersistentRequiredFlags tests the marking of persistent and local flags as required in a command hierarchy. func TestPersistentRequiredFlags(t *testing.T) { parent := &Command{Use: "parent", Run: emptyRun} parent.PersistentFlags().String("foo1", "", "") @@ -893,6 +990,7 @@ func TestPersistentRequiredFlags(t *testing.T) { } } +// TestPersistentRequiredFlagsWithDisableFlagParsing tests that a required persistent flag does not break commands that disable flag parsing. func TestPersistentRequiredFlagsWithDisableFlagParsing(t *testing.T) { // Make sure a required persistent flag does not break // commands that disable flag parsing @@ -924,6 +1022,13 @@ func TestPersistentRequiredFlagsWithDisableFlagParsing(t *testing.T) { } } +// TestInitHelpFlagMergesFlags verifies that when InitDefaultHelpFlag is called on a child command, it merges the help flag from its parent command. +// +// Parameters: +// - t: A pointer to testing.T for test assertions. +// +// Returns: +// None. The function uses t.Errorf to report failures. func TestInitHelpFlagMergesFlags(t *testing.T) { usage := "custom flag" rootCmd := &Command{Use: "root"} @@ -938,6 +1043,8 @@ func TestInitHelpFlagMergesFlags(t *testing.T) { } } +// TestHelpCommandExecuted tests that the 'help' command is executed correctly. +// It verifies that the output contains the long description of the root command. func TestHelpCommandExecuted(t *testing.T) { rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -950,6 +1057,7 @@ func TestHelpCommandExecuted(t *testing.T) { checkStringContains(t, output, rootCmd.Long) } +// TestHelpCommandExecutedOnChild tests that the help command is executed on a child command. func TestHelpCommandExecutedOnChild(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Long: "Long description", Run: emptyRun} @@ -963,6 +1071,7 @@ func TestHelpCommandExecutedOnChild(t *testing.T) { checkStringContains(t, output, childCmd.Long) } +// TestHelpCommandExecutedOnChildWithFlagThatShadowsParentFlag tests that the help command executed on a child command shows the child's flags and not the parent's shadowed flags. func TestHelpCommandExecutedOnChildWithFlagThatShadowsParentFlag(t *testing.T) { parent := &Command{Use: "parent", Run: emptyRun} child := &Command{Use: "child", Run: emptyRun} @@ -995,6 +1104,10 @@ Global Flags: } } +// TestSetHelpCommand tests the SetHelpCommand method of the Command struct. +// +// It creates a new command and sets its help command. The help command runs a specific function that prints an expected string. +// The test then executes the help command and checks if the output contains the expected string, asserting no errors occur during execution. func TestSetHelpCommand(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.AddCommand(&Command{Use: "empty", Run: emptyRun}) @@ -1018,6 +1131,10 @@ func TestSetHelpCommand(t *testing.T) { } } +// TestSetHelpTemplate tests the SetHelpTemplate method of the Command struct. +// +// It verifies that setting a custom help template on a root command and its child commands works as expected. +// It also checks that the default help template is used when no custom template is set. func TestSetHelpTemplate(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -1061,6 +1178,11 @@ func TestSetHelpTemplate(t *testing.T) { } } +// TestHelpFlagExecuted tests if the help flag is executed correctly. +// It creates a root command with a long description and an empty run function. +// The test then executes the command with the "--help" flag and checks if the output contains the long description. +// If any error occurs during execution, it reports the error using the test's Errorf method. +// Additionally, it uses the checkStringContains helper function to verify that the output string contains the expected substring. func TestHelpFlagExecuted(t *testing.T) { rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun} @@ -1072,6 +1194,9 @@ func TestHelpFlagExecuted(t *testing.T) { checkStringContains(t, output, rootCmd.Long) } +// TestHelpFlagExecutedOnChild tests whether the help flag is executed on a child command. +// It sets up a root command with a child command and executes the child command with the "--help" flag. +// The function then checks if the output contains the long description of the child command. func TestHelpFlagExecutedOnChild(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Long: "Long description", Run: emptyRun} @@ -1085,10 +1210,10 @@ func TestHelpFlagExecutedOnChild(t *testing.T) { checkStringContains(t, output, childCmd.Long) } -// TestHelpFlagInHelp checks, -// if '--help' flag is shown in help for child (executing `parent help child`), -// that has no other flags. -// Related to https://github.com/spf13/cobra/issues/302. +// TestHelpFlagInHelp verifies that the '--help' flag is displayed in the help output for a child command when using `parent help child`. +// +// The test ensures that the Cobra library correctly handles the display of the '--help' flag in nested command structures. +// This addresses an issue reported in https://github.com/spf13/cobra/issues/302, ensuring proper functionality and user experience. func TestHelpFlagInHelp(t *testing.T) { parentCmd := &Command{Use: "parent", Run: func(*Command, []string) {}} @@ -1103,6 +1228,7 @@ func TestHelpFlagInHelp(t *testing.T) { checkStringContains(t, output, "[flags]") } +// TestFlagsInUsage tests if the command usage includes flags section. func TestFlagsInUsage(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}} output, err := executeCommand(rootCmd, "--help") @@ -1113,6 +1239,7 @@ func TestFlagsInUsage(t *testing.T) { checkStringContains(t, output, "[flags]") } +// TestHelpExecutedOnNonRunnableChild tests the behavior of help when executed on a non-runnable child command. func TestHelpExecutedOnNonRunnableChild(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Long: "Long description"} @@ -1126,6 +1253,9 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) { checkStringContains(t, output, childCmd.Long) } +// TestSetUsageTemplate tests the functionality of SetUsageTemplate method. +// It verifies that setting a custom usage template on a command and its child commands works as expected. +// It also checks that resetting the template to empty falls back to the default usage format. func TestSetUsageTemplate(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -1165,6 +1295,10 @@ func TestSetUsageTemplate(t *testing.T) { } } +// TestVersionFlagExecuted tests that the --version flag is executed correctly. +// +// It creates a root command with a specific version and an empty run function. +// The function then executes the command with the --version flag and checks if the output contains the expected version string. func TestVersionFlagExecuted(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} @@ -1176,6 +1310,7 @@ func TestVersionFlagExecuted(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +// TestVersionFlagExecutedDiplayName tests if the version flag executed with a display name. func TestVersionFlagExecutedDiplayName(t *testing.T) { rootCmd := &Command{ Use: "kubectl-plugin", @@ -1194,6 +1329,11 @@ func TestVersionFlagExecutedDiplayName(t *testing.T) { checkStringContains(t, output, "kubectl plugin version 1.0.0") } +// TestVersionFlagExecutedWithNoName tests that the version flag is executed when no name is provided. +// +// It creates a root command with a version and an empty run function. It then executes the command with the --version flag and "arg1". +// +// The function asserts that there is no error returned and that the output contains the string "version 1.0.0". func TestVersionFlagExecutedWithNoName(t *testing.T) { rootCmd := &Command{Version: "1.0.0", Run: emptyRun} @@ -1205,6 +1345,14 @@ func TestVersionFlagExecutedWithNoName(t *testing.T) { checkStringContains(t, output, "version 1.0.0") } +// TestShortAndLongVersionFlagInHelp tests that both the short and long version flags are present in the help output. +// +// Parameters: +// - t: A testing.T instance to which assertions can be made. +// +// This function creates a root command with a specified use case, version, and empty run function. +// It then executes the command with the "--help" flag and checks if the "-v, --version" string is present in the output. +// If an error occurs during the execution, it asserts an error with a message indicating the unexpected error. func TestShortAndLongVersionFlagInHelp(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} @@ -1216,6 +1364,13 @@ func TestShortAndLongVersionFlagInHelp(t *testing.T) { checkStringContains(t, output, "-v, --version") } +// TestLongVersionFlagOnlyInHelpWhenShortPredefined tests that the long version flag is only included in help when a short version flag is predefined. +// +// Parameters: +// - t: The testing.T instance for running the test. +// +// Returns: +// None. This function uses t.Errorf to report any unexpected errors encountered during the execution of the test. func TestLongVersionFlagOnlyInHelpWhenShortPredefined(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.Flags().StringP("foo", "v", "", "not a version flag") @@ -1229,6 +1384,9 @@ func TestLongVersionFlagOnlyInHelpWhenShortPredefined(t *testing.T) { checkStringContains(t, output, "--version") } +// TestShorthandVersionFlagExecuted tests if the shorthand version flag is executed correctly. +// It sets up a root command with a specific use and version, executes it with a version flag, +// and checks if the output contains the expected version information. func TestShorthandVersionFlagExecuted(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} @@ -1240,6 +1398,7 @@ func TestShorthandVersionFlagExecuted(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +// TestVersionTemplate tests the custom version template of a command. func TestVersionTemplate(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.SetVersionTemplate(`customized version: {{.Version}}`) @@ -1252,6 +1411,8 @@ func TestVersionTemplate(t *testing.T) { checkStringContains(t, output, "customized version: 1.0.0") } +// TestShorthandVersionTemplate tests the execution of a command with a custom version template. +// It asserts that the output contains the customized version string and no errors occur during execution. func TestShorthandVersionTemplate(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.SetVersionTemplate(`customized version: {{.Version}}`) @@ -1264,6 +1425,7 @@ func TestShorthandVersionTemplate(t *testing.T) { checkStringContains(t, output, "customized version: 1.0.0") } +// TestRootErrPrefixExecutedOnSubcommand tests whether the root command's error prefix is applied to errors returned by subcommands. func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.SetErrPrefix("root error prefix:") @@ -1277,6 +1439,8 @@ func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) { checkStringContains(t, output, "root error prefix: unknown flag: --unknown-flag") } +// TestRootAndSubErrPrefix tests the behavior of setting error prefixes on a root command and its subcommand. +// It checks if the correct error prefix is prepended when an unknown flag is used in either the root or subcommand. func TestRootAndSubErrPrefix(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} subCmd := &Command{Use: "sub", Run: emptyRun} @@ -1297,6 +1461,10 @@ func TestRootAndSubErrPrefix(t *testing.T) { } } +// TestVersionFlagExecutedOnSubcommand tests if the version flag is executed on a subcommand. +// It creates a root command with a specific version and adds a subcommand without a run function. +// It then executes the command with the --version flag followed by the subcommand name. +// Finally, it checks that the output contains the expected version information. func TestVersionFlagExecutedOnSubcommand(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0"} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) @@ -1309,6 +1477,13 @@ func TestVersionFlagExecutedOnSubcommand(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +// TestShorthandVersionFlagExecutedOnSubcommand tests if the shorthand version flag (-v) is executed when passed to a subcommand. +// +// Parameters: +// - t: A testing.T instance used for assertions and error reporting. +// +// Returns: +// - None, but performs assertions on the output and any errors encountered during command execution. func TestShorthandVersionFlagExecutedOnSubcommand(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0"} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) @@ -1321,6 +1496,9 @@ func TestShorthandVersionFlagExecutedOnSubcommand(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +// TestVersionFlagOnlyAddedToRoot checks that the --version flag is only added to the root command and not to its subcommands. +// It creates a root command with a version and adds a subcommand without the version flag. +// It then tries to execute the subcommand with the --version flag and expects an error indicating that the flag is unknown. func TestVersionFlagOnlyAddedToRoot(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) @@ -1333,6 +1511,7 @@ func TestVersionFlagOnlyAddedToRoot(t *testing.T) { checkStringContains(t, err.Error(), "unknown flag: --version") } +// TestShortVersionFlagOnlyAddedToRoot checks if the short version flag is only added to the root command. func TestShortVersionFlagOnlyAddedToRoot(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) @@ -1345,6 +1524,10 @@ func TestShortVersionFlagOnlyAddedToRoot(t *testing.T) { checkStringContains(t, err.Error(), "unknown shorthand flag: 'v' in -v") } +// TestVersionFlagOnlyExistsIfVersionNonEmpty checks if the `--version` flag is only present when the version string is non-empty. +// It creates a root command and tries to execute it with the `--version` flag, expecting an error since the version is empty. +// If no error is returned, it asserts that an error should have been returned. +// It then checks if the error message contains the expected substring "unknown flag: --version". func TestVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} @@ -1355,6 +1538,12 @@ func TestVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { checkStringContains(t, err.Error(), "unknown flag: --version") } +// TestShorthandVersionFlagOnlyExistsIfVersionNonEmpty tests that the version flag shorthand '-v' only exists if the Version field of the Command is non-empty. +// +// Parameters: +// - t: A testing.T instance used to assert test conditions. +// +// The function does not return any values. If the Version field is empty and the shorthand flag '-v' is provided, an error is expected with a specific message indicating that the shorthand flag is unknown. func TestShorthandVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} @@ -1365,6 +1554,9 @@ func TestShorthandVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { checkStringContains(t, err.Error(), "unknown shorthand flag: 'v' in -v") } +// TestShorthandVersionFlagOnlyAddedIfShorthandNotDefined tests that the shorthand version flag is only added if it's not already defined. +// +// It creates a root command with a non-version flag named "notversion" and shorthand "v". When executing the command with "-v", it should return an error because "v" is not a valid argument for the existing shorthand flag. The test checks that the shorthand lookup returns the correct flag name and that the error message contains the expected text. func TestShorthandVersionFlagOnlyAddedIfShorthandNotDefined(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun, Version: "1.2.3"} rootCmd.Flags().StringP("notversion", "v", "", "not a version flag") @@ -1377,6 +1569,10 @@ func TestShorthandVersionFlagOnlyAddedIfShorthandNotDefined(t *testing.T) { checkStringContains(t, err.Error(), "flag needs an argument: 'v' in -v") } +// TestShorthandVersionFlagOnlyAddedIfVersionNotDefined tests that the shorthand version flag is only added if a custom version flag is not defined. +// +// Parameters: +// - t: A testing.T object for running the test. func TestShorthandVersionFlagOnlyAddedIfVersionNotDefined(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun, Version: "1.2.3"} rootCmd.Flags().Bool("version", false, "a different kind of version flag") @@ -1388,6 +1584,7 @@ func TestShorthandVersionFlagOnlyAddedIfVersionNotDefined(t *testing.T) { checkStringContains(t, err.Error(), "unknown shorthand flag: 'v' in -v") } +// TestUsageIsNotPrintedTwice tests that the usage message for a command is not printed more than once. func TestUsageIsNotPrintedTwice(t *testing.T) { var cmd = &Command{Use: "root"} var sub = &Command{Use: "sub"} @@ -1399,6 +1596,7 @@ func TestUsageIsNotPrintedTwice(t *testing.T) { } } +// TestVisitParents tests the VisitParents method to ensure it correctly visits parent commands. func TestVisitParents(t *testing.T) { c := &Command{Use: "app"} sub := &Command{Use: "sub"} @@ -1428,6 +1626,10 @@ func TestVisitParents(t *testing.T) { } } +// TestSuggestions tests the command suggestion feature of the Command struct. +// It checks if the suggested commands are correctly generated based on user input, +// and ensures that suggestions are disabled when requested. The test covers various +// typo cases and expected suggestions or no suggestions at all. func TestSuggestions(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} timesCmd := &Command{ @@ -1475,6 +1677,8 @@ func TestSuggestions(t *testing.T) { } } +// TestCaseInsensitive tests the functionality of case-insensitive command matching. +// It checks if commands and their aliases are matched correctly based on the setting of EnableCaseInsensitive. func TestCaseInsensitive(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun, Aliases: []string{"alternative"}} @@ -1568,8 +1772,7 @@ func TestCaseInsensitive(t *testing.T) { EnableCaseInsensitive = defaultCaseInsensitive } -// This test make sure we keep backwards-compatibility with respect -// to command names case sensitivity behavior. +// TestCaseSensitivityBackwardCompatibility tests backward compatibility with respect to command names case sensitivity behavior. func TestCaseSensitivityBackwardCompatibility(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -1582,6 +1785,7 @@ func TestCaseSensitivityBackwardCompatibility(t *testing.T) { } +// TestRemoveCommand tests the functionality of removing a command from a parent command and ensures that attempting to execute it raises an error. func TestRemoveCommand(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun} @@ -1594,6 +1798,10 @@ func TestRemoveCommand(t *testing.T) { } } +// TestReplaceCommandWithRemove tests replacing a child command with another. +// It sets up a root command and two child commands, removes one child, +// and then adds another. It asserts that the removed command is not called +// while the new command is called when the child command is executed. func TestReplaceCommandWithRemove(t *testing.T) { childUsed := 0 rootCmd := &Command{Use: "root", Run: emptyRun} @@ -1625,6 +1833,8 @@ func TestReplaceCommandWithRemove(t *testing.T) { } } +// TestDeprecatedCommand tests the execution of a deprecated command and verifies that the deprecation message is displayed. +// It creates a root command with a deprecated subcommand, executes the deprecated command, and checks if the deprecation message is present in the output. func TestDeprecatedCommand(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} deprecatedCmd := &Command{ @@ -1642,6 +1852,7 @@ func TestDeprecatedCommand(t *testing.T) { checkStringContains(t, output, deprecatedCmd.Deprecated) } +// TestHooks tests the hook functionality of a command by executing it and verifying that all hooks are called with the correct arguments. func TestHooks(t *testing.T) { var ( persPreArgs string @@ -1694,6 +1905,16 @@ func TestHooks(t *testing.T) { } } +// TestPersistentHooks tests the behavior of persistent hooks in various scenarios. +// +// It enables and disables the EnableTraverseRunHooks flag to test how persistent pre-run, run, +// and post-run hooks are invoked during command execution. The function asserts that the correct +// sequence of hooks is executed based on the traversal enabled status. +// +// Parameters: +// - t: A testing.T instance used for assertions and reporting errors in tests. +// +// The function does not return any values; it executes as part of a test suite. func TestPersistentHooks(t *testing.T) { EnableTraverseRunHooks = true testPersistentHooks(t, []string{ @@ -1716,6 +1937,11 @@ func TestPersistentHooks(t *testing.T) { }) } +// testPersistentHooks tests the execution order of persistent hooks in a command hierarchy. +// It validates that the expected hook run order is followed and that no unexpected output or errors occur. +// Parameters: +// - t: A testing.T instance for reporting errors. +// - expectedHookRunOrder: A slice of strings representing the expected order in which hooks should be run. func testPersistentHooks(t *testing.T, expectedHookRunOrder []string) { var hookRunOrder []string @@ -1785,7 +2011,8 @@ func testPersistentHooks(t *testing.T, expectedHookRunOrder []string) { } } -// Related to https://github.com/spf13/cobra/issues/521. +// TestGlobalNormFuncPropagation tests that setting a global normalization function on a parent command propagates to its child commands. +// It uses a custom normalization function and verifies that it is correctly applied to both the parent and child commands. func TestGlobalNormFuncPropagation(t *testing.T) { normFunc := func(f *pflag.FlagSet, name string) pflag.NormalizedName { return pflag.NormalizedName(name) @@ -1805,7 +2032,7 @@ func TestGlobalNormFuncPropagation(t *testing.T) { } } -// Related to https://github.com/spf13/cobra/issues/521. +// TestNormPassedOnLocal tests if the normalization function is passed to the local flag set. func TestNormPassedOnLocal(t *testing.T) { toUpper := func(f *pflag.FlagSet, name string) pflag.NormalizedName { return pflag.NormalizedName(strings.ToUpper(name)) @@ -1819,7 +2046,8 @@ func TestNormPassedOnLocal(t *testing.T) { } } -// Related to https://github.com/spf13/cobra/issues/521. +// TestNormPassedOnInherited tests that a normalization function is passed on to inherited flag sets when adding commands before and after flags. It ensures that the normalization logic is applied consistently across different commands and their flag sets. +// The test creates a root command with a custom normalization function, adds two child commands, and verifies that the normalization function affects the flag names in both child commands' inherited flag sets. func TestNormPassedOnInherited(t *testing.T) { toUpper := func(f *pflag.FlagSet, name string) pflag.NormalizedName { return pflag.NormalizedName(strings.ToUpper(name)) @@ -1847,7 +2075,7 @@ func TestNormPassedOnInherited(t *testing.T) { } } -// Related to https://github.com/spf13/cobra/issues/521. +// TestConsistentNormalizedName tests that setting different normalization functions does not lead to duplicate flags. It verifies that the global normalization function takes precedence and prevents creation of a flag with an already normalized name. func TestConsistentNormalizedName(t *testing.T) { toUpper := func(f *pflag.FlagSet, name string) pflag.NormalizedName { return pflag.NormalizedName(strings.ToUpper(name)) @@ -1866,6 +2094,8 @@ func TestConsistentNormalizedName(t *testing.T) { } } +// TestFlagOnPflagCommandLine tests if a pflag is added to the command line. +// It checks if the specified flag appears in the help output of the command. func TestFlagOnPflagCommandLine(t *testing.T) { flagName := "flagOnCommandLine" pflag.String(flagName, "", "about my flag") @@ -1879,8 +2109,7 @@ func TestFlagOnPflagCommandLine(t *testing.T) { resetCommandLineFlagSet() } -// TestHiddenCommandExecutes checks, -// if hidden commands run as intended. +// TestHiddenCommandExecutes checks if hidden commands run as intended. func TestHiddenCommandExecutes(t *testing.T) { executed := false c := &Command{ @@ -1902,7 +2131,7 @@ func TestHiddenCommandExecutes(t *testing.T) { } } -// test to ensure hidden commands do not show up in usage/help text +// TestHiddenCommandIsHidden tests that hidden commands do not appear in the usage or help text. func TestHiddenCommandIsHidden(t *testing.T) { c := &Command{Use: "c", Hidden: true, Run: emptyRun} if c.IsAvailableCommand() { @@ -1910,6 +2139,7 @@ func TestHiddenCommandIsHidden(t *testing.T) { } } +// TestCommandsAreSorted tests that commands are sorted alphabetically when EnableCommandSorting is enabled. func TestCommandsAreSorted(t *testing.T) { EnableCommandSorting = true @@ -1932,6 +2162,8 @@ func TestCommandsAreSorted(t *testing.T) { EnableCommandSorting = defaultCommandSorting } +// TestEnableCommandSortingIsDisabled tests the scenario where command sorting is disabled. +// It ensures that commands are added in the order they were created without any sorting applied. func TestEnableCommandSortingIsDisabled(t *testing.T) { EnableCommandSorting = false @@ -1953,6 +2185,7 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) { EnableCommandSorting = defaultCommandSorting } +// TestUsageWithGroup tests the usage of a root command with groups and ensures that the help output is correctly grouped. func TestUsageWithGroup(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} rootCmd.CompletionOptions.DisableDefaultCmd = true @@ -1974,6 +2207,7 @@ func TestUsageWithGroup(t *testing.T) { checkStringContains(t, output, "\ngroup2\n cmd2") } +// TestUsageHelpGroup tests the usage of the help command with groups. func TestUsageHelpGroup(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} rootCmd.CompletionOptions.DisableDefaultCmd = true @@ -1992,6 +2226,7 @@ func TestUsageHelpGroup(t *testing.T) { checkStringContains(t, output, "\ngroup\n help") } +// TestUsageCompletionGroup tests the usage of command completion groups in a root command. func TestUsageCompletionGroup(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} @@ -2012,6 +2247,7 @@ func TestUsageCompletionGroup(t *testing.T) { checkStringContains(t, output, "\ngroup\n completion") } +// TestUngroupedCommand tests the behavior of a root command with an ungrouped command. func TestUngroupedCommand(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} @@ -2034,6 +2270,7 @@ func TestUngroupedCommand(t *testing.T) { checkStringContains(t, output, "\nAdditional Commands:\n yyy") } +// TestAddGroup tests the functionality of adding a group and a command to a root command. func TestAddGroup(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} @@ -2048,6 +2285,8 @@ func TestAddGroup(t *testing.T) { checkStringContains(t, output, "\nTest group\n cmd") } +// TestWrongGroupFirstLevel tests the scenario where a command is added to a non-existent group. +// It verifies that the system panics when attempting to run a command with an invalid group ID. func TestWrongGroupFirstLevel(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} @@ -2066,6 +2305,14 @@ func TestWrongGroupFirstLevel(t *testing.T) { } } +// TestWrongGroupNestedLevel tests the behavior of adding a command to a non-existent group within a nested command structure. +// +// It sets up a root command with a child command and attempts to add a new command to a non-existent group. The test expects +// the code to panic when trying to execute the command, as the group does not exist. The test also verifies that an unexpected +// error is not returned when attempting to execute the command. +// +// Parameters: +// - t: A testing.T instance for reporting test results. func TestWrongGroupNestedLevel(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} var childCmd = &Command{Use: "child", Run: emptyRun} @@ -2086,6 +2333,9 @@ func TestWrongGroupNestedLevel(t *testing.T) { } } +// TestWrongGroupForHelp tests the scenario where a command help is requested using a non-existent group ID. +// It sets up a root command with a child command and attempts to set an invalid help command group ID. +// The test expects a panic due to the missing group and confirms that no error occurs during command execution. func TestWrongGroupForHelp(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} var childCmd = &Command{Use: "child", Run: emptyRun} @@ -2106,6 +2356,9 @@ func TestWrongGroupForHelp(t *testing.T) { } } +// TestWrongGroupForCompletion verifies that the system reacts correctly when setting a completion command group ID that does not exist. +// It creates a root command with a child command and attempts to set an invalid completion group ID, expecting a panic. +// If no panic occurs, it asserts an error. If an error is returned, it is unexpected and causes a test failure. func TestWrongGroupForCompletion(t *testing.T) { var rootCmd = &Command{Use: "root", Short: "test", Run: emptyRun} var childCmd = &Command{Use: "child", Run: emptyRun} @@ -2126,6 +2379,9 @@ func TestWrongGroupForCompletion(t *testing.T) { } } +// TestSetOutput tests the behavior of Setting the output to nil and verifying it reverts to stdout. +// It creates a new Command instance, sets its output to nil, and checks if calling OutOrStdout() +// returns os.Stdout as expected. If not, it fails the test with an error message. func TestSetOutput(t *testing.T) { c := &Command{} c.SetOutput(nil) @@ -2134,6 +2390,8 @@ func TestSetOutput(t *testing.T) { } } +// TestSetOut tests the behavior of setting the output to nil in a Command instance and verifies that it reverts back to standard output. +// It uses a testing.T instance to perform assertions. func TestSetOut(t *testing.T) { c := &Command{} c.SetOut(nil) @@ -2142,6 +2400,8 @@ func TestSetOut(t *testing.T) { } } +// TestSetErr tests the behavior of the SetErr method. +// It verifies that setting an error to nil reverts the Command instance's ErrOrStderr() output to os.Stderr. func TestSetErr(t *testing.T) { c := &Command{} c.SetErr(nil) @@ -2150,6 +2410,9 @@ func TestSetErr(t *testing.T) { } } +// TestSetIn tests the SetIn method of Command. +// +// It sets the input of a command to nil and checks if it reverts back to stdin. If not, it fails the test. func TestSetIn(t *testing.T) { c := &Command{} c.SetIn(nil) @@ -2158,6 +2421,8 @@ func TestSetIn(t *testing.T) { } } +// TestUsageStringRedirected tests the functionality of capturing both standard output and standard error in UsageString. +// It ensures that when multiple Print and PrintErr calls are made, they are consolidated into a single UsageString. func TestUsageStringRedirected(t *testing.T) { c := &Command{} @@ -2174,6 +2439,7 @@ func TestUsageStringRedirected(t *testing.T) { } } +// TestCommandPrintRedirection tests the print redirection functionality of the Command struct. func TestCommandPrintRedirection(t *testing.T) { errBuff, outBuff := bytes.NewBuffer(nil), bytes.NewBuffer(nil) root := &Command{ @@ -2215,6 +2481,9 @@ func TestCommandPrintRedirection(t *testing.T) { } } +// TestFlagErrorFunc tests the FlagErrorFunc of a Command. +// It sets a custom error function that formats an error message with "This is expected:" prefix. +// The test executes a command with an unknown flag and checks if the returned error matches the expected format. func TestFlagErrorFunc(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} @@ -2232,6 +2501,8 @@ func TestFlagErrorFunc(t *testing.T) { } } +// TestFlagErrorFuncHelp tests the FlagErrorFunc functionality by creating a command with a persistent flag and a custom error function. +// It then executes the command with both "--help" and "-h" flags to ensure they do not fail and return the expected output. func TestFlagErrorFuncHelp(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.PersistentFlags().Bool("help", false, "help for c") @@ -2264,9 +2535,8 @@ Flags: } } -// TestSortedFlags checks, -// if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false. -// Related to https://github.com/spf13/cobra/issues/404. +// TestSortedFlags checks if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false. +// This test is related to https://github.com/spf13/cobra/issues/404. func TestSortedFlags(t *testing.T) { c := &Command{} c.Flags().SortFlags = false @@ -2289,10 +2559,15 @@ func TestSortedFlags(t *testing.T) { }) } -// TestMergeCommandLineToFlags checks, -// if pflag.CommandLine is correctly merged to c.Flags() after first call -// of c.mergePersistentFlags. -// Related to https://github.com/spf13/cobra/issues/443. +// TestMergeCommandLineToFlags checks if pflag.CommandLine is correctly merged to c.Flags() after the first call of c.mergePersistentFlags. +// It verifies that flags from CommandLine are available in c.Flags(). +// This function addresses issue https://github.com/spf13/cobra/issues/443. +// +// Parameters: +// - t: The testing.T instance for running the test. +// +// Expected behavior: +// - After merging, the flag "boolflag" should be present in c.Flags(). func TestMergeCommandLineToFlags(t *testing.T) { pflag.Bool("boolflag", false, "") c := &Command{Use: "c", Run: emptyRun} @@ -2304,9 +2579,15 @@ func TestMergeCommandLineToFlags(t *testing.T) { resetCommandLineFlagSet() } -// TestUseDeprecatedFlags checks, -// if cobra.Execute() prints a message, if a deprecated flag is used. -// Related to https://github.com/spf13/cobra/issues/463. +// TestUseDeprecatedFlags checks if cobra.Execute() prints a message when a deprecated flag is used. +// The function tests the behavior of Cobra's Flag system when a deprecated flag is utilized and +// verifies that a specific deprecation message is printed in the output. This test addresses issue #463 from the Cobra repository. +// +// Parameters: +// - t: A testing.T instance to provide context for assertions and error handling during the test. +// +// Returns: +// None func TestUseDeprecatedFlags(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.Flags().BoolP("deprecated", "d", false, "deprecated flag") @@ -2319,6 +2600,20 @@ func TestUseDeprecatedFlags(t *testing.T) { checkStringContains(t, output, "This flag is deprecated") } +// TestTraverseWithParentFlags tests the Traverse method of a Command with parent flags. +// +// It creates a root command and a child command, sets up flags on both, adds the child to the root, +// and then traverses the command tree with specific flag arguments. The test checks if the traversal +// is performed correctly, including handling of flags from parent commands. +// +// Parameters: +// - t: A *testing.T instance for running the test. +// +// Returns: +// None +// +// Errors: +// This function does not return any errors directly but uses t.Errorf to report failures. func TestTraverseWithParentFlags(t *testing.T) { rootCmd := &Command{Use: "root", TraverseChildren: true} rootCmd.Flags().String("str", "", "") @@ -2341,6 +2636,12 @@ func TestTraverseWithParentFlags(t *testing.T) { } } +// TestTraverseNoParentFlags tests the Traverse method of a Command with no parent flags. +// +// It sets up a root command with a flag and a child command without any flags. The test then calls +// the Traverse method on the root command to navigate to the child command using the path "child". +// It verifies that there are no arguments returned, that the command name is correct, and that no error +// occurs during the traversal. func TestTraverseNoParentFlags(t *testing.T) { rootCmd := &Command{Use: "root", TraverseChildren: true} rootCmd.Flags().String("foo", "", "foo things") @@ -2361,6 +2662,10 @@ func TestTraverseNoParentFlags(t *testing.T) { } } +// TestTraverseWithBadParentFlags tests the behavior of the Traverse method when encountering unknown flags. +// +// It creates a root command with a child command and attempts to traverse with a bad parent flag. +// The expected error message is checked, and it's verified that no command is returned. func TestTraverseWithBadParentFlags(t *testing.T) { rootCmd := &Command{Use: "root", TraverseChildren: true} @@ -2379,6 +2684,8 @@ func TestTraverseWithBadParentFlags(t *testing.T) { } } +// TestTraverseWithBadChildFlag tests the behavior of the Traverse method when a bad flag is provided to a child command. +// It sets up a root command with a child command and expects that the Traverse method returns the correct child command and the remaining args without parsing the flags. func TestTraverseWithBadChildFlag(t *testing.T) { rootCmd := &Command{Use: "root", TraverseChildren: true} rootCmd.Flags().String("str", "", "") @@ -2400,6 +2707,7 @@ func TestTraverseWithBadChildFlag(t *testing.T) { } } +// TestTraverseWithTwoSubcommands tests the Traverse method when navigating through a command tree with two levels of subcommands. It verifies that the traversal correctly reaches the deepest subcommand and returns it without errors. func TestTraverseWithTwoSubcommands(t *testing.T) { rootCmd := &Command{Use: "root", TraverseChildren: true} @@ -2420,8 +2728,8 @@ func TestTraverseWithTwoSubcommands(t *testing.T) { } } -// TestUpdateName checks if c.Name() updates on changed c.Use. -// Related to https://github.com/spf13/cobra/pull/422#discussion_r143918343. +// TestUpdateName verifies that the Name method updates when the Use field of a Command is modified. +// This test addresses issue #422 regarding command name updating in Cobra. func TestUpdateName(t *testing.T) { c := &Command{Use: "name xyz"} originalName := c.Name() @@ -2439,6 +2747,9 @@ type calledAsTestcase struct { epm bool } +// test runs the command with the given arguments and asserts that the expected command was called. +// It uses a mock function to capture the called command and its name. +// If the expected command is not called or the CalledAs method does not return the expected value, it fails the test. func (tc *calledAsTestcase) test(t *testing.T) { defer func(ov bool) { EnablePrefixMatching = ov }(EnablePrefixMatching) EnablePrefixMatching = tc.epm @@ -2474,6 +2785,7 @@ func (tc *calledAsTestcase) test(t *testing.T) { } } +// TestCalledAs runs a series of test cases to verify the behavior of the calledAs function. func TestCalledAs(t *testing.T) { tests := map[string]calledAsTestcase{ "find/no-args": {nil, "parent", "parent", false}, @@ -2495,6 +2807,12 @@ func TestCalledAs(t *testing.T) { } } +// TestFParseErrWhitelistBackwardCompatibility tests the backward compatibility of the fparse error handling when encountering an unknown flag. +// It creates a command with a boolean flag and executes it with an unknown flag. The test expects an error indicating an unknown flag and checks if the output contains the expected error message. +// Parameters: +// - t: *testing.T, the testing environment +// Returns: +// None func TestFParseErrWhitelistBackwardCompatibility(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.Flags().BoolP("boola", "a", false, "a boolean flag") @@ -2506,6 +2824,9 @@ func TestFParseErrWhitelistBackwardCompatibility(t *testing.T) { checkStringContains(t, output, "unknown flag: --unknown") } +// TestFParseErrWhitelistSameCommand tests the behavior of FParseErrWhitelist when encountering unknown flags. +// It creates a command with a flag whitelist that allows unknown flags and attempts to execute the command with an unknown flag. +// The test expects no error to be returned, as the unknown flag is whitelisted. func TestFParseErrWhitelistSameCommand(t *testing.T) { c := &Command{ Use: "c", @@ -2522,6 +2843,9 @@ func TestFParseErrWhitelistSameCommand(t *testing.T) { } } +// TestFParseErrWhitelistParentCommand tests the FParseErrWhitelist for a parent command. +// It creates a root command with a whitelist that allows unknown flags and adds a child command with its own flags. +// The test executes the command with an unknown flag, expecting an error indicating an unknown flag. func TestFParseErrWhitelistParentCommand(t *testing.T) { root := &Command{ Use: "root", @@ -2546,6 +2870,7 @@ func TestFParseErrWhitelistParentCommand(t *testing.T) { checkStringContains(t, output, "unknown flag: --unknown") } +// TestFParseErrWhitelistChildCommand tests the FParseErrWhitelist functionality for a child command. func TestFParseErrWhitelistChildCommand(t *testing.T) { root := &Command{ Use: "root", @@ -2569,6 +2894,7 @@ func TestFParseErrWhitelistChildCommand(t *testing.T) { } } +// TestFParseErrWhitelistSiblingCommand tests the FParseErrWhitelist feature when sibling commands are involved. func TestFParseErrWhitelistSiblingCommand(t *testing.T) { root := &Command{ Use: "root", @@ -2600,6 +2926,8 @@ func TestFParseErrWhitelistSiblingCommand(t *testing.T) { checkStringContains(t, output, "unknown flag: --unknown") } +// TestSetContext tests the SetContext method of a Command struct by setting a value in the context and verifying that it can be retrieved within the Run function. +// It takes a testing.T pointer as an argument, which is used for assertion and error reporting during the test. func TestSetContext(t *testing.T) { type key struct{} val := "foobar" @@ -2625,6 +2953,7 @@ func TestSetContext(t *testing.T) { } } +// TestSetContextPreRun tests that the PreRun function sets a value in the context before running the command. func TestSetContextPreRun(t *testing.T) { type key struct{} val := "barr" @@ -2651,6 +2980,8 @@ func TestSetContextPreRun(t *testing.T) { } } +// TestSetContextPreRunOverwrite tests that setting a context with a key and value in the Run method overwrites any existing value for that key. +// It checks if the expected error is returned when trying to access the overwritten key in the context. func TestSetContextPreRunOverwrite(t *testing.T) { type key struct{} val := "blah" @@ -2672,6 +3003,7 @@ func TestSetContextPreRunOverwrite(t *testing.T) { } } +// TestSetContextPersistentPreRun tests if the PersistentPreRun function correctly sets a context value that is accessible to its child command. func TestSetContextPersistentPreRun(t *testing.T) { type key struct{} val := "barbar" @@ -2706,6 +3038,12 @@ func TestSetContextPersistentPreRun(t *testing.T) { const VersionFlag = "--version" const HelpFlag = "--help" +// TestNoRootRunCommandExecutedWithVersionSet tests that when a command without a root run function is executed with version set, the appropriate output is produced. +// +// Parameters: +// - t: A testing.T instance for running the test and reporting errors. +// +// The function sets up a Command tree with a root command that has a version but no run function. It then executes this command and checks if the output contains the long description, help flag, and version flag as expected. If any of these are missing or an error occurs during execution, the test will fail. func TestNoRootRunCommandExecutedWithVersionSet(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Long: "Long description"} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -2720,6 +3058,7 @@ func TestNoRootRunCommandExecutedWithVersionSet(t *testing.T) { checkStringContains(t, output, VersionFlag) } +// TestNoRootRunCommandExecutedWithoutVersionSet verifies that the root command is executed without a version flag set. func TestNoRootRunCommandExecutedWithoutVersionSet(t *testing.T) { rootCmd := &Command{Use: "root", Long: "Long description"} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -2734,6 +3073,7 @@ func TestNoRootRunCommandExecutedWithoutVersionSet(t *testing.T) { checkStringOmits(t, output, VersionFlag) } +// TestHelpCommandExecutedWithVersionSet tests that the help command is executed with version set. func TestHelpCommandExecutedWithVersionSet(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Long: "Long description", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -2748,6 +3088,9 @@ func TestHelpCommandExecutedWithVersionSet(t *testing.T) { checkStringContains(t, output, VersionFlag) } +// TestHelpCommandExecutedWithoutVersionSet tests the scenario where the help command is executed without a version flag set. +// +// It initializes a root command with a child command and then executes the "help" command. The function asserts that the output contains the long description of the root command and the help flag, but does not contain the version flag. func TestHelpCommandExecutedWithoutVersionSet(t *testing.T) { rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -2762,6 +3105,8 @@ func TestHelpCommandExecutedWithoutVersionSet(t *testing.T) { checkStringOmits(t, output, VersionFlag) } +// TestHelpflagCommandExecutedWithVersionSet tests that the help flag command is executed when the version flag is set. +// It verifies that the output contains the root command's long description, the help flag, and the version flag. func TestHelpflagCommandExecutedWithVersionSet(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Long: "Long description", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -2776,6 +3121,13 @@ func TestHelpflagCommandExecutedWithVersionSet(t *testing.T) { checkStringContains(t, output, VersionFlag) } +// TestHelpflagCommandExecutedWithoutVersionSet tests that the help flag is executed without a version set. +// +// Parameters: +// - t: A testing.T instance for running test assertions. +// +// Returns: +// - None func TestHelpflagCommandExecutedWithoutVersionSet(t *testing.T) { rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) @@ -2790,6 +3142,9 @@ func TestHelpflagCommandExecutedWithoutVersionSet(t *testing.T) { checkStringOmits(t, output, VersionFlag) } +// TestFind tests the Find method of the Command type. +// +// It verifies that the method correctly identifies the child command and collects any flags passed to it. func TestFind(t *testing.T) { var foo, bar string root := &Command{ @@ -2879,6 +3234,7 @@ func TestFind(t *testing.T) { } } +// TestUnknownFlagShouldReturnSameErrorRegardlessOfArgPosition tests that an unknown flag returns the same error regardless of its position in the argument list. func TestUnknownFlagShouldReturnSameErrorRegardlessOfArgPosition(t *testing.T) { testCases := [][]string{ // {"--unknown", "--namespace", "foo", "child", "--bar"}, // FIXME: This test case fails, returning the error `unknown command "foo" for "root"` instead of the expected error `unknown flag: --unknown` @@ -2922,6 +3278,13 @@ func TestUnknownFlagShouldReturnSameErrorRegardlessOfArgPosition(t *testing.T) { } } +// TestHelpFuncExecuted tests if the help function is executed correctly when called with a specific context. It checks both the content of the help text and whether the correct context is being used. +// +// Parameters: +// - t: The testing.T object for running the test. +// +// Returns: +// None func TestHelpFuncExecuted(t *testing.T) { helpText := "Long description" diff --git a/command_win.go b/command_win.go index adbef395..89c58c7a 100644 --- a/command_win.go +++ b/command_win.go @@ -27,6 +27,9 @@ import ( var preExecHookFn = preExecHook +// preExecHook runs a hook before executing a command. +// If MousetrapHelpText is not empty and the command was started by the explorer, +// it prints the help text, waits for MousetrapDisplayDuration if specified, or prompts the user to press return before exiting with status 1. func preExecHook(c *Command) { if MousetrapHelpText != "" && mousetrap.StartedByExplorer() { c.Print(MousetrapHelpText) diff --git a/completions.go b/completions.go index a1752f76..5d022524 100644 --- a/completions.go +++ b/completions.go @@ -49,6 +49,11 @@ type flagCompError struct { flagName string } +// Error returns a descriptive error message indicating that the specified flag is not supported by the given subcommand. +// Parameters: +// - e: A pointer to a flagCompError instance containing details about the unsupported flag and subcommand. +// Returns: +// - A string representing the error message. func (e *flagCompError) Error() string { return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'" } @@ -131,7 +136,14 @@ type Completion = string // CompletionFunc is a function that provides completion results. type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) -// CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format. +// CompletionWithDesc constructs a Completion object with the provided choice and description. The completion follows the TAB-delimited format where the choice is followed by a tab character and then the description. +// +// Parameters: +// - choice: A string representing the main element of the completion. +// - description: A string providing additional information about the choice. +// +// Returns: +// - Completion: A new Completion object combining the choice and description in the specified format. func CompletionWithDesc(choice string, description string) Completion { return choice + "\t" + description } @@ -141,25 +153,38 @@ func CompletionWithDesc(choice string, description string) Completion { // // This method satisfies [CompletionFunc]. // It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction]. +// +// Parameters: +// - cmd: The command instance for which file completion is being disabled. +// - args: The current arguments passed to the command. +// - toComplete: The partial string that needs completion. +// +// Returns: +// - []Completion: An empty slice, indicating no completions are available. +// - ShellCompDirectiveNoFileComp: A directive indicating that file completion should not be performed. func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) { return nil, ShellCompDirectiveNoFileComp } -// FixedCompletions can be used to create a completion function which always -// returns the same results. +// FixedCompletions can be used to create a completion function which always returns the same results. // -// This method returns a function that satisfies [CompletionFunc] -// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction]. +// This method returns a function that satisfies [CompletionFunc] and can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction]. func FixedCompletions(choices []Completion, directive ShellCompDirective) CompletionFunc { return func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) { return choices, directive } } -// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. +// RegisterFlagCompletionFunc registers a function to provide completion for a specified flag in the command. // -// You can use pre-defined completion functions such as [FixedCompletions] or [NoFileCompletions], -// or you can define your own. +// The flagName parameter is the name of the flag for which completion needs to be registered. +// The f parameter is the completion function that will be called to generate completion suggestions. +// +// If the flag does not exist, an error is returned with a message indicating that the flag does not exist. +// +// If the flag is already registered, an error is returned with a message indicating that the flag is already registered. +// +// The completion function provided must be thread-safe as it may be called concurrently from multiple goroutines. func (c *Command) RegisterFlagCompletionFunc(flagName string, f CompletionFunc) error { flag := c.Flag(flagName) if flag == nil { @@ -175,7 +200,7 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f CompletionFunc) return nil } -// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available. +// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available. It takes a flagName string as an argument and returns a CompletionFunc and a boolean indicating whether the completion function was found. If the flag does not exist, it returns nil and false. func (c *Command) GetFlagCompletionFunc(flagName string) (CompletionFunc, bool) { flag := c.Flag(flagName) if flag == nil { @@ -189,7 +214,7 @@ func (c *Command) GetFlagCompletionFunc(flagName string) (CompletionFunc, bool) return completionFunc, exists } -// Returns a string listing the different directive enabled in the specified parameter +// string returns a string listing the different directive enabled in the specified parameter. It checks each bit of the ShellCompDirective and appends the corresponding directive name to the directives slice if the bit is set. If no bits are set, it defaults to "ShellCompDirectiveDefault". If the value exceeds shellCompDirectiveMaxValue, it returns an error message indicating an unexpected value. func (d ShellCompDirective) string() string { var directives []string if d&ShellCompDirectiveError != 0 { @@ -566,6 +591,8 @@ func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCo return finalCmd, completions, directive, nil } +// helpOrVersionFlagPresent checks if either the "version" or "help" flag is present and has been changed. +// It returns true if either flag is set, otherwise false. func helpOrVersionFlagPresent(cmd *Command) bool { if versionFlag := cmd.Flags().Lookup("version"); versionFlag != nil && len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed { @@ -578,6 +605,10 @@ func helpOrVersionFlagPresent(cmd *Command) bool { return false } +// getFlagNameCompletions returns completion suggestions for a flag based on the partial name provided. +// It checks if the flag is non-completable and returns an empty slice if true. +// For each prefix match, it appends both the long and short form of the flag (without the '=') to the completions list, +// providing descriptions based on the flag's usage. func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []Completion { if nonCompletableFlag(flag) { return []Completion{} @@ -611,6 +642,9 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []Completion { return completions } +// completeRequireFlags generates completion suggestions for required flags of a command. +// It takes a pointer to the final Command and the string being completed as input. +// It returns a slice of Completion objects representing the suggested flag names. func completeRequireFlags(finalCmd *Command, toComplete string) []Completion { var completions []Completion @@ -636,6 +670,12 @@ func completeRequireFlags(finalCmd *Command, toComplete string) []Completion { return completions } +// checkIfFlagCompletion checks if the given arguments suggest that a flag is being completed and returns the relevant flag, trimmed arguments, and any error. +// If flag completion is not applicable or no flag is found, it returns nil for the flag and the original arguments along with nil error. +// It handles both shorthand and full flag names, as well as flags with an '=' sign. +// If the command has disabled flag parsing, it directly returns without attempting to complete a flag. +// The function ensures that if a flag completion is attempted but does not apply, it reverts to noun completion by resetting the arguments. +// It also checks for unsupported flags and returns an error indicating so. func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { if finalCmd.DisableFlagParsing { // We only do flag completion if we are allowed to parse flags @@ -911,6 +951,17 @@ to your powershell profile. completionCmd.AddCommand(bash, zsh, fish, powershell) } +// findFlag searches for a flag with the given name in the provided command. +// If the name is a single character, it first attempts to convert it into a long flag +// by looking up its shorthand in the current and inherited flag sets. If found, +// it returns the corresponding flag; otherwise, it returns nil. +// +// Parameters: +// - cmd: The command in which to search for the flag. +// - name: The name of the flag to search for. +// +// Returns: +// - *pflag.Flag: The found flag if successful, or nil if not found. func findFlag(cmd *Command, name string) *pflag.Flag { flagSet := cmd.Flags() if len(name) == 1 { @@ -930,10 +981,10 @@ func findFlag(cmd *Command, name string) *pflag.Flag { return cmd.Flag(name) } -// CompDebug prints the specified string to the same file as where the -// completion script prints its logs. -// Note that completion printouts should never be on stdout as they would -// be wrongly interpreted as actual completion choices by the completion script. +// CompDebug prints the specified string to the same file as where the completion script prints its logs. +// Note that completion printouts should never be on stdout as they would be wrongly interpreted as actual completion choices by the completion script. +// The message is prefixed with "[Debug] " before printing. If BASH_COMP_DEBUG_FILE environment variable is set, the message is appended to the specified file. +// If printToStdErr is true, the message is printed to stderr instead of stdout, ensuring it is not read by the completion script. func CompDebug(msg string, printToStdErr bool) { msg = fmt.Sprintf("[Debug] %s", msg) @@ -954,21 +1005,20 @@ func CompDebug(msg string, printToStdErr bool) { } } -// CompDebugln prints the specified string with a newline at the end -// to the same file as where the completion script prints its logs. -// Such logs are only printed when the user has set the environment -// variable BASH_COMP_DEBUG_FILE to the path of some file to be used. +// CompDebugln prints the specified string with a newline at the end to the same file as where the completion script prints its logs. +// It only has an effect if the user has set the environment variable BASH_COMP_DEBUG_FILE to the path of some file to be used. +// If printToStdErr is true, the message will be printed to standard error instead of the debug file. func CompDebugln(msg string, printToStdErr bool) { CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr) } -// CompError prints the specified completion message to stderr. +// CompError prints the specified completion message to stderr with an error prefix. func CompError(msg string) { msg = fmt.Sprintf("[Error] %s", msg) CompDebug(msg, true) } -// CompErrorln prints the specified completion message to stderr with a newline at the end. +// CompErrorln prints the specified completion message to stderr with a newline at the end. It wraps the original CompError function, appending a newline character to the message before printing it. func CompErrorln(msg string) { CompError(fmt.Sprintf("%s\n", msg)) } @@ -981,9 +1031,12 @@ const ( var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`) -// configEnvVar returns the name of the program-specific configuration environment -// variable. It has the format _ where is the name of the -// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`. +// ConfigEnvVar returns the name of the program-specific configuration environment variable. It has the format _ where is the name of the root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`. This format should not be changed: users will be using it explicitly. +// Parameters: +// - name: The name of the root command. +// - suffix: Additional text to append to the program name for uniqueness. +// Returns: +// - A string representing the configuration environment variable name. func configEnvVar(name, suffix string) string { // This format should not be changed: users will be using it explicitly. v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix)) diff --git a/completions_test.go b/completions_test.go index 89da3d50..103d0664 100644 --- a/completions_test.go +++ b/completions_test.go @@ -24,6 +24,12 @@ import ( "testing" ) +// validArgsFunc checks if the number of arguments is valid for a command. +// It returns an empty slice and ShellCompDirectiveNoFileComp if there are any arguments, +// otherwise, it returns a list of completion suggestions and ShellCompDirectiveDefault. + +// AddCompletions appends completion suggestions to the provided completions slice. +// It filters suggestions based on the prefix provided in toComplete. func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { if len(args) != 0 { return nil, ShellCompDirectiveNoFileComp @@ -38,6 +44,15 @@ func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, Sh return completions, ShellCompDirectiveDefault } +// validArgsFunc2 checks if the number of arguments is zero and returns possible completions for a given prefix. +// +// Args: +// cmd: The command being executed. +// args: The current arguments provided to the command. +// toComplete: The prefix that needs completion. +// +// Returns: +// A slice of strings representing possible completions and a ShellCompDirective indicating the directive for shell completion. func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { if len(args) != 0 { return nil, ShellCompDirectiveNoFileComp @@ -52,6 +67,7 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, S return completions, ShellCompDirectiveDefault } +// TestCmdNameCompletionInGo tests the command name completion functionality in Go. func TestCmdNameCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -154,6 +170,7 @@ func TestCmdNameCompletionInGo(t *testing.T) { } } +// TestNoCmdNameCompletionInGo tests the behavior of command name completion in Go when different flags and arguments are present. func TestNoCmdNameCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -319,6 +336,7 @@ func TestNoCmdNameCompletionInGo(t *testing.T) { } } +// TestValidArgsCompletionInGo tests the completion of valid arguments in a Go command. func TestValidArgsCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -373,6 +391,7 @@ func TestValidArgsCompletionInGo(t *testing.T) { } } +// TestValidArgsAndCmdCompletionInGo tests that sub-commands and valid arguments are correctly completed. func TestValidArgsAndCmdCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -423,6 +442,9 @@ func TestValidArgsAndCmdCompletionInGo(t *testing.T) { } } +// TestValidArgsFuncAndCmdCompletionInGo tests the completion functionality of a root command and its child command. +// It verifies that both sub-commands and valid arguments are correctly completed without and with prefixes, +// as well as with descriptions. The function uses a test environment to execute commands and compare the output against expected results. func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -490,6 +512,7 @@ func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) { } } +// TestFlagNameCompletionInGo tests the completion of flag names in a Go command. func TestFlagNameCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -580,6 +603,8 @@ func TestFlagNameCompletionInGo(t *testing.T) { } } +// TestFlagNameCompletionInGoWithDesc tests the completion of flag names in a Go command. +// It verifies that flags are correctly completed when the user provides a prefix and without any prefix. func TestFlagNameCompletionInGoWithDesc(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -677,23 +702,33 @@ type customMultiString []string var _ SliceValue = (*customMultiString)(nil) +// String returns a string representation of the customMultiString. +// It uses fmt.Sprintf to convert the underlying value to a string. func (s *customMultiString) String() string { return fmt.Sprintf("%v", *s) } +// Set appends a value to the customMultiString and returns an error if any. +// Parameters: +// - v: The string value to be appended. +// Returns: +// - error: An error if appending fails; otherwise, nil. func (s *customMultiString) Set(v string) error { *s = append(*s, v) return nil } +// Type returns the type of customMultiString, which is "multi string". func (s *customMultiString) Type() string { return "multi string" } +// GetSlice returns a copy of the slice stored in the customMultiString instance. func (s *customMultiString) GetSlice() []string { return *s } +// TestFlagNameCompletionRepeat tests the completion of flag names without repetition. func TestFlagNameCompletionRepeat(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -841,6 +876,8 @@ func TestFlagNameCompletionRepeat(t *testing.T) { } } +// TestRequiredFlagNameCompletionInGo tests the completion of required flags in a command-line interface. +// It ensures that required flags are suggested even without the - prefix and that they are not suggested once present. func TestRequiredFlagNameCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -1035,6 +1072,7 @@ func TestRequiredFlagNameCompletionInGo(t *testing.T) { } } +// TestFlagFileExtFilterCompletionInGo tests the completion logic for flags with file extensions. func TestFlagFileExtFilterCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -1157,6 +1195,7 @@ func TestFlagFileExtFilterCompletionInGo(t *testing.T) { } } +// TestFlagDirFilterCompletionInGo tests the completion logic for directory filtering flags in a Go command. func TestFlagDirFilterCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -1273,6 +1312,7 @@ func TestFlagDirFilterCompletionInGo(t *testing.T) { } } +// TestValidArgsFuncCmdContext tests the ValidArgsFunction of a child command using a context with specific values. func TestValidArgsFuncCmdContext(t *testing.T) { validArgsFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { ctx := cmd.Context() @@ -1315,6 +1355,10 @@ func TestValidArgsFuncCmdContext(t *testing.T) { } } +// TestValidArgsFuncSingleCmd tests the valid arguments function for a single command. +// +// It creates a root command with a valid arguments function and an empty run function. Then it tests +// completing an empty string and a string with a prefix, checking if the output matches the expected results. func TestValidArgsFuncSingleCmd(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -1354,6 +1398,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) { } } +// TestValidArgsFuncSingleCmdInvalidArg tests the behavior of a root command without subcommands when completing with an invalid number of arguments. The test ensures that the correct error message and completion directive are returned. func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -1381,6 +1426,7 @@ func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) { } } +// TestValidArgsFuncChildCmds tests the completion functionality for child commands with valid argument functions. func TestValidArgsFuncChildCmds(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child1Cmd := &Command{ @@ -1485,6 +1531,8 @@ func TestValidArgsFuncChildCmds(t *testing.T) { } } +// TestValidArgsFuncAliases tests the functionality of valid arguments function aliases in a command. +// It verifies that the completion works correctly with different sub-commands and prefixes. func TestValidArgsFuncAliases(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -1541,6 +1589,8 @@ func TestValidArgsFuncAliases(t *testing.T) { } } +// TestValidArgsFuncInBashScript tests that the bash completion function is generated correctly when a valid args function is set for a command. +// It creates a root command with a child command and validates that the bash completion output includes the valid args function. func TestValidArgsFuncInBashScript(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -1557,6 +1607,8 @@ func TestValidArgsFuncInBashScript(t *testing.T) { check(t, output, "has_completion_function=1") } +// TestNoValidArgsFuncInBashScript tests the generation of Bash completion script when no valid arguments function is provided. +// It asserts that the generated script does not include a completion function. func TestNoValidArgsFuncInBashScript(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -1572,6 +1624,9 @@ func TestNoValidArgsFuncInBashScript(t *testing.T) { checkOmit(t, output, "has_completion_function=1") } +// TestCompleteCmdInBashScript tests the generation of bash completion for a command. +// It sets up a root command and adds a child command to it. Then, it generates bash completion +// and checks if the output contains the expected shell completion flag without description requests. func TestCompleteCmdInBashScript(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -1588,6 +1643,8 @@ func TestCompleteCmdInBashScript(t *testing.T) { check(t, output, ShellCompNoDescRequestCmd) } +// TestCompleteNoDesCmdInZshScript tests that a zsh completion script is generated without descriptions for commands. +// It verifies the expected command name and description absence in the generated script. func TestCompleteNoDesCmdInZshScript(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -1604,6 +1661,7 @@ func TestCompleteNoDesCmdInZshScript(t *testing.T) { check(t, output, ShellCompNoDescRequestCmd) } +// TestCompleteCmdInZshScript tests the generation of Zsh completion for a command. func TestCompleteCmdInZshScript(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ @@ -1621,6 +1679,7 @@ func TestCompleteCmdInZshScript(t *testing.T) { checkOmit(t, output, ShellCompNoDescRequestCmd) } +// TestFlagCompletionInGo tests flag completion functionality in a Go command. func TestFlagCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -1714,6 +1773,7 @@ func TestFlagCompletionInGo(t *testing.T) { } } +// TestValidArgsFuncChildCmdsWithDesc tests the completion of sub-commands using valid args functions. func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child1Cmd := &Command{ @@ -2041,6 +2101,8 @@ func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) { } } +// TestFlagCompletionWorksRootCommandAddedAfterFlags tests that flag completion works for a subcommand after it is added to the root command. +// It specifically checks if the completion function is registered correctly and if the flag completion provides the expected suggestions. func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{ @@ -2076,6 +2138,15 @@ func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) { } } +// TestFlagCompletionForPersistentFlagsCalledFromSubCmd tests that persistent flag completion works for a command called from a subcommand. +// It sets up a root command with a persistent string flag and a child command with its own valid arguments function and a boolean flag. +// The test executes the command to trigger the flag completion and checks if the output matches the expected result. +// Args: +// - t: A testing.T instance for assertions and logging. +// Returns: +// - None +// Raises: +// - An error if an unexpected error occurs during the execution of the command. func TestFlagCompletionForPersistentFlagsCalledFromSubCmd(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.PersistentFlags().String("string", "", "test string flag") @@ -2109,15 +2180,9 @@ func TestFlagCompletionForPersistentFlagsCalledFromSubCmd(t *testing.T) { } } -// This test tries to register flag completion concurrently to make sure the -// code handles concurrency properly. -// This was reported as a problem when tests are run concurrently: -// https://github.com/spf13/cobra/issues/1320 +// TestFlagCompletionConcurrentRegistration tests the registration of flag completion concurrently to ensure proper handling of concurrency. // -// NOTE: this test can sometimes pass even if the code were to not handle -// concurrency properly. This is not great but the important part is that -// it should never fail. Therefore, if the tests fails sometimes, we will -// still be able to know there is a problem. +// It registers flags on a root command and a child command and then attempts to register completion functions for these flags concurrently in different goroutines. The test asserts that flag completion works correctly for each flag, regardless of whether it belongs to the root or child command. func TestFlagCompletionConcurrentRegistration(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} const maxFlags = 50 @@ -2184,6 +2249,7 @@ func TestFlagCompletionConcurrentRegistration(t *testing.T) { } } +// TestFlagCompletionInGoWithDesc tests flag completion functionality in a Go application with descriptions. func TestFlagCompletionInGoWithDesc(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -2277,6 +2343,8 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) { } } +// TestValidArgsNotValidArgsFunc tests the behavior when both ValidArgs and ValidArgsFunction are present. +// It ensures that only ValidArgs is considered during command completion. func TestValidArgsNotValidArgsFunc(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -2320,6 +2388,11 @@ func TestValidArgsNotValidArgsFunc(t *testing.T) { } } +// TestArgAliasesCompletionInGo tests the completion behavior of argument aliases in a Go command. +// +// It creates a root command with specific valid arguments and their aliases. The test cases check +// the completion behavior when there are matching valid arguments and when no matches exist, ensuring that +// only the aliased arguments are returned for incomplete input prefixes. func TestArgAliasesCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -2378,6 +2451,7 @@ func TestArgAliasesCompletionInGo(t *testing.T) { } } +// TestCompleteHelp tests the completion of the help command in a root command with sub-commands. func TestCompleteHelp(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child1Cmd := &Command{ @@ -2448,6 +2522,8 @@ func TestCompleteHelp(t *testing.T) { } } +// removeCompCmd removes the completion command from the given root command. +// It searches through the root command's subcommands and removes the one with the name `compCmdName`. func removeCompCmd(rootCmd *Command) { // Remove completion command for the next test for _, cmd := range rootCmd.commands { @@ -2458,6 +2534,7 @@ func removeCompCmd(rootCmd *Command) { } } +// TestDefaultCompletionCmd tests the default completion command behavior in different scenarios. func TestDefaultCompletionCmd(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -2604,6 +2681,7 @@ func TestDefaultCompletionCmd(t *testing.T) { removeCompCmd(rootCmd) } +// TestCompleteCompletion tests the completion functionality of a root command without any sub-commands and with sub-commands. func TestCompleteCompletion(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} @@ -2691,6 +2769,12 @@ func TestCompleteCompletion(t *testing.T) { } } +// TestMultipleShorthandFlagCompletion tests the completion functionality of multiple shorthand flags in a command. +// +// It sets up a root command with various flags and their shorthand versions. The test then executes the command with different combinations of shorthand flags to verify that the correct completions are returned. +// +// Parameters: +// - t: A pointer to a testing.T instance used for assertions and error reporting. func TestMultipleShorthandFlagCompletion(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -2784,6 +2868,7 @@ func TestMultipleShorthandFlagCompletion(t *testing.T) { } } +// TestCompleteWithDisableFlagParsing tests the behavior of Cobra's flag completion when DisableFlagParsing is set on a command. func TestCompleteWithDisableFlagParsing(t *testing.T) { flagValidArgs := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { @@ -2847,6 +2932,7 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) { } } +// TestCompleteWithRootAndLegacyArgs tests a lonely root command that uses legacyArgs(). The root command should accept any number of arguments and completion should behave accordingly. func TestCompleteWithRootAndLegacyArgs(t *testing.T) { // Test a lonely root command which uses legacyArgs(). In such a case, the root // command should accept any number of arguments and completion should behave accordingly. @@ -2893,6 +2979,7 @@ func TestCompleteWithRootAndLegacyArgs(t *testing.T) { } } +// TestCompletionFuncCompatibility tests the compatibility of completion functions with different formats and types. func TestCompletionFuncCompatibility(t *testing.T) { t.Run("validate signature", func(t *testing.T) { t.Run("format with []string", func(t *testing.T) { @@ -2978,6 +3065,7 @@ func TestCompletionFuncCompatibility(t *testing.T) { }) } +// TestFixedCompletions tests the FixedCompletions completion function. func TestFixedCompletions(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} choices := []string{"apple", "banana", "orange"} @@ -3005,6 +3093,7 @@ func TestFixedCompletions(t *testing.T) { } } +// TestFixedCompletionsWithCompletionHelpers tests the FixedCompletions function with various completion helpers to ensure correct behavior. func TestFixedCompletionsWithCompletionHelpers(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} // here we are mixing string, [Completion] and [CompletionWithDesc] @@ -3055,6 +3144,9 @@ func TestFixedCompletionsWithCompletionHelpers(t *testing.T) { }) } +// TestCompletionForGroupedFlags tests the completion functionality for grouped flags in a command. +// It verifies that flags within a group are suggested together and only when required, and that +// flags outside of a group are not suggested with a leading '-' prefix unless they are part of a required group. func TestCompletionForGroupedFlags(t *testing.T) { getCmd := func() *Command { rootCmd := &Command{ @@ -3155,6 +3247,11 @@ func TestCompletionForGroupedFlags(t *testing.T) { } } +// TestCompletionForOneRequiredGroupFlags tests the completion logic when one group of flags is required. +// +// It sets up a command with multiple subcommands and flags, marking some flags as part of a required group. The test cases +// verify that only the required flags are suggested for completion when no flag from the group is provided. It also checks +// scenarios where flags within the group are already present or other conditions affect the suggestion. func TestCompletionForOneRequiredGroupFlags(t *testing.T) { getCmd := func() *Command { rootCmd := &Command{ @@ -3253,6 +3350,7 @@ func TestCompletionForOneRequiredGroupFlags(t *testing.T) { } } +// TestCompletionForMutuallyExclusiveFlags tests completion behavior for commands with mutually exclusive flags. func TestCompletionForMutuallyExclusiveFlags(t *testing.T) { getCmd := func() *Command { rootCmd := &Command{ @@ -3581,6 +3679,7 @@ func TestCompletionCobraFlags(t *testing.T) { } } +// TestArgsNotDetectedAsFlagsCompletionInGo is a regression test ensuring that the bug described in https://github.com/spf13/cobra/issues/1816 does not occur anymore. func TestArgsNotDetectedAsFlagsCompletionInGo(t *testing.T) { // Regression test that ensures the bug described in // https://github.com/spf13/cobra/issues/1816 does not occur anymore. @@ -3645,6 +3744,10 @@ Completion ended with directive: ShellCompDirectiveNoFileComp } } +// TestGetFlagCompletion tests the GetFlagCompletionFunc method of the Command type. +// It verifies that the function correctly retrieves and returns the completion function for a given flag, +// including both local and persistent flags, as well as child commands. The test cases cover various scenarios +// to ensure the functionality works as expected. func TestGetFlagCompletion(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} @@ -3735,6 +3838,8 @@ func TestGetFlagCompletion(t *testing.T) { } } +// TestGetEnvConfig tests the getEnvConfig function with various test cases to ensure it correctly handles environment variable overrides and fallbacks. +// Each test case checks different scenarios for command and global environment variables, as well as their expected outcomes. func TestGetEnvConfig(t *testing.T) { testCases := []struct { desc string @@ -3820,6 +3925,7 @@ func TestGetEnvConfig(t *testing.T) { } } +// TestDisableDescriptions tests the functionality of disabling descriptions for command completion. func TestDisableDescriptions(t *testing.T) { rootCmd := &Command{ Use: "root", diff --git a/doc/man_docs.go b/doc/man_docs.go index 2138f248..c5dc2523 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -30,11 +30,15 @@ import ( "github.com/spf13/pflag" ) -// GenManTree will generate a man page for this command and all descendants -// in the directory given. The header may be nil. This function may not work -// correctly if your command names have `-` in them. If you have `cmd` with two -// subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third` -// it is undefined which help output will be in the file `cmd-sub-third.1`. +// GenManTree generates a man page for the provided command and all its descendants in the specified directory. +// +// It takes a cobra.Command pointer representing the root command, an optional GenManHeader pointer to customize the header information, and a string specifying the output directory. +// +// The function assumes that command names do not contain hyphens (`-`). If they do, unexpected behavior may occur. +// +// Note: If a command named `cmd` has subcommands `sub` and `sub-third`, and `sub` itself has a subcommand called `third`, it is undefined which help output will be written to the file `cmd-sub-third.1`. +// +// Returns an error if the generation process encounters any issues, such as invalid input or permission errors when writing files to the specified directory. func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error { return GenManTreeFromOpts(cmd, GenManTreeOptions{ Header: header, @@ -44,7 +48,15 @@ func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error { } // GenManTreeFromOpts generates a man page for the command and all descendants. -// The pages are written to the opts.Path directory. +// The pages are written to the opts.Path directory. It recursively processes each command in the tree, +// skipping non-available or additional help topic commands. Each man page is saved with a filename based on the command path and section. + +// Parameters: +// - cmd: A pointer to the root cobra.Command for which man pages need to be generated. +// - opts: A GenManTreeOptions struct containing options for generating the man pages, such as header details, output directory, and separators. + +// Returns: +// - error: If any error occurs during the generation of the man pages, it is returned. func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error { header := opts.Header if header == nil { @@ -100,8 +112,15 @@ type GenManHeader struct { Manual string } -// GenMan will generate a man page for the given command and write it to -// w. The header argument may be nil, however obviously w may not. +// GenMan generates a man page for the given command and writes it to the specified writer. +// +// Parameters: +// cmd - The cobra.Command for which to generate the man page. +// header - A pointer to a GenManHeader that contains additional information for the man page. If nil, a default header will be used. +// w - The io.Writer to which the generated man page will be written. +// +// Returns: +// error - If an error occurs during the generation of the man page, it will be returned here. func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error { if header == nil { header = &GenManHeader{} @@ -115,6 +134,15 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error { return err } +// fillHeader populates the GenManHeader with default values if they are not already set. +// +// It sets the title to the uppercase version of `name`, replacing spaces with hyphens. +// If the section is empty, it defaults to "1". +// If the date is nil, it sets the date to the current time or a time specified by the SOURCE_DATE_EPOCH environment variable. +// The formatted date is stored in header.date. +// If source is empty and auto-generation is not disabled, it sets the source to "Auto generated by spf13/cobra". +// +// It returns an error if there is an issue parsing the SOURCE_DATE_EPOCH environment variable. func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { if header.Title == "" { header.Title = strings.ToUpper(strings.ReplaceAll(name, " ", "\\-")) @@ -140,6 +168,17 @@ func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { return nil } +// manPreamble writes the preamble for a manual page to the given buffer. +// +// Parameters: +// buf - the io.StringWriter to write the preamble to. +// header - a pointer to a GenManHeader containing metadata for the manual page. +// cmd - a pointer to a cobra.Command representing the command being documented. +// dashedName - the dash-separated name of the command. +// +// This function constructs the preamble section of a man page, including +// the title, section, date, source, and manual. It also includes the command's +// short description and synopsis. func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, dashedName string) { description := cmd.Long if len(description) == 0 { @@ -156,6 +195,12 @@ func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, cobra.WriteStringAndCheck(buf, description+"\n\n") } +// manPrintFlags prints the flags in a format suitable for a man page. +// +// It takes an io.StringWriter to write to and a pflag.FlagSet containing the flags to print. +// For each flag, it checks if it is deprecated or hidden. If not, it formats the flag name, +// shorthand, and usage information according to its type (string or other) and whether it has +// a default value. The formatted string is then written to the provided writer. func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) { flags.VisitAll(func(flag *pflag.Flag) { if len(flag.Deprecated) > 0 || flag.Hidden { @@ -184,6 +229,8 @@ func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) { }) } +// manPrintOptions writes the options for a command to a StringWriter. +// It includes both non-inherited and inherited flags, if available. func manPrintOptions(buf io.StringWriter, command *cobra.Command) { flags := command.NonInheritedFlags() if flags.HasAvailableFlags() { @@ -199,6 +246,17 @@ func manPrintOptions(buf io.StringWriter, command *cobra.Command) { } } +// genMan generates a man page for the given Cobra command and header. +// It initializes default help commands and flags, processes the command path, +// and constructs the man page content including preamble, options, examples, +// see also sections, and history. +// +// Parameters: +// - cmd: The Cobra command for which to generate the man page. +// - header: Header information for the man page, such as section and date. +// +// Returns: +// - A byte slice containing the generated man page content. func genMan(cmd *cobra.Command, header *GenManHeader) []byte { cmd.InitDefaultHelpCmd() cmd.InitDefaultHelpFlag() diff --git a/fish_completions.go b/fish_completions.go index 12d61b69..3b5aa3ac 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -272,7 +272,14 @@ complete -k -c %[2]s -n '__%[1]s_requires_order_preservation && __%[1]s_prepare_ ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name))) } -// GenFishCompletion generates fish completion file and writes to the passed writer. +// GenFishCompletion generates a fish completion file and writes it to the provided writer. +// +// Parameters: +// - w: io.Writer to which the completion file will be written. +// - includeDesc: boolean indicating whether to include descriptions in the completion file. +// +// Returns: +// - error: if an error occurs during the generation or writing process. func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) genFishComp(buf, c.Name(), includeDesc) @@ -280,7 +287,9 @@ func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error { return err } -// GenFishCompletionFile generates fish completion file. +// GenFishCompletionFile generates a fish completion file based on the provided filename and whether to include descriptions. +// It takes a filename as a string and a boolean indicating whether to include descriptions in the completion file. +// It returns an error if creating or writing to the file fails, or if generating the completion data fails. func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error { outFile, err := os.Create(filename) if err != nil { diff --git a/fish_completions_test.go b/fish_completions_test.go index ce2a531d..e69de29b 100644 --- a/fish_completions_test.go +++ b/fish_completions_test.go @@ -1,143 +0,0 @@ -// Copyright 2013-2023 The Cobra Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cobra - -import ( - "bytes" - "errors" - "fmt" - "os" - "path/filepath" - "testing" -) - -func TestCompleteNoDesCmdInFishScript(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - buf := new(bytes.Buffer) - assertNoErr(t, rootCmd.GenFishCompletion(buf, false)) - output := buf.String() - - check(t, output, ShellCompNoDescRequestCmd) -} - -func TestCompleteCmdInFishScript(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - buf := new(bytes.Buffer) - assertNoErr(t, rootCmd.GenFishCompletion(buf, true)) - output := buf.String() - - check(t, output, ShellCompRequestCmd) - checkOmit(t, output, ShellCompNoDescRequestCmd) -} - -func TestProgWithDash(t *testing.T) { - rootCmd := &Command{Use: "root-dash", Args: NoArgs, Run: emptyRun} - buf := new(bytes.Buffer) - assertNoErr(t, rootCmd.GenFishCompletion(buf, false)) - output := buf.String() - - // Functions name should have replace the '-' - check(t, output, "__root_dash_perform_completion") - checkOmit(t, output, "__root-dash_perform_completion") - - // The command name should not have replaced the '-' - check(t, output, "-c root-dash") - checkOmit(t, output, "-c root_dash") -} - -func TestProgWithColon(t *testing.T) { - rootCmd := &Command{Use: "root:colon", Args: NoArgs, Run: emptyRun} - buf := new(bytes.Buffer) - assertNoErr(t, rootCmd.GenFishCompletion(buf, false)) - output := buf.String() - - // Functions name should have replace the ':' - check(t, output, "__root_colon_perform_completion") - checkOmit(t, output, "__root:colon_perform_completion") - - // The command name should not have replaced the ':' - check(t, output, "-c root:colon") - checkOmit(t, output, "-c root_colon") -} - -func TestFishCompletionNoActiveHelp(t *testing.T) { - c := &Command{Use: "c", Run: emptyRun} - - buf := new(bytes.Buffer) - assertNoErr(t, c.GenFishCompletion(buf, true)) - output := buf.String() - - // check that active help is being disabled - activeHelpVar := activeHelpEnvVar(c.Name()) - check(t, output, fmt.Sprintf("%s=0", activeHelpVar)) -} - -func TestGenFishCompletionFile(t *testing.T) { - tmpFile, err := os.CreateTemp("", "cobra-test") - if err != nil { - t.Fatal(err.Error()) - } - - defer os.Remove(tmpFile.Name()) - - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - assertNoErr(t, rootCmd.GenFishCompletionFile(tmpFile.Name(), false)) -} - -func TestFailGenFishCompletionFile(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "cobra-test") - if err != nil { - t.Fatal(err.Error()) - } - - defer os.RemoveAll(tmpDir) - - f, _ := os.OpenFile(filepath.Join(tmpDir, "test"), os.O_CREATE, 0400) - defer f.Close() - - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - got := rootCmd.GenFishCompletionFile(f.Name(), false) - if !errors.Is(got, os.ErrPermission) { - t.Errorf("got: %s, want: %s", got.Error(), os.ErrPermission.Error()) - } -} diff --git a/flag_groups.go b/flag_groups.go index 560612fd..56e5185f 100644 --- a/flag_groups.go +++ b/flag_groups.go @@ -29,7 +29,10 @@ const ( ) // MarkFlagsRequiredTogether marks the given flags with annotations so that Cobra errors -// if the command is invoked with a subset (but not all) of the given flags. +// if the command is invoked with a subset (but not all) of the given flags. It ensures +// that all flags provided in flagNames must be used together or none at all. +// Parameters: +// - flagNames: A slice of strings representing the names of the flags to mark. func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) { c.mergePersistentFlags() for _, v := range flagNames { @@ -45,7 +48,8 @@ func (c *Command) MarkFlagsRequiredTogether(flagNames ...string) { } // MarkFlagsOneRequired marks the given flags with annotations so that Cobra errors -// if the command is invoked without at least one flag from the given set of flags. +// if the command is invoked without at least one flag from the given set of flags. The +// `flagNames` parameter is a slice of strings containing the names of the flags to be marked. func (c *Command) MarkFlagsOneRequired(flagNames ...string) { c.mergePersistentFlags() for _, v := range flagNames { @@ -60,8 +64,11 @@ func (c *Command) MarkFlagsOneRequired(flagNames ...string) { } } -// MarkFlagsMutuallyExclusive marks the given flags with annotations so that Cobra errors -// if the command is invoked with more than one flag from the given set of flags. +// MarkFlagsMutuallyExclusive marks the given flags with annotations so that Cobra errors if the command is invoked with more than one flag from the given set of flags. +// It takes a variable number of strings representing the names of the flags to be marked as mutually exclusive. Each time this method is called, it adds a new entry to the annotation. +// If any of the specified flags are not found, it panics with an error message. +// The flagNames parameter contains one or more string values that represent the names of the flags to be marked as mutually exclusive. +// There is no return value. func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) { c.mergePersistentFlags() for _, v := range flagNames { @@ -76,8 +83,7 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) { } } -// ValidateFlagGroups validates the mutuallyExclusive/oneRequired/requiredAsGroup logic and returns the -// first error encountered. +// ValidateFlagGroups validates the mutuallyExclusive/oneRequired/requiredAsGroup logic and returns the first error encountered. func (c *Command) ValidateFlagGroups() error { if c.DisableFlagParsing { return nil @@ -108,6 +114,8 @@ func (c *Command) ValidateFlagGroups() error { return nil } +// hasAllFlags checks if all flags in the provided flag names exist within the given FlagSet. +// It returns true if all flags are found, otherwise it returns false. func hasAllFlags(fs *flag.FlagSet, flagnames ...string) bool { for _, fname := range flagnames { f := fs.Lookup(fname) @@ -118,6 +126,16 @@ func hasAllFlags(fs *flag.FlagSet, flagnames ...string) bool { return true } +// processFlagForGroupAnnotation processes flags for group annotations, updating the group status based on the provided flag set and flag. +// +// Parameters: +// - flags: A pointer to the flag.FlagSet containing all flags. +// - pflag: A pointer to the flag.Flag being processed. +// - annotation: The name of the annotation to process. +// - groupStatus: A map tracking the status of groups, where the outer key is the group name and the inner map tracks individual flags within that group. +// +// This function checks if the provided flag has an annotation matching the specified annotation. If it does, it processes each group associated with the annotation. For each group, it ensures all flags in the group are defined in the provided flag set. If they are, it initializes the group status for any new groups and updates the status of the current flag within its group. +// func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annotation string, groupStatus map[string]map[string]bool) { groupInfo, found := pflag.Annotations[annotation] if found { @@ -141,6 +159,9 @@ func processFlagForGroupAnnotation(flags *flag.FlagSet, pflag *flag.Flag, annota } } +// validateRequiredFlagGroups checks if any flag groups have required flags that are not all set. +// It takes a map where keys represent flag groups and values are maps of flag names to their set status. +// Returns an error if any group contains flags that are either all unset or none unset, which is invalid. func validateRequiredFlagGroups(data map[string]map[string]bool) error { keys := sortedKeys(data) for _, flagList := range keys { @@ -164,6 +185,13 @@ func validateRequiredFlagGroups(data map[string]map[string]bool) error { return nil } +// validateOneRequiredFlagGroups checks that at least one flag from each group is set. It takes a map where keys are groups and values are maps indicating whether each flag within a group is set. +// +// Parameters: +// - data: A map of string to map of string to bool, representing the flags and their statuses in different groups. +// +// Returns: +// - error: An error if any group does not have at least one required flag set. If all required flags are set, it returns nil. func validateOneRequiredFlagGroups(data map[string]map[string]bool) error { keys := sortedKeys(data) for _, flagList := range keys { @@ -185,6 +213,22 @@ func validateOneRequiredFlagGroups(data map[string]map[string]bool) error { return nil } +// validateExclusiveFlagGroups checks that within a map of flag groups, no more than one flag from each group is set. +// +// Parameters: +// - data: A map where keys are group names and values are maps of flag names to their status (true if set). +// +// Returns: +// - error: If any group contains more than one set flag, an error is returned with details. Otherwise, returns nil. +// +// Example: +// err := validateExclusiveFlagGroups(map[string]map[string]bool{ +// "group1": {"flagA": true, "flagB": false}, +// "group2": {"flagC": false, "flagD": true}, +// }) +// if err != nil { +// fmt.Println(err) +// } func validateExclusiveFlagGroups(data map[string]map[string]bool) error { keys := sortedKeys(data) for _, flagList := range keys { @@ -206,6 +250,8 @@ func validateExclusiveFlagGroups(data map[string]map[string]bool) error { return nil } +// sortedKeys returns a slice of strings containing the keys of the input map `m`, sorted in ascending order. +// The function does not modify the original map and ensures that the keys are returned as sorted lexicographically. func sortedKeys(m map[string]map[string]bool) []string { keys := make([]string, len(m)) i := 0 @@ -217,11 +263,11 @@ func sortedKeys(m map[string]map[string]bool) []string { return keys } -// enforceFlagGroupsForCompletion will do the following: -// - when a flag in a group is present, other flags in the group will be marked required -// - when none of the flags in a one-required group are present, all flags in the group will be marked required -// - when a flag in a mutually exclusive group is present, other flags in the group will be marked as hidden -// This allows the standard completion logic to behave appropriately for flag groups +// enforceFlagGroupsForCompletion enforces the completion logic for flag groups in a command. +// It ensures that when a flag in a group is present, other flags in the group are marked required, +// and when none of the flags in a one-required group are present, all flags in the group are marked required. +// Additionally, it hides flags that are mutually exclusive to others. This allows the standard completion logic +// to behave appropriately for flag groups. func (c *Command) enforceFlagGroupsForCompletion() { if c.DisableFlagParsing { return diff --git a/powershell_completions.go b/powershell_completions.go index 746dcb92..3cc08ab2 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -310,6 +310,8 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock ${__%[2]sCompleterB ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name))) } +// genPowerShellCompletion generates PowerShell completion script for the command. +// It writes the generated script to the provided writer and returns any errors encountered. func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) genPowerShellComp(buf, c.Name(), includeDesc) @@ -317,6 +319,14 @@ func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { return err } +// genPowerShellCompletionFile generates a PowerShell completion file for the command. +// +// Parameters: +// - filename: The name of the file to generate. +// - includeDesc: A boolean indicating whether to include descriptions in the completion file. +// +// Returns: +// - error: If an error occurs during file creation or completion generation, it returns the error. Otherwise, it returns nil. func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) error { outFile, err := os.Create(filename) if err != nil { @@ -327,24 +337,29 @@ func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) return c.genPowerShellCompletion(outFile, includeDesc) } -// GenPowerShellCompletionFile generates powershell completion file without descriptions. +// GenPowerShellCompletionFile generates a PowerShell completion file for the command without including descriptions. +// +// Parameters: +// - filename: The name of the file to generate. +// +// Returns: +// - An error if an error occurs during the generation process. func (c *Command) GenPowerShellCompletionFile(filename string) error { return c.genPowerShellCompletionFile(filename, false) } -// GenPowerShellCompletion generates powershell completion file without descriptions -// and writes it to the passed writer. +// GenPowerShellCompletion generates a PowerShell completion script without descriptions +// and writes it to the provided writer. It returns an error if the operation fails. func (c *Command) GenPowerShellCompletion(w io.Writer) error { return c.genPowerShellCompletion(w, false) } -// GenPowerShellCompletionFileWithDesc generates powershell completion file with descriptions. +// GenPowerShellCompletionFileWithDesc generates a PowerShell completion file for the command with detailed descriptions. It takes a filename as input and returns an error if any occurs during the generation process. The boolean parameter indicates whether to include descriptions in the generated file. func (c *Command) GenPowerShellCompletionFileWithDesc(filename string) error { return c.genPowerShellCompletionFile(filename, true) } -// GenPowerShellCompletionWithDesc generates powershell completion file with descriptions -// and writes it to the passed writer. +// GenPowerShellCompletionWithDesc generates a PowerShell completion file with descriptions and writes it to the provided writer. It takes a writer as an argument and returns an error if any occurs during the generation process. func (c *Command) GenPowerShellCompletionWithDesc(w io.Writer) error { return c.genPowerShellCompletion(w, true) } diff --git a/powershell_completions_test.go b/powershell_completions_test.go index 603b50c9..cc6c6a5b 100644 --- a/powershell_completions_test.go +++ b/powershell_completions_test.go @@ -20,6 +20,9 @@ import ( "testing" ) +// TestPwshCompletionNoActiveHelp tests the generation of PowerShell completion for a command without active help. +// +// It creates a new Command instance with the name "c" and an empty Run function. Then, it generates PowerShell completion output using GenPowerShellCompletion method and checks if the environment variable for disabling active help is set to 0. func TestPwshCompletionNoActiveHelp(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} diff --git a/shell_completions.go b/shell_completions.go index b035742d..57cbc475 100644 --- a/shell_completions.go +++ b/shell_completions.go @@ -18,23 +18,20 @@ import ( "github.com/spf13/pflag" ) -// MarkFlagRequired instructs the various shell completion implementations to -// prioritize the named flag when performing completion, -// and causes your command to report an error if invoked without the flag. +// MarkFlagRequired instructs the various shell completion implementations to prioritize the named flag when performing completion, and causes your command to report an error if invoked without the flag. func (c *Command) MarkFlagRequired(name string) error { return MarkFlagRequired(c.Flags(), name) } -// MarkPersistentFlagRequired instructs the various shell completion implementations to -// prioritize the named persistent flag when performing completion, -// and causes your command to report an error if invoked without the flag. +// MarkPersistentFlagRequired instructs the various shell completion implementations to prioritize the named persistent flag when performing completion, and causes your command to report an error if invoked without the flag. func (c *Command) MarkPersistentFlagRequired(name string) error { return MarkFlagRequired(c.PersistentFlags(), name) } -// MarkFlagRequired instructs the various shell completion implementations to -// prioritize the named flag when performing completion, +// MarkFlagRequired instructs the various shell completion implementations to prioritize the named flag when performing completion, // and causes your command to report an error if invoked without the flag. +// It takes a pointer to a pflag.FlagSet and the name of the flag as parameters. +// The function returns an error if setting the annotation fails. func MarkFlagRequired(flags *pflag.FlagSet, name string) error { return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) } @@ -46,11 +43,17 @@ func (c *Command) MarkFlagFilename(name string, extensions ...string) error { } // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. -// The bash completion script will call the bash function f for the flag. +// The bash completion script will call the bash function `f` for the flag. // -// This will only work for bash completion. -// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows -// to register a Go function which will work across all shells. +// This will only work for bash completion. It is recommended to instead use +// `c.RegisterFlagCompletionFunc(...)` which allows registering a Go function that works across all shells. +// +// Parameters: +// - name: the name of the flag to annotate +// - f: the name of the bash function to call for bash completion +// +// Returns: +// - error if marking the flag fails, nil otherwise func (c *Command) MarkFlagCustom(name string, f string) error { return MarkFlagCustom(c.Flags(), name, f) } @@ -62,24 +65,40 @@ func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) return MarkFlagFilename(c.PersistentFlags(), name, extensions...) } -// MarkFlagFilename instructs the various shell completion implementations to -// limit completions for the named flag to the specified file extensions. +// MarkFlagFilename instructs the various shell completion implementations to limit completions for the named flag to the specified file extensions. +// +// Parameters: +// - flags: A pointer to a pflag.FlagSet containing the flags. +// - name: The name of the flag to be marked. +// - extensions: Variable number of string arguments representing the file extensions to limit completions to. +// +// Returns: +// - error: An error if setting the annotation fails, otherwise nil. func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { return flags.SetAnnotation(name, BashCompFilenameExt, extensions) } // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. -// The bash completion script will call the bash function f for the flag. +// The bash completion script will call the bash function `f` for the flag. // // This will only work for bash completion. // It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows // to register a Go function which will work across all shells. +// +// Parameters: +// - flags: The FlagSet containing the flags. +// - name: The name of the flag to annotate. +// - f: The bash function to call for completion. +// +// Returns: +// - error if setting the annotation fails. func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { return flags.SetAnnotation(name, BashCompCustom, []string{f}) } // MarkFlagDirname instructs the various shell completion implementations to -// limit completions for the named flag to directory names. +// limit completions for the named flag to directory names. It takes a pointer to a Command and a flag name as parameters. +// The function calls MarkFlagDirname on the command's flags with the provided name and returns any errors encountered during the operation. func (c *Command) MarkFlagDirname(name string) error { return MarkFlagDirname(c.Flags(), name) } @@ -87,12 +106,23 @@ func (c *Command) MarkFlagDirname(name string) error { // MarkPersistentFlagDirname instructs the various shell completion // implementations to limit completions for the named persistent flag to // directory names. +// +// Parameters: +// - c: The command instance that contains the persistent flags. +// - name: The name of the persistent flag to mark. +// +// Returns: +// - error: If any error occurs during the marking process, it is returned here. func (c *Command) MarkPersistentFlagDirname(name string) error { return MarkFlagDirname(c.PersistentFlags(), name) } -// MarkFlagDirname instructs the various shell completion implementations to -// limit completions for the named flag to directory names. +// MarkFlagDirname instructs the various shell completion implementations to limit completions for the named flag to directory names. +// Parameters: +// - flags: A pointer to a pflag.FlagSet containing all available flags. +// - name: The name of the flag to be modified. +// Returns: +// - error: If an error occurs during the annotation setting, it is returned. Otherwise, nil is returned. func MarkFlagDirname(flags *pflag.FlagSet, name string) error { return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{}) } diff --git a/zsh_completions.go b/zsh_completions.go index 1856e4c7..dade04f7 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -21,24 +21,50 @@ import ( "os" ) -// GenZshCompletionFile generates zsh completion file including descriptions. +// GenZshCompletionFile generates a zsh completion file for the command, including descriptions. It takes a filename as an argument and returns an error if any occurs during the generation process. If descriptions are included in the completion file, it calls the genZshCompletionFile method with additional parameters. func (c *Command) GenZshCompletionFile(filename string) error { return c.genZshCompletionFile(filename, true) } -// GenZshCompletion generates zsh completion file including descriptions -// and writes it to the passed writer. +// GenZshCompletion generates a zsh completion script for the command. It includes descriptions for each command and option, +// and writes the script to the provided writer. +// +// Parameters: +// - w: io.Writer where the generated zsh completion script will be written. +// +// Returns: +// - error if there is an issue generating or writing the completion script. +// +// Example usage: +// +// var cmd Command +// file, err := os.Create("completion.zsh") +// if err != nil { +// log.Fatal(err) +// } +// defer file.Close() +// err = cmd.GenZshCompletion(file) +// if err != nil { +// log.Fatal(err) +// } func (c *Command) GenZshCompletion(w io.Writer) error { return c.genZshCompletion(w, true) } -// GenZshCompletionFileNoDesc generates zsh completion file without descriptions. +// GenZshCompletionFileNoDesc generates a zsh completion file without descriptions for the Command instance. +// It takes a filename as input and returns an error if the operation fails. The function does not include descriptions in the generated file. func (c *Command) GenZshCompletionFileNoDesc(filename string) error { return c.genZshCompletionFile(filename, false) } -// GenZshCompletionNoDesc generates zsh completion file without descriptions -// and writes it to the passed writer. +// GenZshCompletionNoDesc generates a zsh completion file for the command without descriptions. +// It writes the generated completion script to the provided writer. +// +// Parameters: +// - w: The writer to which the completion script will be written. +// +// Returns: +// - error: If an error occurs during the generation process, it will be returned. func (c *Command) GenZshCompletionNoDesc(w io.Writer) error { return c.genZshCompletion(w, false) } @@ -51,22 +77,26 @@ func (c *Command) GenZshCompletionNoDesc(w io.Writer) error { // To achieve file extension filtering, one can use ValidArgsFunction and // ShellCompDirectiveFilterFileExt. // -// Deprecated +// Deprecated: Use ShellCompDirectiveDefault or specify a valid completion function instead. func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { return nil } -// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore -// been disabled. -// To achieve the same behavior across all shells, one can use -// ValidArgs (for the first argument only) or ValidArgsFunction for -// any argument (can include the first one also). +// MarkZshCompPositionalArgumentWords marks the positional arguments of a command with zsh completion words. +// This function is deprecated. For cross-shell compatibility, use ValidArgs for the first argument or ValidArgsFunction for any argument. // -// Deprecated +// Deprecated: Use ValidArgs or ValidArgsFunction instead. func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { return nil } +// genZshCompletionFile generates a Zsh completion file for the command. +// +// It takes two parameters: +// - filename: The name of the file where the completion script will be written. +// - includeDesc: A boolean indicating whether to include descriptions in the completion script. +// +// It returns an error if there is an issue creating or writing to the file. func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error { outFile, err := os.Create(filename) if err != nil { @@ -77,6 +107,14 @@ func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error return c.genZshCompletion(outFile, includeDesc) } +// genZshCompletion generates Zsh completion script for the command and writes it to the provided writer. +// +// Parameters: +// - w: The io.Writer where the completion script will be written. +// - includeDesc: A boolean indicating whether to include descriptions in the completion script. +// +// Returns: +// - error: An error if there was an issue writing to the writer or generating the script. func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) genZshComp(buf, c.Name(), includeDesc)