Merge pull request #1 from Snorkell-ai/penify/auto_doc_2025-04-26-20-11_1a97c

[Penify] Full Repo Documentation
This commit is contained in:
Suman Saurabh 2025-04-27 02:47:29 +05:30 committed by GitHub
commit 9b80e9f380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1709 additions and 478 deletions

View file

@ -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 <PROGRAM>_ACTIVE_HELP where <PROGRAM> 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 <PROGRAM>_ACTIVE_HELP where <PROGRAM> 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)
}

View file

@ -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",

52
args.go
View file

@ -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)
}

View file

@ -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}

View file

@ -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+=(<command_name>)' 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 {

View file

@ -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)
}

View file

@ -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}

View file

@ -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}

View file

@ -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)

View file

@ -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) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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)

View file

@ -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 <PROGRAM>_<SUFFIX> where <PROGRAM> 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 <PROGRAM>_<SUFFIX> where <PROGRAM> 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))

View file

@ -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",

View file

@ -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()

View file

@ -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 {

View file

@ -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())
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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}

View file

@ -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{})
}

View file

@ -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)