Generated Documentation

This commit is contained in:
penify-dev[bot] 2025-04-26 20:40:10 +00:00
parent 16f7019823
commit ea92b272f6
2 changed files with 66 additions and 173 deletions

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

@ -1,165 +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"
)
// TestCompleteNoDesCmdInFishScript tests the completion functionality for a command without descriptions in a Fish shell script.
//
// It sets up a root command and a child command, adds the child to the root, generates Fish completion script,
// and checks if the output contains the expected completion information without descriptions.
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)
}
// TestCompleteCmdInFishScript tests the generation of fish completion script for a command.
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)
}
// TestProgWithDash tests the generation of fish completion for a command with a hyphen in its name.
// It verifies that the hyphen is replaced in function names but remains intact in the command name.
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")
}
// TestProgWithColon tests the generation of Fish completion for a command with a colon in its name.
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")
}
// TestFishCompletionNoActiveHelp tests the generation of Fish completion script without active help enabled.
//
// Parameters:
// - t: A testing.T instance for assertions and logging.
//
// The function creates a new Command, generates its Fish completion script with active help disabled,
// and asserts that the output does not include an active help variable set to 1.
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))
}
// TestGenFishCompletionFile tests the generation of a Fish completion file for a Cobra command.
// It creates a temporary file, sets up a Cobra command hierarchy, and asserts that no errors occur during the completion file generation process.
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))
}
// TestFailGenFishCompletionFile tests the GenFishCompletionFile method for permission errors.
//
// It creates a temporary directory and file, sets up a command structure,
// and attempts to generate Fish completion file. It checks if the error returned
// matches os.ErrPermission as expected. If not, it fails the test with an error message.
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())
}
}