subcmd

package module
v1.0.4 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2021 License: MIT Imports: 7 Imported by: 1

README

Subcmd - command-line interfaces with subcommands and flags

Go Reference Go Report Card Tests Coverage Status

This is subcmd, a Go package for writing command-line programs that require flag parsing and that have “subcommands” that also require flag parsing.

Use it when you want your program to parse command lines that look like this:

command -globalopt subcommand -subopt1 FOO -subopt2 ARG1 ARG2

Subcommands may have sub-subcommands and so on.

This is a layer on top of the standard Go flag package.

Usage

func main() {
  // Parse global flags normally.
  dbname := flag.String("db", "", "database connection string")
  flag.Parse()

  db, err := sql.Open(dbdriver, *dbname)
  if err != nil { ... }

  // Stash global options in a top-level command object.
  c := command{db: db}

  // Run the subcommand given in the remainder of the command line.
  err = subcmd.Run(context.Background(), c, flag.Args())
  if err != nil { ... }
}

// The top-level command object.
type command struct {
  db *sql.DB
}

// To be used in subcmd.Run above, `command` must implement this method.
func (c command) Subcmds() subcmd.Map {
  return subcmd.Commands(
    // The "list" subcommand takes one flag, -reverse.
    "list", c.list, subcmd.Params(
      "reverse", subcmd.Bool, false, "reverse order of list",
    ),

    // The "add" subcommand takes no flags.
    "add", c.add, nil,
  )
}

// Implementation of the "list" subcommand.
// The value of the -reverse flag is passed as an argument.
func (c command) list(ctx context.Context, reverse bool, _ []string) error {
  query := "SELECT name FROM employees ORDER BY name"
  if reverse {
    query += " DESC"
  }
  rows, err := c.db.QueryContext(ctx, query)
  if err != nil { ... }
  defer rows.Close()
  for rows.Next() { ... }
  return rows.Err()
}

// Implementation of the "add" subcommand.
func (c command) add(ctx context.Context, args []string) error {
  if len(args) != 1 { ...usage error... }
  _, err := c.db.ExecContext(ctx, "INSERT INTO employees (name) VALUES ($1)", args[0])
  return err
}

Documentation

Overview

Package subcmd provides types and functions for creating command-line interfaces with subcommands and flags.

Example
package main

import (
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"strconv"

	"github.com/bobg/subcmd"
)

func main() {
	// First, parse global flags normally.
	var (
		verbose = flag.Bool("verbose", false, "be verbose")
		config  = flag.String("config", ".config", "path to config file")
	)
	flag.Parse()

	confData, err := ioutil.ReadFile(*config)
	if err != nil {
		panic(err)
	}

	// Stash the relevant info into an object that implements the subcmd.Cmd interface.
	cmd := command{
		conf:    string(confData),
		verbose: *verbose,
	}

	// Pass that object to subcmd.Run,
	// which will parse a subcommand and its flags
	// from the remaining command-line arguments
	// and run them.
	err = subcmd.Run(context.Background(), cmd, flag.Args())
	if err != nil {
		panic(err)
	}
}

// Type command implements the subcmd.Cmd interface,
// meaning that it can report its subcommands,
// their names,
// and their parameters and types
// via the Subcmds method.
type command struct {
	conf    string
	verbose bool
}

func (c command) Subcmds() subcmd.Map {
	// There are two subcommands:
	// hello, which takes -name and -spanish flags,
	// and add, which takes no flags.
	return subcmd.Commands(
		"hello", hello, subcmd.Params(
			"name", subcmd.String, "", "name to greet",
			"spanish", subcmd.Bool, false, "greet in Spanish",
		),
		"add", c.add, nil,
	)
}

// The implementation of a subcommand takes a context object,
// the values of its parsed flags,
// and a slice of remaining command-line arguments
// (which could be used in another call to subcmd.Run
// to implement a sub-subcommand).
// It can optionally return an error.
func hello(_ context.Context, name string, spanish bool, _ []string) {
	if spanish {
		fmt.Print("Hola")
	} else {
		fmt.Print("Hello")
	}
	if name != "" {
		fmt.Printf(" %s", name)
	}
	fmt.Print("\n")
}

