-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstate.go
More file actions
80 lines (70 loc) · 2.41 KB
/
state.go
File metadata and controls
80 lines (70 loc) · 2.41 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
package cli
import (
"flag"
"fmt"
"io"
)
// State holds command information during Exec function execution, allowing child commands to access
// parent flags. Use [GetFlag] to get flag values across the command hierarchy.
type State struct {
// Args contains the remaining arguments after flag parsing.
Args []string
// Standard I/O streams.
Stdin io.Reader
Stdout, Stderr io.Writer
// path is the command hierarchy from the root command to the current command. The root command
// is the first element in the path, and the terminal command is the last element.
path []*Command
}
// GetFlag retrieves a flag value by name from the command hierarchy. It first checks the current
// command's flags, then walks up through parent commands.
//
// If the flag doesn't exist or if the type doesn't match the requested type T an error will be
// raised in the Run function. This is an internal error and should never happen in normal usage.
// This ensures flag-related programming errors are caught early during development.
//
// verbose := GetFlag[bool](state, "verbose")
// count := GetFlag[int](state, "count")
// path := GetFlag[string](state, "path")
func GetFlag[T any](s *State, name string) T {
// Try to find the flag in each command's flag set, starting from the current command
for i := len(s.path) - 1; i >= 0; i-- {
cmd := s.path[i]
if cmd.Flags == nil {
continue
}
if f := cmd.Flags.Lookup(name); f != nil {
if getter, ok := f.Value.(flag.Getter); ok {
value := getter.Get()
if v, ok := value.(T); ok {
return v
}
err := fmt.Errorf("type mismatch for flag %q in command %q: registered %T, requested %T",
formatFlagName(name),
getCommandPath(s.path),
value,
*new(T),
)
// Flag exists but type doesn't match - this is an internal error
panic(&internalError{err: err})
}
}
}
// If flag not found anywhere in hierarchy, panic with helpful message
err := fmt.Errorf("flag %q not found in command %q flag set",
formatFlagName(name),
getCommandPath(s.path),
)
panic(&internalError{err: err})
}
// internalError is a marker type for errors that originate from the cli package itself. These are
// programming errors (e.g., flag type mismatches) that should be caught during development.
type internalError struct {
err error
}
func (e *internalError) Error() string {
return e.err.Error()
}
func (e *internalError) Unwrap() error {
return e.err
}