clive

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 21, 2024 License: MIT Imports: 9 Imported by: 0

README

CLI, made Very Easy!, second overhaul (clive2)

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:

type app struct {
	*clive.Command `cli:"usage:'this command does a, b and c'"` // embedding this is necessary
	Run            clive.RunFunc
	FlagHost       string
	FlagPort       int
	FlagDoStuff    bool
}
func (*app) Action(ctx *cli.Context){
	a := c.Current(ctx).(*app)
	fmt.Println(a.FlagHost)
	fmt.Println(a.FlagPort)
	fmt.Println(a.FlagDoStuff)
	return nil
}
func main() {
	err := clive.Build(&app{}).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"`

Single quotes ('') are allowed to escape the comma (,) character.

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

  • required: set the required flag

  • positional: converts flag into a positional argument (taken from ctx.Args())

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

View Source
var ErrNil = errors.New("obj is n ull")

Functions

func Build

func Build(obj 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 initialization 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 ErrCommandNotImplemented

func ErrCommandNotImplemented() error

func Reflected

func Reflected[T any]() reflect.Type

Types

type Actionable

type Actionable interface {
	Action(*cli.Context) error
}

type ActionableNotImplementedError

type ActionableNotImplementedError struct {
	Type string
}

func (*ActionableNotImplementedError) Error

type ByValueError

type ByValueError struct {
	Type string
}

func (*ByValueError) Error

func (e *ByValueError) Error() string

type Command

type Command struct {
	*cli.Command
	// contains filtered or unexported fields
}

func (*Command) Action

func (c *Command) Action(ctx *cli.Context) error

func (*Command) Current

func (c *Command) Current(ctx *cli.Context) interface{}

func (*Command) Parent

func (c *Command) Parent(ctx *cli.Context) interface{}

func (*Command) Root

func (c *Command) Root(ctx *cli.Context) interface{}

type CommandLike

type CommandLike interface {
	Root(ctx *cli.Context) interface{}
	Parent(ctx *cli.Context) interface{}
}

type HasAfter added in v0.1.1

type HasAfter interface {
	After(*cli.Context) error
}

type HasBefore added in v0.1.1

type HasBefore interface {
	Before(*cli.Context) error
}

type HasSubcommand added in v0.2.0

type HasSubcommand interface {
	Subcommand(c *cli.App, parentCommandPath string) *cli.Command
}

type HasVariants

type HasVariants interface {
	Variants() []string
}

type HiddenPositionalError

type HiddenPositionalError struct {
	Name string
}

func (*HiddenPositionalError) Error

func (e *HiddenPositionalError) Error() string

type InterfaceType

type InterfaceType struct {
	// contains filtered or unexported fields
}

func (*InterfaceType) IsVariadic

func (ifaceType *InterfaceType) IsVariadic() bool

func (*InterfaceType) NewFlag

func (ifaceType *InterfaceType) NewFlag(cmdMeta commandMetadata) (cli.Flag, error)

func (*InterfaceType) Predicate

func (ifaceType *InterfaceType) Predicate(fType reflect.Type) bool

func (*InterfaceType) SetValueFromContext

func (ifaceType *InterfaceType) SetValueFromContext(value reflect.Value, flagName string, context *cli.Context) (err error)

func (*InterfaceType) SetValueFromString

func (ifaceType *InterfaceType) SetValueFromString(value reflect.Value, s string) (err error)

func (*InterfaceType) SetValueFromStrings

func (ifaceType *InterfaceType) SetValueFromStrings(value reflect.Value, s []string) (err error)

type PointerTo

type PointerTo struct {
	// contains filtered or unexported fields
}

func (*PointerTo) IsVariadic

func (ptrTo *PointerTo) IsVariadic() bool

func (*PointerTo) NewFlag

func (ptrTo *PointerTo) NewFlag(cmdMeta commandMetadata) (cli.Flag, error)

func (*PointerTo) Predicate

func (ptrTo *PointerTo) Predicate(fType reflect.Type) bool

func (*PointerTo) SetValueFromContext

func (ptrTo *PointerTo) SetValueFromContext(value reflect.Value, flagName string, context *cli.Context) error

func (*PointerTo) SetValueFromString

func (ptrTo *PointerTo) SetValueFromString(value reflect.Value, s string) (err error)

func (*PointerTo) SetValueFromStrings

func (ptrTo *PointerTo) SetValueFromStrings(value reflect.Value, s []string) (err error)

type PositionalAfterVariadicError

type PositionalAfterVariadicError struct {
	CurrentName string
	FirstName   string
}

func (*PositionalAfterVariadicError) Error

type RunFunc

type RunFunc func(*Command, *cli.Context) error

type StandardType

type StandardType struct {
	// contains filtered or unexported fields
}

func NewStandardType

func NewStandardType[T any, Flag any]() *StandardType

func (*StandardType) IsVariadic

func (nt *StandardType) IsVariadic() bool

func (*StandardType) NewFlag

func (nt *StandardType) NewFlag(cmdMeta commandMetadata) (cli.Flag, error)

func (*StandardType) Predicate

func (nt *StandardType) Predicate(fType reflect.Type) bool

func (*StandardType) SetValueFromContext

func (nt *StandardType) SetValueFromContext(value reflect.Value, flagName string, context *cli.Context) error

func (*StandardType) SetValueFromString

func (nt *StandardType) SetValueFromString(val reflect.Value, s string) (err error)

func (*StandardType) SetValueFromStrings

func (nt *StandardType) SetValueFromStrings(val reflect.Value, s []string) (err error)

type TypeFunctions

type TypeFunctions interface {
	NewFlag(cmdMeta commandMetadata) (cli.Flag, error)
	SetValueFromString(val reflect.Value, s string) (err error)
	SetValueFromContext(value reflect.Value, flagName string, context *cli.Context) (err error)
	IsVariadic() bool
	SetValueFromStrings(val reflect.Value, s []string) (err error)
}

type TypeInterface

type TypeInterface interface {
	TypePredicate
	TypeFunctions
}

type TypePredicate

type TypePredicate interface {
	Predicate(reflect.Type) bool
}

type WithDescription

type WithDescription interface {
	Description() string
}

type WithVersion

type WithVersion interface {
	Version() string
}

type WrongFirstFieldError

type WrongFirstFieldError struct {
	NumFields int

	FieldName string
	Type      string
}

func (*WrongFirstFieldError) Error

func (e *WrongFirstFieldError) Error() string

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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