|
1 | 1 | package command |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "fmt" |
5 | | - "reflect" |
6 | | - "strconv" |
7 | | - "strings" |
8 | | - |
9 | 4 | "github.com/go-playground/validator/v10" |
10 | 5 | "github.com/pkg/errors" |
11 | | - "github.com/pot-code/web-cli/internal/util" |
12 | 6 | "github.com/pot-code/web-cli/internal/validate" |
13 | | - log "github.com/sirupsen/logrus" |
14 | 7 | "github.com/urfave/cli/v2" |
15 | 8 | ) |
16 | 9 |
|
17 | 10 | type CliCommand struct { |
18 | 11 | cmd *cli.Command |
19 | 12 | cfg interface{} |
20 | | - services []CommandHandler |
| 13 | + handlers []CommandHandler |
21 | 14 | } |
22 | 15 |
|
23 | 16 | type CommandOption interface { |
@@ -53,236 +46,49 @@ func NewCliCommand(name, usage string, defaultConfig interface{}, options ...Com |
53 | 46 | Name: name, |
54 | 47 | Usage: usage, |
55 | 48 | } |
56 | | - |
57 | | - visitor := NewExtractFlagsVisitor() |
58 | | - IterateCliConfig(defaultConfig, visitor, nil) |
59 | | - cmd.Flags = append(cmd.Flags, visitor.Flags...) |
60 | | - |
61 | 49 | for _, option := range options { |
62 | 50 | option.apply(cmd) |
63 | 51 | } |
64 | 52 | return &CliCommand{cmd, defaultConfig, []CommandHandler{}} |
65 | 53 | } |
66 | 54 |
|
67 | 55 | func (cc *CliCommand) AddHandlers(h ...CommandHandler) *CliCommand { |
68 | | - cc.services = append(cc.services, h...) |
| 56 | + cc.handlers = append(cc.handlers, h...) |
69 | 57 | return cc |
70 | 58 | } |
71 | 59 |
|
72 | 60 | func (cc *CliCommand) BuildCommand() *cli.Command { |
73 | | - cc.cmd.Action = func(c *cli.Context) error { |
74 | | - cfg := cc.cfg |
| 61 | + cfg := cc.cfg |
75 | 62 |
|
76 | | - if cfg != nil { |
77 | | - IterateCliConfig(cfg, NewSetCliValueVisitor(), c) |
78 | | - if err := validate.V.Struct(cfg); err != nil { |
79 | | - if v, ok := err.(validator.ValidationErrors); ok { |
80 | | - msg := v[0].Translate(validate.T) |
81 | | - cli.ShowCommandHelp(c, c.Command.Name) |
82 | | - return util.NewCommandError(c.Command.Name, errors.New(msg)) |
83 | | - } |
84 | | - return err |
85 | | - } |
| 63 | + efv := newExtractFlagsVisitor() |
| 64 | + walkConfig(cfg, efv) |
| 65 | + |
| 66 | + cc.cmd.Flags = append(cc.cmd.Flags, efv.getFlags()...) |
| 67 | + cc.cmd.Before = func(c *cli.Context) error { |
| 68 | + scv := newSetConfigVisitor(c) |
| 69 | + walkConfig(cfg, scv) |
| 70 | + |
| 71 | + if errs := scv.getErrors(); errs != nil { |
| 72 | + return errs[0] |
86 | 73 | } |
87 | 74 |
|
88 | | - for _, s := range cc.services { |
| 75 | + if err := validate.V.Struct(cfg); err != nil { |
| 76 | + if v, ok := err.(validator.ValidationErrors); ok { |
| 77 | + msg := v[0].Translate(validate.T) |
| 78 | + cli.ShowCommandHelp(c, c.Command.Name) |
| 79 | + return errors.New(msg) |
| 80 | + } |
| 81 | + return err |
| 82 | + } |
| 83 | + return nil |
| 84 | + } |
| 85 | + cc.cmd.Action = func(c *cli.Context) error { |
| 86 | + for _, s := range cc.handlers { |
89 | 87 | if err := s.Handle(c, cfg); err != nil { |
90 | 88 | return err |
91 | 89 | } |
92 | 90 | } |
93 | | - |
94 | 91 | return nil |
95 | 92 | } |
96 | 93 | return cc.cmd |
97 | 94 | } |
98 | | - |
99 | | -type ConfigStructVisitor interface { |
100 | | - VisitStringType(reflect.StructField, reflect.Value, *cli.Context) error |
101 | | - VisitBooleanType(reflect.StructField, reflect.Value, *cli.Context) error |
102 | | - VisitIntType(reflect.StructField, reflect.Value, *cli.Context) error |
103 | | -} |
104 | | - |
105 | | -func IterateCliConfig(config interface{}, visitor ConfigStructVisitor, ctx *cli.Context) { |
106 | | - if config == nil { |
107 | | - return |
108 | | - } |
109 | | - |
110 | | - directType := reflect.TypeOf(config) |
111 | | - if directType.Kind() != reflect.Ptr { |
112 | | - panic("config must be of pointer type") |
113 | | - } |
114 | | - structType := directType.Elem() |
115 | | - |
116 | | - configStruct := reflect.Indirect(reflect.ValueOf(config)) // reflection wrapper for the config struct |
117 | | - for i := structType.NumField() - 1; i >= 0; i-- { |
118 | | - fieldType := structType.Field(i) |
119 | | - fieldValue := configStruct.Field(i) |
120 | | - |
121 | | - if !fieldValue.CanSet() { |
122 | | - log.WithFields(log.Fields{ |
123 | | - "caller": "ParseCliConfig", |
124 | | - }).Warnf("config field [ %s ] can't be set, maybe it's not exported", fieldType.Name) |
125 | | - continue |
126 | | - } |
127 | | - |
128 | | - switch fieldType.Type.Kind() { |
129 | | - case reflect.String: |
130 | | - visitor.VisitStringType(fieldType, fieldValue, ctx) |
131 | | - case reflect.Bool: |
132 | | - visitor.VisitBooleanType(fieldType, fieldValue, ctx) |
133 | | - case reflect.Int: |
134 | | - visitor.VisitIntType(fieldType, fieldValue, ctx) |
135 | | - default: |
136 | | - panic("not implemented") |
137 | | - } |
138 | | - } |
139 | | -} |
140 | | - |
141 | | -type ExtractFlagsVisitor struct { |
142 | | - Flags []cli.Flag |
143 | | -} |
144 | | - |
145 | | -func NewExtractFlagsVisitor() *ExtractFlagsVisitor { |
146 | | - return &ExtractFlagsVisitor{Flags: []cli.Flag{}} |
147 | | -} |
148 | | - |
149 | | -var _ ConfigStructVisitor = &ExtractFlagsVisitor{} |
150 | | - |
151 | | -func (efv *ExtractFlagsVisitor) VisitStringType(tf reflect.StructField, vf reflect.Value, c *cli.Context) error { |
152 | | - tag := tf.Tag |
153 | | - |
154 | | - if flagName := tag.Get("flag"); flagName != "" { |
155 | | - alias := tag.Get("alias") |
156 | | - usage := tag.Get("usage") |
157 | | - |
158 | | - required := false |
159 | | - if vf.IsZero() && validate.IsRequired(tag) { |
160 | | - required = true |
161 | | - } |
162 | | - |
163 | | - flag := &cli.StringFlag{ |
164 | | - Name: flagName, |
165 | | - Required: required, |
166 | | - } |
167 | | - |
168 | | - if alias != "" { |
169 | | - flag.Aliases = strings.Split(alias, ",") |
170 | | - } |
171 | | - |
172 | | - if !vf.IsZero() { |
173 | | - flag.Value = vf.String() |
174 | | - } |
175 | | - |
176 | | - options := validate.GetOneOfItems(tag) |
177 | | - if len(options) > 0 { |
178 | | - usage += fmt.Sprintf(" (options: %s)", strings.Join(options, ", ")) |
179 | | - } |
180 | | - flag.Usage = usage |
181 | | - |
182 | | - log.WithFields(log.Fields{ |
183 | | - "name": flagName, |
184 | | - "type": tf.Type.String(), |
185 | | - "required": required, |
186 | | - "alias": alias, |
187 | | - "usage": usage, |
188 | | - }).Debug("register flag") |
189 | | - |
190 | | - efv.Flags = append(efv.Flags, flag) |
191 | | - } |
192 | | - return nil |
193 | | -} |
194 | | - |
195 | | -func (efv *ExtractFlagsVisitor) VisitBooleanType(tf reflect.StructField, vf reflect.Value, c *cli.Context) error { |
196 | | - if flagName := tf.Tag.Get("flag"); flagName != "" { |
197 | | - usage := tf.Tag.Get("usage") |
198 | | - alias := tf.Tag.Get("alias") |
199 | | - |
200 | | - flag := &cli.BoolFlag{ |
201 | | - Name: flagName, |
202 | | - Usage: usage, |
203 | | - } |
204 | | - if alias != "" { |
205 | | - flag.Aliases = strings.Split(alias, ",") |
206 | | - } |
207 | | - if !vf.IsZero() { |
208 | | - flag.Value = vf.Bool() |
209 | | - } |
210 | | - |
211 | | - log.WithFields(log.Fields{ |
212 | | - "name": flagName, |
213 | | - "type": tf.Type.String(), |
214 | | - "alias": alias, |
215 | | - "usage": usage, |
216 | | - }).Debug("register flag") |
217 | | - efv.Flags = append(efv.Flags, flag) |
218 | | - } |
219 | | - return nil |
220 | | -} |
221 | | - |
222 | | -func (efv *ExtractFlagsVisitor) VisitIntType(tf reflect.StructField, vf reflect.Value, c *cli.Context) error { |
223 | | - if flagTag := tf.Tag.Get("flag"); flagTag != "" { |
224 | | - usage := tf.Tag.Get("usage") |
225 | | - alias := tf.Tag.Get("alias") |
226 | | - |
227 | | - flagName := &cli.IntFlag{ |
228 | | - Name: flagTag, |
229 | | - Usage: usage, |
230 | | - } |
231 | | - if alias != "" { |
232 | | - flagName.Aliases = strings.Split(alias, ",") |
233 | | - } |
234 | | - if !vf.IsZero() { |
235 | | - flagName.Value = int(vf.Int()) |
236 | | - } |
237 | | - |
238 | | - log.WithFields(log.Fields{ |
239 | | - "name": flagName, |
240 | | - "type": tf.Type.String(), |
241 | | - "alias": alias, |
242 | | - "usage": usage, |
243 | | - }).Debug("register flag") |
244 | | - efv.Flags = append(efv.Flags, flagName) |
245 | | - } |
246 | | - return nil |
247 | | -} |
248 | | - |
249 | | -type SetCliValueVisitor struct{} |
250 | | - |
251 | | -func NewSetCliValueVisitor() *SetCliValueVisitor { |
252 | | - return &SetCliValueVisitor{} |
253 | | -} |
254 | | - |
255 | | -var _ ConfigStructVisitor = &SetCliValueVisitor{} |
256 | | - |
257 | | -func (efv *SetCliValueVisitor) VisitStringType(tf reflect.StructField, vf reflect.Value, c *cli.Context) error { |
258 | | - if arg := tf.Tag.Get("arg"); arg != "" { |
259 | | - index, err := strconv.Atoi(arg) |
260 | | - if err != nil { |
261 | | - panic(fmt.Sprintf("failed to convert [ %s ] to number", arg)) |
262 | | - } |
263 | | - |
264 | | - kind := tf.Type.Kind() |
265 | | - if kind == reflect.String { |
266 | | - vf.SetString(c.Args().Get(index)) |
267 | | - } else { |
268 | | - panic(fmt.Errorf("unsupported arg type: %s", kind.String())) |
269 | | - } |
270 | | - } else if flag := tf.Tag.Get("flag"); flag != "" { |
271 | | - vf.SetString(c.String(flag)) |
272 | | - } |
273 | | - return nil |
274 | | -} |
275 | | - |
276 | | -func (efv *SetCliValueVisitor) VisitBooleanType(tf reflect.StructField, vf reflect.Value, c *cli.Context) error { |
277 | | - if flag := tf.Tag.Get("flag"); flag != "" { |
278 | | - vf.SetBool(c.Bool(flag)) |
279 | | - } |
280 | | - return nil |
281 | | -} |
282 | | - |
283 | | -func (efv *SetCliValueVisitor) VisitIntType(tf reflect.StructField, vf reflect.Value, c *cli.Context) error { |
284 | | - if flag := tf.Tag.Get("flag"); flag != "" { |
285 | | - vf.SetInt(int64(c.Int("flag"))) |
286 | | - } |
287 | | - return nil |
288 | | -} |
0 commit comments