goat

package module
v0.0.0-...-7201fef Latest Latest
Warning

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

Go to latest
Published: Dec 17, 2022 License: MIT Imports: 6 Imported by: 2

README

goat 🐐

GO Approximation of Typer

Intro

Typer is one of my favourite Python packages. It allows building CLI apps with next to zero boilerplate. See the Typer docs for an example.

Working in Go, I want something similar. So I am creating Goat 🐐, trying to get the best experience possible.

package main

import (
	"fmt"
	"github.com/tmr232/goat" // One explicit dependency
)

// Generate all the necessary magic
//go:generate go run github.com/tmr232/goat/cmd/goater

func app(name string, goodbye bool) {
	if goodbye {
		fmt.Printf("Goodbye, %s.\n", name)
	} else {
		fmt.Printf("Hello, %s!\n", name)

	}
}

func main() {
	// Let goat know what to run
	goat.Run(app)
}

Status & Contibuting

Slowly moving forward, but not yet stable.

Experimentation, bug reports, and feature requests are very welcome.

API

The Goat API is built of 4 main parts, numbered in the code below.

package main

import (
	"fmt"
	"github.com/tmr232/goat"
)
// (1) The goater command
//go:generate go run github.com/tmr232/goat/cmd/goater

// (3) The app function signature
func app(name string, goodbye bool, question *string, times int) {
	// (4) Flag Descriptors
	goat.Flag(name).
		Usage("The name to greet")
	goat.Flag(goodbye).
		Name("bye").
		Usage("Enable to say Goodbye")
	goat.Flag(question).
		Usage("Instead of a greeting, ask a question.")
	goat.Flag(times).
		Usage("Number of repetitions").
		Default(1)

	for i := 0; i < times; i++ {
		if question != nil {
			fmt.Printf("%s, %s?", *question, name)
		} else {
			if goodbye {
				fmt.Printf("Goodbye, %s.\n", name)
			} else {
				fmt.Printf("Hello, %s!\n", name)
			}
		}
	}
}

func main() {
	// (2) The Run function
	goat.Run(app)
}

1. The goater Command

To get the functionality we aim for, we use code generation. We read the user's code, infer the relevant information, and generate wrapper code that later calls into it. This generation is done using the goater command. You can either run it manually in the relevant package directory, or use go:generate to do it for you.

2. The goat.Run Function

To let the goater command know which functions to wrap, we call goat.Run with those function. In this case - goat.Run(app) means that we'll wrap the app function.

Later, during execution, goat.Run calls into the wrapper for app.

3. The App Function Signature

This is where things get interesting. The app function (any function passed into goat.Run) is parsed during code generation, and a wrapper is generated for it.

The signature of the function determines the types of flags that will be generated. An int argument will result in an int flag, a bool argument in a bool flag, and a string argument in a string flag.

If an argument is a pointer (*string, for example) the flag will be optional. If an argument is not a pointer, it'll be a required flag. bool is an exception as it is never required.

4. Flag Descriptors

A name and a type for a flag are nice, but hardly enough. We may want to define aliases, usage strings, or default values. To do this - we describe our flags as follows:

goat.Flag(name).
	Usage("The name to greet")

You can use the following to add data to your flags:

  1. Usage(string) - add a usage string
  2. Name(string) - set the name of the flag
  3. Default(any) - set the flag's default value. Works only with non-pointer flags.

Subcommands & Context

Goat also allows defining subcommands

package main

import (
	"fmt"
	"github.com/tmr232/goat" // One explicit dependency
)

//go:generate go run github.com/tmr232/goat/cmd/goater

func server(name string) {
	
}

func app(name string, goodbye bool) {
	if goodbye {
		fmt.Printf("Goodbye, %s.\n", name)
	} else {
		fmt.Printf("Hello, %s!\n", name)

	}
}

func main() {
	// Let goat know what to run
	goat.Run(app)
}

Dependencies

Goat currently uses urfave/cli for parsing flags. Other than that, the generated code currently only depends on the standard library.

In the future, I plan to write backends for other popular flag-parsing libraries (namely Cobra and flag) so that users can choose what they depend on.


Internals

See this blog post for more implementation details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FuncToApp

func FuncToApp(f any) *cli.App

func GetFlag

func GetFlag[T any](ctx *Context, f any, name string) (T, error)

func PartsToCommands

func PartsToCommands(parts []AppPart) []*cli.Command

func Register

func Register(app any, config RunConfig)

Register registers a RunConfig generated from a function.

This is only used in generated code.

func Run

func Run(f any)

Run takes a free function and runs it as a CLI app, terminating with a log if an error occurs.

func RunE

func RunE(f any) error

RunE takes a free function and runs it as a CLI app.

func RunWithArgsE

func RunWithArgsE(f any, args []string) error

Types

type AppPart

type AppPart interface {
	// contains filtered or unexported methods
}

type Application

type Application struct{ *cli.App }

func App

func App(name string, commands ...AppPart) Application

func (Application) Run

func (app Application) Run()

func (Application) RunE

func (app Application) RunE() error

func (Application) RunWithArgsE

func (app Application) RunWithArgsE(args []string) error

type Context

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

func GetContext

func GetContext(c *cli.Context) *Context

func (*Context) GetFlag

func (ctx *Context) GetFlag(f any, name string) (any, error)

func (*Context) GetWriter

func (ctx *Context) GetWriter() io.Writer

type FluentFlag

type FluentFlag struct{}

func Flag

func Flag(any) FluentFlag

Flag creates a flag-descriptor to be used during code-generation to describe the flag.

Should be used with the FluentFlag.Name, FluentFlag.Usage and FluentFlag.Default methods.

Example:

func f(myFlag int) {
	Flag(myFlag).
		Name("my-flag").
		Usage("Just a flag.")
}

func (FluentFlag) Default

func (f FluentFlag) Default(any) FluentFlag

Default sets the default value for a flag.

Must be called with the same type as the flag.

func (FluentFlag) Name

func (f FluentFlag) Name(string) FluentFlag

Name sets the name of a flag.

func (FluentFlag) Usage

func (f FluentFlag) Usage(string) FluentFlag

Usage sets the usage of a flag.

type FluentSelf

type FluentSelf struct{}

func Self

func Self() FluentSelf

Self begins a description-chain for the current function.

func (FluentSelf) Name

func (s FluentSelf) Name(string) FluentSelf

Name sets the name of the current function.

func (FluentSelf) Usage

func (s FluentSelf) Usage(string) FluentSelf

Usage sets the usage of the current function.

type GoatCommand

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

func Command

func Command(f any, subcommands ...AppPart) *GoatCommand

type GoatGroup

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

func Group

func Group(name string, subcommands ...AppPart) *GoatGroup

func (*GoatGroup) Usage

func (g *GoatGroup) Usage(usage string) *GoatGroup

type RunConfig

type RunConfig struct {
	Flags []cli.Flag
	// TODO: Replace this with a function that takes the action function and returns ActionFunc
	//		 This is needed for supporting function literals instead of named functions.
	//		 This is also required for anything beyond named functions.
	Action         cli.ActionFunc
	CtxFlagBuilder func(c *cli.Context) map[string]any
	Name           string
	Usage          string
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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