Skip to content

Commit

Permalink
Added documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesse van den Kieboom committed Aug 31, 2012
1 parent 526f310 commit cfc3da2
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 31 deletions.
42 changes: 42 additions & 0 deletions flags.go
Original file line number Diff line number Diff line change
@@ -1 +1,43 @@
// Package flags provides an extensive command line option parser.
// The flags package is similar in functionality to the go builtin flag package
// but provides more options and uses reflection to provide a convenient and
// succinct way of specifying command line options.
//
// Supported features:
// Options with short names (-v)
// Options with long names (--verbose)
// Options with and without arguments (bool v.s. other type)
// Options with optional arguments and default values
// Multiple option groups each containing a set of options
// Generate and print well-formatted help message
// Passing remaining command line arguments after -- (optional)
// Ignoring unknown command line options (optional)
// Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
// Supports multiple short options -aux
// Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
// Supports same option multiple times (can store in slice or last option counts)
//
// The flags package uses structs, reflection and struct field tags
// to allow users to specify command line options. This results in very simple
// and consise specification of your application options. For example:
//
// type Options struct {
// Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"
// }
//
// This specifies one option with a short name -v and a long name --verbose.
// When either -v or --verbose is found on the command line, a 'true' value
// will be appended to the Verbose field. e.g. when specifying -vvv, the
// resulting value of Verbose will be {[true, true, true]}.
//
// Available field tags:
// short: the short name of the option (single character)
// long: the long name of the option
// description: the description of the option (optional)
// optional: whether an argument of the option is optional (optional)
// default: the default argument value if the option occurs without
// an argument (optional)
//
// Either short: or long: must be specified to make the field eligible as an
// option.
package flags
73 changes: 59 additions & 14 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,71 @@ import (
"unicode/utf8"
)

var _ = fmt.Printf

var ErrNotPointer = errors.New("provided data is not a pointer")
// The provided container is not a pointer to a struct
var ErrNotPointerToStruct = errors.New("provided data is not a pointer to struct")

// The provided short name is longer than a single character
var ErrShortNameTooLong = errors.New("short names can only be 1 character")

// Option flag information. Contains a description of the option, short and
// long name as well as a default value and whether an argument for this
// flag is optional.
type Info struct {
// The short name of the option (a single character). If not 0, the
// option flag can be 'activated' using -<ShortName>. Either ShortName
// or LongName needs to be non-empty.
ShortName rune

// The long name of the option. If not "", the option flag can be
// activated using --<LongName>. Either ShortName or LongName needs
// to be non-empty.
LongName string

// The description of the option flag. This description is shown
// automatically in the builtin help.
Description string

// The default value of the option. The default value is used when
// the option flag is marked as having an OptionalArgument. This means
// that when the flag is specified, but no option argument is given,
// the value of the field this option represents will be set to
// Default. This is only valid for non-boolean options.
Default string

// If true, specifies that the argument to an option flag is optional.
// When no argument to the flag is specified on the command line, the
// value of Default will be set in the field this option represents.
// This is only valid for non-boolean options.
OptionalArgument bool

Value reflect.Value
Options reflect.StructTag
value reflect.Value
options reflect.StructTag
}

// An option group. The option group has a name and a set of options.
type Group struct {
// The name of the group.
Name string

// A map of long names to option info descriptions.
LongNames map[string]*Info

// A map of short names to option info descriptions.
ShortNames map[rune]*Info

// A list of all the options in the group.
Options []*Info

Data interface{}
// An error which occurred when creating the group.
Error error

data interface{}
}

