A flexible command and option parser for Go

Build Status Coverage Report Card GoDoc

Writ

Overview

Writ is a flexible option parser with thorough test coverage. It's meant to be simple and "just work". Applications using writ look and behave similar to common GNU command-line applications, making them comfortable for end-users.

Writ implements option decoding with GNU getopt_long conventions. All long and short-form option variations are supported: --with-x, --name Sam, --day=Friday, -i FILE, -vvv, etc.

Help output generation is supported using text/template. The default template can be overriden with a custom template.

API Promise

Minor breaking changes may occur prior to the 1.0 release. After the 1.0 release, the API is guaranteed to remain backwards compatible.

Basic Use

Please see the godocs for additional information.

This example uses writ.New() to build a command from the Greeter's struct fields. The resulting *writ.Command decodes and updates the Greeter's fields in-place. The Command.ExitHelp() method is used to display help content if --help is specified, or if invalid input arguments are received.

Source:

package main

import (
    "fmt"
    "github.com/bobziuchkovski/writ"
    "strings"
)

type Greeter struct {
    HelpFlag  bool   `flag:"help" description:"Display this help message and exit"`
    Verbosity int    `flag:"v, verbose" description:"Display verbose output"`
    Name      string `option:"n, name" default:"Everyone" description:"The person or people to greet"`
}

func main() {
    greeter := &Greeter{}
    cmd := writ.New("greeter", greeter)
    cmd.Help.Usage = "Usage: greeter [OPTION]... MESSAGE"
    cmd.Help.Header = "Greet users, displaying MESSAGE"

    // Use cmd.Decode(os.Args[1:]) in a real application
    _, positional, err := cmd.Decode([]string{"-vvv", "--name", "Sam", "How's it going?"})
    if err != nil || greeter.HelpFlag {
        cmd.ExitHelp(err)
    }

    message := strings.Join(positional, " ")
    fmt.Printf("Hi %s! %s\n", greeter.Name, message)
    if greeter.Verbosity > 0 {
        fmt.Printf("I'm feeling re%slly chatty today!\n", strings.Repeat("a", greeter.Verbosity))
    }

    // Output:
    // Hi Sam! How's it going?
    // I'm feeling reaaally chatty today!
}

Help output:

Usage: greeter [OPTION]... MESSAGE
Greet users, displaying MESSAGE

Available Options:
  --help                    Display this help message and exit
  -v, --verbose             Display verbose output
  -n, --name=ARG            The person or people to greet

Subcommands

Please see the godocs for additional information.

This example demonstrates subcommands in a busybox style. There's no requirement that subcommands implement the Run() method shown here. It's just an example of how subcommands might be implemented.

Source:

package main

import (
    "errors"
    "github.com/bobziuchkovski/writ"
    "os"
)

type GoBox struct {
    Link Link `command:"ln" alias:"link" description:"Create a soft or hard link"`
    List List `command:"ls" alias:"list" description:"List directory contents"`
}

type Link struct {
    HelpFlag bool `flag:"h, help" description:"Display this message and exit"`
    Symlink  bool `flag:"s" description:"Create a symlink instead of a hard link"`
}

type List struct {
    HelpFlag   bool `flag:"h, help" description:"Display this message and exit"`
    LongFormat bool `flag:"l" description:"Use long-format output"`
}

func (g *GoBox) Run(p writ.Path, positional []string) {
    // The user didn't specify a subcommand.  Give them help.
    p.Last().ExitHelp(errors.New("COMMAND is required"))
}

func (l *Link) Run(p writ.Path, positional []string) {
    if l.HelpFlag {
        p.Last().ExitHelp(nil)
    }
    if len(positional) != 2 {
        p.Last().ExitHelp(errors.New("ln requires two arguments, OLD and NEW"))
    }
    // Link operation omitted for brevity.  This would be os.Link or os.Symlink
    // based on the l.Symlink value.
}

func (l *List) Run(p writ.Path, positional []string) {
    if l.HelpFlag {
        p.Last().ExitHelp(nil)
    }
    // Listing operation omitted for brevity.  This would be a call to ioutil.ReadDir
    // followed by conditional formatting based on the l.LongFormat value.
}

