Giter Site home page Giter Site logo

opts's Introduction

logo
A Go (golang) package for building frictionless command-line interfaces

GoDoc CI


Creating command-line interfaces should be simple:

package main

import (
	"log"

	"github.com/jpillora/opts"
)

func main() {
	type config struct {
		File  string `opts:"help=file to load"`
		Lines int    `opts:"help=number of lines to show"`
	}
	c := config{}
	opts.Parse(&c)
	log.Printf("%+v", c)
}
$ go build -o my-prog
$ ./my-prog --help

  Usage: my-prog [options]

  Options:
  --file, -f   file to load
  --lines, -l  number of lines to show
  --help, -h   display help
$ ./my-prog -f foo.txt -l 42
{File:foo.txt Lines:42}

Try it out https://play.golang.org/p/D0jWFwmxRgt

Features (with examples)

  • Easy to use (eg-helloworld)
  • Promotes separation of CLI code and library code (eg-app)
  • Automatically generated --help text via struct tags (eg-help)
  • Default values by modifying the struct prior to Parse() (eg-defaults)
  • Default values from a JSON config file, unmarshalled via your config struct (eg-config)
  • Default values from environment, defined by your field names (eg-env)
  • Repeated flags using slices (eg-repeated-flag)
  • Group your flags in the help output (eg-groups)
  • Sub-commands by nesting structs (eg-commands-inline)
  • Sub-commands by providing child Opts (eg-commands-main)
  • Infers program name from executable name
  • Infers command names from struct or package name
  • Define custom flags types via opts.Setter or flag.Value (eg-custom-flag)
  • Customizable help text by modifying the default templates (eg-help)
  • Built-in shell auto-completion (eg-complete)

Find these examples and more in the opts-examples repository.

Package API

See https://godoc.org/github.com/jpillora/opts#Opts

GoDoc

Struct Tag API

opts tries to set sane defaults so, for the most part, you'll get the desired behaviour by simply providing a configuration struct.

However, you can customise this behaviour by providing the opts struct tag with a series of settings in the form of key=value:

`opts:"key=value,key=value,..."`

Where key must be one of:

  • - (dash) - Like json:"-", the dash character will cause opts to ignore the struct field. Unexported fields are always ignored.

  • name - Name is used to display the field in the help text. By default, the flag name is infered by converting the struct field name to lowercase and adding dashes between words.

  • help - The help text used to summaryribe the field. It will be displayed next to the flag name in the help output.

    Note: help can also be set as a stand-alone struct tag (i.e. help:"my text goes here"). You must use the stand-alone tag if you wish to use a comma , in your help string.

  • mode - The opts mode assigned to the field. All fields will be given a mode. Where the mode value must be one of:

    • flag - The field will be treated as a flag: an optional, named, configurable field. Set using ./program --<flag-name> <flag-value>. The struct field must be a flag-value type. flag is the default mode for any flag-value.

    • arg - The field will be treated as an argument: a required, positional, unamed, configurable field. Set using ./program <argument-value>. The struct field must be a flag-value type.

    • embedded - A special mode which causes the fields of struct to be used in the current struct. Useful if you want to split your command-line options across multiple files (default for struct fields). The struct field must be a struct. embedded is the default mode for a struct. Tip You can play group all fields together placing an group tag on the struct field.

    • cmd - A inline command, shorthand for .AddCommmand(opts.New(&field)), which also implies the struct field must be a struct.

    • cmdname - A special mode which will assume the name of the selected command. The struct field must be a string.

  • short - One letter to be used a flag's "short" name. By default, the first letter of name will be used. It will remain unset if there is a duplicate short name or if opts:"short=-". Only valid when mode is flag.

  • group - The name of the flag group to store the field. Defining this field will create a new group of flags in the help text (will appear as "<group> options"). The default flag group is the empty string (which will appear as "Options"). Only valid when mode is flag or embedded.

  • env - An environent variable to use as the field's default value. It can always be overridden by providing the appropriate flag. Only valid when mode is flag.

    For example, opts:"env=FOO". It can also be infered using the field name with simply opts:"env". You can enable inference on all flags with the opts.Opts method UseEnv().

  • min max - A minimum or maximum length of a slice. Only valid when mode is arg, and the struct field is a slice.

flag-values:

