-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.go
157 lines (138 loc) · 4.24 KB
/
lib.go
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package conq
import (
"fmt"
"io"
"reflect"
"github.com/alexflint/go-scalar"
"github.com/patroclos/go-conq/completion"
"github.com/posener/complete"
"golang.org/x/text/message"
)
// Cmd is a node in a rooted node tree describing a command-hierarchy.
type Cmd struct {
Name string
Commands []*Cmd
Run func(Ctx) error
Opts Opts
Args Opts
Env Opts
Version string
}
type Pth []*Cmd
// Ctx is the context in which a command (Cmd) runs. It contains the std-streams,
// arguments (options extracted before calling Cmd.Run), option-values, the path
// within the command-tree this invocation is located in and a locale-aware
// message-printer.
type Ctx struct {
In io.Reader
Out, Err io.Writer
Args []string
Values map[string]any
Strings map[string]string
Printer *message.Printer
Path Pth
Com Commander
}
type Commander interface {
ResolveCmd(root *Cmd, ctx Ctx) Ctx
Execute(root *Cmd, ctx Ctx) error
Optioner() Optioner
}
// Optioner provides extraction and completion of CLI options.
type Optioner interface {
ExtractOptions(Ctx, ...Opter) (Ctx, error)
CompleteOptions(completion.Context, ...Opter) []string
}
// This interface exists to facilitate the Opt[T] and ReqOpt[T] types with filter effects
type Opter interface {
Opt() O
}
// O is a descriptor for command-parameters (options, positional arguments or environment variables)
type O struct {
// a comma-separated list of at least one name followed by aliases
Name string
// should invoking a command fail, if this option isn't set?
Require bool
// a parser for the string extracted from the shell arguments
Parse func(string) (any, error)
// describes the type of results returned by Parse
Type reflect.Type
// shell-completion
Predict complete.Predictor
}
func (o O) WithName(name string) O {
o.Name = name
return o
}
// Opt[T any] wraps a base-option (usually only containing a name) in an Opter
// interface, which will apply defaults to O.Parse and O.Type values.
// The default O.Parse implementation will use the github.com/alexflint/go-scalar
// package to parse a (value T) from a string. The default implementations
// supports the encoding.TextUnmarshaler interface.
// Opt[T] is meant both as the definition for the option and as the access-hatch
// for it's values, so it provides `Get(Ctx)(T,error)` and `Getp(Ctx)(*T, error)`
// to access the options value or pointer to it from the Ctx.Values map.
type Opt[T any] O
type Opts []Opter
func (o Opt[T]) Get(c Ctx) (val T, err error) {
x, ok := c.Values[o.Name]
if !ok {
return val, fmt.Errorf("option %q not present", o.Name)
}
val, ok = x.(T)
if !ok {
return val, fmt.Errorf("value for option %q is of type %T, expected %T", o.Name, x, val)
}
return
}
func (o Opt[T]) Getp(c Ctx) (val *T, err error) {
x, ok := c.Values[o.Name]
if !ok {
return val, fmt.Errorf("option %q not present", o.Name)
}
v, ok := x.(T)
if !ok {
return val, fmt.Errorf("value for option %q is of type %T, expected %T", o.Name, x, val)
}
return &v, nil
}
func (o Opt[T]) Opt() O {
typ := reflect.TypeOf((*T)(nil)).Elem()
if o.Parse == nil {
o.Parse = func(s string) (interface{}, error) {
if !scalar.CanParse(typ) {
return nil, fmt.Errorf("cannot automatically parse non-scalar value into %q option", o.Name)
}
var val T
err := scalar.Parse(&val, s)
return val, err
}
}
o.Type = typ
return O(o)
}
// ReqOpt[T any] is a simple wrapper for Opt[T]. It's Opter implementation sets
// the O.Require to true. Assuming the Cmd has been setup correctly we can now
// know for sure that the Ctx is going to have a value for this option. That's
// why the `Get(Ctx)T` and `Getp(Ctx)*T` have been simplified from their counterparts
// in Opt[T]
type ReqOpt[T any] O
func (o ReqOpt[T]) Get(c Ctx) (val T) {
val, err := Opt[T](o.Opt()).Get(c)
if err != nil {
panic(fmt.Sprintf("unexpected error accessing required option, this is likely a bug in the commander: %v", err))
}
return val
}
func (o ReqOpt[T]) Getp(c Ctx) (val *T) {
val, err := Opt[T](o.Opt()).Getp(c)
if err != nil {
panic(fmt.Sprintf("unexpected error accessing required option, this is likely a bug in the commander: %v", err))
}
return val
}
func (o ReqOpt[T]) Opt() O {
x := Opt[T](o).Opt()
x.Require = true
return x
}