Skip to content

Commit 12bb7bc

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) - external commands are also implemented over the common handler cmd.RunModuleFunc to reduce code duplication Close #1136
1 parent 22ce087 commit 12bb7bc

File tree

5 files changed

+56
-130
lines changed

5 files changed

+56
-130
lines changed

cli/cmd/external.go

+35-48
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,69 @@
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
// configureExternalCmd 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)
14+
}
15+
16+
// externalModuleHelpFunc returns function that displays help for the specified external module.
17+
func externalModuleHelpFunc(manifest modules.Manifest) func(*cobra.Command, []string) {
18+
return func(cmd *cobra.Command, args []string) {
19+
help, err := modules.GetExternalModuleHelp(manifest.Main)
20+
if err != nil {
21+
cmd.PrintErrf("failed to get help for module %q: %s\n", manifest.Name, err)
22+
return
23+
}
24+
cmd.Print(help)
25+
}
1726
}
1827

1928
// configureExistsCmd configures an external commands
2029
// that have internal implementation.
2130
func configureExistsCmd(rootCmd *cobra.Command, modulesInfo *modules.ModulesInfo,
2231
forceInternal bool) {
2332
for _, cmd := range rootCmd.Commands() {
24-
if _, found := (*modulesInfo)[cmd.CommandPath()]; found {
25-
cmd.DisableFlagParsing = !forceInternal
33+
if manifest, found := (*modulesInfo)[cmd.CommandPath()]; found && !forceInternal {
34+
cmd.DisableFlagParsing = true
35+
cmd.SetHelpFunc(externalModuleHelpFunc(manifest))
2636
}
2737
}
2838
}
2939

3040
// configureNonExistentCmd configures an external command that
3141
// 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-
42+
func configureNonExistentCmd(rootCmd *cobra.Command, modulesInfo *modules.ModulesInfo) {
4443
// We avoid overwriting existing commands - we should add a command only
4544
// if it doesn't have an internal implementation in Tarantool CLI.
45+
// So first collect list of internal command names.
46+
internalCmdNames := []string{"help"}
4647
for _, cmd := range rootCmd.Commands() {
47-
if cmd.Name() == externalCmd {
48-
return
49-
}
48+
internalCmdNames = append(internalCmdNames, cmd.Name())
5049
}
5150

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"}))
51+
// Add external command only if it doesn't have an internal implementation in Tarantool CLI.
52+
for _, manifest := range *modulesInfo {
53+
if !slices.Contains(internalCmdNames, manifest.Name) {
54+
rootCmd.AddCommand(newExternalCmd(manifest))
55+
}
5956
}
6057
}
6158

62-
// newExternalCommand returns a pointer to a new external
59+
// newExternalCmd returns a pointer to a new external
6360
// 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-
},
61+
func newExternalCmd(manifest modules.Manifest) *cobra.Command {
62+
var cmd = &cobra.Command{
63+
Use: manifest.Name,
64+
Run: RunModuleFunc(nil),
65+
DisableFlagParsing: true,
7866
}
79-
80-
cmd.DisableFlagParsing = true
67+
cmd.SetHelpFunc(externalModuleHelpFunc(manifest))
8168
return cmd
8269
}

cli/cmd/help.go

+17-58
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,39 @@ 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) {
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
72-
}
73-
74-
cmd.Print(helpMsg)
75-
}
26+
helpCmd := &cobra.Command{
27+
Use: "help [command]",
28+
Short: "Help about any command",
29+
Run: RunModuleFunc(internalHelpModule),
30+
}
7631

77-
return nil
32+
// Add valid arguments for completion.
33+
for _, subCmd := range rootCmd.Commands() {
34+
helpCmd.ValidArgs = append(helpCmd.ValidArgs, subCmd.Name())
7835
}
36+
37+
rootCmd.SetHelpCommand(helpCmd)
7938
}
8039

8140
// getExternalCommandsString returns a pretty string

cli/cmd/root.go

+2-8
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,13 +313,8 @@ 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)
323-
if err != nil {
324-
log.Fatalf("Failed to set up help command: %s", err)
325-
}
319+
configureHelpCommand(rootCmd, &modulesInfo)
326320
}

cli/modules/run.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,8 @@ func RunCmd(cmdCtx *cmdcontext.CmdCtx, cmdPath string, modulesInfo *ModulesInfo,
3030
internal InternalFunc, args []string,
3131
) error {
3232
manifest, found := (*modulesInfo)[cmdPath]
33-
if !found || cmdCtx.Cli.ForceInternal {
34-
if internal != nil {
35-
return internal(cmdCtx, args)
36-
}
37-
return fmt.Errorf("no internal command for %q to run", cmdPath)
33+
if !found || (cmdCtx.Cli.ForceInternal && internal != nil) {
34+
return internal(cmdCtx, args)
3835
}
3936

4037
f, err := cmdCtx.Integrity.Repository.Read(manifest.Main)

cli/util/util.go

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

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

0 commit comments

Comments
 (0)