NewRootCommand function

NewRootCommand creates a new root command.

Parameters:

- name: The name of the root command.

- usage: The usage of the root command.

- description: The description of the root command.

- version: The version of the application.

Parameters:

  • name string
  • usage string
  • description string
  • version string

Returns:

  • *RootCommand
Show/Hide Function Body
{
	rc := &RootCommand{
		Version: version,
		Command: command.Command{
			Name:        name,
			Usage:       usage,
			Description: description,
			Flags:       flag.NewFlagSet(name, flag.ExitOnError),
			SubCommands: []*command.Command{},
			ArgFlags:    make(map[string]bool),
			Logger:      &log.DefaultLogger{ComponentName: name},
		},
		Commands: make(map[string]*command.Command),
	}
	// Adds the built-in commands (version and completion)
	rc.addBuiltInCommands()
	return rc
}

RootCommand struct

RootCommand represents the root command. Can be assigned while creating

a new root command with NewRootCommand.

Fields:

  • Version (string)
  • Commands (map[string]*command.Command)

Methods:

AddCommand

AddCommand adds a subcommand.

Parameters:

- cmd: The subcommand to add.


Parameters:
  • cmd *command.Command

Show/Hide Method Body
{
	cmd.Logger = &log.DefaultLogger{ComponentName: cmd.Name}
	rc.SubCommands = append(rc.SubCommands, cmd)
	rc.Commands[cmd.Name] = cmd
}

Execute

Execute runs the main command.


Returns:
  • error

Show/Hide Method Body
{
	// Required flags check
	setFlags := make(map[string]bool)
	var requiredFlags []string

	// Parse root flags
	if len(os.Args) > 1 {
		if err := rc.Flags.Parse(os.Args[1:]); err != nil {
			return err
		}

		// Check for required flags on the root command
		rc.Flags.Visit(func(f *flag.Flag) {
			setFlags[f.Name] = true
		})

		for _, requiredFlag := range rc.RequiredFlags {
			if _, isSet := setFlags[requiredFlag]; !isSet {
				requiredFlags = append(requiredFlags, "'-"+requiredFlag+"'")
			}
		}
	}

	args := rc.Flags.Args()
	if len(args) == 0 {
		rc.PrintHelp()
		return nil
	}

	var cmd *command.Command
	currentLevelCmds := rc.SubCommands
	var commandsTraversed int

	// Find the command to execute by traversing the command tree
	for i, arg := range args {
		var foundCmd *command.Command
		for _, c := range currentLevelCmds {
			if c.Name == arg {
				foundCmd = c
				break
			}
		}

		if foundCmd != nil {
			cmd = foundCmd
			currentLevelCmds = cmd.SubCommands
			commandsTraversed = i + 1
		} else {
			break
		}
	}

	if cmd == nil {
		rc.PrintHelp()
		return nil
	}

	cmdArgs := args[commandsTraversed:]

	// If the command has subcommands and no arguments are provided, show help
	if len(cmd.SubCommands) > 0 && len(cmdArgs) == 0 {
		cmd.PrintCommandHelp()
		return nil
	}

	// Handle help flag for the found subcommand
	for _, arg := range cmdArgs {
		if arg == "-h" || arg == "--help" {
			cmd.PrintCommandHelp()
			return nil
		}
	}

	var remainingArgs []string

	// Parse flags for the subcommand
	if cmd.Flags != nil {
		expandedArgs := make([]string, 0, len(cmdArgs))
		for _, arg := range cmdArgs {
			if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) == 2 {
				shortName := strings.TrimPrefix(arg, "-")
				if longName, ok := cmd.ShortFlagMap[shortName]; ok {
					expandedArgs = append(expandedArgs, fmt.Sprintf("--%s", longName))
				} else {
					expandedArgs = append(expandedArgs, arg)
				}
			} else {
				expandedArgs = append(expandedArgs, arg)
			}
		}

		if err := cmd.Flags.Parse(expandedArgs); err != nil {
			return err
		}

		// Check for required flags on the subcommand
		cmd.Flags.Visit(func(f *flag.Flag) {
			setFlags[f.Name] = true
		})

		for _, requiredFlag := range cmd.RequiredFlags {
			if _, isSet := setFlags[requiredFlag]; !isSet {
				requiredFlags = append(requiredFlags, "'-"+requiredFlag+"'")
			}
		}

		remainingArgs = cmd.Flags.Args()
	} else {
		remainingArgs = cmdArgs
	}

	// Check for any missing required flags (both root and subcommand)
	// add a subcommand to a subcommand
	if len(requiredFlags) > 0 {
		cmd.Logger.Error("Missing required flags: %s", strings.Join(requiredFlags, ", "))
		cmd.PrintCommandHelp()
		return nil
	}

	if cmd.Run == nil {
		err := fmt.Errorf("no main `Run` function defined for command, can't call any hooks (BeforeRun, Run, AfterRun) '%s'", cmd.Name)
		cmd.Logger.Error("%v", err)
		return err
	}

	parsedRootFlags := &command.RootFlags{FlagSet: rc.Flags}

	if cmd.BeforeRun != nil {
		if err := cmd.BeforeRun(cmd, parsedRootFlags, remainingArgs); err != nil {
			cmd.Logger.Error("Error running before main command: %v", err)
			return err
		}
	}

	if err := cmd.Run(cmd, parsedRootFlags, remainingArgs); err != nil {
		cmd.Logger.Error("Error running main command: %v", err)
		return err
	}

	if cmd.AfterRun != nil {
		if err := cmd.AfterRun(cmd, parsedRootFlags, remainingArgs); err != nil {
			cmd.Logger.Error("Error running after main command: %v", err)
			return err
		}
	}

	return nil
}

PrintHelp

PrintHelp prints the help message for the root command.


Show/Hide Method Body
{
	fmt.Printf("%s - %s\n\n", rc.Name, rc.Description)
	if rc.Version != "" {
		fmt.Printf("Version: %s\n\n", rc.Version)
	}
	fmt.Printf("Usage: %s\n\n", rc.Usage)

	fmt.Println("Commands:")
	maxNameLength := 0
	for _, cmd := range rc.SubCommands {
		if len(cmd.Name) > maxNameLength {
			maxNameLength = len(cmd.Name)
		}
	}
	for _, cmd := range rc.SubCommands {
		fmt.Printf("  %-*s  %s\n", maxNameLength, cmd.Name, cmd.Description)
	}
	if rc.Flags != nil {
		fmt.Println("\nFlags:")
		rc.Flags.VisitAll(func(f *flag.Flag) {
			fmt.Printf("  -%s: %s\n", f.Name, f.Usage)
		})
	}
}

AddAlias

AddAlias adds an alias for a command or subcommand chain of any depth.

Parameters:

- alias: The name of the alias.

- targets: The target command and optional subcommands in sequence (e.g., "cmd", "subcmd", "subsubcmd").


Parameters:
  • alias string
  • targets ...string

Show/Hide Method Body
{
	if len(targets) == 0 {
		fmt.Fprintf(os.Stderr, "No target command specified for alias '%s'\n", alias)
		return
	}

	// Check if the first command exists
	if _, ok := rc.Commands[targets[0]]; !ok {
		fmt.Fprintf(os.Stderr, "Target command '%s' not found for alias '%s'\n", targets[0], alias)
		return
	}

	// Build description based on targets depth
	var description string
	if len(targets) == 1 {
		description = fmt.Sprintf("Alias for '%s'", targets[0])
	} else {
		description = fmt.Sprintf("Alias for '%s'", strings.Join(targets, " "))
	}

	// Create alias command
	aliasCmd := &command.Command{
		Name:        alias,
		Usage:       alias,
		Description: description,
		Run: func(cmd *command.Command, rootFlags *command.RootFlags, args []string) error {
			newArgs := append([]string{}, targets...)
			newArgs = append(newArgs, args...)
			return utils.Reexec(newArgs)
		},
	}

	aliasCmd.SetupLogger(alias)
	rc.AddCommand(aliasCmd)
}

AddAliasSubCommand

AddAliasSubCommand adds an alias for a subcommand of a command.


Parameters:
  • alias string
  • targetCmd string
  • targetSubCmd string
  • targets ...string

Deprecated:

Parameters:

- alias: The name of the alias.

- targetCmd: The name of the target command.

- targetSubCmd: The name of the target subcommand.

- targets: Optional additional targets for deeper nesting.


Show/Hide Method Body
{
	allTargets := append([]string{targetCmd, targetSubCmd}, targets...)
	rc.AddAlias(alias, allTargets...)
}

AddAliasCommand

AddAliasCommand adds an alias for a command.


Parameters:
  • alias string
  • targetCmd string

Deprecated:

Parameters:

- alias: The name of the alias.

- targetCmd: The name of the target command.


Show/Hide Method Body
{
	rc.AddAlias(alias, targetCmd)
}

addBuiltInCommands

addBuiltInCommands add all the built-in commands to the root command.


Show/Hide Method Body
{
	rc.addVersionCommand()
	rc.addCompletionCommand()
}

getCommandNames

getCommandNames returns a slice with the names of all commands.


Returns:
  • []string

Show/Hide Method Body
{
	var names []string
	for _, cmd := range rc.SubCommands {
		names = append(names, cmd.Name)
	}
	return names
}

addVersionCommand

addVersionCommand adds the version command to the root command.