func main() {
    gobox := &GoBox{}
    cmd := writ.New("gobox", gobox)
    cmd.Help.Usage = "Usage: gobox COMMAND [OPTION]... [ARG]..."
    cmd.Subcommand("ln").Help.Usage = "Usage: gobox ln [-s] OLD NEW"
    cmd.Subcommand("ls").Help.Usage = "Usage: gobox ls [-l] [PATH]..."

    path, positional, err := cmd.Decode(os.Args[1:])
    if err != nil {
        // Using path.Last() here ensures the user sees relevant help for their
        // command selection
        path.Last().ExitHelp(err)
    }

    // At this point, cmd.Decode() has already decoded option values into the gobox
    // struct, including subcommand values.  We just need to dispatch the command.
    // path.String() is guaranteed to represent the user command selection.
    switch path.String() {
    case "gobox":
        gobox.Run(path, positional)
    case "gobox ln":
        gobox.Link.Run(path, positional)
    case "gobox ls":
        gobox.List.Run(path, positional)
    default:
        panic("BUG: Someone added a new command and forgot to add it's path here")
    }
}

Help output, gobox:

Usage: gobox COMMAND [OPTION]... [ARG]...

Available Commands:
  ln                        Create a soft or hard link
  ls                        List directory contents

Help output, gobox ln:

Usage: gobox ln [-s] OLD NEW

Available Options:
  -h, --help                Display this message and exit
  -s                        Create a symlink instead of a hard link

Help output, gobox ls:

Usage: gobox ls [-l] [PATH]...

Available Options:
  -h, --help                Display this message and exit
  -l                        Use long-format output

Explicit Commands and Options

Please see the godocs for additional information.

This example demonstrates explicit Command and Option creation, along with explicit option grouping. It checks the host platform and dynamically adds a --bootloader option if the example is run on Linux. The same result could be achieved by using writ.New() to construct a Command, and then adding the platform-specific option to the resulting Command directly.

Source:

package main

import (
    "github.com/bobziuchkovski/writ"
    "os"
    "runtime"
)

type Config struct {
    help       bool
    verbosity  int
    bootloader string
}

func main() {
    config := &Config{}
    cmd := &writ.Command{Name: "explicit"}
    cmd.Help.Usage = "Usage: explicit [OPTION]... [ARG]..."
    cmd.Options = []*writ.Option{
        {
            Names:       []string{"h", "help"},
            Description: "Display this help text and exit",
            Decoder:     writ.NewFlagDecoder(&config.help),
            Flag:        true,
        },
        {
            Names:       []string{"v"},
            Description: "Increase verbosity; may be specified more than once",
            Decoder:     writ.NewFlagAccumulator(&config.verbosity),
            Flag:        true,
            Plural:      true,
        },
    }

    // Note the explicit option grouping.  Using writ.New(), a single option group is
    // created for all options/flags that have descriptions.  Without writ.New(), we
    // need to create the OptionGroup(s) ourselves.
    general := cmd.GroupOptions("help", "v")
    general.Header = "General Options:"
    cmd.Help.OptionGroups = append(cmd.Help.OptionGroups, general)

    // Dynamically add --bootloader on Linux
    if runtime.GOOS == "linux" {
        cmd.Options = append(cmd.Options, &writ.Option{
            Names:       []string{"bootloader"},
            Description: "Use the specified bootloader (grub, grub2, or lilo)",
            Decoder:     writ.NewOptionDecoder(&config.bootloader),
            Placeholder: "NAME",
        })
        platform := cmd.GroupOptions("bootloader")
        platform.Header = "Platform Options:"
        cmd.Help.OptionGroups = append(cmd.Help.OptionGroups, platform)
    }

    // Decode the options
    _, _, err := cmd.Decode(os.Args[1:])
    if err != nil || config.help {
        cmd.ExitHelp(err)
    }
}

Help output, Linux:

General Options:
  -h, --help                Display this help text and exit
  -v                        Increase verbosity; may be specified more than once

Platform Options:
  --bootloader=NAME         Use the specified bootloader (grub, grub2, or lilo)

