Skip to content

Commit

Permalink
cmd/cue: refactor help texts and topics
Browse files Browse the repository at this point in the history
Because additional help topics used to be non-runnable commands hanging
from the root `cue` command, they used to behave in rather odd ways.
For example, even though we often suggested using them via
`cue help filetypes`, they still worked as `cue filetypes` or
`cue filetypes --help` for no good reason.

Hang the non-runnable commands from the `cue help` subcommand,
meaning that `cue filetypes` is now treated as an unknown command.
This seems better for the user experience too; for example,
if a user tried to tab-complete `cue fi`, we should only suggest
`cue fix` and not `cue filetypes`.

Because help topics now hang as subcommands from `cue help`,
spf13/cobra's default help template would no longer show them
in the help text for the root command.
Start using our own template, as a simplified and tweaked version of
the default template, which resolves this issue.

Our own template also tweaks a few other sharp edges while here:
* Do not show global flags in `cue help`; they take up space,
  and they aren't useful unless a user is running a subcommand.
  Subcommand help texts already list the same global flags.
* Suggest `cue help [command]` rather than `cue [command] --help`,
  which reads better and is shorter to type, and is also consistent
  with additional help topics such as `cue help filetypes`.
* Hide `cue help` from the available commands list;
  we already suggest it as the way to get help texts, so we don't need
  to show it twice in the top-level help text.

This is a precursor to removing support for `cue foo` as a short-hand
for `cue cmd foo`, as some of the logic to handle these extra
root-level commands is shared.

For #2519.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: I76d0c687e88bc3f237d952a98b4578516f36c5c0
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1199622
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Reviewed-by: Matthew Sackman <matthew@cue.works>
  • Loading branch information
mvdan committed Aug 19, 2024
1 parent 54ef570 commit 7e589e3
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 91 deletions.
4 changes: 2 additions & 2 deletions cmd/cue/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ const (
func addOutFlags(f *pflag.FlagSet, allowNonCUE bool) {
if allowNonCUE {
f.String(string(flagOut), "",
`output format (run 'cue filetypes' for more info)`)
`output format (run 'cue help filetypes' for more info)`)
}
f.StringP(string(flagOutFile), "o", "",
`filename or - for stdout with optional file prefix (run 'cue filetypes' for more info)`)
`filename or - for stdout with optional file prefix (run 'cue help filetypes' for more info)`)
f.BoolP(string(flagForce), "f", false, "force overwriting existing files")
}