In general an opts flag-value type aims to be any type that can be get and set using a string. Currently, opts supports the following types:

  • string
  • bool
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • opts.Setter
    • The interface func Set(string) error
  • flag.Value
    • Is an opts.Setter
  • time.Duration
  • encoding.TextUnmarshaler
    • Includes time.Time and net.IP
  • encoding.BinaryUnmarshaler
    • Includes url.URL

In addition, flags and args can also be a slice of any flag-value type. Slices allow multiple flags/args. For example, a struct field flag Foo []int could be set with --foo 1 --foo 2, and would result in []int{1,2}.

Help text

By default, opts attempts to output well-formatted help text when the user provides the --help (-h) flag. The examples repositories shows various combinations of this default help text, resulting from using various features above.

Modifications be made by customising the underlying Go templates found here DefaultTemplates.

Talk

I gave a talk on opts at the Go Meetup Sydney (golang-syd) on the 23rd of May, 2019. You can find the slides here https://github.com/jpillora/opts-talk.

Other projects

Other related projects which infer flags from struct tags but aren't as feature-complete:

MIT License

Copyright © 2019 <[email protected]>

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.

opts's People

Contributors

dolmen avatar gmwxio avatar jpillora avatar millergarym avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

opts's Issues

API change - added Call - Tag required - v1.1.0

Would be really nice if Go code could be auto checked for major and minor version suggestions.
Git hooks (pre-commit, pre-receive) would be a nice place to invoke these.

Without the tag it is difficult to spec this in go.mod.

Having option flags after arguments

  Usage: kv put [options] <key> <value>

The options cannot be places after the key and/or value, but must go first. Would be nice if that is more flexible, eg kv put myval 20 --base hex or kv put myval --base hex 20. Usage template does not have to be updated.
(and related to another issue, kv --base hex put myval 20 would also be a big plus).

As user of such a cli, I would like to be more free. Eg, when using some option flags i might want to just copy some part of the command and just add/edit the last bit (eg the value to put) in an easy way or a quick modifier at the end (ls -la folder -h)

Discussion about Add Command signature

Discussion about Add Command signature

Current
This issue is it doesn't make for a very fluent interface.

type Opts interface {
       ....
	//subcommands
	AddCommand(Opts) Opts
       ...
}
	bar := Bar{}
	b := opts.New(&bar).Name("bar")
	foo := Foo{}
	f := opts.New(&foo).Name("foo").AddCommand(b)

Alternative 1
currently playing with the interface - implementation is possible

type Opts interface {
       ....
	//subcommands
	AddCommand(name string, cmd interface{}) NextOpts
        Parent() Opts
       ...
}
type NextOpts interface {
   Current() Opts
   New() Opts
}

leading to usage

func Register(ro opts.Opts) opts.Opts {
	wh := &webhooks{}
	who := ro.AddCommand("webhooks", wh).Current().
		AddCommand("subscriptions", &subscriptions{}).New().
		/**/ AddCommand("get", &getSubs{}).Current().
		/**/ AddCommand("post", &postSubs{}).Current().
		Parent().
		AddCommand("serve", &listen{}).Current().
		AddCommand("settings", &settings{}).New().
		/**/ AddCommand("get", &settingsGet{}).Current().
		/**/ AddCommand("post", &settingsPost{}).Current().
		Parent()
	return who
}

Alternative 2
This is the one I am currently using and has informed 1

type Opts interface {
       ....
        //subcommands
        // adds sub cmd and return current
	AddSubCmd(name string, cmd Config) Opts
        // adds sub cmd and returns new sub cmd
	SubCmd(name string, cmd Config) Opts
        // get sub command by name
	GetSubCmd(name string) Opts
        // get parent
	Parent() Opts
       ...
}

Lands up with client looking like

func Register(ro opts.Opts) opts.Opts {
	wh := &webhooks{}
	who := ro.SubCmd("webhooks", wh).
		SubCmd("subscriptions", &subscriptions{}).
		AddSubCmd("get", &getSubs{}).
		AddSubCmd("post", &postSubs{}).
		Parent().AddSubCmd("serve", &listen{}).
		SubCmd("settings", &settings{}).
		AddSubCmd("get", &settingsGet{}).
		AddSubCmd("post", &settingsPost{})
	return who
}
//or
func Register2(ro opts.Opts) opts.Opts {
	wh := &webhooks{}
	who := ro.SubCmd("webhooks", wh).
		SubCmd("subscriptions", &subscriptions{}).
		SubCmd("get", &getSubs{}).Parent().
		SubCmd("post", &postSubs{}).Parent().Parent().
		SubCmd("serve", &listen{}).Parent().
		SubCmd("settings", &settings{}).
		SubCmd("get", &settingsGet{}).Parent().
		SubCmd("post", &settingsPost{}).Parent().Parent()
	return who
}
//or
// using AddSubCmd().Get ...

