wand/input.go
2025-08-18 14:24:31 +02:00

167 lines
3.0 KiB
Go

package wand
import (
"fmt"
"reflect"
"slices"
)
func validateEnv(cmd Cmd, e env) error {
mf := mapFields(cmd.impl)
for name, values := range e.values {
f, ok := mf[name]
if !ok {
continue
}
for _, fi := range f {
if len(values) > 1 && !fi.acceptsMultiple {
return fmt.Errorf(
"expected only one value, received %d, as environment value, %s",
len(values),
e.originalNames[name],
)
}
for _, v := range values {
if !canScan(fi.typ, v) {
return fmt.Errorf(
"environment variable cannot be applied, type mismatch: %s",
e.originalNames[name],
)
}
}
}
}
return nil
}
func validateOptions(cmd Cmd, o []option) error {
ml := make(map[string]string)
ms := make(map[string]string)
for i := 0; i < len(cmd.shortForms); i += 2 {
l, s := cmd.shortForms[i], cmd.shortForms[i+1]
ml[l] = s
ms[s] = l
}
mo := make(map[string][]option)
for _, oi := range o {
n := oi.name
if ln, ok := ms[n]; ok && oi.shortForm {
n = ln
}
mo[n] = append(mo[n], oi)
}
mf := mapFields(cmd.impl)
for n, os := range mo {
f := mf[n]
for _, fi := range f {
en := "--" + n
if sn, ok := ml[n]; ok {
en += ", -" + sn
}
if len(os) > 1 && !fi.acceptsMultiple {
return fmt.Errorf(
"expected only one value, received %d, as option, %s",
len(os),
en,
)
}
for _, oi := range os {
if oi.value.isBool && fi.typ.Kind() != reflect.Bool {
return fmt.Errorf(
"received boolean value for field that does not accept it: %s",
en,
)
}
if !oi.value.isBool && !canScan(fi.typ, oi.value.str) {
return fmt.Errorf(
"option cannot be applied, type mismatch: %s",
en,
)
}
}
}
}
return nil
}
func validatePositionalArgs(cmd Cmd, a []string) error {
v := reflect.ValueOf(cmd.impl)
v = unpack(v)
t := v.Type()
p := positionalParameters(t)
ior, iow := ioParameters(p)
last := t.NumIn()-1
lastVariadic := t.IsVariadic() &&
!isStruct(t.In(last)) &&
!slices.Contains(ior, last) &&
!slices.Contains(iow, last)
length := len(p) - len(ior) - len(iow)
min := length
max := length
if lastVariadic {
min--
max = -1
}
if cmd.minPositional > min {
min = cmd.minPositional
}
if cmd.maxPositional > 0 {
max = cmd.maxPositional
}
if len(a) < min {
return fmt.Errorf("not enough positional arguments, expected minimum %d", min)
}
if max >= 0 && len(a) > max {
return fmt.Errorf("too many positional arguments, expected maximum %d", max)
}
for i, ai := range a {
var pi reflect.Type
if i >= length {
pi = p[length-1]
} else {
pi = p[i]
}
if !canScan(pi, ai) {
return fmt.Errorf(
"cannot apply positional argument at index %d, expecting %v",
i,
pi,
)
}
}
return nil
}
func validateInput(cmd Cmd, e env, cl commandLine) error {
if err := validateEnv(cmd, e); err != nil {
return err
}
if err := validateOptions(cmd, cl.options); err != nil {
return err
}
if err := validatePositionalArgs(cmd, cl.positional); err != nil {
return err
}
return nil
}