Expand Down
52 changes: 47 additions & 5 deletions cmd/cue/cmd/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import (
// but knows how to load custom commands in `cue help cmd`.
func newHelpCmd(c *Command) *cobra.Command {
cmd := &cobra.Command{
Use: "help [command]",
Short: "show help text for a command or topic",
Use: "help [command]",
Short: "show help text for a command or topic",
Hidden: true, // not shown as a runnable command in `cue help`
Run: func(_ *cobra.Command, args []string) {
findCmd := func() (*cobra.Command, bool) {
cmd, rest, err := c.Root().Find(args)
Expand Down Expand Up @@ -90,9 +91,50 @@ func newHelpCmd(c *Command) *cobra.Command {
return cmd
}

// TODO(mvdan): having the help topics as top-level commands means that `cue topic`
// is taken and works as well as `cue help topic`, which is unnecessary.
// Consider removing support for the short form at some point.
// We use a custom Cobra help text template for the sake of being a bit more
// in control of its formatting, as well as the contents of the root `cue help`.
//
// TODO(mvdan): the text below should not jump straight into `cue cmd`,
// and instead give a high level overview of CUE and its commands.
var helpTemplate = `
{{- if not .HasParent}}{{/* Special template for the root help. */ -}}
cue evaluates CUE files, an extension of JSON, and sends them
to user-defined commands for processing.
Commands are defined in CUE as follows:
import "tool/exec"
command: deploy: {
exec.Run
cmd: "kubectl"
args: ["-f", "deploy"]
in: json.Encode(userValue) // encode the emitted configuration.
}
cue can also combine the results of http or grpc request with the input
configuration for further processing. For more information on defining commands
run 'cue help cmd' or go to cuelang.org/pkg/cmd.
For more information on writing CUE configuration files see cuelang.org.
Usage:
{{.CommandPath}} [command]
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
Use "{{.CommandPath}} help [command]" for more information about a command.
{{/* We want to show additional help topics from the root command's help text
even though they are only reachable via 'cue help'. */ -}}
Additional help topics:{{range .Commands}}{{if eq .Name "help"}}{{range .Commands}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}
{{else}}{{/* Subcommands use a fairly standard template. */ -}}
{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}
{{- end -}}
`

var helpTopics = []*cobra.Command{
commandsHelp,
Expand Down
71 changes: 25 additions & 46 deletions cmd/cue/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,34 +186,9 @@ func mkRunE(c *Command, f runFunction) func(*cobra.Command, []string) error {
// The returned error is always nil, and is a historical artifact.
func New(args []string) (*Command, error) {
cmd := &cobra.Command{
Use: "cue",
Use: "cue",
// TODO: the short help text below seems to refer to `cue cmd`, like helpTemplate.
Short: "cue emits configuration files to user-defined commands.",
// TODO(mvdan): the text below should not jump straight into `cue cmd`,
// and instead give a high level overview of CUE and its commands.
// TODO(mvdan): cobra's help text template ends with
// Use "cue [command] --help" for more information about a command.
// which isn't great; use our own template to say
// Use "cue help [command]" for more information about a command.
// TODO(mvdan): cobra's help text template suggests help topics
// such as `cue inputs`; swap with `cue help inputs`.
Long: `cue evaluates CUE files, an extension of JSON, and sends them
to user-defined commands for processing.
Commands are defined in CUE as follows:
import "tool/exec"
command: deploy: {
exec.Run
cmd: "kubectl"
args: ["-f", "deploy"]
in: json.Encode(userValue) // encode the emitted configuration.
}
cue can also combine the results of http or grpc request with the input
configuration for further processing. For more information on defining commands
run 'cue help cmd' or go to cuelang.org/pkg/cmd.
For more information on writing CUE configuration files see cuelang.org.`,

// ArbitraryArgs allows us to forward the top-level RunE to cmdCmd.RunE,
// which supports `cue mycmd` as a short-cut for `cue cmd mycmd`.
Expand Down Expand Up @@ -243,7 +218,28 @@ For more information on writing CUE configuration files see cuelang.org.`,
cmdCmd := newCmdCmd(c)
c.cmd = cmdCmd

subCommands := []*cobra.Command{
addGlobalFlags(cmd.PersistentFlags())
// We add the injection flags to the root command for the sake of the short form "cue -t foo=bar mycmd".
// Note that they are not persistent, so that they aren't inherited by sub-commands like "cue fmt".
addInjectionFlags(cmd.Flags(), false, true)

// Cobra's --help flag shows up in help text by default, which is unnecessary.
cmd.InitDefaultHelpFlag()
cmd.Flag("help").Hidden = true

// "help" is treated as a special command by cobra.
// We use our own template to be more in control of the structure of `cue help`.
// Note that we need to add helpCmd as a subcommand first, for cobra to work out
// the proper help text paddings for additional help topics.
helpCmd := newHelpCmd(c)
cmd.AddCommand(helpCmd)
for _, sub := range helpTopics {
helpCmd.AddCommand(sub)
}
cmd.SetHelpCommand(helpCmd)
cmd.SetHelpTemplate(helpTemplate)

for _, sub := range []*cobra.Command{
cmdCmd,
newCompletionCmd(c),
newEvalCmd(c),
Expand All @@ -261,27 +257,10 @@ For more information on writing CUE configuration files see cuelang.org.`,
// Hidden
newAddCmd(c),
newLoginCmd(c),
}
subCommands = append(subCommands, helpTopics...)

addGlobalFlags(cmd.PersistentFlags())
// We add the injection flags to the root command for the sake of the short form "cue -t foo=bar mycmd".
// Note that they are not persistent, so that they aren't inherited by sub-commands like "cue fmt".
addInjectionFlags(cmd.Flags(), false, true)

for _, sub := range subCommands {
} {
cmd.AddCommand(sub)
}

// Cobra's --help flag shows up in help text by default, which is unnecessary.
cmd.InitDefaultHelpFlag()
cmd.Flag("help").Hidden = true

// "help" is treated as a special command by cobra.
// TODO(mvdan): hide this command; `cue help` showing itself in the list of commands
// is not particularly helpful, and we already mention it as the way to get help.
cmd.SetHelpCommand(newHelpCmd(c))

// For `cue mycmd` to be a shortcut for `cue cmd mycmd`.
cmd.RunE = cmdCmd.RunE

Expand Down
69 changes: 31 additions & 38 deletions cmd/cue/cmd/testdata/script/help.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ stdout -count=1 '^Publish the current module to an OCI registry\.'
exec cue mod publish --help
stdout -count=1 '^Publish the current module to an OCI registry\.'

# Requesting additional help topics, with or without "help" in between.
# Requesting additional help topics; the form without "help" is phased out.

exec cue help filetypes
stdout -count=1 '^The cue tools supports the following file types:$'

exec cue filetypes
stdout -count=1 '^The cue tools supports the following file types:$'
! exec cue filetypes
! stdout .
stderr -count=1 '^no commands defined'
# TODO(mvdan): once `cue somecmd` is phased out, we can give a better error message here.
# stderr -count=1 '^unknown command'

# Requesting help for missing commands and sub-commands fails and prints the help text.

Expand Down Expand Up @@ -75,43 +78,33 @@ run 'cue help cmd' or go to cuelang.org/pkg/cmd.
For more information on writing CUE configuration files see cuelang.org.

Usage:
cue [flags]
cue [command]

Available Commands:
cmd run a user-defined workflow command
completion Generate completion script
def print consolidated definitions
eval evaluate and print a configuration
export output data in a standard format
fix rewrite packages to latest standards
fmt formats CUE configuration files
get add non-CUE dependencies to the current module
help show help text for a command or topic
import convert other formats to CUE files
login log into a CUE registry
mod module maintenance
trim remove superfluous fields
version print CUE version
vet validate data

Flags:
-E, --all-errors print all available errors
-i, --ignore proceed in the presence of errors
-s, --simplify simplify output
--strict report errors for lossy mappings
--trace trace computation
-v, --verbose print information about progress
cmd run a user-defined workflow command
completion Generate completion script
def print consolidated definitions
eval evaluate and print a configuration
export output data in a standard format
fix rewrite packages to latest standards
fmt formats CUE configuration files
get add non-CUE dependencies to the current module
import convert other formats to CUE files
login log into a CUE registry
mod module maintenance
trim remove superfluous fields
version print CUE version
vet validate data

Use "cue help [command]" for more information about a command.

Additional help topics:
cue commands user-defined commands
cue embed file embedding
cue environment environment variables
cue filetypes supported file types and qualifiers
cue flags common flags for composing packages
cue injection inject files or values into specific fields for a build
cue inputs package list, patterns, and files
cue modules module support
cue registryconfig module registry configuration

Use "cue [command] --help" for more information about a command.
cue help commands user-defined commands
cue help embed file embedding
cue help environment environment variables
cue help filetypes supported file types and qualifiers
cue help flags common flags for composing packages
cue help injection inject files or values into specific fields for a build
cue help inputs package list, patterns, and files
cue help modules module support
cue help registryconfig module registry configuration
15 changes: 15 additions & 0 deletions cmd/cue/cmd/testdata/script/unknown_args.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
! exec cue unknown
! stdout .
cmp stderr unknown_cmd.out

! exec cue --unknown
! stdout .
cmp stderr unknown_flag.out

-- unknown_cmd.out --
no commands defined
Ensure custom commands are defined in a "_tool.cue" file.
Run 'cue help cmd' to list available custom commands.
Run 'cue help' to see the built-in 'cue' commands.
-- unknown_flag.out --
unknown flag: --unknown

0 comments on commit 7e589e3

Please sign in to comment.