clifromyaml

command module
v0.0.0-...-e462c98 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2022 License: MIT Imports: 14 Imported by: 0

README

clifromyaml

clifromyaml is a tool to generate a Go CLI for an application. Simply define a CLI in yaml, run the clifromyaml tool to generate the bindings, and then get on with the interesting bits of coding: application logic.

Install

go get github.com/tkennon/clifromyaml and make sure that ~/go/bin is in your PATH.

Why clifromyaml?

There are many other mature and feature-full cli packages for Go, and by comparison clifromyaml seems rather basic. I wrote this package to make getting up and running with a console app quick and easy for developers, not to provide every possible configuration option under the sun. I am sure there will be applications for which clifromyaml does not meet all the requirements, but those projects will be well served by something like github.com/spf13/cobra or github.com/urfave/cli.

Goals:

  • simplicity
  • readability
  • speed of development

Non-goals:

  • feature compatibility with other Go CLI packages
  • infinitely extensible and customisable CLI options

This is not to say that features won't be added, but they won't be added at the expense of any of the project goals.

Example

Define a CLI in a yaml file

# Declares the name of the application as it will be invoked by a user
# (required).
app: example
# Declares the top level command (required). Commands consist of arguments,
# variadic arguments, flags, and sub-commands. A command may declare either
# further subcommands, or a combination of args, vargs, and flags.
run:
  # The help string for the command. Will be printed whenever the user asks for
  # help through the automatically generated -h or --help flags.
  help: This is my application to do stuff
  # This example application declares two sub-commands: foo and bar.
  subcommands:
    foo:
      help: Do a foo
      # Declares that foo takes exactly two ordered arguments. The generated Go
      # code will refer to these arguments as `in` and `out` respectively. Both
      # args have an associated description which will appear in the printed
      # help output and example usage.
      args:
        - in: the input to foo
        - out: the output of foo
      # Declares the foo can optionally take two flags. Note that the generated
      # Go code uses the stdlib "flag" package and so the flag names may be
      # prefixed with either single or double dashes by the user (for example
      # `--wait 2m3s` or `-wait=1s`). As a consequence, single letter aliases
      # are not supported (`-w 1s`).
      flags:
        dry-run:
          # As with commands and arguments, flags have help strings. The better
          # the help strings, the easier the application will be to use.
          help: don't actually write to the output
          # A default must be decalred: this is how clifromyaml infers the type
          # of the flag. Integer, boolean, string, and time.Duration types are
          # supported.
          default: false
        wait:
          help: wait a bit before writing to the output
          default: 5s
    bar:
      # The bar command takes at least one argument, but may take a variadic
      # number.
      help: Do lots of bar
      args:
       - first: the first bar
      # The description of the vargs appears in example usage generated in the
      # printed help output.
      vargs: bars
      flags:
        # Optionally takes a flag baz that must be one of the predefined
        # choices. If it is not then an error is returned.
        baz:
          help: some optional extra baz
          default: red
          oneof: [red, blue, yellow]

Generate the CLI by running clifromyaml path/to/cli.yaml. This will create a file called path/to/cli.yaml.go that contains (among other things) an Application interface.

// Application defines the entrypoints to the application logic.
type Application interface {
	ExampleBar
	ExampleFoo
}

type ExampleBar interface {
	RunExampleBar(baz string, first string, bars ...string) error
}

type ExampleFoo interface {
	RunExampleFoo(dryRun bool, wait time.Duration, in string, out string) error
}

Then, simply implement a type that satisfies the Application interface, and run the CLI with it.

package main

import (
	"fmt"
	"time"
)

type myApplication struct {
	// Stuff
}

func (a *myApplication) RunExampleFoo(dryRun bool, wait time.Duration, in string, out string) error {
	fmt.Printf("Doing foo: dryRun: %t, wait: %s, in: %s, out: %s\n", dryRun, wait, in, out)
	return nil
}