func (c command) add(_ context.Context, args []string) error {
	if c.verbose {
		fmt.Printf("adding %d value(s)\n", len(args))
	}
	var result float64
	for _, arg := range args {
		aval, err := strconv.ParseFloat(arg, 64)
		if err != nil {
			return err
		}
		result += aval
	}
	fmt.Println(result)
	return nil
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoArgs is the error when Run is called with an empty list of args.
	ErrNoArgs = errors.New("no arguments")

	// ErrUnknown is the error when Run is called with an unknown subcommand as args[0].
	ErrUnknown = errors.New("unknown subcommand")
)

Functions

func FlagSet

func FlagSet(ctx context.Context) *flag.FlagSet

FlagSet produces the *flag.FlagSet used in a call to a Subcmd function.

func Run

func Run(ctx context.Context, c Cmd, args []string) error

Run runs the subcommand of c named in args[0]. The remaining args are parsed with a new flag.FlagSet, populated according to the parameters of the named Subcmd. The Subcmd's function is invoked with a context object, the parameter values parsed by the FlagSet, and a slice of the args left over after FlagSet parsing. The FlagSet is placed in the context object that's passed to the Subcmd's function, and can be retrieved if needed with the FlagSet function. No FlagSet is present if the subcommand takes no parameters.

Types

type Cmd

type Cmd interface {
	// Subcmds returns this Cmd's subcommands as a map,
	// whose keys are subcommand names and values are Subcmd objects.
	// Implementations may use the Commands function to build this map.
	Subcmds() Map
}

Cmd is the way a command tells Run how to parse and run its subcommands.

type Map

type Map = map[string]Subcmd

Map is the type of the data structure returned by Cmd.Subcmds and by Commands. It maps a subcommand name to its Subcmd structure.

func Commands

func Commands(args ...interface{}) Map

Commands is a convenience function for producing the map needed by a Cmd. It takes 3n arguments, where n is the number of subcommands. Each group of three is: - the subcommand name, a string; - the function implementing the subcommand; - the list of parameters for the function, a slice of Param (which can be produced with Params).

A call like this:

Commands(
  "foo", foo, Params(
    "verbose", Bool, false, "be verbose",
  ),
  "bar", bar, Params(
    "level", Int, 0, "barness level",
  ),
)

is equivalent to:

 Map{
   "foo": Subcmd{
     F: foo,
     Params: []Param{
       {
         Name: "verbose",
         Type: Bool,
         Default: false,
         Doc: "be verbose",
       },
     },
   },
   "bar": Subcmd{
     F: bar,
     Params: []Param{
       {
         Name: "level",
         Type: Int,
         Default: 0,
         Doc: "barness level",
       },
     },
   },
}

This function panics if the number or types of the arguments are wrong.

type Param

type Param struct {
	// Name is the flag name for the parameter
	// (e.g., "verbose" for a -verbose flag).
	Name string

	// Type is the type of the parameter.
	Type Type

	// Default is a default value for the parameter.
	// Its type must be suitable for Type.
	Default interface{}

	// Doc is a docstring for the parameter.
	Doc string
}

Param is one parameter of a Subcmd.

func Params

func Params(a ...interface{}) []Param

Params is a convenience function for producing the list of parameters needed by a Subcmd. It takes 4n arguments, where n is the number of parameters. Each group of four is: - the flag name for the parameter, a string (e.g. "verbose" for a -verbose flag); - the type of the parameter, a Type constant; - the default value of the parameter, - the doc string for the parameter.

This function panics if the number or types of the arguments are wrong.

type Subcmd

type Subcmd struct {
	// F is the function implementing the subcommand.
	// Its signature must be func(context.Context, ..., []string) error,
	// where the number and types of parameters between the context and the string slice
	// is given by Params.
	// The error return is optional.
	F interface{}

	// Params describes the parameters to F
	// (excluding the initial context.Context that F takes, and the final []string).
	Params []Param
}

Subcmd is one subcommand of a Cmd.

type Type

type Type int

Type is the type of a Param.

const (
	Bool Type = iota + 1
	Int
	Int64
	Uint
	Uint64
	String
	Float64
	Duration
)

Possible Param types. These correspond with the types in the standard flag package.

Jump to

Keyboard shortcuts

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