Skip to content

Commit 5a70d95

Browse files
committed
help: simplify implementation of help subsystem
Following changes are applied: - the main help command is implemented in a way similar to the other commands (choice between internal and external variation of the command is handled with the common handler cmd.RunModuleFunc) - help for every external command is tied to a command itself over cmd.SetHelp function rather than add separate help subcommand to the main help command (it allows to obtain help for internal and external commands uniformly) - corresponding Cobra commands are created for all available external commands rather than only for the invoked one (being combined with the uniform handling mentioned above it allows to make implementation of the internal variation of help command trivial) In addition: - `tt help` completion is improved (also suggest completion for subcommands, if any) - external commands are also implemented over the common handler cmd.RunModuleFunc to reduce code duplication Close #1136
1 parent a92b7ef commit 5a70d95

File tree

4 files changed

+58
-120
lines changed

4 files changed

+58
-120
lines changed

cli/cmd/external.go

+22-46
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
package cmd
22

33
import (
4-
"strings"
5-
6-
"github.com/apex/log"
74
"github.com/spf13/cobra"
85
"github.com/tarantool/tt/cli/modules"
9-
"github.com/tarantool/tt/cli/util"
6+
"golang.org/x/exp/slices"
107
)
118

129
// ExternalCmd configures external commands.
13-
func configureExternalCmd(rootCmd *cobra.Command,
14-
modulesInfo *modules.ModulesInfo, forceInternal bool, args []string) {
10+
func configureExternalCmd(rootCmd *cobra.Command, modulesInfo *modules.ModulesInfo,
11+
forceInternal bool) {
1512
configureExistsCmd(rootCmd, modulesInfo, forceInternal)
16-
configureNonExistentCmd(rootCmd, modulesInfo, args)
13+
configureNonExistentCmd(rootCmd, modulesInfo)
1714
}
1815

1916
// configureExistsCmd configures an external commands
@@ -29,54 +26,33 @@ func configureExistsCmd(rootCmd *cobra.Command, modulesInfo *modules.ModulesInfo
2926

3027
// configureNonExistentCmd configures an external command that
3128
// has no internal implementation within the Tarantool CLI.
32-
func configureNonExistentCmd(rootCmd *cobra.Command,
33-
modulesInfo *modules.ModulesInfo, args []string) {
34-
// Since the user can pass flags, to determine the name of
35-
// an external command we have to take the first non-flag argument.
36-
externalCmd := args[0]
37-
for _, name := range args {
38-
if !strings.HasPrefix(name, "-") && name != "help" {
39-
externalCmd = name
40-
break
41-
}
42-
}
43-
29+
func configureNonExistentCmd(rootCmd *cobra.Command, modulesInfo *modules.ModulesInfo) {
4430
// We avoid overwriting existing commands - we should add a command only
4531
// if it doesn't have an internal implementation in Tarantool CLI.
32+
// So first collect list of internal command names.
33+
internalCmdNames := []string{"help"}
4634
for _, cmd := range rootCmd.Commands() {
47-
if cmd.Name() == externalCmd {
48-
return
49-
}
35+
internalCmdNames = append(internalCmdNames, cmd.Name())
5036
}
5137

52-
helpCmd := util.GetHelpCommand(rootCmd)
53-
externalCmdPath := rootCmd.Name() + " " + externalCmd
54-
if _, found := (*modulesInfo)[externalCmdPath]; found {
55-
rootCmd.AddCommand(newExternalCommand(modulesInfo, externalCmd,
56-
externalCmdPath, nil))
57-
helpCmd.AddCommand(newExternalCommand(modulesInfo, externalCmd, externalCmdPath,
58-
[]string{"--help"}))
38+
// Add external command only if it doesn't have an internal implementation in Tarantool CLI.
39+
for _, manifest := range *modulesInfo {
40+
if !slices.Contains(internalCmdNames, manifest.Name) {
41+
rootCmd.AddCommand(newExternalCmd(manifest))
42+
}
5943
}
6044
}
6145

62-
// newExternalCommand returns a pointer to a new external
46+
// newExternalCmd returns a pointer to a new external
6347
// command that will call modules.RunCmd.
64-
func newExternalCommand(modulesInfo *modules.ModulesInfo,
65-
cmdName, cmdPath string, addArgs []string) *cobra.Command {
66-
cmd := &cobra.Command{
67-
Use: cmdName,
68-
Run: func(cmd *cobra.Command, args []string) {
69-
if addArgs != nil {
70-
args = append(args, addArgs...)
71-
}
72-
73-
cmdCtx.Cli.ForceInternal = false
74-
if err := modules.RunCmd(&cmdCtx, cmdPath, modulesInfo, nil, args); err != nil {
75-
log.Fatalf(err.Error())
76-
}
77-
},
48+
func newExternalCmd(manifest modules.Manifest) *cobra.Command {
49+
var cmd = &cobra.Command{
50+
Use: manifest.Name,
51+
Run: RunModuleFunc(nil),
52+
DisableFlagParsing: true,
7853
}
79-
80-
cmd.DisableFlagParsing = true
54+
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
55+
cmd.Print(manifest.Help)
56+
})
8157
return cmd
8258
}

cli/cmd/help.go

+34-58
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,56 @@ package cmd
22

33
import (
44
"fmt"
5-
"os"
65
"strings"
76

8-
"github.com/apex/log"
97
"github.com/spf13/cobra"
108
"github.com/tarantool/tt/cli/cmdcontext"
119
"github.com/tarantool/tt/cli/modules"
1210
"github.com/tarantool/tt/cli/util"
1311
)
1412

15-
// DefaultHelpFunc is a type of the standard built-in
16-
// cobra implementation of the help function.
17-
type DefaultHelpFunc func(*cobra.Command, []string)
18-
19-
// configureHelpCommand configures our own help module
20-
// so that it can be external as well.
21-
// If the help is called for an external module
22-
// (for example `tt help version`), we try to get the help from it.
23-
func configureHelpCommand(cmdCtx *cmdcontext.CmdCtx, rootCmd *cobra.Command) error {
13+
func configureHelpCommand(rootCmd *cobra.Command, modulesInfo *modules.ModulesInfo) error {
2414
// Add information about external modules into help template.
25-
rootCmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, getExternalCommandsString(&modulesInfo)))
26-
defaultHelp := rootCmd.HelpFunc()
27-
28-
rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
29-
cmdCtx.CommandName = cmd.Name()
30-
if len(os.Args) == 1 || util.Find(args, "-h") != -1 || util.Find(args, "--help") != -1 {
31-
defaultHelp(cmd, nil)
32-
return
33-
}
15+
rootCmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, getExternalCommandsString(modulesInfo)))
3416

35-
args = modules.GetDefaultCmdArgs("help")
36-
err := modules.RunCmd(cmdCtx, "tt help", &modulesInfo,
37-
getInternalHelpFunc(cmd, defaultHelp), args)
17+
internalHelpModule := func(cmdCtx *cmdcontext.CmdCtx, args []string) error {
18+
cmd, _, err := rootCmd.Find(args)
3819
if err != nil {
39-
log.Fatalf(err.Error())
20+
return err
4021
}
41-
})
42-
43-
// Add valid arguments for completion.
44-
helpCmd := util.GetHelpCommand(rootCmd)
45-
for name := range modulesInfo {
46-
helpCmd.ValidArgs = append(helpCmd.ValidArgs, name)
22+
cmd.Help()
23+
return nil
4724
}
4825

49-
return nil
50-
}
51-
52-
// getInternalHelpFunc returns a internal implementation of help module.
53-
func getInternalHelpFunc(cmd *cobra.Command, help DefaultHelpFunc) modules.InternalFunc {
54-
return func(cmdCtx *cmdcontext.CmdCtx, args []string) error {
55-
switch manifest, found := modulesInfo[cmd.CommandPath()]; {
56-
// Cases when we have to run the "default" help:
57-
// - `tt help` and no external help module.
58-
// It looks strange: if we type the command `tt help`,
59-
// the call to cmd.Name() returns `tt` and I don’t know
60-
// what is the reason. If there is an external help module,
61-
// then we also cannot be here (see the code below) and it
62-
// is enough to check cmd.Name() == "tt".
63-
// - `tt help -I`
64-
// - `tt --help`, `tt -h` or `tt` (look code above).
65-
case cmd.Name() == "tt", !found:
66-
help(cmd, nil)
67-
// We make a call to the external module (if it exists) with the `--help` flag.
68-
default:
69-
helpMsg, err := modules.GetExternalModuleHelp(manifest.Main)
70-
if err != nil {
71-
return err
26+
helpCmd := &cobra.Command{
27+
Use: "help [command]",
28+
Short: "Help about any command",
29+
Run: RunModuleFunc(internalHelpModule),
30+
ValidArgsFunction: func(c *cobra.Command, args []string, toComplete string) (
31+
[]string, cobra.ShellCompDirective) {
32+
var completions []string
33+
cmd, _, e := c.Root().Find(args)
34+
if e != nil {
35+
return nil, cobra.ShellCompDirectiveNoFileComp
7236
}
73-
74-
cmd.Print(helpMsg)
75-
}
76-
77-
return nil
37+
if cmd == nil {
38+
// Root help command.
39+
cmd = c.Root()
40+
}
41+
for _, subCmd := range cmd.Commands() {
42+
if subCmd.IsAvailableCommand() || subCmd == c {
43+
if strings.HasPrefix(subCmd.Name(), toComplete) {
44+
completions = append(completions,
45+
fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
46+
}
47+
}
48+
}
49+
return completions, cobra.ShellCompDirectiveNoFileComp
50+
},
7851
}
52+
53+
rootCmd.SetHelpCommand(helpCmd)
54+
return nil
7955
}
8056

8157
// getExternalCommandsString returns a pretty string

cli/cmd/root.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ After that tt will be able to manage the application using 'replicaset_example'
228228

229229
// Setup decoration for Command's error messages.
230230
rootCmd.SetErr(&logErrorWriterDecorator{rootCmd.ErrOrStderr(), logHandler})
231-
rootCmd.InitDefaultHelpCmd()
232231

233232
return rootCmd
234233
}
@@ -314,12 +313,10 @@ func InitRoot() {
314313

315314
// External commands must be configured in a special way.
316315
// This is necessary, for example, so that we can pass arguments to these commands.
317-
if len(os.Args) > 1 {
318-
configureExternalCmd(rootCmd, &modulesInfo, cmdCtx.Cli.ForceInternal, os.Args[1:])
319-
}
316+
configureExternalCmd(rootCmd, &modulesInfo, cmdCtx.Cli.ForceInternal)
320317

321318
// Configure help command.
322-
err = configureHelpCommand(&cmdCtx, rootCmd)
319+
err = configureHelpCommand(rootCmd, &modulesInfo)
323320
if err != nil {
324321
log.Fatalf("Failed to set up help command: %s", err)
325322
}

cli/util/util.go

-11
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,6 @@ func ParseYAML(path string) (map[string]interface{}, error) {
156156
return raw, nil
157157
}
158158

159-
// GetHelpCommand returns the help command for the passed cmd argument.
160-
func GetHelpCommand(cmd *cobra.Command) *cobra.Command {
161-
for _, subcmd := range cmd.Commands() {
162-
if subcmd.Name() == "help" {
163-
return subcmd
164-
}
165-
}
166-
167-
return nil
168-
}
169-
170159
// GetHomeDir returns current home directory.
171160
func GetHomeDir() (string, error) {
172161
usr, err := user.Current()

0 commit comments

Comments
 (0)