mainer

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jan 27, 2023 License: BSD-3-Clause Imports: 13 Imported by: 2

README

Go Reference Build Status

mainer

Package mainer defines types relevant to command entrypoint implementation. It includes the Stdio struct that abstracts the I/O and working directory of a command, and the Parser struct that implements a simple flag parser with support for struct tags and environment variables-based argument values.

Installation

$ go get github.com/mna/mainer

Description

The code documentation is the canonical source for documentation.

A typical main entrypoint looks like this, where the cmd struct could be implemented in a distinct package so the main package is minimal (just the main function at the end) and the command implementation is easily testable:

type cmd struct {
  Help    bool   `flag:"h,help"`
  Version bool   `flag:"v,version"`
  FooBar  string `flag:"foo-bar" env:"FOO_BAR"`
}

func (c *cmd) Validate() error {
  // the struct may implement a Validate method, and if it does the
  // Parser will call it once the flags are stored in the fields.
  return nil
}

func (c *cmd) Main(args []string, stdio mainer.Stdio) mainer.ExitCode {
  // parse the flags, using env var <CMD>_FOO_BAR for the --foo-bar
  // flag if the flag is not set (where <CMD> defaults to the base name
  // of the executable, in uppercase and without extension).
  p := &mainer.Parser{EnvVars: true}
  if err := p.Parse(args, c); err != nil {
    fmt.Fprintln(stdio.Stderr, err)
    return mainer.InvalidArgs
  }

  // execute the command...
  return mainer.Success
}

func main() {
  var c cmd
  os.Exit(int(c.Main(os.Args, mainer.CurrentStdio())))
}

Breaking changes