// IsBool determines if the option is a boolean type option. Boolean type
// options include options of type bool and []bool
func (info *Info) IsBool() bool {
tp := info.Value.Type()
tp := info.value.Type()

switch tp.Kind() {
case reflect.Bool:
Expand All @@ -47,10 +83,14 @@ func (info *Info) IsBool() bool {
return false
}

// Set the value of an option to the specified value. An error will be returned
// if the specified value could not be converted to the corresponding option
// value type.
func (info *Info) Set(value string) error {
return convert(value, info.Value, info.Options)
return convert(value, info.value, info.options)
}

// Convert an option to a human friendly readable string describing the option.
func (info *Info) String() string {
var s string
var short string
Expand All @@ -76,12 +116,17 @@ func (info *Info) String() string {
return s
}

func NewGroup(name string, s interface{}) *Group {
// NewGroup creates a new option group with a given name and underlying data
// container. The data container is a pointer to a struct. The fields of the
// struct represent the command line options (using field tags) and their values
// will be set when their corresponding options appear in the command line
// arguments.
func NewGroup(name string, data interface{}) *Group {
ret := &Group{
Name: name,
LongNames: make(map[string]*Info),
ShortNames: make(map[rune]*Info),
Data: s,
data: data,
}

ret.Error = ret.scan()
Expand All @@ -90,10 +135,10 @@ func NewGroup(name string, s interface{}) *Group {

func (g *Group) scan() error {
// Get all the public fields in the data struct
ptrval := reflect.ValueOf(g.Data)
ptrval := reflect.ValueOf(g.data)

if ptrval.Type().Kind() != reflect.Ptr {
return ErrNotPointer
return ErrNotPointerToStruct
}

stype := ptrval.Type().Elem()
Expand Down Expand Up @@ -149,8 +194,8 @@ func (g *Group) scan() error {
LongName: longname,
Default: def,
OptionalArgument: optional,
Value: realval.Field(i),
Options: field.Tag,
value: realval.Field(i),
options: field.Tag,
}

g.Options = append(g.Options, info)
Expand Down
2 changes: 2 additions & 0 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func (p *Parser) showHelpOption(writer *bufio.Writer, info *Info, maxlen int, ha
writer.WriteString("\n")
}

// ShowHelp writes a help message containing all the possible options and
// their descriptions to the provided writer.
func (p *Parser) ShowHelp(writer io.Writer) {
if writer == nil {
return
Expand Down
105 changes: 88 additions & 17 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,73 @@ import (
"unicode/utf8"
)

// Expected an argument but got none
var ErrExpectedArgument = errors.New("expected option argument")

// Unknown option flag was found
var ErrUnknownFlag = errors.New("unknown flag")

// The builtin help message was printed
var ErrHelp = errors.New("help shown")

// An argument for a boolean value was specified
var ErrNoArgumentForBool = errors.New("bool flags cannot have arguments")

type Help struct {
type help struct {
writer io.Writer
IsHelp bool `long:"help" short:"h" description:"Show help options"`
}

// A Parser provides command line option parsing. It can contain several
// option groups each with their own set of options.
//
// Example:
// type Options struct {
// Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"
// }
//
// opts := new(Options)
//
// parser := flags.NewParser("testapp")
// parser.AddHelp(os.Stderr)
//
// parser.AddGroup(flags.NewGroup("Application Options", opts))
//
// args, err := parser.Parser(os.Args[1:])
//
// if err != nil {
// if err != flags.ErrHelp {
// parser.PrintError(os.Stderr)
// }
//
// os.Exit(1)
// }
type Parser struct {
// The option groups available to the parser
Groups []*Group

// The parser application name
ApplicationName string

// The usage (e.g. [OPTIONS] <filename>)
Usage string

// If true, all arguments after a double dash (--) will be passed
// as remaining command line arguments. Defaults to false.
PassDoubleDash bool

// If true, unknown command line arguments are ignored and passed as
// remaining command line arguments. Defaults to false.
IgnoreUnknown bool

Help *Help
ErrorAt interface{}
help *help
errorAt interface{}
}

// NewParser creates a new parser. The appname is used to display the
// executable name in the builtin help message. An initial set of option groups
// can be specified when constructing a parser, but you can also add additional
// option groups later (see Parser.AddGroup).
func NewParser(appname string, groups ...*Group) *Parser {
return &Parser{
ApplicationName: appname,
Expand All @@ -39,24 +85,34 @@ func NewParser(appname string, groups ...*Group) *Parser {
}
}

func (p *Parser) AddGroup(groups ...*Group) {
// AddGroup adds one or more option groups to the parser. It returns the parser
// itself again so multiple calls can be chained.
func (p *Parser) AddGroup(groups ...*Group) *Parser {
p.Groups = append(p.Groups, groups...)
return p
}

func (p *Parser) AddHelp(writer io.Writer) {
if p.Help == nil {
p.Help = &Help{
// AddHelp adds a common help option group to the parser. The help option group
// contains only one option, with short name -h and long name --help.
// When either -h or --help appears, a help message listing all the options
// will be written to the specified writer. It returns the parser itself
// again so multiple calls can be chained.
func (p *Parser) AddHelp(writer io.Writer) *Parser {
if p.help == nil {
p.help = &help{
writer: writer,
}

p.AddGroup(NewGroup("Help Options", p.Help))
p.AddGroup(NewGroup("Help Options", p.help))
}

return p
}

func (p *Parser) parseOption(group *Group, args []string, name string, info *Info, canarg bool, argument *string, index int) (error, int) {
if info.IsBool() {
if canarg && argument != nil {
p.ErrorAt = info
p.errorAt = info
return ErrNoArgumentForBool, index
}

Expand All @@ -71,7 +127,7 @@ func (p *Parser) parseOption(group *Group, args []string, name string, info *Inf
} else if info.OptionalArgument {
info.Set(info.Default)
} else {
p.ErrorAt = info
p.errorAt = info
return ErrExpectedArgument, index
}

Expand All @@ -85,7 +141,7 @@ func (p *Parser) parseLong(args []string, name string, argument *string, index i
}
}

p.ErrorAt = fmt.Sprintf("--%v", name)
p.errorAt = fmt.Sprintf("--%v", name)
return ErrUnknownFlag, index
}

Expand All @@ -109,22 +165,37 @@ func (p *Parser) parseShort(args []string, name rune, islast bool, argument *str

if info != nil {
if !info.IsBool() && !islast && !info.OptionalArgument {
p.ErrorAt = info
p.errorAt = info
return ErrExpectedArgument, index
}

return p.parseOption(grp, args, string(names), info, islast, argument, index)
}

p.ErrorAt = fmt.Sprintf("-%v", string(names))
p.errorAt = fmt.Sprintf("-%v", string(names))
return ErrUnknownFlag, index
}

// PrintError prints an error which occurred while parsing command line
// arguments. This is more useful than simply printing the error message
// because context information on where the error occurred will also be
// written. The error is printed to writer (commonly os.Stderr).
func (p *Parser) PrintError(writer io.Writer, err error) {
s := fmt.Sprintf("Error at `%v': %s\n", p.ErrorAt, err)
s := fmt.Sprintf("Error at `%v': %s\n", p.errorAt, err)
writer.Write([]byte(s))
}

// Parse parses the command line arguments according to the option groups that
// were added to the parser. On successful parsing of the arguments, the
// remaining, non-option, arguments (if any) are returned. The returned error
// indicates a parsing error and can be used with PrintError to display
// contextual information on where the error occurred exactly.
//
// When the common help group has been added (AddHelp) and either -h or --help
// was specified in the command line arguments, a help message will be
// automatically printed. Furthermore, the special error ErrHelp is returned
// to indicate that the help was shown. It is up to the caller to exit the
// program if so desired.
func (p *Parser) Parse(args []string) ([]string, error) {
ret := make([]string, 0, len(args))
i := 0
Expand Down Expand Up @@ -197,9 +268,9 @@ func (p *Parser) Parse(args []string) ([]string, error) {
}
}

if p.Help != nil && p.Help.IsHelp {
p.ShowHelp(p.Help.writer)
p.ErrorAt = "--help"
if p.help != nil && p.help.IsHelp {
p.ShowHelp(p.help.writer)
p.errorAt = "--help"

return ret, ErrHelp
}
Expand Down

0 comments on commit cfc3da2

Please sign in to comment.