Skip to content

Commit a8ce4a8

Browse files
committed
help: reimplemented help subsystem
In order to simplify help implementation: - main help command is implemented in a way similar to the other commands (internal/external variation of help command is handled with the common handler cmd.RunModuleFunc) - help for every external command is tied to a command itself over cmd.SetHelp rather than add separate help subcommand to the main help command (it allows to get help uniformly for internal and external commands) - corresponding Cobra commands are created for all available external commands rather than only for the invoked one (it is needed for the uniform handling mentioned above) In addition: - completion for help command is improved (also suggested/completed subcommands, if any) - external commands are also implemented over the common handler cmd.RunModuleFunc to reduce code duplication Close #1136
1 parent a92b7ef commit a8ce4a8

File tree

3 files changed

+59
-110
lines changed

3 files changed

+59
-110
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

+35-59
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,59 @@ 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 == cmd.helpCommand {
43+
if subCmd.IsAvailableCommand() {
44+
if strings.HasPrefix(subCmd.Name(), toComplete) {
45+
completions = append(completions, 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

81-
// getExternalCommandsString returns a pretty string
57+
// getExternalCommandString returns a pretty string
8258
// of descriptions for external modules.
8359
func getExternalCommandsString(modulesInfo *modules.ModulesInfo) string {
8460
str := ""

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
}

0 commit comments

Comments
 (0)