v0.3
  • Requires Go 1.19+.
  • Use github.com/caarlos0/env/v6 as environment-variable parsing package instead of github.com/kelseyhightower/envconfig (envconfig is a great package but env/v6 aligns better with the flags behaviour and has a more consistent struct tag usage).
  • SetFlags now reports set flags using a canonical flag name (the first flag defined on the field). Which of the various flag aliases was used should not matter (if it does, define distinct fields instead).
  • More data types are supported for flags (not a breaking change per se, but can mean that a flag struct tag that would've failed on older versions would now be valid).

License

The BSD 3-Clause license.

Documentation

Overview

Package mainer defines types relevant to command entrypoint implementation. It includes the Stdio struct that abstracts the I/O and working directory of a command, and the Parser struct that implements a simple flag parser with support for struct tags and environment variables-based argument values.

A typical main entrypoint looks like this, where the cmd struct could be implemented in a distinct package so the main package is minimal (just the main function at the end):

 type cmd struct {
   Help    bool   `flag:"h,help"`
   Version bool   `flag:"v,version"`
   FooBar  string `flag:"foo-bar" env:"FOO_BAR"`
 }

 func (c *cmd) Validate() error {
   // the struct may implement a Validate method, and if it does the
   // Parser will call it once the flags are stored in the fields.
   return nil
 }

 func (c *cmd) Main(args []string, stdio mainer.Stdio) mainer.ExitCode {
   // parse the flags, using env var <CMD>_FOO_BAR for the --foo-bar
   // flag if the flag is not set (where <CMD> defaults to the base name
   // of the executable, in uppercase and without extension).
	 p := &mainer.Parser{EnvVars: true}
	 if err := p.Parse(args, c); err != nil {
	 	 fmt.Fprintln(stdio.Stderr, err)
	 	 return mainer.InvalidArgs
	 }

   // execute the command...
   return mainer.Success
 }

 func main() {
   var c cmd
   os.Exit(int(c.Main(os.Args, mainer.CurrentStdio())))
 }

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CancelOnSignal

func CancelOnSignal(ctx context.Context, signals ...os.Signal) context.Context

CancelOnSignal returns a context that is canceled when the process receives one of the specified signals.

Types

type ExitCode

type ExitCode int

ExitCode is the type of a process exit code.

const (
	Success ExitCode = iota
	Failure
	InvalidArgs
)

List of pre-defined exit codes.

type Mainer

type Mainer interface {
	Main([]string, Stdio) ExitCode
}

Mainer defines the method to implement for a type that implements a Main entrypoint of a command.

type Parser

type Parser struct {
	// EnvVars indicates if environment variables are used to read flag values.
	EnvVars bool

	// EnvPrefix is the prefix to use in front of each flag's environment
	// variable name. If it is empty, the name of the program (as read from the
	// args slice at index 0) is used, all uppercase and with dashes replaced
	// with underscores. Set it to "-" to disable any prefix.
	EnvPrefix string
}

Parser implements a command-line flags parser that uses struct tags to configure supported flags and returns any error it encounters, without printing anything automatically. It can optionally read flag values from environment variables first, with the command-line flags used to override them.

The struct tag to specify flags is `flag`, while the one to specify environment variables is `env`. See the env/v6 package for full details on struct tags configuration and decoding support: https://github.com/caarlos0/env.

Flag parsing uses the stdlib's flag package internally, and as such shares the same behaviour regarding short and long flags. However, it does support mixing order of flag arguments and non-flag ones.

func (*Parser) Parse

func (p *Parser) Parse(args []string, v interface{}) error

Parse parses args into v, using struct tags to detect flags. Note that the args slice should start with the program name (as is the case for `os.Args`, which is typically used). The tag must be named "flag" and multiple flags may be set for the same field using a comma-separated list.

v must be a pointer to a struct and the flags must be defined on exported fields with one of those types:

  • string
  • int/int64
  • uint/uint64
  • float64
  • bool
  • time.Duration
  • a type that directly implements encoding.TextMarshaler/TextUnmarshaler (both interfaces must be satisfied), or a type T that implements those interfaces on *T (a pointer to the type)
  • a slice of any of those types

For slices, by default a new value is appended each time the flag is encountered. This behaviour can be altered by adding a "flagSeparator" struct tag to the field, in addition to the "flag" one, e.g.:

type S struct {
  Name []string `flag:"name" flagSeparator:","`
}

This causes the field to be filled with a single flag value being set, and that value is split on the provided separator.

If Parser.EnvVars is true, flag values are initialized from corresponding environment variables first, as defined by the github.com/caarlos0/env/v6 package (which is used for environment parsing).

Flags and arguments can be interspersed, but flag parsing stops if it encounters the "--" value; all subsequent values are treated as arguments.

After parsing, if v implements a Validate method that returns an error, it is called and any non-nil error is returned as error.

If v has a SetArgs([]string) method, it is called with the list of non-flag arguments (a slice of strings) that respects the provided order.

If v has a SetFlags(map[string]bool) method, it is called with the set of flags that were explicitly set by args (a map[string]bool). Note that if a field can be set with multiple flags, the key is canonicalized to the first flag defined on the field.

If v has a SetFlagsCount(map[string]int) method, it is called with the map of flags that were explicitly set by args, and the associated value is the number of times the flag was provided. As for SetFlags, the key is canonicalized to the first flag defined on the field.

Environment variables parsing has no effect on the values reported by SetFlags and SetFlagsCount, only the actual flags parsed from the args.

It panics if v is not a pointer to a struct or if a flag is defined with an unsupported type.

type Stdio

type Stdio struct {
	// Cwd is the current working directory.
	Cwd string

	// Stdin is the standard input reader.
	Stdin io.Reader

	// Stdout is the standard output writer.
	Stdout io.Writer

	// Stderr is the standard error writer.
	Stderr io.Writer
}

Stdio defines the OS abstraction for standard I/O.

func CurrentStdio

func CurrentStdio() Stdio

CurrentStdio returns the Stdio for the current process. Its Cwd field reflects the working directory at the time of the call.

Jump to

Keyboard shortcuts

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