Runner Runner

Should the runner interfaces be

A.

type runner1 interface {
	Run() error
}

type runner2 interface {
	Run()
}

Or

B.

type runner1 interface {
	Run() error
}

type runner2 interface {
	Run(args []string) error
}

It is possible to argue that B. is not needed as the client can always call os.Args(), but this implies the client need to parser / ignore parent args. This complicates the client and might make custom completion quite difficult.

After having typed this, I think the answer might be C.. It complicates the library (not all that much) and simplifies the clients.
C.

type runner1 interface {
	Run() error
}
type runner1b interface {
	Run()
}
type runner2 interface {
	Run(args []string) error
}
type runner2b interface {
	Run(args []string) error
}

Customer 1st FTW.
or is it
WTF over complicating things :-)

Inheriting flags with nested opts

Given some nested opts, with a flag I want to be able to use in all sub opts:

type Config struct {
	Foo  `opts:"mode=cmd,help=This text also becomes commands summary text"`
	*Bar `opts:"mode=cmd,help=command two of two"`
	Baz  string
}
type Foo struct {}
type Bar struct {}

This could be done by embedding, eg

type Config struct {
	Foo  `opts:"mode=cmd,help=This text also becomes commands summary text"`
	*Bar `opts:"mode=cmd,help=command two of two"`
	Baz
}
type Baz struct {
	Baz  string
}
type Foo struct {
	Baz
}
type Bar struct {
	Baz
}

However, if setting a default, this needs to be done for all subcommands.

Also, something like mycmd --baz 2000 foo should be equal to mycmd foo --baz 2000

Set default flag when default tag is set and no value present

I noticed that when defining something like the following:

	type Config struct {
		Foo string `opts:"default=bar"`
	}

The default value is not respected when parsing an empty argument e.g. /my/bin without the --foo bar will not produce Config{Foo: "bar"}. I think it would be nicer if this package would set the default value of bar when the flag is not present in the args. What do you think?

If there is interest in implementing this, I already have a draft PR ready which I wrote for my own personal use. I'd be happy to upstream it here.

goopts: Create struct from existing help

The help from Cobra based CLI command is pretty standardised.

This feature request is that goopts parsers this help and creates a struct (and maybe a proxy) to existing CLIs.

eg docker run --help | goopts parser_cobra_help > my_docker_run.go

Parse() calls os.Exit(0) in case of parse error

Hello,

I guess this is a design choice, but I would like the opposite, is there a way?

  • Parse() exits with 1 in case of parse error related to argument (this I like)
  • Parse() exits with 0 in case of parse error related to options (this I would like to change)

I would like:

  • Parse() to always exit with a non-zero exit code in case of parse error.
  • Even better I would like Parse() (or another function) not to exit behind my back and return a classic Go tuple with an error.

Rationale: any UNIX/POSIX command-line utility exits with non-zero in case of user calling it incorrectly.

Example with cp on macOS:

> cp a b ; echo $status
cp: a: No such file or directory
1
> cp ; echo $status
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file
       cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... target_directory
64
> cp -x; echo $status
cp: illegal option -- x
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file
       cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... target_directory
64

What are my options with opts ? Would you consider a PR ? Thanks!

Sort commands in help output

Currently, commands are printed from a map so they are not sorted, they should be in insertion/alphabetical order

env vars should have the program name as prefix

Hello,

thanks for opts!

I think that when generating environment variable names (either when using opts:"env" without the =FOO part or when using UseEnv(), the environment variables should always have a prefix.

The default prefix should be the auto-detected program name (as already used in the --help output), and maybe (but personally I am not convinced about this) with a way to explicitly set it to what the user wants.

The rationale for requiring the prefix is to avoid unexpected errors caused by common env var names that can be present in the enviroment. Names such as MAX_FOO, MIN_FOO, FILE, EDITOR, HOST, PORT, USER, PASSWORD, ... are way to easy to find around. It is too dangerous to allow to pick them up. This problem already happened in other Go packages (cannot find the reference right now).

This would be a breaking change, and in my personal opinion this would be fine, it is for the greater good :-)

Override config file option value with option parameter

The "Features with examples" sections includes:

Default values from a JSON config file, unmarshalled via your config struct

