|
|
|
|
@ -99,15 +99,32 @@ func Run(args []string) (err error) { |
|
|
|
|
if errors.Is(err, flag.ErrHelp) { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
if noexec := (ffcli.NoExecError{}); errors.As(err, &noexec) { |
|
|
|
|
// When the user enters an unknown subcommand, ffcli tries to run
|
|
|
|
|
// the closest valid parent subcommand with everything else as args,
|
|
|
|
|
// returning NoExecError if it doesn't have an Exec function.
|
|
|
|
|
cmd := noexec.Command |
|
|
|
|
args := cmd.FlagSet.Args() |
|
|
|
|
if len(cmd.Subcommands) > 0 { |
|
|
|
|
if len(args) > 0 { |
|
|
|
|
return fmt.Errorf("%s: unknown subcommand: %s", fullCmd(rootCmd, cmd), args[0]) |
|
|
|
|
} |
|
|
|
|
subs := make([]string, 0, len(cmd.Subcommands)) |
|
|
|
|
for _, sub := range cmd.Subcommands { |
|
|
|
|
subs = append(subs, sub.Name) |
|
|
|
|
} |
|
|
|
|
return fmt.Errorf("%s: missing subcommand: %s", fullCmd(rootCmd, cmd), strings.Join(subs, ", ")) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if envknob.Bool("TS_DUMP_HELP") { |
|
|
|
|
walkCommands(rootCmd, func(w cmdWalk) bool { |
|
|
|
|
fmt.Println("===") |
|
|
|
|
c := w.cmd |
|
|
|
|
// UsageFuncs are typically called during Command.Run which ensures
|
|
|
|
|
// FlagSet is not nil.
|
|
|
|
|
c := w.Command |
|
|
|
|
if c.FlagSet == nil { |
|
|
|
|
c.FlagSet = flag.NewFlagSet(c.Name, flag.ContinueOnError) |
|
|
|
|
} |
|
|
|
|
@ -182,7 +199,12 @@ change in the future. |
|
|
|
|
driveCmd, |
|
|
|
|
}, |
|
|
|
|
FlagSet: rootfs, |
|
|
|
|
Exec: func(context.Context, []string) error { return flag.ErrHelp }, |
|
|
|
|
Exec: func(ctx context.Context, args []string) error { |
|
|
|
|
if len(args) > 0 { |
|
|
|
|
return fmt.Errorf("tailscale: unknown subcommand: %s", args[0]) |
|
|
|
|
} |
|
|
|
|
return flag.ErrHelp |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
if envknob.UseWIPCode() { |
|
|
|
|
rootCmd.Subcommands = append(rootCmd.Subcommands, |
|
|
|
|
@ -195,8 +217,8 @@ change in the future. |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
walkCommands(rootCmd, func(w cmdWalk) bool { |
|
|
|
|
if w.cmd.UsageFunc == nil { |
|
|
|
|
w.cmd.UsageFunc = usageFunc |
|
|
|
|
if w.UsageFunc == nil { |
|
|
|
|
w.UsageFunc = usageFunc |
|
|
|
|
} |
|
|
|
|
return true |
|
|
|
|
}) |
|
|
|
|
@ -220,10 +242,24 @@ var rootArgs struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type cmdWalk struct { |
|
|
|
|
cmd *ffcli.Command |
|
|
|
|
*ffcli.Command |
|
|
|
|
parents []*ffcli.Command |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (w cmdWalk) Path() string { |
|
|
|
|
if len(w.parents) == 0 { |
|
|
|
|
return w.Name |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var sb strings.Builder |
|
|
|
|
for _, p := range w.parents { |
|
|
|
|
sb.WriteString(p.Name) |
|
|
|
|
sb.WriteString(" ") |
|
|
|
|
} |
|
|
|
|
sb.WriteString(w.Name) |
|
|
|
|
return sb.String() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// walkCommands calls f for root and all of its nested subcommands until f
|
|
|
|
|
// returns false or all have been visited.
|
|
|
|
|
func walkCommands(root *ffcli.Command, f func(w cmdWalk) (more bool)) { |
|
|
|
|
@ -243,6 +279,21 @@ func walkCommands(root *ffcli.Command, f func(w cmdWalk) (more bool)) { |
|
|
|
|
walk(root, nil, f) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// fullCmd returns the full "tailscale ... cmd" invocation for a subcommand.
|
|
|
|
|
func fullCmd(root, cmd *ffcli.Command) (full string) { |
|
|
|
|
walkCommands(root, func(w cmdWalk) bool { |
|
|
|
|
if w.Command == cmd { |
|
|
|
|
full = w.Path() |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
return true |
|
|
|
|
}) |
|
|
|
|
if full == "" { |
|
|
|
|
return cmd.Name |
|
|
|
|
} |
|
|
|
|
return full |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// usageFuncNoDefaultValues is like usageFunc but doesn't print default values.
|
|
|
|
|
func usageFuncNoDefaultValues(c *ffcli.Command) string { |
|
|
|
|
return usageFuncOpt(c, false) |
|
|
|
|
|