clive

package module
v0.0.0-...-775e6e1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2019 License: MIT Imports: 7 Imported by: 0

README

CLI, made Very Easy! (clive)

This compacts the huge declaration of a cli.App into a declarative, (mostly) compile-time checked and automated generator based on struct tags.

Motivation

Aside from urfave/cli declarations getting rather huge, there tend to be a ton of duplication which not only is it annoying to write and maintain, it can also introduce subtle bugs due to the usage of string literals instead of typed objects that are checked by tools and the compiler.

It's also fun to mess around with structs, reflection and tags!

Usage

A single struct can declare your entire application, then at run-time all you have to do is bind the Action, Before, After, etc. fields to your functions.

One Command

A single-command instance will make all the flags global and assign the action function to the root App object:

func main() {
	type app struct {
		cli.Command `cli:"usage:'this command does a, b and c'"`
		FlagHost    string
		FlagPort    int
		FlagDoStuff bool
	}
	err := clive.Build(app{
		Command: cli.Command{
			Action: func(c *cli.Context) error {
				flags, ok := clive.Flags(app{}, c).(app)
				if !ok {
					return errors.New("failed to decode flags")
				}

				fmt.Println("Flags:")
				fmt.Println(flags.FlagHost)
				fmt.Println(flags.FlagPort)
				fmt.Println(flags.FlagDoStuff)

				return nil
			},
		},
	}).Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

This will produce a cli.App object that looks something like this:

&cli.App{
    Name:                 "application-thing",
    Usage:                "this command does a, b and c",
    Version:              "60f4851-master (2018-10-25T16:33:27+0000)",
    Description:          "the contents of the APP_DESCRIPTION environment variable",
    Flags:       {
        cli.StringFlag{
            Name:        "flag-host",
            EnvVar:      "FLAG_HOST",
        },
        cli.StringFlag{
            Name:        "flag-port",
            EnvVar:      "FLAG_PORT",
        },
        cli.StringFlag{
            Name:        "do-stuff",
            EnvVar:      "DO_STUFF",
        },
    },
}

The flag names and environment variables have been filled in automatically and converted to their respective cases (kebab and screaming-snake).

In the Action function, the flags := clive.Flags(c, run{}).(run) line is responsible for taking the *cli.Context parameter that is passed in by cli, extracting the flag values and returning a value that you can safely cast to the original struct type.

This allows you to access the flag values in a type safe way without relying on string-literals that can be mistyped.

Multiple Command

If you supply multiple structs to clive.Build, they will form an App with Commands instead of an App with an Action function and global Flags.

func main() {
	type run struct {
		cli.Command `cli:"usage:this command runs things"` // embedding this is necessary
		FlagHost    string
		FlagPort    int
		FlagDoStuff bool
	}

	type debug struct {
		cli.Command `cli:"name:dbg,usage:this command debugs things"` // embedding this is necessary
		FlagTarget  string
		FlagMethod  string
		FlagTime    time.Duration `cli:"default:1h"`
		FlagForce   bool
	}

	err := clive.Build(
		run{
			Command: cli.Command{
				Action: func(c *cli.Context) error {
					flags, ok := clive.Flags(run{}, c).(run)
					if !ok {
						return errors.New("failed to decode flags")
					}

					fmt.Println(flags)
					return nil
				},
			},
		},
		debug{
			Command: cli.Command{
				Action: func(c *cli.Context) error {
					flags, ok := clive.Flags(debug{}, c).(debug)
					if !ok {
						return errors.New("failed to decode flags")
					}

					fmt.Println(flags)
					return nil
				},
			},
		}).Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

Available Tags

The cli struct tag group can be used to tweak flags. In the example above, it's used on the cli.Command field to set the "usage" text but it can also be used on Flag fields.

The format is:

`cli:"key:value,key:value,key:value"`

The available tag names are:

  • name: override the flag name
  • usage: set the usage text for the flag
  • hidden: hide the flag
  • default: set the default value

The only tag used for the top-level App is usage which must be applied to the embedded cli.Command struct.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Build

func Build(objs ...interface{}) (c *cli.App)

Build constructs a urfave/cli App from an instance of a decorated struct Since it is designed to be used 1. on initialisation and; 2. with static data that is compile-time only - it does not return an error but instead panics. The idea is you will do all your setup once and as long as it doesn't change this will never break, so there is little need to pass errors back.

func Flags

func Flags(obj interface{}, c *cli.Context) (result interface{})

Flags is a helper function for use within a command Action function. It takes an instance of the struct that was used to generate the command and the cli.Context pointer that is passed to the action function. It will then call the necessary flag access functions (such as c.String("...")) and return an instance of the input struct with the necessary fields set.

Types

This section is empty.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL