internal/tool: implement structured help command

'gopls help cmd...' is redirected to 'gopls cmd... -h'.
The tool.Run operation for each subcommand already has
logic to show the usage message when it parses a -h flag.

Examples:
   $ gopls help
   $ gopls help remote
   $ gopls help remote sessions

Fixes golang/go#52598

Change-Id: I5135c6e7b130d839f880d0613534ac08de199bcf
Reviewed-on: https://go-review.googlesource.com/c/tools/+/403677
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Alan Donovan 2022-05-03 15:42:21 -04:00
parent d27d783e99
commit 45c8a71312
6 changed files with 80 additions and 0 deletions

View File

@ -245,6 +245,7 @@ func (app *Application) mainCommands() []tool.Application {
&app.Serve,
&version{app: app},
&bug{app: app},
&help{app: app},
&apiJSON{app: app},
&licenses{app: app},
}

View File

@ -17,8 +17,57 @@ import (
"golang.org/x/tools/internal/lsp/browser"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/tool"
)
// help implements the help command.
type help struct {
app *Application
}
func (h *help) Name() string { return "help" }
func (h *help) Parent() string { return h.app.Name() }
func (h *help) Usage() string { return "" }
func (h *help) ShortHelp() string { return "print usage information for subcommands" }
func (h *help) DetailedHelp(f *flag.FlagSet) {
fmt.Fprint(f.Output(), `
Examples:
$ gopls help # main gopls help message
$ gopls help remote # help on 'remote' command
$ gopls help remote sessions # help on 'remote sessions' subcommand
`)
printFlagDefaults(f)
}
// Run prints help information about a subcommand.
func (h *help) Run(ctx context.Context, args ...string) error {
find := func(cmds []tool.Application, name string) tool.Application {
for _, cmd := range cmds {
if cmd.Name() == name {
return cmd
}
}
return nil
}
// Find the subcommand denoted by args (empty => h.app).
var cmd tool.Application = h.app
for i, arg := range args {
cmd = find(getSubcommands(cmd), arg)
if cmd == nil {
return tool.CommandLineErrorf(
"no such subcommand: %s", strings.Join(args[:i+1], " "))
}
}
// 'gopls help cmd subcmd' is equivalent to 'gopls cmd subcmd -h'.
// The flag package prints the usage information (defined by tool.Run)
// when it sees the -h flag.
fs := flag.NewFlagSet(cmd.Name(), flag.ExitOnError)
return tool.Run(ctx, fs, h.app, append(args[:len(args):len(args)], "-h"))
}
// version implements the version command.
type version struct {
JSON bool `flag:"json" help:"outputs in json format."`

View File

@ -42,3 +42,18 @@ func (s subcommands) Run(ctx context.Context, args ...string) error {
}
return tool.CommandLineErrorf("unknown subcommand %v", command)
}
func (s subcommands) Commands() []tool.Application { return s }
// getSubcommands returns the subcommands of a given Application.
func getSubcommands(a tool.Application) []tool.Application {
// This interface is satisfied both by tool.Applications
// that embed subcommands, and by *cmd.Application.
type hasCommands interface {
Commands() []tool.Application
}
if sub, ok := a.(hasCommands); ok {
return sub.Commands()
}
return nil
}

View File

@ -0,0 +1,10 @@
print usage information for subcommands
Usage:
gopls [flags] help
Examples:
$ gopls help # main gopls help message
$ gopls help remote # help on 'remote' command
$ gopls help remote sessions # help on 'remote sessions' subcommand

View File

@ -14,6 +14,7 @@ Main
serve run a server for Go code using the Language Server Protocol
version print the gopls version information
bug report a bug in gopls
help print usage information for subcommands
api-json print json describing gopls API
licenses print licenses of included software

View File

@ -92,6 +92,10 @@ func Main(ctx context.Context, app Application, args []string) {
if err := Run(ctx, s, app, args); err != nil {
fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err)
if _, printHelp := err.(commandLineError); printHelp {
// TODO(adonovan): refine this. It causes
// any command-line error to result in the full
// usage message, which typically obscures
// the actual error.
s.Usage()
}
os.Exit(2)