chinampa/internal/registry/registry.go

193 lines
5.5 KiB
Go
Raw Permalink Normal View History

2022-12-19 03:04:34 +00:00
// Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
2022-12-31 05:53:24 +00:00
// SPDX-License-Identifier: Apache-2.0
2022-12-19 03:04:34 +00:00
package registry
import (
"fmt"
"os"
"sort"
"strings"
2022-12-31 05:53:24 +00:00
"git.rob.mx/nidito/chinampa/internal/commands"
2022-12-19 03:04:34 +00:00
"git.rob.mx/nidito/chinampa/pkg/command"
2022-12-31 05:53:24 +00:00
"git.rob.mx/nidito/chinampa/pkg/errors"
2023-03-20 06:15:53 +00:00
"git.rob.mx/nidito/chinampa/pkg/logger"
2022-12-31 05:53:24 +00:00
"git.rob.mx/nidito/chinampa/pkg/statuscode"
2022-12-19 03:04:34 +00:00
"github.com/fatih/color"
"github.com/spf13/cobra"
)
2022-12-31 05:53:24 +00:00
// ContextKeyRuntimeIndex is the string key used to store context in a cobra Command.
const ContextKeyRuntimeIndex = "x-chinampa-runtime-index"
var ErrorHandler = errors.HandleCobraExit
2023-03-20 06:15:53 +00:00
var log = logger.Sub("registry")
2022-12-31 23:53:49 +00:00
2022-12-19 03:04:34 +00:00
var registry = &CommandRegistry{
kv: map[string]*command.Command{},
}
type ByPath []*command.Command
func (cmds ByPath) Len() int { return len(cmds) }
func (cmds ByPath) Swap(i, j int) { cmds[i], cmds[j] = cmds[j], cmds[i] }
func (cmds ByPath) Less(i, j int) bool { return cmds[i].FullName() < cmds[j].FullName() }
type CommandRegistry struct {
kv map[string]*command.Command
byPath []*command.Command
}
func Register(cmd *command.Command) {
2023-03-20 01:18:10 +00:00
log.Tracef("adding to registry: %s", cmd.FullName())
2022-12-19 03:04:34 +00:00
registry.kv[cmd.FullName()] = cmd
}
func Get(id string) *command.Command {
return registry.kv[id]
}
func CommandList() []*command.Command {
if len(registry.byPath) == 0 {
list := []*command.Command{}
for _, v := range registry.kv {
list = append(list, v)
}
sort.Sort(ByPath(list))
registry.byPath = list
}
return registry.byPath
}
func Execute(version string) error {
2022-12-31 23:53:49 +00:00
log.Debug("starting execution")
2022-12-19 03:04:34 +00:00
cmdRoot := command.Root
2022-12-29 19:05:58 +00:00
ccRoot := newCobraRoot(command.Root)
2022-12-19 03:04:34 +00:00
ccRoot.CompletionOptions.HiddenDefaultCmd = true
2022-12-30 07:00:26 +00:00
ccRoot.PersistentFlags().AddFlagSet(cmdRoot.FlagSet())
2023-01-23 04:29:18 +00:00
if version != "" {
name := commands.VersionCommandName
ccRoot.Annotations["version"] = version
commands.Version.Hidden = strings.HasPrefix(name, "_")
commands.Version.Use = name
ccRoot.AddCommand(commands.Version)
}
2022-12-31 05:53:24 +00:00
ccRoot.AddCommand(commands.GenerateCompletions)
2022-12-19 03:04:34 +00:00
for name, opt := range cmdRoot.Options {
if err := ccRoot.RegisterFlagCompletionFunc(name, opt.CompletionFunction); err != nil {
2022-12-31 23:53:49 +00:00
log.Errorf("Failed setting up autocompletion for option <%s> of command <%s>", name, cmdRoot.FullName())
2022-12-19 03:04:34 +00:00
}
}
2022-12-31 23:53:49 +00:00
2022-12-19 03:04:34 +00:00
ccRoot.SetHelpFunc(cmdRoot.HelpRenderer(cmdRoot.Options))
for _, cmd := range CommandList() {
cmd := cmd
container := ccRoot
for idx, cp := range cmd.Path {
if idx == len(cmd.Path)-1 {
2022-12-31 05:53:24 +00:00
leaf := ToCobra(cmd, cmdRoot.Options)
2022-12-19 03:04:34 +00:00
container.AddCommand(leaf)
if container != ccRoot {
container.ValidArgs = append(container.ValidArgs, leaf.Name())
}
2023-03-20 01:18:10 +00:00
log.Tracef("cobra: %s => %s", leaf.Name(), container.CommandPath())
2022-12-19 03:04:34 +00:00
break
}
query := []string{cp}
2022-12-31 05:53:24 +00:00
found := false
if len(query) == 1 && query[0] == "help" {
container = commands.Help
continue
}
2022-12-31 23:53:49 +00:00
for _, sub := range container.Commands() {
if sub.Name() == cp {
container = sub
found = true
2022-12-31 05:53:24 +00:00
}
}
if !found {
2022-12-19 03:04:34 +00:00
groupName := strings.Join(query, " ")
2022-12-31 05:53:24 +00:00
groupPath := append(cmdRoot.Path, append(cmd.Path[0:idx], query...)...) // nolint:gocritic
2022-12-19 03:04:34 +00:00
cc := &cobra.Command{
Use: cp,
Short: fmt.Sprintf("%s subcommands", groupName),
DisableAutoGenTag: true,
SuggestionsMinimumDistance: 2,
SilenceUsage: true,
SilenceErrors: true,
Annotations: map[string]string{
2022-12-31 05:53:24 +00:00
ContextKeyRuntimeIndex: strings.Join(groupPath, " "),
2022-12-19 03:04:34 +00:00
},
2023-03-20 06:15:53 +00:00
ValidArgs: []string{},
2022-12-19 03:04:34 +00:00
Args: func(cmd *cobra.Command, args []string) error {
if err := cobra.OnlyValidArgs(cmd, args); err == nil {
return nil
}
suggestions := []string{}
bold := color.New(color.Bold)
for _, l := range cmd.SuggestionsFor(args[len(args)-1]) {
suggestions = append(suggestions, bold.Sprint(l))
}
last := len(args) - 1
parent := cmd.CommandPath()
errMessage := fmt.Sprintf("Unknown subcommand %s of known command %s", bold.Sprint(args[last]), bold.Sprint(parent))
if len(suggestions) > 0 {
errMessage += ". Perhaps you meant " + strings.Join(suggestions, ", ") + "?"
}
return errors.NotFound{Msg: errMessage, Group: []string{}}
},
RunE: func(cc *cobra.Command, args []string) error {
if len(args) == 0 {
if cc.Name() == "help" {
return cc.Help()
}
2022-12-19 03:04:34 +00:00
return errors.NotFound{Msg: "No subcommand provided", Group: []string{}}
}
2022-12-31 05:53:24 +00:00
os.Exit(statuscode.NotFound)
2022-12-19 03:04:34 +00:00
return nil
},
}
groupParent := &command.Command{
2022-12-31 05:53:24 +00:00
Path: groupPath,
2022-12-19 03:04:34 +00:00
Summary: fmt.Sprintf("%s subcommands", groupName),
Description: fmt.Sprintf("Runs subcommands within %s", groupName),
Arguments: command.Arguments{},
Options: command.Options{},
}
Register(groupParent)
cc.SetHelpFunc(groupParent.HelpRenderer(command.Options{}))
2022-12-31 23:53:49 +00:00
cc.SetHelpCommand(commands.Help)
2022-12-19 03:04:34 +00:00
container.AddCommand(cc)
container = cc
}
}
2022-12-30 07:00:26 +00:00
cmd.Path = append(cmdRoot.Path, cmd.Path...)
2022-12-19 03:04:34 +00:00
}
cmdRoot.SetCobra(ccRoot)
ccRoot.SetHelpCommand(commands.Help)
2022-12-19 03:04:34 +00:00
2022-12-31 05:53:24 +00:00
current, remaining, err := ccRoot.Find(os.Args[1:])
2022-12-19 03:04:34 +00:00
if err != nil {
2022-12-23 05:29:58 +00:00
current = ccRoot
}
2022-12-31 23:53:49 +00:00
log.Debugf("exec: found command %s with args: %s", current.CommandPath(), remaining)
2022-12-31 05:53:24 +00:00
if sub, _, err := current.Find(remaining); err == nil && sub != current {
2022-12-31 23:53:49 +00:00
log.Debugf("exec: found sub-command %s", sub.CommandPath())
2022-12-31 05:53:24 +00:00
current = sub
}
2022-12-31 23:53:49 +00:00
log.Debugf("exec: calling %s", current.CommandPath())
2022-12-19 03:04:34 +00:00
return ErrorHandler(current, current.Execute())
2022-12-19 03:04:34 +00:00
}