Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/audriusbutkevicius/recli

Reflection based CLI (command line interface) generator for Golang
https://github.com/audriusbutkevicius/recli

cli command-line generator go golang reflection

Last synced: 19 days ago
JSON representation

Reflection based CLI (command line interface) generator for Golang

Awesome Lists containing this project

README

        

recli - Reflection based CLI (command line interface) generator for Golang
--------------------------------------------------------------------------

[![GoDoc](https://godoc.org/github.com/AudriusButkevicius/recli?status.svg)](https://godoc.org/github.com/AudriusButkevicius/recli)

For a given struct, builds a set of [urfave/cli](https://github.com/urfave/cli) commands which allows you
to modify it from the command line.

Useful for generating command line clients for your application configuration that is stored in a Go struct.

## Features

* Nested struct support
* Enum/Custom complex type support via MarshalText/UnmarshalText
* Slice support, including complex types
* Slice indexing by struct field
* Map support
* Default primitive value support when adding items to slices

## Known limitations

* Adding new struct to a slice only allows setting primitive fields (use add-json as a work-around)
* Only primitive types supported for map keys and values
* No defaults for maps

## Examples

Example config

```go
type Config struct {
Address string `usage:"Address on which to listen"` // Description printed in -help
AuthMode AuthMode // Enum support
ThreadingOptions ThreadingOptions // Nested struct support
Backends []Backend // Slice support
EnvVars map[string]string // Map support
}

type Backend struct {
Hostname string `recli:"id"` // Constructs commands for indexing into the array based on the value of this field
Port int `default:"2019"` // Default support
BackoffIntervals []int `default:"10,20"` // Slice default support
IPAddressCached net.IP `recli:"-" json:"-"` // Skips the field
}

type ThreadingOptions struct {
MaxThreads int
}
```

Sample input data
```json
{
"Address":"http://website.com",
"AuthMode":"static",
"ThreadingOptions":{
"MaxThreads":10
},
"Backends":[
{
"Hostname":"backend1.com",
"Port":1010
},
{
"Hostname":"backend2.com",
"Port":2020
}
],
"EnvVars":{
"CC":"/usr/bin/gcc"
}
}
```

Full example code

```go
package main

import (
"encoding/json"
"fmt"
"net"
"os"

"github.com/AudriusButkevicius/recli"
"github.com/urfave/cli"
)

type Config struct {
Address string `usage:"Address on which to listen"` // Description printed in -help
AuthMode AuthMode // Enum support
ThreadingOptions ThreadingOptions // Nested struct support
Backends []Backend // Slice support
EnvVars map[string]string // Map support
}

type Backend struct {
Hostname string `recli:"id"` // Constructs commands for indexing into the array based on the value of this field
Port int `default:"2019"` // Default support
BackoffIntervals []int `default:"10,20"` // Slice default support
IPAddressCached net.IP `recli:"-" json:"-"` // Skips the field
}

type ThreadingOptions struct {
MaxThreads int
}

type AuthMode int

const (
AuthModeStatic AuthMode = iota // default is static
AuthModeLDAP
)

func (t AuthMode) MarshalText() ([]byte, error) {
switch t {
case AuthModeStatic:
return []byte("static"), nil
case AuthModeLDAP:
return []byte("ldap"), nil
}
return nil, fmt.Errorf("unknown value: %s", t)
}

func (t *AuthMode) UnmarshalText(bs []byte) error {
switch string(bs) {
case "ldap":
*t = AuthModeLDAP
case "static":
*t = AuthModeStatic
default:
return fmt.Errorf("unknown value: %s", string(bs))
}
return nil
}

const (
sampleData = `
{
"Address":"http://website.com",
"AuthMode":"static",
"ThreadingOptions":{
"MaxThreads":10
},
"Backends":[
{
"Hostname":"backend1.com",
"Port":1010
},
{
"Hostname":"backend2.com",
"Port":2020
}
],
"EnvVars":{
"CC":"/usr/bin/gcc"
}
}`
)

func main() {
cfg := &Config{}

if err := json.Unmarshal([]byte(sampleData), cfg); err != nil {
panic(err)
}

cmds, err := recli.Default.Construct(cfg)
if err != nil {
panic(err)
}

dump := false

app := cli.NewApp()
app.Commands = cmds
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "dump",
Destination: &dump,
},
}

if err := app.Run(os.Args); err != nil {
panic(err)
}

if dump {
bs, err := json.MarshalIndent(&cfg, "", " ")
if err != nil {
panic(err)
}

fmt.Print(string(bs))
}
}
```

Get a field

```bash
$ go run main.go address get
http://website.com
```

Set a field

```bash
$ go run main.go -dump address set foo
{
"Address": "foo",
"AuthMode": "static",
"ThreadingOptions": {
"MaxThreads": 10
},
"Backends": [
{
"Hostname": "backend1.com",
"Port": 1010,
"BackoffIntervals": null
},
{
"Hostname": "backend2.com",
"Port": 2020,
"BackoffIntervals": null
}
],
"EnvVars": {
"CC": "/usr/bin/gcc"
}
}
```

Set a nested field

```bash
$ go run main.go -dump threading-options max-threads set 9000
{
"Address": "http://website.com",
"AuthMode": "static",
"ThreadingOptions": {
"MaxThreads": 9000
},
"Backends": [
{
"Hostname": "backend1.com",
"Port": 1010,
"BackoffIntervals": null
},
{
"Hostname": "backend2.com",
"Port": 2020,
"BackoffIntervals": null
}
],
"EnvVars": {
"CC": "/usr/bin/gcc"
}
}
```

Listing available slice items (with a custom slice index key)

```bash
$ go run main.go backends
NAME:
main.exe backends -

USAGE:
main.exe backends command [command options] [arguments...]

COMMANDS:
ACTIONS:
add Add a new item to collection
add-json Add a new item to collection deserialised from JSON

ITEMS:
backend1.com
backend2.com

OPTIONS:
--help, -h show help

```

Deleting a slice item

```bash
$ go run main.go -dump backends backend1.com delete
{
"Address": "http://website.com",
"AuthMode": "static",
"ThreadingOptions": {
"MaxThreads": 10
},
"Backends": [
{
"Hostname": "backend2.com",
"Port": 2020,
"BackoffIntervals": null
}
],
"EnvVars": {
"CC": "/usr/bin/gcc"
}
}
```

Adding a slice item (with defaults)

```bash
$ go run main.go -dump backends add -hostname="testback.end"
{
"Address": "http://website.com",
"AuthMode": "static",
"ThreadingOptions": {
"MaxThreads": 10
},
"Backends": [
{
"Hostname": "backend1.com",
"Port": 1010,
"BackoffIntervals": null
},
{
"Hostname": "backend2.com",
"Port": 2020,
"BackoffIntervals": null
},
{
"Hostname": "testback.end",
"Port": 2019,
"BackoffIntervals": [
10,
20
]
}
],
"EnvVars": {
"CC": "/usr/bin/gcc"
}
}
```

Setting map keys

```bash
$ go run main.go -dump env-vars set GCC /usr/bin/true
{
"Address": "http://website.com",
"AuthMode": "static",
"ThreadingOptions": {
"MaxThreads": 10
},
"Backends": [
{
"Hostname": "backend1.com",
"Port": 1010,
"BackoffIntervals": null
},
{
"Hostname": "backend2.com",
"Port": 2020,
"BackoffIntervals": null
}
],
"EnvVars": {
"CC": "/usr/bin/gcc",
"GCC": "/usr/bin/true"
}
}
```