Show/Hide Method Body
{
	versionCmd := &command.Command{
		Name:        "version",
		Usage:       "version",
		Description: "Print the application version",
		Run: func(cmd *command.Command, rootFlags *command.RootFlags, args []string) error {
			fmt.Println(rc.Version)
			return nil
		},
	}
	versionCmd.SetupLogger("version")
	rc.AddCommand(versionCmd)
}

addCompletionCommand

addCompletionCommand adds the 'completion' command to the root command.


Show/Hide Method Body
{
	completionCmd := &command.Command{
		Name:        "completion",
		Usage:       "completion [bash|zsh|fish]",
		Description: "Generates shell completion scripts",
		Run:         rc.runCompletion,
	}
	completionCmd.SetupLogger("completion")
	rc.AddCommand(completionCmd)
}

runCompletion

runCompletion generates the completion script for the specified shell.


Parameters:
  • cmd *command.Command
  • rootFlags *command.RootFlags
  • args []string

Returns:
  • error

Show/Hide Method Body
{
	if len(args) < 1 {
		return fmt.Errorf("shell type required (bash, zsh, fish)")
	}

	shell := args[0]
	switch shell {
	case "bash":
		return rc.genBashCompletion()
	case "zsh":
		return rc.genZshCompletion()
	case "fish":
		return rc.genFishCompletion()
	default:
		return fmt.Errorf("unsupported shell type: %s", shell)
	}
}

genBashCompletion

genBashCompletion generates the bash completion script.


Returns:
  • error

Show/Hide Method Body
{
	fmt.Printf(`
_%s_completion()
{
	local cur prev opts
	COMPREPLY=()
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[COMP_PREV_CWORD]}"
	opts="%s"

	if [[ "${cur}" == "-" ]]; then
			COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
			return 0
	fi

	local completions=()
	for opt in ${opts}; do
			if [[ "${opt}" == "${cur}"* ]]; then
					completions+=("${opt}")
			fi
	done
	COMPREPLY=("${completions[@]}")
	return 0
}
complete -F _%s_completion %s
`, rc.Name, strings.Join(rc.getCommandNames(), " "), rc.Name, rc.Name)
	return nil
}

genZshCompletion

genZshCompletion generates the zsh completion script.


Returns:
  • error

Show/Hide Method Body
{
	fmt.Printf(`
#compdef %s

_%s_completion() {
        local line
        read -L line

        local commands="%s"
        local words=("${(s/ /)line}")
        local cur="${words[-1]}"

        if [[ $words[1] == "-*" ]]; then
                reply=("${(@(|)${(M)commands:#$cur} )}")
        else
                reply=("${(@(|)${(M)commands:#$cur} )}")
        fi
}

_%s_completion
`, rc.Name, rc.Name, strings.Join(rc.getCommandNames(), " "), rc.Name)
	return nil
}

genFishCompletion

genFishCompletion generates the fish completion script.


Returns:
  • error

Show/Hide Method Body
{
	fmt.Printf(`
function __fish_%s_complete
        set -lx COMP_WORDS (commandline -o | string split -s ' ')
        set -lx COMP_CWORD (math (contains -i -- (commandline -o | string split -s ' ') -- (commandline -o | string cursor)) - 1)
        set -lx COMP_LINE (commandline -o)

        set -lx cmd (string split -s ' ' -- (commandline -o))[1]

        for word in (%s)
                if string match -q $word $COMP_WORDS[$COMP_CWORD]
                        echo $word
                end
        end
end

complete -f -c %s -a "(__fish_%s_complete)"
`, rc.Name, strings.Join(rc.getCommandNames(), " "), rc.Name, rc.Name)
	return nil
}

flag import

Import example:

import "flag"

fmt import

Import example:

import "fmt"

os import

Import example:

import "os"

strings import

Import example:

import "strings"

github.com/mirkobrombin/go-cli-builder/v1/command import

Import example:

import "github.com/mirkobrombin/go-cli-builder/v1/command"

github.com/mirkobrombin/go-cli-builder/v1/log import

Import example:

import "github.com/mirkobrombin/go-cli-builder/v1/log"

github.com/mirkobrombin/go-cli-builder/v1/utils import

Import example:

import "github.com/mirkobrombin/go-cli-builder/v1/utils"

github.com/mirkobrombin/go-cli-builder/v1/command import

Import example:

import "github.com/mirkobrombin/go-cli-builder/v1/command"

fmt import

Import example:

import "fmt"

github.com/mirkobrombin/go-cli-builder/v1/command import

Import example:

import "github.com/mirkobrombin/go-cli-builder/v1/command"

fmt import

Import example:

import "fmt"

strings import

Import example:

import "strings"

github.com/mirkobrombin/go-cli-builder/v1/command import

Import example:

import "github.com/mirkobrombin/go-cli-builder/v1/command"