Help output, other platforms:

General Options:
  -h, --help                Display this help text and exit
  -v                        Increase verbosity; may be specified more than once

Authors

Bob Ziuchkovski (@bobziuchkovski)

License (MIT)

Copyright (c) 2016 Bob Ziuchkovski

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Owner
Comments
  • Option.Default field?

    Option.Default field?

    Has an Option.Default field been considered? I prefer to write options without struct tags (since struct tags aren't checked by the compiler, leading to my issue yesterday), but right now you need to write the following:

        {
            Names:       []string{"count"},
            Description: "The (maximum) number of logs to request.",
            Placeholder: "<num>",
            Decoder:     writ.NewDefaulter(writ.NewOptionDecoder(config.Count), "1"),
        },
    

    This isn't bad, but it:

    • Limits you to strings - if the field is an int64 or other time (say, for Unix timestamps) then you can't set a default.
    • A Default field would be more explicit and would better map against the existing struct tag names.
  • get filenames for io.Reader/io.Writer

    get filenames for io.Reader/io.Writer

    Is it possible to get the filenames for io.Reader and io.Writer options? I like the automatic handling of them, but still prefer to sometimes get to the filenames (for error messages and the like)

  • Placeholder Silently Fails for non-string Options

    Placeholder Silently Fails for non-string Options

    Setting a placeholder:"<some-thing>" as a struct tag for types other thanstring` causes the option parsing to silently fail and omit that option:

    type Config struct {
        # Included
        Start     int64  `option:"start-time" description:"The timestamp (in Unix seconds) to request logs from."`
        # Omitted (i.e. not parsed, not included in help text)
        End       int64  `option:"end-time" placeholder="<unix-timestamp>" description:"The timestamp (in Unix seconds) to request logs to."`
        # Included  Count     int    `option:"c, count" default:"1" description:"The number (count) of logs to retrieve."`
    }
    

    Is this intended? Can be useful to describe formatting.

  • Error message for subcommand is wrong

    Error message for subcommand is wrong

    $ cat test.go

    package main
    
    import (
            "os"
    
            "github.com/bobziuchkovski/writ"
    )
    
    type Foo struct {
            Bar Bar `command:"bar"`
    }
    
    type Bar struct {
            HelpFlag bool `flag:"h, help"`
    }
    
    func main() {
            foo := &Foo{}
            cmd := writ.New("foo", foo)
            path, _, err := cmd.Decode(os.Args[1:])
            if err != nil {
                    path.Last().ExitHelp(err)
            }
    }
    

    $ go run test.go bar --help --help

    Usage: foo bar [OPTION]... [ARG]...
    
    Error: option "bar" specified too many times
    exit status 1
    

    I believe the error message should be option "help" specified too many times

  • Support Embedded Structs for Commands

    Support Embedded Structs for Commands

    This allows you to have a common set of options/flags across sub-commands - e.g.

    type Common struct {
        FileName string
        Headers string
        ...
    }
    
    type Fetch struct {
        Common
        From string
    }
    
    type Restore struct {
        Common
        To []string
    }
    

    The structs here are simple, but should illustrate the use-case. The embedded struct does not seem to be parsed.

  • Expose default values as a

    Expose default values as a "Default" field on Options

    @elithrar provided some great feedback on the default value handling. After some thought, I've put together a PR that decouples the OptionDefaulter and OptionDecoder interfaces. With this change, Options would have a separate Default field. As a result, string defaults can be assigned via opt.Default = writ.StringDefault("value"), and a ChainedDefault can be used to chain together defaults from a []Defaulter slice, returning the first non-empty default. This is how environment defaults are stacked with regular defaults with this PR.

    If I merge this, I'll target the merge for the v1.0 release. I think I'll probably collect breaking changes and merge them to a separate 1.0-wip branch.

  • Overridable flag

    Overridable flag

    E.g. if -a and -b are both specified then the last one specified will be used.

    A workaround might be adding --mode option and specify --mode=a or --mode=b.

    I'd propose to extend/change the OptionDecoder interface, where both option name and value are passed to the Decode function.

Fully featured Go (golang) command line option parser with built-in auto-completion support.

go-getoptions Go option parser inspired on the flexibility of Perl’s GetOpt::Long. Table of Contents Quick overview Examples Simple script Program wit

Dec 14, 2022
A command line parser written in Go

go-cmdline Introduction cmdline is a Go library to parse command line options (with optional default values), arguments and subcommands. Usage The fol

Oct 9, 2022
A command-line arguments parser that will make you smile.

docopt-go An implementation of docopt in the Go programming language. docopt helps you create beautiful command-line interfaces easily: package main

Jan 7, 2023
A Go library for implementing command-line interfaces.

Go CLI Library cli is a library for implementing powerful command-line interfaces in Go. cli is the library that powers the CLI for Packer, Serf, Cons

Jan 1, 2023
go command line option parser

go-flags: a go library for parsing command line arguments This library provides similar functionality to the builtin flag library of go, but provides

Jan 4, 2023
Fully featured Go (golang) command line option parser with built-in auto-completion support.

go-getoptions Go option parser inspired on the flexibility of Perl’s GetOpt::Long. Table of Contents Quick overview Examples Simple script Program wit

Dec 14, 2022
go command line option parser

go-flags: a go library for parsing command line arguments This library provides similar functionality to the builtin flag library of go, but provides

Dec 22, 2022
Fully featured Go (golang) command line option parser with built-in auto-completion support.

go-getoptions Go option parser inspired on the flexibility of Perl’s GetOpt::Long. Table of Contents Quick overview Examples Simple script Program wit

Dec 14, 2022
Flag is a simple but powerful command line option parsing library for Go support infinite level subcommand

Flag Flag is a simple but powerful commandline flag parsing library for Go. Documentation Documentation can be found at Godoc Supported features bool

Sep 26, 2022
Go-timeparser - Flexible Time Parser for Golang

go-timeparser Flexible Time Parser for Golang Installation Download timeparser w

Dec 29, 2022
Brigodier is a command parser & dispatcher, designed and developed for command lines such as for Discord bots or Minecraft chat commands. It is a complete port from Mojang's "brigadier" into Go.

brigodier Brigodier is a command parser & dispatcher, designed and developed to provide a simple and flexible command framework. It can be used in man

Dec 15, 2022
Concurrent ssh-tail sessions and sink option
Concurrent ssh-tail sessions and sink option

ssh-tail This project is one of the problems that I generally face while debugging some system. When I am reproducing the issue on the machine i also

Dec 2, 2022
`runenv` create gcloud run deploy `--set-env-vars=` option and export shell environment from yaml file.

runenv runenv create gcloud run deploy --set-env-vars= option and export shell environment from yaml file. Motivation I want to manage Cloud Run envir

Feb 10, 2022
Golang ergonomic declarative generics module inspired by Rust, including Result, Option, and more.

gust Golang ergonomic declarative generics module inspired by Rust. Go Version go≥1.18 Features Result Avoid if err != nil, handle result with chain m

Jan 7, 2023
Maybe is a Go package to provide basic functionality for Option type structures

Maybe Maybe is a library that adds an Option data type for some native Go types. What does it offer: The types exported by this library are immutable

Oct 4, 2022
Key-value database stored in memory with option of persistence
Key-value database stored in memory with option of persistence

Easy and intuitive command line tool allows you to spin up a database avaliable from web or locally in a few seconds. Server can be run over a custom TCP protocol or over HTTP.

Aug 1, 2022
A very simple library for interactively selecting an option on a terminal
A very simple library for interactively selecting an option on a terminal

go-choice A very simple library for interactively selecting an option on a terminal Usage package main import ( "fmt" "github.com/TwiN/go-ch

Dec 30, 2021
Option container for golang

Option Option provides an Option container which can be used to force some addit

Dec 21, 2021
Package create provides a generic option pattern for creating new values of any type

create Package create provides a generic option pattern for creating new values

Dec 30, 2021
Option: a busy optional parameter for golang

Option option is a busy optional parameter init opt := option.New() set opt.Set("parameter name", 3.141592) apply a := 0.0 opt.Apply("parameter name",

Dec 28, 2021