Repository: spf13/cobra
Pull request #1943 (2023-03-30 10:06:08)

feat: add getters for flag completions
Would provide a way for reading flagCompletionFunctions map

 https://github.com/spf13/cobra/issues/1942

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/spf13/cobra?pullRequest=1943) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/spf13/cobra?pullRequest=1943) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/spf13/cobra?pullRequest=1943) it.</sub>

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 2578d94..a618ec2 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -19,7 +19,7 @@ linters:
   disable-all: true
   enable:
     #- bodyclose
-    - deadcode
+    # - deadcode ! deprecated since v1.49.0; replaced by 'unused'
     #- depguard
     #- dogsled
     #- dupl
@@ -51,12 +51,12 @@ linters:
     #- rowserrcheck
     #- scopelint
     #- staticcheck
-    - structcheck
+    #- structcheck ! deprecated since v1.49.0; replaced by 'unused'
     #- stylecheck
     #- typecheck
     - unconvert
     #- unparam
-    #- unused
-    - varcheck
+    - unused
+    # - varcheck ! deprecated since v1.49.0; replaced by 'unused'
     #- whitespace
   fast: false
diff --git a/README.md b/README.md
index 592c0b8..6444f4b 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Cobra is a library for creating powerful modern CLI applications.
 
 Cobra is used in many Go projects such as [Kubernetes](https://kubernetes.io/),
 [Hugo](https://gohugo.io), and [GitHub CLI](https://github.com/cli/cli) to
-name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra.
+name a few. [This list](site/content/projects_using_cobra.md) contains a more extensive list of projects using Cobra.
 
 [![](https://img.shields.io/github/actions/workflow/status/spf13/cobra/test.yml?branch=main&longCache=true&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest)
 [![Go Reference](https://pkg.go.dev/badge/github.com/spf13/cobra.svg)](https://pkg.go.dev/github.com/spf13/cobra)
@@ -80,7 +80,7 @@ which maintains the same interface while adding POSIX compliance.
 
 # Installing
 Using Cobra is easy. First, use `go get` to install the latest version
-of the library.     
+of the library.
 
 ```
 go get -u github.com/spf13/cobra@latest
@@ -105,8 +105,8 @@ go install github.com/spf13/cobra-cli@latest
 
 For complete details on using the Cobra-CLI generator, please read [The Cobra Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md)
 
-For complete details on using the Cobra library, please read the [The Cobra User Guide](user_guide.md).
+For complete details on using the Cobra library, please read the [The Cobra User Guide](site/content/user_guide.md).
 
 # License
 
-Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt)
+Cobra is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt)
diff --git a/bash_completions.go b/bash_completions.go
index 10c7884..8a53151 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -85,7 +85,7 @@ __%[1]s_handle_go_custom_completion()
     local out requestComp lastParam lastChar comp directive args
 
     # Prepare the command to request completions for the program.
-    # Calling ${words[0]} instead of directly %[1]s allows to handle aliases
+    # Calling ${words[0]} instead of directly %[1]s allows handling aliases
     args=("${words[@]:1}")
     # Disable ActiveHelp which is not supported for bash completion v1
     requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}"
diff --git a/bash_completionsV2.go b/bash_completionsV2.go
index 19b0956..1cce5c3 100644
--- a/bash_completionsV2.go
+++ b/bash_completionsV2.go
@@ -57,7 +57,7 @@ __%[1]s_get_completion_results() {
     local requestComp lastParam lastChar args
 
     # Prepare the command to request completions for the program.
-    # Calling ${words[0]} instead of directly %[1]s allows to handle aliases
+    # Calling ${words[0]} instead of directly %[1]s allows handling aliases
     args=("${words[@]:1}")
     requestComp="${words[0]} %[2]s ${args[*]}"
 
diff --git a/cobra.go b/cobra.go
index b07b44a..f23f509 100644
--- a/cobra.go
+++ b/cobra.go
@@ -48,7 +48,7 @@ const (
 	defaultCaseInsensitive = false
 )
 
-// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
+// EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing
 // to automatically enable in CLI tools.
 // Set this to true to enable it.
 var EnablePrefixMatching = defaultPrefixMatching
diff --git a/command.go b/command.go
index 01f7c6f..6866f7d 100644
--- a/command.go
+++ b/command.go
@@ -181,6 +181,9 @@ type Command struct {
 	// versionTemplate is the version template defined by user.
 	versionTemplate string
 
+	// errPrefix is the error message prefix defined by user.
+	errPrefix string
+
 	// inReader is a reader defined by the user that replaces stdin
 	inReader io.Reader
 	// outWriter is a writer defined by the user that replaces stdout
@@ -346,6 +349,11 @@ func (c *Command) SetVersionTemplate(s string) {
 	c.versionTemplate = s
 }
 
+// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix.
+func (c *Command) SetErrPrefix(s string) {
+	c.errPrefix = s
+}
+
 // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
 // The user should not have a cyclic dependency on commands.
 func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
@@ -595,6 +603,18 @@ func (c *Command) VersionTemplate() string {
 `
 }
 
+// ErrPrefix return error message prefix for the command
+func (c *Command) ErrPrefix() string {
+	if c.errPrefix != "" {
+		return c.errPrefix
+	}
+
+	if c.HasParent() {
+		return c.parent.ErrPrefix()
+	}
+	return "Error:"
+}
+
 func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
 	flag := fs.Lookup(name)
 	if flag == nil {
@@ -752,7 +772,9 @@ func (c *Command) findNext(next string) *Command {
 	}
 
 	if len(matches) == 1 {
-		return matches[0]
+		// Temporarily disable gosec G602, which produces a false positive.
+		// See https://github.com/securego/gosec/issues/1005.
+		return matches[0] // #nosec G602
 	}
 
 	return nil
@@ -1048,7 +1070,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 			c = cmd
 		}
 		if !c.SilenceErrors {
-			c.PrintErrln("Error:", err.Error())
+			c.PrintErrln(c.ErrPrefix(), err.Error())
 			c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
 		}
 		return c, err
@@ -1077,7 +1099,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
 		// If root command has SilenceErrors flagged,
 		// all subcommands should respect it
 		if !cmd.SilenceErrors && !c.SilenceErrors {
-			c.PrintErrln("Error:", err.Error())
+			c.PrintErrln(cmd.ErrPrefix(), err.Error())
 		}
 
 		// If root command has SilenceUsage flagged,
diff --git a/completions.go b/completions.go
index ee38c4d..55a9f62 100644
--- a/completions.go
+++ b/completions.go
@@ -145,6 +145,25 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman
 	return nil
 }
 
+// GetFlagCompletion returns the completion function for the given flag, if available.
+func GetFlagCompletion(flag *pflag.Flag) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) {
+	flagCompletionMutex.RLock()
+	defer flagCompletionMutex.RUnlock()
+
+	completionFunc, exists := flagCompletionFunctions[flag]
+	return completionFunc, exists
+}
+
+// GetFlagCompletionByName returns the completion function for the given flag in the command by name, if available.
+func (c *Command) GetFlagCompletionByName(flagName string) (func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective), bool) {
+	flag := c.Flags().Lookup(flagName)
+	if flag == nil {
+		return nil, false
+	}
+
+	return GetFlagCompletion(flag)
+}
+
 // Returns a string listing the different directive enabled in the specified parameter
 func (d ShellCompDirective) string() string {
 	var directives []string
diff --git a/fish_completions.go b/fish_completions.go
index 12ca0d2..12d61b6 100644
--- a/fish_completions.go
+++ b/fish_completions.go
@@ -113,7 +113,7 @@ function __%[1]s_clear_perform_completion_once_result
     __%[1]s_debug ""
     __%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable =========="
     set --erase __%[1]s_perform_completion_once_result
-    __%[1]s_debug "Succesfully erased the variable __%[1]s_perform_completion_once_result"
+    __%[1]s_debug "Successfully erased the variable __%[1]s_perform_completion_once_result"
 end
 
 function __%[1]s_requires_order_preservation
diff --git a/fish_completions.md b/fish_completions.md
deleted file mode 100644
index 19b2ed1..0000000
--- a/fish_completions.md
+++ /dev/null
@@ -1,4 +0,0 @@
-## Generating Fish Completions For Your cobra.Command
-
-Please refer to [Shell Completions](shell_completions.md) for details.
-
diff --git a/flag_groups.go b/flag_groups.go
index b35fde1..0671ec5 100644
--- a/flag_groups.go
+++ b/flag_groups.go
@@ -24,6 +24,7 @@ import (
 
 const (
 	requiredAsGroup   = "cobra_annotation_required_if_others_set"
+	oneRequired       = "cobra_annotation_one_required"
 	mutuallyExclusive = "cobra_annotation_mutually_exclusive"
 )
 
@@ -43,6 +44,22 @@ 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.
+func (c *Command) MarkFlagsOneRequired(flagNames ...string) {
+	c.mergePersistentFlags()
+	for _, v := range flagNames {
+		f := c.Flags().Lookup(v)
+		if f == nil {
+			panic(fmt.Sprintf("Failed to find flag %q and mark it as being in a one-required flag group", v))
+		}
+		if err := c.Flags().SetAnnotation(v, oneRequired, append(f.Annotations[oneRequired], strings.Join(flagNames, " "))); err != nil {
+			// Only errs if the flag isn't found.
+			panic(err)
+		}
+	}
+}
+
 // 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.
 func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
@@ -59,7 +76,7 @@ func (c *Command) MarkFlagsMutuallyExclusive(flagNames ...string) {
 	}
 }
 
-// ValidateFlagGroups validates the mutuallyExclusive/requiredAsGroup logic and returns the
+// ValidateFlagGroups validates the mutuallyExclusive/oneRequired/requiredAsGroup logic and returns the
 // first error encountered.
 func (c *Command) ValidateFlagGroups() error {
 	if c.DisableFlagParsing {
@@ -71,15 +88,20 @@ func (c *Command) ValidateFlagGroups() error {
 	// groupStatus format is the list of flags as a unique ID,
 	// then a map of each flag name and whether it is set or not.
 	groupStatus := map[string]map[string]bool{}
+	oneRequiredGroupStatus := map[string]map[string]bool{}
 	mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
 	flags.VisitAll(func(pflag *flag.Flag) {
 		processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus)
+		processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus)
 		processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus)
 	})
 
 	if err := validateRequiredFlagGroups(groupStatus); err != nil {
 		return err
 	}
+	if err := validateOneRequiredFlagGroups(oneRequiredGroupStatus); err != nil {
+		return err
+	}
 	if err := validateExclusiveFlagGroups(mutuallyExclusiveGroupStatus); err != nil {
 		return err
 	}
@@ -142,6 +164,27 @@ func validateRequiredFlagGroups(data map[string]map[string]bool) error {
 	return nil
 }
 
+func validateOneRequiredFlagGroups(data map[string]map[string]bool) error {
+	keys := sortedKeys(data)
+	for _, flagList := range keys {
+		flagnameAndStatus := data[flagList]
+		var set []string
+		for flagname, isSet := range flagnameAndStatus {
+			if isSet {
+				set = append(set, flagname)
+			}
+		}
+		if len(set) >= 1 {
+			continue
+		}
+
+		// Sort values, so they can be tested/scripted against consistently.
+		sort.Strings(set)
+		return fmt.Errorf("at least one of the flags in the group [%v] is required", flagList)
+	}
+	return nil
+}
+
 func validateExclusiveFlagGroups(data map[string]map[string]bool) error {
 	keys := sortedKeys(data)
 	for _, flagList := range keys {
@@ -176,6 +219,7 @@ func sortedKeys(m map[string]map[string]bool) []string {
 
 // 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
 func (c *Command) enforceFlagGroupsForCompletion() {
@@ -185,9 +229,11 @@ func (c *Command) enforceFlagGroupsForCompletion() {
 
 	flags := c.Flags()
 	groupStatus := map[string]map[string]bool{}
+	oneRequiredGroupStatus := map[string]map[string]bool{}
 	mutuallyExclusiveGroupStatus := map[string]map[string]bool{}
 	c.Flags().VisitAll(func(pflag *flag.Flag) {
 		processFlagForGroupAnnotation(flags, pflag, requiredAsGroup, groupStatus)
+		processFlagForGroupAnnotation(flags, pflag, oneRequired, oneRequiredGroupStatus)
 		processFlagForGroupAnnotation(flags, pflag, mutuallyExclusive, mutuallyExclusiveGroupStatus)
 	})
 
@@ -204,6 +250,26 @@ func (c *Command) enforceFlagGroupsForCompletion() {
 		}
 	}
 
+	// If none of the flags of a one-required group are present, we make all the flags
+	// of that group required so that the shell completion suggests them automatically
+	for flagList, flagnameAndStatus := range oneRequiredGroupStatus {
+		set := 0
+
+		for _, isSet := range flagnameAndStatus {
+			if isSet {
+				set++
+			}
+		}
+
+		// None of the flags of the group are set, mark all flags in the group
+		// as required
+		if set == 0 {
+			for _, fName := range strings.Split(flagList, " ") {
+				_ = c.MarkFlagRequired(fName)
+			}
+		}
+	}
+
 	// If a flag that is mutually exclusive to others is present, we hide the other
 	// flags of that group so the shell completion does not suggest them
 	for flagList, flagnameAndStatus := range mutuallyExclusiveGroupStatus {
diff --git a/powershell_completions.go b/powershell_completions.go
index 177d275..5519519 100644
--- a/powershell_completions.go
+++ b/powershell_completions.go
@@ -47,7 +47,7 @@ filter __%[1]s_escapeStringWithSpecialChars {
 `+"    $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+`
 }
 
-[scriptblock]$__%[2]sCompleterBlock = {
+[scriptblock]${__%[2]sCompleterBlock} = {
     param(
             $WordToComplete,
             $CommandAst,
@@ -122,7 +122,7 @@ filter __%[1]s_escapeStringWithSpecialChars {
 
     __%[1]s_debug "Calling $RequestComp"
     # First disable ActiveHelp which is not supported for Powershell
-    $env:%[10]s=0
+    ${env:%[10]s}=0
 
     #call the command store the output in $out and redirect stderr and stdout to null
     # $Out is an array contains each line per element
@@ -279,7 +279,7 @@ filter __%[1]s_escapeStringWithSpecialChars {
     }
 }
 
-Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock $__%[2]sCompleterBlock
+Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock ${__%[2]sCompleterBlock}
 `, name, nameForVar, compCmd,
 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
 		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
diff --git a/powershell_completions.md b/powershell_completions.md
deleted file mode 100644
index c449f1e..0000000
--- a/powershell_completions.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generating PowerShell Completions For Your Own cobra.Command
-
-Please refer to [Shell Completions](shell_completions.md#powershell-completions) for details.
diff --git a/active_help.md b/site/content/active_help.md
similarity index 100%
rename from active_help.md
rename to site/content/active_help.md
diff --git a/shell_completions.md b/site/content/completions/_index.md
similarity index 98%
rename from shell_completions.md
rename to site/content/completions/_index.md
index 065c062..4efad29 100644
--- a/shell_completions.md
+++ b/site/content/completions/_index.md
@@ -416,7 +416,7 @@ completion     firstcommand   secondcommand
 ### Bash legacy dynamic completions
 
 For backward compatibility, Cobra still supports its bash legacy dynamic completion solution.
-Please refer to [Bash Completions](bash_completions.md) for details.
+Please refer to [Bash Completions](bash.md) for details.
 
 ### Bash completion V2
 
@@ -425,13 +425,13 @@ Cobra provides two versions for bash completion.  The original bash completion (
 
 A new V2 bash completion version is also available.  This version can be used by calling `GenBashCompletionV2()` or
 `GenBashCompletionFileV2()`.  The V2 version does **not** support the legacy dynamic completion
-(see [Bash Completions](bash_completions.md)) but instead works only with the Go dynamic completion
+(see [Bash Completions](bash.md)) but instead works only with the Go dynamic completion
 solution described in this document.
 Unless your program already uses the legacy dynamic completion solution, it is recommended that you use the bash
 completion V2 solution which provides the following extra features:
 - Supports completion descriptions (like the other shells)
 - Small completion script of less than 300 lines (v1 generates scripts of thousands of lines; `kubectl` for example has a bash v1 completion script of over 13K lines)
-- Streamlined user experience thanks to a completion behavior aligned with the other shells 
+- Streamlined user experience thanks to a completion behavior aligned with the other shells
 
 `Bash` completion V2 supports descriptions for completions. When calling `GenBashCompletionV2()` or `GenBashCompletionFileV2()`
 you must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra
@@ -448,7 +448,7 @@ show    (show information of a chart)
 $ helm s[tab][tab]
 search  show  status
 ```
-**Note**: Cobra's default `completion` command uses bash completion V2.  If for some reason you need to use bash completion V1, you will need to implement your own `completion` command. 
+**Note**: Cobra's default `completion` command uses bash completion V2.  If for some reason you need to use bash completion V1, you will need to implement your own `completion` command.
 ## Zsh completions
 
 Cobra supports native zsh completion generated from the root `cobra.Command`.
@@ -482,7 +482,7 @@ search  show  status
 ### Zsh completions standardization
 
 Cobra 1.1 standardized its zsh completion support to align it with its other shell completions.  Although the API was kept backward-compatible, some small changes in behavior were introduced.
-Please refer to [Zsh Completions](zsh_completions.md) for details.
+Please refer to [Zsh Completions](zsh.md) for details.
 
 ## fish completions
 
@@ -535,7 +535,7 @@ search  (search for a keyword in charts)  show  (show information of a chart)  s
 
 # With descriptions and Mode 'MenuComplete' The description of the current selected value will be displayed below the suggestions.
 $ helm s[tab]
-search    show     status  
+search    show     status
 
 search for a keyword in charts
 
diff --git a/bash_completions.md b/site/content/completions/bash.md
similarity index 98%
rename from bash_completions.md
rename to site/content/completions/bash.md
index 52919b2..6838a3a 100644
--- a/bash_completions.md
+++ b/site/content/completions/bash.md
@@ -1,6 +1,6 @@
 # Generating Bash Completions For Your cobra.Command
 
-Please refer to [Shell Completions](shell_completions.md) for details.
+Please refer to [Shell Completions](_index.md) for details.
 
 ## Bash legacy dynamic completions
 
diff --git a/site/content/completions/fish.md b/site/content/completions/fish.md
new file mode 100644
index 0000000..5253f7d
--- /dev/null
+++ b/site/content/completions/fish.md
@@ -0,0 +1,4 @@
+## Generating Fish Completions For Your cobra.Command
+
+Please refer to [Shell Completions](_index.md) for details.
+
diff --git a/site/content/completions/powershell.md b/site/content/completions/powershell.md
new file mode 100644
index 0000000..024b119
--- /dev/null
+++ b/site/content/completions/powershell.md
@@ -0,0 +1,3 @@
+# Generating PowerShell Completions For Your Own cobra.Command
+
+Please refer to [Shell Completions](_index.md#powershell-completions) for details.
diff --git a/zsh_completions.md b/site/content/completions/zsh.md
similarity index 98%
rename from zsh_completions.md
rename to site/content/completions/zsh.md
index 7cff617..3e27208 100644
--- a/zsh_completions.md
+++ b/site/content/completions/zsh.md
@@ -1,6 +1,6 @@
 ## Generating Zsh Completion For Your cobra.Command
 
-Please refer to [Shell Completions](shell_completions.md) for details.
+Please refer to [Shell Completions](_index.md) for details.
 
 ## Zsh completions standardization
 
diff --git a/doc/README.md b/site/content/docgen/_index.md
similarity index 73%
rename from doc/README.md
rename to site/content/docgen/_index.md
index 8e07baa..eba2a5f 100644
--- a/doc/README.md
+++ b/site/content/docgen/_index.md
@@ -1,9 +1,9 @@
 # Documentation generation
 
-- [Man page docs](./man_docs.md)
-- [Markdown docs](./md_docs.md)
-- [Rest docs](./rest_docs.md)
-- [Yaml docs](./yaml_docs.md)
+- [Man page docs](man.md)
+- [Markdown docs](md.md)
+- [Rest docs](rest.md)
+- [Yaml docs](yaml.md)
 
 ## Options
 ### `DisableAutoGenTag`
diff --git a/doc/man_docs.md b/site/content/docgen/man.md
similarity index 100%
rename from doc/man_docs.md
rename to site/content/docgen/man.md
diff --git a/doc/md_docs.md b/site/content/docgen/md.md
similarity index 100%
rename from doc/md_docs.md
rename to site/content/docgen/md.md
diff --git a/doc/rest_docs.md b/site/content/docgen/rest.md
similarity index 100%
rename from doc/rest_docs.md
rename to site/content/docgen/rest.md
diff --git a/doc/yaml_docs.md b/site/content/docgen/yaml.md
similarity index 100%
rename from doc/yaml_docs.md
rename to site/content/docgen/yaml.md
diff --git a/projects_using_cobra.md b/site/content/projects_using_cobra.md
similarity index 100%
rename from projects_using_cobra.md
rename to site/content/projects_using_cobra.md
diff --git a/user_guide.md b/site/content/user_guide.md
similarity index 94%
rename from user_guide.md
rename to site/content/user_guide.md
index 85201d8..93daadf 100644
--- a/user_guide.md
+++ b/site/content/user_guide.md
@@ -29,8 +29,8 @@ func main() {
 
 ## Using the Cobra Generator
 
-Cobra-CLI is its own program that will create your application and add any
-commands you want. It's the easiest way to incorporate Cobra into your application.
+Cobra-CLI is its own program that will create your application and add any commands you want.
+It's the easiest way to incorporate Cobra into your application.
 
 For complete details on using the Cobra generator, please refer to [The Cobra-CLI Generator README](https://github.com/spf13/cobra-cli/blob/main/README.md)
 
@@ -349,7 +349,16 @@ rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML")
 rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
 ```
 
-In both of these cases:
+If you want to require at least one flag from a group to be present, you can use `MarkFlagsOneRequired`.
+This can be combined with `MarkFlagsMutuallyExclusive` to enforce exactly one flag from a given group:
+```go
+rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON")
+rootCmd.Flags().BoolVar(&ofYaml, "yaml", false, "Output in YAML")
+rootCmd.MarkFlagsOneRequired("json", "yaml")
+rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")
+```
+
+In these cases:
   - both local and persistent flags can be used
     - **NOTE:** the group is only enforced on commands where every flag is defined
   - a flag may appear in multiple groups
@@ -587,6 +596,12 @@ Running an application with the '--version' flag will print the version to stdou
 the version template. The template can be customized using the
 `cmd.SetVersionTemplate(s string)` function.
 
+## Error Message Prefix
+
+Cobra prints an error message when receiving a non-nil error value.
+The default error message is `Error: <error contents>`.
+The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` function.
+
 ## PreRun and PostRun Hooks
 
 It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`.  The `Persistent*Run` functions will be inherited by children if they do not declare their own.  These functions are run in the following order:
@@ -715,12 +730,17 @@ Run 'kubectl help' for usage.
 
 ## Generating documentation for your command
 
-Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md).
+Cobra can generate documentation based on subcommands, flags, etc.
+Read more about it in the [docs generation documentation](docgen/_index.md).
 
 ## Generating shell completions
 
-Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell. If you add more information to your commands, these completions can be amazingly powerful and flexible.  Read more about it in [Shell Completions](shell_completions.md).
+Cobra can generate a shell-completion file for the following shells: bash, zsh, fish, PowerShell.
+If you add more information to your commands, these completions can be amazingly powerful and flexible.
+Read more about it in [Shell Completions](completions/_index.md).
 
 ## Providing Active Help
 
-Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users.  Active Help are messages (hints, warnings, etc) printed as the program is being used.  Read more about it in [Active Help](active_help.md).
+Cobra makes use of the shell-completion system to define a framework allowing you to provide Active Help to your users.
+Active Help are messages (hints, warnings, etc) printed as the program is being used.
+Read more about it in [Active Help](active_help.md).


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 6a0c7bb..6b4c165 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -43,13 +43,13 @@ jobs:
 
       - uses: actions/setup-go@v3
         with:
-          go-version: '^1.20'
+          go-version: '^1.21'
           check-latest: true
           cache: true
 
       - uses: actions/checkout@v3
 
-      - uses: golangci/golangci-lint-action@v3.4.0
+      - uses: golangci/golangci-lint-action@v3.7.0
         with:
           version: latest
           args: --verbose
@@ -67,6 +67,7 @@ jobs:
         - 18
         - 19
         - 20
+        - 21
     name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
     runs-on: ${{ matrix.platform }}-latest
     steps:
diff --git a/command_test.go b/command_test.go
index 0212f5a..4afb7f7 100644
--- a/command_test.go
+++ b/command_test.go
@@ -438,7 +438,7 @@ func TestFlagLong(t *testing.T) {
 
 	output, err := executeCommand(c, "--intf=7", "--sf=abc", "one", "--", "two")
 	if output != "" {
-		t.Errorf("Unexpected output: %v", err)
+		t.Errorf("Unexpected output: %v", output)
 	}
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
@@ -475,7 +475,7 @@ func TestFlagShort(t *testing.T) {
 
 	output, err := executeCommand(c, "-i", "7", "-sabc", "one", "two")
 	if output != "" {
-		t.Errorf("Unexpected output: %v", err)
+		t.Errorf("Unexpected output: %v", output)
 	}
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
@@ -504,7 +504,7 @@ func TestChildFlag(t *testing.T) {
 
 	output, err := executeCommand(rootCmd, "child", "-i7")
 	if output != "" {
-		t.Errorf("Unexpected output: %v", err)
+		t.Errorf("Unexpected output: %v", output)
 	}
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
@@ -1099,6 +1099,39 @@ func TestShorthandVersionTemplate(t *testing.T) {
 	checkStringContains(t, output, "customized version: 1.0.0")
 }
 
+func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) {
+	rootCmd := &Command{Use: "root", Run: emptyRun}
+	rootCmd.SetErrPrefix("root error prefix:")
+	rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})
+
+	output, err := executeCommand(rootCmd, "sub", "--unknown-flag")
+	if err == nil {
+		t.Errorf("Expected error")
+	}
+
+	checkStringContains(t, output, "root error prefix: unknown flag: --unknown-flag")
+}
+
+func TestRootAndSubErrPrefix(t *testing.T) {
+	rootCmd := &Command{Use: "root", Run: emptyRun}
+	subCmd := &Command{Use: "sub", Run: emptyRun}
+	rootCmd.AddCommand(subCmd)
+	rootCmd.SetErrPrefix("root error prefix:")
+	subCmd.SetErrPrefix("sub error prefix:")
+
+	if output, err := executeCommand(rootCmd, "--unknown-root-flag"); err == nil {
+		t.Errorf("Expected error")
+	} else {
+		checkStringContains(t, output, "root error prefix: unknown flag: --unknown-root-flag")
+	}
+
+	if output, err := executeCommand(rootCmd, "sub", "--unknown-sub-flag"); err == nil {
+		t.Errorf("Expected error")
+	} else {
+		checkStringContains(t, output, "sub error prefix: unknown flag: --unknown-sub-flag")
+	}
+}
+
 func TestVersionFlagExecutedOnSubcommand(t *testing.T) {
 	rootCmd := &Command{Use: "root", Version: "1.0.0"}
 	rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})
diff --git a/completions_test.go b/completions_test.go
index 0588da0..0172466 100644
--- a/completions_test.go
+++ b/completions_test.go
@@ -2830,6 +2830,104 @@ func TestCompletionForGroupedFlags(t *testing.T) {
 	}
 }
 
+func TestCompletionForOneRequiredGroupFlags(t *testing.T) {
+	getCmd := func() *Command {
+		rootCmd := &Command{
+			Use: "root",
+			Run: emptyRun,
+		}
+		childCmd := &Command{
+			Use: "child",
+			ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
+				return []string{"subArg"}, ShellCompDirectiveNoFileComp
+			},
+			Run: emptyRun,
+		}
+		rootCmd.AddCommand(childCmd)
+
+		rootCmd.PersistentFlags().Int("ingroup1", -1, "ingroup1")
+		rootCmd.PersistentFlags().String("ingroup2", "", "ingroup2")
+
+		childCmd.Flags().Bool("ingroup3", false, "ingroup3")
+		childCmd.Flags().Bool("nogroup", false, "nogroup")
+
+		// Add flags to a group
+		childCmd.MarkFlagsOneRequired("ingroup1", "ingroup2", "ingroup3")
+
+		return rootCmd
+	}
+
+	// Each test case uses a unique command from the function above.
+	testcases := []struct {
+		desc           string
+		args           []string
+		expectedOutput string
+	}{
+		{
+			desc: "flags in group suggested without - prefix",
+			args: []string{"child", ""},
+			expectedOutput: strings.Join([]string{
+				"--ingroup1",
+				"--ingroup2",
+				"--ingroup3",
+				"subArg",
+				":4",
+				"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
+		},
+		{
+			desc: "flags in group suggested with - prefix",
+			args: []string{"child", "-"},
+			expectedOutput: strings.Join([]string{
+				"--ingroup1",
+				"--ingroup2",
+				"--ingroup3",
+				":4",
+				"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
+		},
+		{
+			desc: "when any flag in group present, other flags in group not suggested without - prefix",
+			args: []string{"child", "--ingroup2", "value", ""},
+			expectedOutput: strings.Join([]string{
+				"subArg",
+				":4",
+				"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
+		},
+		{
+			desc: "when all flags in group present, flags not suggested without - prefix",
+			args: []string{"child", "--ingroup1", "8", "--ingroup2", "value2", "--ingroup3", ""},
+			expectedOutput: strings.Join([]string{
+				"subArg",
+				":4",
+				"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
+		},
+		{
+			desc: "group ignored if some flags not applicable",
+			args: []string{"--ingroup2", "value", ""},
+			expectedOutput: strings.Join([]string{
+				"child",
+				"completion",
+				"help",
+				":4",
+				"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.desc, func(t *testing.T) {
+			c := getCmd()
+			args := []string{ShellCompNoDescRequestCmd}
+			args = append(args, tc.args...)
+			output, err := executeCommand(c, args...)
+			switch {
+			case err == nil && output != tc.expectedOutput:
+				t.Errorf("expected: %q, got: %q", tc.expectedOutput, output)
+			case err != nil:
+				t.Errorf("Unexpected error %q", err)
+			}
+		})
+	}
+}
+
 func TestCompletionForMutuallyExclusiveFlags(t *testing.T) {
 	getCmd := func() *Command {
 		rootCmd := &Command{
diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go
index c111d45..a4435e6 100644
--- a/doc/man_docs_test.go
+++ b/doc/man_docs_test.go
@@ -150,7 +150,7 @@ func TestGenManSeeAlso(t *testing.T) {
 	}
 }
 
-func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
+func TestManPrintFlagsHidesShortDeprecated(t *testing.T) {
 	c := &cobra.Command{}
 	c.Flags().StringP("foo", "f", "default", "Foo flag")
 	assertNoErr(t, c.Flags().MarkShorthandDeprecated("foo", "don't use it no more"))
diff --git a/fish_completions_test.go b/fish_completions_test.go
index 10d97d8..ce2a531 100644
--- a/fish_completions_test.go
+++ b/fish_completions_test.go
@@ -16,9 +16,10 @@ package cobra
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
-	"log"
 	"os"
+	"path/filepath"
 	"testing"
 )
 
@@ -98,12 +99,12 @@ func TestFishCompletionNoActiveHelp(t *testing.T) {
 }
 
 func TestGenFishCompletionFile(t *testing.T) {
-	err := os.Mkdir("./tmp", 0755)
+	tmpFile, err := os.CreateTemp("", "cobra-test")
 	if err != nil {
-		log.Fatal(err.Error())
+		t.Fatal(err.Error())
 	}
 
-	defer os.RemoveAll("./tmp")
+	defer os.Remove(tmpFile.Name())
 
 	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
 	child := &Command{
@@ -113,18 +114,18 @@ func TestGenFishCompletionFile(t *testing.T) {
 	}
 	rootCmd.AddCommand(child)
 
-	assertNoErr(t, rootCmd.GenFishCompletionFile("./tmp/test", false))
+	assertNoErr(t, rootCmd.GenFishCompletionFile(tmpFile.Name(), false))
 }
 
 func TestFailGenFishCompletionFile(t *testing.T) {
-	err := os.Mkdir("./tmp", 0755)
+	tmpDir, err := os.MkdirTemp("", "cobra-test")
 	if err != nil {
-		log.Fatal(err.Error())
+		t.Fatal(err.Error())
 	}
 
-	defer os.RemoveAll("./tmp")
+	defer os.RemoveAll(tmpDir)
 
-	f, _ := os.OpenFile("./tmp/test", os.O_CREATE, 0400)
+	f, _ := os.OpenFile(filepath.Join(tmpDir, "test"), os.O_CREATE, 0400)
 	defer f.Close()
 
 	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
@@ -135,18 +136,8 @@ func TestFailGenFishCompletionFile(t *testing.T) {
 	}
 	rootCmd.AddCommand(child)
 
-	got := rootCmd.GenFishCompletionFile("./tmp/test", false)
-	if got == nil {
-		t.Error("should raise permission denied error")
-	}
-
-	if os.Getenv("MSYSTEM") == "MINGW64" {
-		if got.Error() != "open ./tmp/test: Access is denied." {
-			t.Errorf("got: %s, want: %s", got.Error(), "open ./tmp/test: Access is denied.")
-		}
-	} else {
-		if got.Error() != "open ./tmp/test: permission denied" {
-			t.Errorf("got: %s, want: %s", got.Error(), "open ./tmp/test: permission denied")
-		}
+	got := rootCmd.GenFishCompletionFile(f.Name(), false)
+	if !errors.Is(got, os.ErrPermission) {
+		t.Errorf("got: %s, want: %s", got.Error(), os.ErrPermission.Error())
 	}
 }
diff --git a/flag_groups_test.go b/flag_groups_test.go
index bf988d7..cffa855 100644
--- a/flag_groups_test.go
+++ b/flag_groups_test.go
@@ -43,13 +43,15 @@ func TestValidateFlagGroups(t *testing.T) {
 
 	// Each test case uses a unique command from the function above.
 	testcases := []struct {
-		desc                      string
-		flagGroupsRequired        []string
-		flagGroupsExclusive       []string
-		subCmdFlagGroupsRequired  []string
-		subCmdFlagGroupsExclusive []string
-		args                      []string
-		expectErr                 string
+		desc                        string
+		flagGroupsRequired          []string
+		flagGroupsOneRequired       []string
+		flagGroupsExclusive         []string
+		subCmdFlagGroupsRequired    []string
+		subCmdFlagGroupsOneRequired []string
+		subCmdFlagGroupsExclusive   []string
+		args                        []string
+		expectErr                   string
 	}{
 		{
 			desc: "No flags no problem",
@@ -62,6 +64,11 @@ func TestValidateFlagGroups(t *testing.T) {
 			flagGroupsRequired: []string{"a b c"},
 			args:               []string{"--a=foo"},
 			expectErr:          "if any flags in the group [a b c] are set they must all be set; missing [b c]",
+		}, {
+			desc:                  "One-required flag group not satisfied",
+			flagGroupsOneRequired: []string{"a b"},
+			args:                  []string{"--c=foo"},
+			expectErr:             "at least one of the flags in the group [a b] is required",
 		}, {
 			desc:                "Exclusive flag group not satisfied",
 			flagGroupsExclusive: []string{"a b c"},
@@ -72,6 +79,11 @@ func TestValidateFlagGroups(t *testing.T) {
 			flagGroupsRequired: []string{"a b c", "a d"},
 			args:               []string{"--c=foo", "--d=foo"},
 			expectErr:          `if any flags in the group [a b c] are set they must all be set; missing [a b]`,
+		}, {
+			desc:                  "Multiple one-required flag group not satisfied returns first error",
+			flagGroupsOneRequired: []string{"a b", "d e"},
+			args:                  []string{"--c=foo", "--f=foo"},
+			expectErr:             `at least one of the flags in the group [a b] is required`,
 		}, {
 			desc:                "Multiple exclusive flag group not satisfied returns first error",
 			flagGroupsExclusive: []string{"a b c", "a d"},
@@ -82,32 +94,57 @@ func TestValidateFlagGroups(t *testing.T) {
 			flagGroupsRequired: []string{"a d", "a b", "a c"},
 			args:               []string{"--a=foo"},
 			expectErr:          `if any flags in the group [a b] are set they must all be set; missing [b]`,
+		}, {
+			desc:                  "Validation of one-required groups occurs on groups in sorted order",
+			flagGroupsOneRequired: []string{"d e", "a b", "f g"},
+			args:                  []string{"--c=foo"},
+			expectErr:             `at least one of the flags in the group [a b] is required`,
 		}, {
 			desc:                "Validation of exclusive groups occurs on groups in sorted order",
 			flagGroupsExclusive: []string{"a d", "a b", "a c"},
 			args:                []string{"--a=foo", "--b=foo", "--c=foo"},
 			expectErr:           `if any flags in the group [a b] are set none of the others can be; [a b] were all set`,
 		}, {
-			desc:                "Persistent flags utilize both features and can fail required groups",
+			desc:                "Persistent flags utilize required and exclusive groups and can fail required groups",
 			flagGroupsRequired:  []string{"a e", "e f"},
 			flagGroupsExclusive: []string{"f g"},
 			args:                []string{"--a=foo", "--f=foo", "--g=foo"},
 			expectErr:           `if any flags in the group [a e] are set they must all be set; missing [e]`,
 		}, {
-			desc:                "Persistent flags utilize both features and can fail mutually exclusive groups",
+			desc:                  "Persistent flags utilize one-required and exclusive groups and can fail one-required groups",
+			flagGroupsOneRequired: []string{"a b", "e f"},
+			flagGroupsExclusive:   []string{"e f"},
+			args:                  []string{"--e=foo"},
+			expectErr:             `at least one of the flags in the group [a b] is required`,
+		}, {
+			desc:                "Persistent flags utilize required and exclusive groups and can fail mutually exclusive groups",
 			flagGroupsRequired:  []string{"a e", "e f"},
 			flagGroupsExclusive: []string{"f g"},
 			args:                []string{"--a=foo", "--e=foo", "--f=foo", "--g=foo"},
 			expectErr:           `if any flags in the group [f g] are set none of the others can be; [f g] were all set`,
 		}, {
-			desc:                "Persistent flags utilize both features and can pass",
+			desc:                "Persistent flags utilize required and exclusive groups and can pass",
 			flagGroupsRequired:  []string{"a e", "e f"},
 			flagGroupsExclusive: []string{"f g"},
 			args:                []string{"--a=foo", "--e=foo", "--f=foo"},
+		}, {
+			desc:                  "Persistent flags utilize one-required and exclusive groups and can pass",
+			flagGroupsOneRequired: []string{"a e", "e f"},
+			flagGroupsExclusive:   []string{"f g"},
+			args:                  []string{"--a=foo", "--e=foo", "--f=foo"},
 		}, {
 			desc:                     "Subcmds can use required groups using inherited flags",
 			subCmdFlagGroupsRequired: []string{"e subonly"},
 			args:                     []string{"subcmd", "--e=foo", "--subonly=foo"},
+		}, {
+			desc:                        "Subcmds can use one-required groups using inherited flags",
+			subCmdFlagGroupsOneRequired: []string{"e subonly"},
+			args:                        []string{"subcmd", "--e=foo", "--subonly=foo"},
+		}, {
+			desc:                        "Subcmds can use one-required groups using inherited flags and fail one-required groups",
+			subCmdFlagGroupsOneRequired: []string{"e subonly"},
+			args:                        []string{"subcmd"},
+			expectErr:                   "at least one of the flags in the group [e subonly] is required",
 		}, {
 			desc:                      "Subcmds can use exclusive groups using inherited flags",
 			subCmdFlagGroupsExclusive: []string{"e subonly"},
@@ -130,12 +167,18 @@ func TestValidateFlagGroups(t *testing.T) {
 			for _, flagGroup := range tc.flagGroupsRequired {
 				c.MarkFlagsRequiredTogether(strings.Split(flagGroup, " ")...)
 			}
+			for _, flagGroup := range tc.flagGroupsOneRequired {
+				c.MarkFlagsOneRequired(strings.Split(flagGroup, " ")...)
+			}
 			for _, flagGroup := range tc.flagGroupsExclusive {
 				c.MarkFlagsMutuallyExclusive(strings.Split(flagGroup, " ")...)
 			}
 			for _, flagGroup := range tc.subCmdFlagGroupsRequired {
 				sub.MarkFlagsRequiredTogether(strings.Split(flagGroup, " ")...)
 			}
+			for _, flagGroup := range tc.subCmdFlagGroupsOneRequired {
+				sub.MarkFlagsOneRequired(strings.Split(flagGroup, " ")...)
+			}
 			for _, flagGroup := range tc.subCmdFlagGroupsExclusive {
 				sub.MarkFlagsMutuallyExclusive(strings.Split(flagGroup, " ")...)
 			}
diff --git a/powershell_completions_test.go b/powershell_completions_test.go
index b409213..603b50c 100644
--- a/powershell_completions_test.go
+++ b/powershell_completions_test.go
@@ -29,5 +29,5 @@ func TestPwshCompletionNoActiveHelp(t *testing.T) {
 
 	// check that active help is being disabled
 	activeHelpVar := activeHelpEnvVar(c.Name())
-	check(t, output, fmt.Sprintf("%s=0", activeHelpVar))
+	check(t, output, fmt.Sprintf("${env:%s}=0", activeHelpVar))
 }