# Kong Go CLI


I'm a fan of [Kong the CLI argument parser](https://github.com/alecthomas/kong), I use it to
automatically create manual pages, (bash) completion and do a lot of validation inside it. (This guy
also has other really interesting packages, and he's a huge fan of Go's struct tags).

I wanted to do validation inside Kong as well, for this Kong has
[validation](https://github.com/alecthomas/kong?tab=readme-ov-file#validation), which is just a
method (`Validate() error`) on a type. Now most types I have are just `string` or any other builtin
type. For validation to work, all these types must be re-typed to another type on which the method
can be applied, which is annoying. I wanted a better way...

For instance I have this field in a struct for a sub-command:

```
User string `arg:"" name:"loginname|UID" help:"User or UID to list." optional:"" completion:"c user list --comp"`
```

The `completion` tag is my invention, see https://github.com/miekg/king, on how that works,
anyway, I want to syntax check this `User` and with Kong's custom mappers, you can do the following.

```go
loginMapper := kong.MapperFunc(func(ctx *kong.DecodeContext, v reflect.Value) error {
        var login string
        if err := ctx.Scan.PopValueInto("string", &login); err != nil {
                return err
        }
        v.SetString(login)

        if err := dbuser.Valid(login); err == nil {
                return nil
        }

        // might be uid...
        uid, err := strconv.ParseUint(login, 10, 64)
        if err != nil {
                return err
        }
        if err := dbuser.ValidUID(uint(uid)); err != nil && uid > 0 {
                return err
        }
        return nil
})
```

This code calls some other checking function which are not that important, and defines a
`kong.MapperFunc` which can be used when parsing the command line:

```go
c := Cli{}
kctx := kong.Parse(&c, kong.NamedMapper("login", loginMapper))
```

And with this you get a `type:"login"`, which you can apply to all the places where you want this
check to happen:

```go
User string `arg:"" name:"loginname|UID" help:"User or UID to list." optional:"" completion:"c user list --comp" type:"login"`
```

No extra methods, no new types, just `type:"login"`. Love it.

## Update

A (string)slice arguments, that mapper becomes slightly more complex, but still elegant, although I
could refactor this a bit and make it even shorter.

```go
oginMapper = kong.MapperFunc(func(ctx *kong.DecodeContext, v reflect.Value) error {
        if v.Type().String() == "[]string" {
                logins := ctx.Scan.PopWhile(func(t kong.Token) bool { return !t.IsEOL() })
                names := []string{}
                for i := range logins {
                        names = append(names, logins[i].Value.(string))
                        if err := dbuser.Valid(logins[i].Value.(string)); err == nil {
                                continue
                        }
                        uid, err := strconv.ParseUint(logins[i].Value.(string), 10, 64)
                        if err != nil {
                                return err
                        }
                        if err := dbuser.ValidUID(uint(uid)); err != nil && uid > 0 {
                                return err
                        }
                }
                v.Set(reflect.ValueOf(names))
                return nil
        }

        var login string
        if err := ctx.Scan.PopValueInto("login mapper", &login); err != nil {
                return err
        }
        if login == "" {
                return nil
        }
        v.SetString(login)

        if err := dbuser.Valid(login); err == nil {
                return nil
        }
        uid, err := strconv.ParseUint(login, 10, 64)
        if err != nil {
                return err
        }
        if err := dbuser.ValidUID(uint(uid)); err != nil && uid > 0 {
                return err
        }
        return nil
})
```