It refers to eg-config, which shows the program returns the config file default value, even when the command-line option specifies a different value.

$ config --bar moon
hello
world

Is this a bug or a feature?

If I store a static value 'world' for bar in config.json, but want to override it for this one-time execution on the command line, with --bar moon, I expect to see moon, not world.

I only expect to see world if I don't specify any option, which means the value of bar should be its default one, read from the config.json file.

Do I interpret that incorrectly?
Should I expect to override on command-line, with options, a default value read from a configuration file?

UseEnv() should not apply to auto-generated flags such as --help

Hello,

UseEnv() is quite handy. On the other hand, I would suggest that it should skip autogenerated flags, such as --help (probably the only auto-generated flag).

Rationale: setting an env var for --help doesn't make sense and is quite confusing:

  Usage: hello [options]

  Options:
  --file, -f   file to load (env FILE)
  --lines, -l  number of lines to show (env LINES)
  --help, -h   display help (env HELP)

I understand that the workaround is simply not to use useEnv() and to explicitly add to the single fields opts:"env" :-)

SubOpts

Jaime,

Was playing with a SubOpts interface.
Go's type system is pretty $#!??y when is comes to expressing this.
So landed up with SubXxxx methods.

type Opts interface {
	 ...
	AddCommand(SubOpts) Opts
	...
}

type SubOpts interface {
	//Name of the command. For the root command, Name defaults to the executable's
	//base name. For subcommands, Name defaults to the package name, unless its the
	//main package, then it defaults to the struct name.
	SubName(name string) SubOpts
	//Summary adds an arbitrarily long string to below the usage text
	SubSummary(summary string) SubOpts
	//AddCommand adds another Opts instance as a subcommand.
	SubAddCommand(SubOpts) SubOpts
}

See
master...millergarym:subopts_interface#diff-0cd986ed6644b9d8bc249897c3cb2fbcR86
This commit also has examples and opts-cli added.

Low-level parser API

Some features, like JSON config, would be nice to do a double sweep - first sweep to grab the JSON file path, use the file to define opts defaults, then another sweep to execute the current opts parse.

This is a low level API would replace usage of standard library flags.

No type parsing at this layer, just strings. Here's the API I was thinking:

// Parse given args and return Results in parse-order
func Parse(args []string) ([]Result, error)
func ParseWith(args []string, options Options) ([]Result, error)

type Options struct {
  Flags []string //explicit white-list flags
  Values []string //explicit white-list values
  MultiShort bool //enable multi-short parsing
  SingleDash bool //enable flags with a single dash (go default, but non-standard)
  // other parsing styles...
}

type Result struct {
  Name string
  Value string
}

func (r Result) IsFlag() bool {
  return r.Name != "" && r.Value == ""
}

func (r Result) IsValue() bool {
  return r.Name != "" && r.Value != ""
}

func (r Result) IsArg() bool {
  return r.Name == "" && r.Value != ""
}
  • given ps -aux (multi-shorts) or ps -a -u -x (normal shorts)
    • results [{"value": "ps"}, {"name":"a"}, {"name":"u"}, {"name":"x"}]
  • given cmd --foo --bar (normal flags)
    • results [{"name":"foo"}, {"name":"foo"}]
  • given cmd --foo 42 (normal value) or cmd --foo=42 (force value)
    • results [{"name":"foo", "value": "42"}]

flag: *customtype - unsupported type

type Config struct {
	Zing *opts.RepeatedStringOpt
// instead of 
// Zing opts.RepeatedStringOpt
}

leads to

./eg-complete --zing a
opts usage error: Flagset error: [opts] Option 'zing' has unsupported type

Completion not implemented.

The simple example in example/cmdscomplete doesn't seem to do anything.

func main() {

	bar := Bar{}
	b := opts.New(&bar).Name("bar")

	foo := Foo{}
	f := opts.New(&foo).Name("foo").AddCommand(b)

	config := Config{}
	opts.New(&config).
		Name("root").
		AddCommand(f).
		Complete().
		Parse().
		RunFatal()
}

The above example code made me thing of what would it mean if one added completion to a subcommand. I realised that AddCommand returns the parent, but what if someone wrote;

	ro := opts.New(&root{}).
		AddCommand(opts.New(&foo{}).Name("foo").Complete())
	ro.Parse().RunFatal()

Should this be an error, if so when.
If it isn't an error is there come sensible way of configuring completions to handle it, does the completions library cater for this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.