Codebase list golang-github-alecthomas-kong / 2475740
Enum fields must be required or have a default. This is a breaking change, but the previous behaviour was broken so I'm not concerned. Also made most programmer errors more useful by giving type.field context information. Fixes #179. Alec Thomas 2 years ago
7 changed file(s) with 35 addition(s) and 25 deletion(s). Raw diff Collapse all Expand all
443443 `format:"X"` | Format for parsing input, if supported.
444444 `sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting.
445445 `mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting.
446 `enum:"X,Y,..."` | Set of valid values allowed for this flag.
446 `enum:"X,Y,..."` | Set of valid values allowed for this flag. An enum field must be `required` or have a valid `default`.
447447 `group:"X"` | Logical group for a flag or command.
448448 `xor:"X,Y,..."` | Exclusive OR groups for flags. Only one flag in the group can be used which is restricted within the same command. When combined with `required`, at least one of the `xor` group will be required.
449449 `prefix:"X"` | Prefix for all sub-flags.
5050 for i := 0; i < v.NumField(); i++ {
5151 ft := v.Type().Field(i)
5252 fv := v.Field(i)
53 tag := parseTag(fv, ft)
53 tag := parseTag(v, fv, ft)
5454 if tag.Ignored {
5555 continue
5656 }
155155 // a positional argument is provided to the child, and move it to the branching argument field.
156156 if tag.Arg {
157157 if len(child.Positional) == 0 {
158 fail("positional branch %s.%s must have at least one child positional argument named %q",
159 v.Type().Name(), ft.Name, name)
158 failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
160159 }
161160
162161 value := child.Positional[0]
167166
168167 child.Name = value.Name
169168 if child.Name != name {
170 fail("first field in positional branch %s.%s must have the same name as the parent field (%s).",
171 v.Type().Name(), ft.Name, child.Name)
169 failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
172170 }
173171
174172 child.Argument = value
178176 node.Children = append(node.Children, child)
179177
180178 if len(child.Positional) > 0 && len(child.Children) > 0 {
181 fail("can't mix positional arguments and branching arguments on %s.%s", v.Type().Name(), ft.Name)
179 failField(v, ft, "can't mix positional arguments and branching arguments")
182180 }
183181 }
184182
185183 func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
186184 mapper := k.registry.ForNamedValue(tag.Type, fv)
187185 if mapper == nil {
188 fail("unsupported field type %s.%s (of type %s), perhaps missing a cmd:\"\" tag?", v.Type(), ft.Name, ft.Type)
186 failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
189187 }
190188
191189 value := &Value{
208206 node.Positional = append(node.Positional, value)
209207 } else {
210208 if seenFlags["--"+value.Name] {
211 fail("duplicate flag --%s", value.Name)
209 failField(v, ft, "duplicate flag --%s", value.Name)
212210 } else {
213211 seenFlags["--"+value.Name] = true
214212 }
215213 if tag.Short != 0 {
216214 if seenFlags["-"+string(tag.Short)] {
217 fail("duplicate short flag -%c", tag.Short)
215 failField(v, ft, "duplicate short flag -%c", tag.Short)
218216 } else {
219217 seenFlags["-"+string(tag.Short)] = true
220218 }
3131
3232 func TestConfigValidation(t *testing.T) {
3333 var cli struct {
34 Flag string `json:"flag,omitempty" enum:"valid"`
34 Flag string `json:"flag,omitempty" enum:"valid" required:""`
3535 }
3636
3737 cli.Flag = "invalid"
1919
2020 func fail(format string, args ...interface{}) {
2121 panic(Error{msg: fmt.Sprintf(format, args...)})
22 }
23
24 func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) {
25 name := parent.Type().Name()
26 if name == "" {
27 name = "<anonymous struct>"
28 }
29 msg := fmt.Sprintf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...))
30 panic(Error{msg: msg})
2231 }
2332
2433 // Must creates a new Parser or panics if there is an error.
642642 func TestInterpolationIntoModel(t *testing.T) {
643643 var cli struct {
644644 Flag string `default:"${default}" help:"Help, I need ${somebody}" enum:"${enum}"`
645 EnumRef string `enum:"a,b" help:"One of ${enum}"`
645 EnumRef string `enum:"a,b" required:"" help:"One of ${enum}"`
646646 }
647647 _, err := kong.New(&cli)
648648 require.Error(t, err)
762762
763763 func TestEnum(t *testing.T) {
764764 var cli struct {
765 Flag string `enum:"a,b,c"`
765 Flag string `enum:"a,b,c" required:""`
766766 }
767767 _, err := mustNew(t, &cli).Parse([]string{"--flag", "d"})
768768 require.EqualError(t, err, "--flag must be one of \"a\",\"b\",\"c\" but got \"d\"")
977977 func TestEnumArg(t *testing.T) {
978978 var cli struct {
979979 Nested struct {
980 One string `arg:"" enum:"a,b,c"`
980 One string `arg:"" enum:"a,b,c" required:""`
981981 Two string `arg:""`
982982 } `cmd:""`
983983 }
11411141 Flag2 bool `short:"t"`
11421142 }{}
11431143 _, err := kong.New(&cli)
1144 require.EqualError(t, err, "duplicate short flag -t")
1144 require.EqualError(t, err, "<anonymous struct>.Flag2: duplicate short flag -t")
11451145 }
11461146
11471147 func TestDuplicateNestedShortFlags(t *testing.T) {
11521152 } `cmd:""`
11531153 }{}
11541154 _, err := kong.New(&cli)
1155 require.EqualError(t, err, "duplicate short flag -t")
1156 }
1155 require.EqualError(t, err, "<anonymous struct>.Flag2: duplicate short flag -t")
1156 }
128128 return r == ',' || r == ' '
129129 }
130130
131 func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
131 func parseTag(parent, fv reflect.Value, ft reflect.StructField) *Tag {
132132 if ft.Tag.Get("kong") == "-" {
133133 t := newEmptyTag()
134134 t.Ignored = true
145145 required := t.Has("required")
146146 optional := t.Has("optional")
147147 if required && optional {
148 fail("can't specify both required and optional")
148 failField(parent, ft, "can't specify both required and optional")
149149 }
150150 t.Required = required
151151 t.Optional = optional
160160 t.Env = t.Get("env")
161161 t.Short, err = t.GetRune("short")
162162 if err != nil && t.Get("short") != "" {
163 fail("invalid short flag name %q: %s", t.Get("short"), err)
163 failField(parent, ft, "invalid short flag name %q: %s", t.Get("short"), err)
164164 }
165165 t.Hidden = t.Has("hidden")
166166 t.Format = t.Get("format")
174174 t.Embed = t.Has("embed")
175175 negatable := t.Has("negatable")
176176 if negatable && ft.Type.Kind() != reflect.Bool {
177 fail("negatable can only be set on booleans")
177 failField(parent, ft, "negatable can only be set on booleans")
178178 }
179179 t.Negatable = negatable
180180 aliases := t.Get("aliases")
185185 for _, set := range t.GetAll("set") {
186186 parts := strings.SplitN(set, "=", 2)
187187 if len(parts) == 0 {
188 fail("set should be in the form key=value but got %q", set)
188 failField(parent, ft, "set should be in the form key=value but got %q", set)
189189 }
190190 t.Vars[parts[0]] = parts[1]
191191 }
194194 t.PlaceHolder = strings.ToUpper(dashedString(fv.Type().Name()))
195195 }
196196 t.Enum = t.Get("enum")
197 if t.Enum != "" && !(t.Required || t.Default != "") {
198 failField(parent, ft, "enum value is only valid if it is either required or has a valid default value")
199 }
197200 passthrough := t.Has("passthrough")
198201 if passthrough && !t.Arg {
199 fail("passthrough only makes sense for positional arguments")
202 failField(parent, ft, "passthrough only makes sense for positional arguments")
200203 }
201204 t.Passthrough = passthrough
202205 return t
197197 Flag bool `short:"invalid"`
198198 }{}
199199 _, err := kong.New(&cli)
200 require.EqualError(t, err, "invalid short flag name \"invalid\": invalid rune")
201 }
200 require.EqualError(t, err, "<anonymous struct>.Flag: invalid short flag name \"invalid\": invalid rune")
201 }