-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand.go
More file actions
141 lines (119 loc) · 4.52 KB
/
command.go
File metadata and controls
141 lines (119 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package cli
import (
"context"
"flag"
"fmt"
"strings"
"github.com/pressly/cli/pkg/suggest"
)
// ErrHelp is returned by [Parse] when the -help or -h flag is invoked. It is identical to
// [flag.ErrHelp] but re-exported here so callers using [Parse] and [Run] separately do not need to
// import the flag package solely for error checking.
//
// Note: [ParseAndRun] handles this automatically and never surfaces ErrHelp to the caller.
var ErrHelp = flag.ErrHelp
// Command represents a CLI command or subcommand within the application's command hierarchy.
type Command struct {
// Name is always a single word representing the command's name. It is used to identify the
// command in the command hierarchy and in help text.
Name string
// Usage provides the command's full usage pattern.
//
// Example: "cli todo list [flags]"
Usage string
// ShortHelp is a brief description of the command's purpose. It is displayed in the help text
// when the command is shown.
ShortHelp string
// UsageFunc is an optional function that can be used to generate a custom usage string for the
// command. It receives the current command and should return a string with the full usage
// pattern.
UsageFunc func(*Command) string
// Flags holds the command-specific flag definitions. Each command maintains its own flag set
// for parsing arguments.
Flags *flag.FlagSet
// FlagOptions is an optional list of flag options to extend the FlagSet with additional
// behavior. This is useful for tracking required flags, short aliases, and local flags.
FlagOptions []FlagOption
// SubCommands is a list of nested commands that exist under this command.
SubCommands []*Command
// Exec defines the command's execution logic. It receives the current application [State] and
// returns an error if execution fails. This function is called when [Run] is invoked on the
// command.
Exec func(ctx context.Context, s *State) error
state *State
}
// Path returns the command chain from root to current command. It can only be called after the root
// command has been parsed and the command hierarchy has been established.
func (c *Command) Path() []*Command {
if c.state == nil {
return nil
}
return c.state.path
}
func (c *Command) terminal() *Command {
if c.state == nil || len(c.state.path) == 0 {
return c
}
// Get the last command in the path - this is our terminal command
return c.state.path[len(c.state.path)-1]
}
// FlagOption holds additional options for a flag, such as whether it is required or has a short
// alias.
type FlagOption struct {
// Name is the flag's name. Must match the flag name in the flag set.
Name string
// Short is an optional single-character alias for the flag. When set, users can use either -v
// or -verbose (if Short is "v" and Name is "verbose"). Must be a single ASCII letter.
Short string
// Required indicates whether the flag is required.
Required bool
// Local indicates that the flag should not be inherited by child commands. When true, the flag
// is only available on the command that defines it.
Local bool
}
// FlagsFunc is a helper function that creates a new [flag.FlagSet] and applies the given function
// to it. Intended for use in command definitions to simplify flag setup. Example usage:
//
// cmd.Flags = cli.FlagsFunc(func(f *flag.FlagSet) {
// f.Bool("verbose", false, "enable verbose output")
// f.String("output", "", "output file")
// f.Int("count", 0, "number of items")
// })
func FlagsFunc(fn func(f *flag.FlagSet)) *flag.FlagSet {
fset := flag.NewFlagSet("", flag.ContinueOnError)
fn(fset)
return fset
}
// findSubCommand searches for a subcommand by name and returns it if found. Returns nil if no
// subcommand with the given name exists.
func (c *Command) findSubCommand(name string) *Command {
for _, sub := range c.SubCommands {
if strings.EqualFold(sub.Name, name) {
return sub
}
}
return nil
}
func (c *Command) formatUnknownCommandError(unknownCmd string) error {
var known []string
for _, sub := range c.SubCommands {
known = append(known, sub.Name)
}
suggestions := suggest.FindSimilar(unknownCmd, known, 3)
if len(suggestions) > 0 {
return fmt.Errorf("unknown command %q. Did you mean one of these?\n\t%s",
unknownCmd,
strings.Join(suggestions, "\n\t"))
}
return fmt.Errorf("unknown command %q", unknownCmd)
}
func formatFlagName(name string) string {
return "-" + name
}
func getCommandPath(commands []*Command) string {
var commandPath []string
for _, c := range commands {
commandPath = append(commandPath, c.Name)
}
return strings.Join(commandPath, " ")
}