func (a *myApplication) RunExampleBar(baz string, first string, bars ...string) error {
	fmt.Printf("Doing bar with %s for %s and %v\n", baz, first, bars)
	return nil
}

func main() {
	a := myApplication{}
	if err := NewCLI(&a).Run(); err != nil {
		fmt.Println(err)
	}
}

When built this runs as

$ ./example -h
This is my application to do stuff

Usage: example <command>

Commands:
  bar: Do lots of bar
  foo: Do a foo
$ ./example foo -h
Do a foo

Usage: example foo [--dry-run] [--wait <duration>] <in> <out>

Arguments:
  in: the input to foo
  out: the output of foo

Flags:
  -dry-run
        don't actually write to the output
  -wait duration
        wait a bit before writing to the output (default 5s)
$ ./example foo --wait 2m first second
Doing foo: dryRun: false, wait: 2m0s, in: first, out: second
$ ./example bar --baz pink first
'baz' must be one of [red blue yellow]
$ ./example bar a b c d e f g h i j k l m n o p
Doing bar with red for a and [b c d e f g h i j k l m n o p]

To complete the setup, include a //go:generate clifromyaml path/to/cli.yaml line in main.go so that the CLI bindings are rebuilt with a go generate; go build.

Check out the example/ directory for further examples. Also checkout cli.yaml which defines the CLI used for clifromyaml itself.

Usage

$ clifromyaml --help
Generate Golang CLI bindings from a YAML definition.

Usage: clifromyaml [--dry-run] [--outfile <file>] [--package-name <string>] [--stdout] [--version] <yaml-spec>

Arguments:
  yaml-spec: the YAML file containing the CLI definition

Flags:
  -dry-run
        Don't write the generated Go bindings anywhere, just parse the yaml and print any errors.
  -outfile file
        The file that the generated CLI bindings should be written to. If empty then they will be written to <yaml-spec>.go.
  -package-name string
        The package name to use for the generated Go bindings. (default "main")
  -stdout
        Print the generated CLI bindings to stdout.
  -version
        print version

Yaml specification

app: <app>
version: <version>
run:
  help: <help>
  subcommands:
  args:
   - <name>: <help>
  vargs: <name>
  flags:
    <flag name>:
      help: <help>
      default: <default>
      oneof: [<choices, ...>]
app

app is required: it is the name of the application as it will be used in a shell, e.g. go, git, clifromyaml etc.

version

A special --version flag will be automatically added if the top-level version key is specified in the yaml configuration, and myapp --version will print the value of the version key to stdout.

e.g.

app: foo
version: 1.2.3
run:
  help: Simply prints a version

will generate an application that does:

$ ./foo --version
1.2.3

You can specify environment variables using the ${} syntax:

app: foo
version: ${FOO_VERSION}
run:
  help: Print the version in the environment at build time
$ FOO_VERSION=3.2.1 go generate; go build
$ ./foo --version
3.2.1
run

run is the entrypoint to the application; it defines what happens when the application is run.

help

help is the string that will be printed if the application is invoked with -h or --help. A well documented application is an easy to use application.

args

args declares a list of key:value pairs describing positional arguments that the command expects. The keys are the names of the arguments, and the values are a description, e.g. - config: The configuration file. If the application is invoked with more or less than the exact number of arguments specified then a descriptive error is returned.

vargs

vargs declares that the command takes a variadic number of arguments after the poisitonal arguments (if args is empty then everything after the command is parsed as a varg).

flags

flags declares a set of flags the command optionally accepts:

  • help is a help string for the flag
  • default defines the default value of the flag. This must be specified so that clifromyaml can infer the type of the flag.
  • oneof optionally specifies that the flag must be set to one of the given choices. If the app is invoked by the user with any value not in this set then an error will be returned.

Flags must be passed before any arguments: ./myapp [flags] <args>.

subcommands

subcommands allows you to recursively define subcommands (i,e, test and build are subcommands of go). You may not specify args, vargs, or flags as well as subcommands.

Documentation

Overview

AUTOGENERATED -- DO NOT EDIT

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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