GithubHelp home page GithubHelp logo

go-arg's Introduction

go-arg
go-arg

Struct-based argument parsing for Go

Sourcegraph Documentation Build Status Coverage Status Go Report Card


Declare command line arguments for your program by defining a struct.

var args struct {
	Foo string
	Bar bool
}
arg.MustParse(&args)
fmt.Println(args.Foo, args.Bar)
$ ./example --foo=hello --bar
hello true

Installation

go get github.com/alexflint/go-arg

Required arguments

var args struct {
	ID      int `arg:"required"`
	Timeout time.Duration
}
arg.MustParse(&args)
$ ./example
Usage: example --id ID [--timeout TIMEOUT]
error: --id is required

Positional arguments

var args struct {
	Input   string   `arg:"positional"`
	Output  []string `arg:"positional"`
}
arg.MustParse(&args)
fmt.Println("Input:", args.Input)
fmt.Println("Output:", args.Output)
$ ./example src.txt x.out y.out z.out
Input: src.txt
Output: [x.out y.out z.out]

Environment variables

var args struct {
	Workers int `arg:"env"`
}
arg.MustParse(&args)
fmt.Println("Workers:", args.Workers)
$ WORKERS=4 ./example
Workers: 4
$ WORKERS=4 ./example --workers=6
Workers: 6

You can also override the name of the environment variable:

var args struct {
	Workers int `arg:"env:NUM_WORKERS"`
}
arg.MustParse(&args)
fmt.Println("Workers:", args.Workers)
$ NUM_WORKERS=4 ./example
Workers: 4

You can provide multiple values using the CSV (RFC 4180) format:

var args struct {
    Workers []int `arg:"env"`
}
arg.MustParse(&args)
fmt.Println("Workers:", args.Workers)
$ WORKERS='1,99' ./example
Workers: [1 99]

You can also have an environment variable that doesn't match the arg name:

var args struct {
	Workers int `arg:"--count,env:NUM_WORKERS"`
}
arg.MustParse(&args)
fmt.Println("Workers:", args.Workers)
$ NUM_WORKERS=6 ./example
Workers: 6
$ NUM_WORKERS=6 ./example --count 4
Workers: 4

Usage strings

var args struct {
	Input    string   `arg:"positional"`
	Output   []string `arg:"positional"`
	Verbose  bool     `arg:"-v,--verbose" help:"verbosity level"`
	Dataset  string   `help:"dataset to use"`
	Optimize int      `arg:"-O" help:"optimization level"`
}
arg.MustParse(&args)
$ ./example -h
Usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]]

Positional arguments:
  INPUT
  OUTPUT

Options:
  --verbose, -v            verbosity level
  --dataset DATASET        dataset to use
  --optimize OPTIMIZE, -O OPTIMIZE
                           optimization level
  --help, -h               print this help message

Default values

var args struct {
	Foo string `default:"abc"`
	Bar bool
}
arg.MustParse(&args)

Default values (before v1.2)

var args struct {
	Foo string
	Bar bool
}
arg.Foo = "abc"
arg.MustParse(&args)

Combining command line options, environment variables, and default values

You can combine command line arguments, environment variables, and default values. Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an env tag was provided), then if none is found, we check for a default tag containing a default value.

var args struct {
    Test  string `arg:"-t,env:TEST" default:"something"`
}
arg.MustParse(&args)

Ignoring environment variables and/or default values

The values in an existing structure can be kept in-tact by ignoring environment variables and/or default values.

var args struct {
    Test  string `arg:"-t,env:TEST" default:"something"`
}

p, err := arg.NewParser(arg.Config{
    IgnoreEnv: true,
    IgnoreDefault: true,
}, &args)

err = p.Parse(os.Args)

Arguments with multiple values

var args struct {
	Database string
	IDs      []int64
}
arg.MustParse(&args)
fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs)
./example -database foo -ids 1 2 3
Fetching the following IDs from foo: [1 2 3]

Arguments that can be specified multiple times, mixed with positionals

var args struct {
    Commands  []string `arg:"-c,separate"`
    Files     []string `arg:"-f,separate"`
    Databases []string `arg:"positional"`
}
arg.MustParse(&args)
./example -c cmd1 db1 -f file1 db2 -c cmd2 -f file2 -f file3 db3 -c cmd3
Commands: [cmd1 cmd2 cmd3]
Files [file1 file2 file3]
Databases [db1 db2 db3]

Arguments with keys and values

var args struct {
	UserIDs map[string]int
}
arg.MustParse(&args)
fmt.Println(args.UserIDs)
./example --userids john=123 mary=456
map[john:123 mary:456]

Custom validation

var args struct {
	Foo string
	Bar string
}
p := arg.MustParse(&args)
if args.Foo == "" && args.Bar == "" {
	p.Fail("you must provide either --foo or --bar")
}
./example
Usage: samples [--foo FOO] [--bar BAR]
error: you must provide either --foo or --bar

Version strings

type args struct {
	...
}

func (args) Version() string {
	return "someprogram 4.3.0"
}

func main() {
	var args args
	arg.MustParse(&args)
}
$ ./example --version
someprogram 4.3.0

Note If a --version flag is defined in args or any subcommand, it overrides the built-in versioning.

Overriding option names

var args struct {
	Short        string `arg:"-s"`
	Long         string `arg:"--custom-long-option"`
	ShortAndLong string `arg:"-x,--my-option"`
	OnlyShort    string `arg:"-o,--"`
}
arg.MustParse(&args)
$ ./example --help
Usage: example [-o ONLYSHORT] [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION]

Options:
  --short SHORT, -s SHORT
  --custom-long-option CUSTOM-LONG-OPTION
  --my-option MY-OPTION, -x MY-OPTION
  -o ONLYSHORT
  --help, -h             display this help and exit

Embedded structs

The fields of embedded structs are treated just like regular fields:

type DatabaseOptions struct {
	Host     string
	Username string
	Password string
}

type LogOptions struct {
	LogFile string
	Verbose bool
}

func main() {
	var args struct {
		DatabaseOptions
		LogOptions
	}
	arg.MustParse(&args)
}

As usual, any field tagged with arg:"-" is ignored.

Supported types

The following types may be used as arguments:

  • built-in integer types: int, int8, int16, int32, int64, byte, rune
  • built-in floating point types: float32, float64
  • strings
  • booleans
  • URLs represented as url.URL
  • time durations represented as time.Duration
  • email addresses represented as mail.Address
  • MAC addresses represented as net.HardwareAddr
  • pointers to any of the above
  • slices of any of the above
  • maps using any of the above as keys and values
  • any type that implements encoding.TextUnmarshaler

Custom parsing

Implement encoding.TextUnmarshaler to define your own parsing logic.

// Accepts command line arguments of the form "head.tail"
type NameDotName struct {
	Head, Tail string
}

func (n *NameDotName) UnmarshalText(b []byte) error {
	s := string(b)
	pos := strings.Index(s, ".")
	if pos == -1 {
		return fmt.Errorf("missing period in %s", s)
	}
	n.Head = s[:pos]
	n.Tail = s[pos+1:]
	return nil
}

func main() {
	var args struct {
		Name NameDotName
	}
	arg.MustParse(&args)
	fmt.Printf("%#v\n", args.Name)
}
$ ./example --name=foo.bar
main.NameDotName{Head:"foo", Tail:"bar"}

$ ./example --name=oops
Usage: example [--name NAME]
error: error processing --name: missing period in "oops"

Custom parsing with default values

Implement encoding.TextMarshaler to define your own default value strings:

// Accepts command line arguments of the form "head.tail"
type NameDotName struct {
	Head, Tail string
}

func (n *NameDotName) UnmarshalText(b []byte) error {
	// same as previous example
}

// this is only needed if you want to display a default value in the usage string
func (n *NameDotName) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)), nil
}

func main() {
	var args struct {
		Name NameDotName `default:"file.txt"`
	}
	arg.MustParse(&args)
	fmt.Printf("%#v\n", args.Name)
}
$ ./example --help
Usage: test [--name NAME]

Options:
  --name NAME [default: file.txt]
  --help, -h             display this help and exit

$ ./example
main.NameDotName{Head:"file", Tail:"txt"}

Custom placeholders

Introduced in version 1.3.0

Use the placeholder tag to control which placeholder text is used in the usage text.

var args struct {
	Input    string   `arg:"positional" placeholder:"SRC"`
	Output   []string `arg:"positional" placeholder:"DST"`
	Optimize int      `arg:"-O" help:"optimization level" placeholder:"LEVEL"`
	MaxJobs  int      `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"`
}
arg.MustParse(&args)
$ ./example -h
Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]

Positional arguments:
  SRC
  DST

Options:
  --optimize LEVEL, -O LEVEL
                         optimization level
  --maxjobs N, -j N      maximum number of simultaneous jobs
  --help, -h             display this help and exit

Description strings

A descriptive message can be added at the top of the help text by implementing a Description function that returns a string.

type args struct {
	Foo string
}

func (args) Description() string {
	return "this program does this and that"
}

func main() {
	var args args
	arg.MustParse(&args)
}
$ ./example -h
this program does this and that
Usage: example [--foo FOO]

Options:
  --foo FOO
  --help, -h             display this help and exit

Similarly an epilogue can be added at the end of the help text by implementing the Epilogue function.

type args struct {
	Foo string
}

func (args) Epilogue() string {
	return "For more information visit github.com/alexflint/go-arg"
}

func main() {
	var args args
	arg.MustParse(&args)
}
$ ./example -h
Usage: example [--foo FOO]

Options:
  --foo FOO
  --help, -h             display this help and exit

For more information visit github.com/alexflint/go-arg

Subcommands

Introduced in version 1.1.0

Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the git tool:

$ git checkout [arguments specific to checking out code]
$ git commit [arguments specific to committing]
$ git push [arguments specific to pushing]

The strings "checkout", "commit", and "push" are different from simple positional arguments because the options available to the user change depending on which subcommand they choose.

This can be implemented with go-arg as follows:

type CheckoutCmd struct {
	Branch string `arg:"positional"`
	Track  bool   `arg:"-t"`
}
type CommitCmd struct {
	All     bool   `arg:"-a"`
	Message string `arg:"-m"`
}
type PushCmd struct {
	Remote      string `arg:"positional"`
	Branch      string `arg:"positional"`
	SetUpstream bool   `arg:"-u"`
}
var args struct {
	Checkout *CheckoutCmd `arg:"subcommand:checkout"`
	Commit   *CommitCmd   `arg:"subcommand:commit"`
	Push     *PushCmd     `arg:"subcommand:push"`
	Quiet    bool         `arg:"-q"` // this flag is global to all subcommands
}

arg.MustParse(&args)

switch {
case args.Checkout != nil:
	fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch)
case args.Commit != nil:
	fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message)
case args.Push != nil:
	fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote)
}

Some additional rules apply when working with subcommands:

  • The subcommand tag can only be used with fields that are pointers to structs
  • Any struct that contains a subcommand must not contain any positionals

This package allows to have a program that accepts subcommands, but also does something else when no subcommands are specified. If on the other hand you want the program to terminate when no subcommands are specified, the recommended way is:

p := arg.MustParse(&args)
if p.Subcommand() == nil {
    p.Fail("missing subcommand")
}

API Documentation

https://godoc.org/github.com/alexflint/go-arg

Rationale

There are many command line argument parsing libraries for Go, including one in the standard library, so why build another?

The flag library that ships in the standard library seems awkward to me. Positional arguments must precede options, so ./prog x --foo=1 does what you expect but ./prog --foo=1 x does not. It also does not allow arguments to have both long (--foo) and short (-f) forms.

Many third-party argument parsing libraries are great for writing sophisticated command line interfaces, but feel to me like overkill for a simple script with a few flags.

The idea behind go-arg is that Go already has an excellent way to describe data structures using structs, so there is no need to develop additional levels of abstraction. Instead of one API to specify which arguments your program accepts, and then another API to get the values of those arguments, go-arg replaces both with a single struct.

Backward compatibility notes

Earlier versions of this library required the help text to be part of the arg tag. This is still supported but is now deprecated. Instead, you should use a separate help tag, described above, which removes most of the limits on the text you can write. In particular, you will need to use the new help tag if your help text includes any commas.

go-arg's People

Contributors

alexflint avatar andrew-morozko avatar brettlangdon avatar daenney avatar dallbee avatar dependabot[bot] avatar dmzkrsk avatar evgenv123 avatar greyxor avatar hhromic avatar iljan avatar illia-v avatar k3a avatar kenshaw avatar leozhantw avatar marco-m avatar mnsmar avatar mwlazlo-tls avatar padilo avatar pborzenkov avatar purpleidea avatar sebastiaanpasterkamp avatar testwill avatar walle 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-arg's Issues

Default value vs required arg

go-arg supports Default values. Required arguments are supported, too.

Here is a slightly modified example:

package main

import "github.com/alexflint/go-arg"

var args struct {
    Foo string `arg:"required"`
    Bar bool
}

func main() {
    args.Foo = "default value"
    arg.MustParse(&args)
}

Is:

usage: Build main.go and rungo --foo FOO [--bar]
error: --foo is required

Should be: As "foo" is set, I would expect the arguments to be parsed.

similar to subcommand, I would like to have subarg

Here is the idea...

type MainOpts struct {
	Port    int `arg:"--port"`
	Inner InnerOpts `arg:"subarg:inner"`
}
type InnerOpts struct {
	Enabled bool `arg:"enabled"`
}

should become

--port
--inner-enabled

Don't know whether it is already supported but this nicely fits in for nested structs.
It's similar to custom parsing for values but for arg names.

Feature request: optionally toggle environment variable handling on/off

As discussed on #94, please consider adding the option to toggle environment variables support in this package. It could be useful to only evaluate flags, disregarding any environment variables that are set. This would allow parsing them with another package in order to change the configuration precedence.

Thank you in advance for your consideration.

Issue in Struct Parsing with Duplicate Property Names in Substructs

There appears to be an issue when it parses structs in the normal way, for example:

type Config struct {
	Environment string `arg:"env:GO_ENV"`
	ServerConfig
         DBConfig
 }

type ServerConfig struct {
	Host    string `arg:"env:SERVER_HOST"`
	Port    string `arg:"env:SERVER_PORT"`
	Version string `arg:"env:SERVER_VERSION"`
}
type DBConfig struct {
	Host    string `arg:"env:DB_HOST"`
}

In this instance it returns back an error:

panic: reflect: call of reflect.Value.Type on zero Value

From doing some testing it appears to be when two of the nested structs have one of the same property names

Not able to declare defaults for sub-command args

There doesn't seem to be a way to set defaults on argument fields declared within a subcommand, such as the following code. Pre-populating the subcommand pointers defeats the switch cases after parsing args.

type ServerCmd struct {
	Bind string
}

type ClientCmd struct {
	ServerAddress string
}

var args struct {
	Server *ServerCmd `arg:"subcommand:server"`
	Client *ClientCmd `arg:"subcommand:client"`
}

func main() {
	// if defaults are set like this
	args.Server = &ServerCmd{Bind:":7676"}
	args.Client = &ClientCmd{ServerAddress:"127.0.0.1:7676"}
	arg.MustParse(&args)

	switch {
	case args.Client != nil:
		// then this case always gets selected
		fmt.Println("Running client command")

	case args.Server != nil:
		// and never this
		fmt.Println("Running server command")
	}
}

Adding a default field tag, as mentioned in #68, could be an elegant way to solve this.

Is there support for determining whether an option was set?

As a disclaimer, I'm new to Go and may just be overlooking obvious.

Short version

Is it possible to determine whether a user set an environment variable/flag?

From spf13/cobra#23:

Is there a way that I can see if a user set a flag, vs. just used the default?

Desired behavior:

  1. If no config file is specified, use default flag value.
  2. If config file is specified, and the user did not set the flag, use the value in the config file
  3. If config file is specified, but the user did set the flag, use the value specified by the user, even if it's just the default flag value.

I'm trying to avoid the situation where a non-default is set in the config, but the user wants to force the program to use the default.

I have a hacky solution that involves parsing the flags twice using different defaults, is there a better approach?

Details

I recently began using the pelletier/go-toml package to support providing configuration options via a file in addition to the env vars/flags support that this package provides.

I first removed the default struct tag from the config struct since pelletier/go-toml supports that same struct tag name and my hope was that I would learn how to "feather" multiple configuration sources together in order to create a composite configuration struct of user-specified and default settings. I moved the job of applying default settings back to a basic constructor that I'm using to setup a default config object.

I then "merge" in a file config struct, overwriting default values in the destination struct with non-default values from the incoming/source struct. This seemed to make sense during the initial implementation, but after a time I came to realize (even being new) that this was not the ideal approach; using this approach still allowed for invalid values to creep in:

  1. the default constructor gives sane initial values to a new config struct (i.e., "base" struct)
  2. create config structs to hold config file settings and env vars/flag settings
  • now three config structs
  1. the config file introduces a non-default value to its copy of the config struct
  2. the args package is provided a default value via flag and the user assumes that their explicit choice will override settings from the config file
  3. config struct merging takes place which checks for non-default values in the "incoming" struct and copies them into the "base" struct, overwriting any values there
  4. when the env vars/flags config struct is evaluated, the explicit value that happens to be the initial default value is examined, and then discarded
  5. the values specified in the config file end up overriding explicit default values chosen via env vars/command-line

In the end, it looks like I need to check to see if the user specified a value explicitly and use that, likely continuing to apply at least light validation to discovered values prior to assigning it to the config struct.

Sub-command support

This issue is a feature discussion

I really enjoy this project. The API is extremely simple and the help message is detailed. In fact, I think go-arg is the most elegant choice in this list.

However, go-arg doesn't support sub-command, which is a common feature used by many command line tools (much common than nested flags). If go-arg supports it, I think go-args could cover 80%~90% use cases of a command line tool.

Many third-party argument parsing libraries are geared for writing sophisticated command line interfaces. The excellent codegangsta/cli is perfect for working with multiple sub-commands and nested flags, but is probably overkill for a simple script with a handful of flags.

Thanks again for such an awesome project!

Misleading "Usage:" when slice option and positional arguments?

Hello, thanks a lot for go-arg, I love it!

consider the following program (using go-arg v1.1.0)

package main

import (
	"fmt"
	arg "github.com/alexflint/go-arg"
)

func main() {
	var args struct {
		Foo []string
		A   string `arg:"positional,required"`
	}
	arg.MustParse(&args)
	fmt.Printf("%+v\n", args)
}

Let's ask for its help:

$ ./bin/positional -h
Usage: positional [--foo FOO] A

Positional arguments:
  A

Options:
  --foo FOO
  --help, -h             display this help and exit

If I try to follow the "Usage" example, I get:

$ ./bin/positional --foo 1 x
Usage: positional [--foo FOO] A
error: a is required

which is very confusing for the end user (and at first for the programmer too, then the programmer understands the reason :-D)

After thinking about it, I tried the classic -- to mean end of flag processing, but it didn't work:

 $ ./bin/positional --foo 1 -- x
Usage: positional [--foo FOO] A
error: a is required

After a while I saw the light and inverted the order, and it worked as expected:

$ ./bin/positional x --foo 1
{Foo:[1] A:x}

So I guess I have two questions:

  1. Would it be possible/suitable to make the -- trick work? (Is this maybe a regression of #55 ?)
  2. Would it make sense, in presence of a required positional argument AND a slice of options, to invert the usage, something like:
Usage: positional A [--foo FOO]

or any other way to give an hint to the user?

Is the "default" label provided for flag help text meant to show "stock" defaults or provided flag values?

I'm new to Go, so my apologies if I'm off-base and have an obvious user error here.


I've configured a configuration option to have some default values via the "old way" (in reference to #91) and when not providing command-line flag values the [default: VALUE] labels are showing as expected.

However, when I use a method to validate the received flag values against a set of allowed values, return an error and then let the caller handle it by calling WriteHelp(os.Stdout) I see the provided flag values listed as defaults.

Example (severely truncated) output shown when calling the application with -h flag:

  --console-output CONSOLE-OUTPUT
                         Specify how log messages are logged to the console. [default: stdout]

When calling WriteHelp(os.Stdout) after failing validation due to an unsupported flag value (again with heavily truncated output):

configuration validation failed: invalid option "tacos" provided for console output destination

  --console-output CONSOLE-OUTPUT
                         Specify how log messages are logged to the console. [default: tacos]

In this case "tacos" isn't the default value, but rather what was chosen. This might be too nitpicky to worry with, but as a new user of the package it stood out to me and I wanted to mention it just in case this was unintended behavior.

Embedded structs doesn't work for sub commands

after adding embedded structure to sub command I go:
ArgsAddJob.Test: *job.ArgsTests fields are not supported

type ArgsTests struct {
	val1 string
	val2 string
}

type ArgsAddJob struct {
	Test          *ArgsTests
}

where ArgsAddJob is subcommand

the presence of subcommands is not mentioned in the usage message

to make this issue easier to discuss about, I created a corresponding PR #102.
That PR is not meant to be merged as-is, it is more a reproducible example of what this
ticket is about.

Running the tests in that PR will fail, and the expected outcome is what I am suggesting
in this ticket.

Case 1, backward-compatible

Consider the example Example_helpTextWithSubcommand() in example_test.go.

Although it supports two subcommands, get and list, they are not mentioned. More in details:

It is called with

os.Args = split("./example --help")

and it outputs:

Usage: example [--verbose]
[..snip..]

while in my opinion it should output something similar to:

Usage: example [--verbose] <command> [<args>]
[..snip..]

to hint to the user that the program supports and wants subcommands.

Case 2, non backward-compatible

Another example:

Consider the same configuration in Example_helpTextWithSubcommand(), but this time
imagine that the program is called with no arguments:

os.Args = split("./example")

it would output nothing

while I think that it should output

Usage: example [--verbose] <command> [<args>]
error: <command> is required

This is non backward-compatible because it would change the behavior of MustParse, since currently it returns to the caller, while this change would make MustParse terminate the program.

If what is proposed in this ticket (or a variant) is considered reasonable, I will be happy
to propose an implementation PR for further discussion.

Interested in YAML/JSON configuration?

Hey there,

So far go-arg is hitting all the sweet spots with flag and env variable parsing. I am curious however do you see JSON and YAML configuration file parsing/generation as a welcome addition to this library? If so I'd take a stab at following the patterns you provide and implementing it. However if you feel that is out of scope for go-arg I will fork and maintain.

Let me know your thoughts! thanks.

Can you explicitly specify both short and long argument names?

In the docs it appears that I can explictly specify either the short flag name or the long flag name, but not both. To borrow the example from https://pkg.go.dev/github.com/alexflint/go-arg?tab=doc:

var args struct {
	Input string   `arg:"positional"`
	Log string     `arg:"positional,required"`
	Debug bool     `arg:"-d" help:"turn on debug mode"`
	RealMode bool  `arg:"--real"
	Wr io.Writer   `arg:"-"`
}

This does not appear to be supported, correct?

var args struct {
        // ...
	Debug bool     `arg:"-d, --debug-mode" help:"turn on debug mode"`
        // ...
}

I have a project currently using the flag standard library package where I've specified short and long form flags which do not match the struct field name. I'd like to use the same short and long form flag names with this package if possible, but I understand if that's not supported.

Snippet for reference:

func (c *Config) handleFlagsConfig() {

// ...
	flag.StringVar(&c.configFile, "cf", "", configFileFlagHelp+" (shorthand)")
	flag.StringVar(&c.configFile, "config-file", "", configFileFlagHelp)
// ...

Thanks for your help.

Optional long name

Hello Alex!

I want to make the long name optional, what do you think about that? Defining them could look something like this -- without a name seems pretty clear.

type Args struct {
	EnvOnly   string `arg:"--,env"`
	ShortOnly string `arg:"--,-s"`
}

Help could look like this, (perhaps add hinting at the env-only arguments)

Optional long name example
Usage: example [-s SHORTONLY]

Options:
  -s SHORTONLY
  --help, -h             display this help and exit

Can create a pull request if you're OK with this.

support for version string in help

would be nice if the struct met some interface, e.g.

type Versioned interface {
    Version() string
}

then the help would print the version string somewhere in the help.

Struct argument support

I've been working on moving all of our projects to go-arg, as it makes way more sense than flag. We use nested structs in flag in order to be able to re-use flag configuration between different CLI commands. Additionally, we also sometimes use a struct directly from another class as a CLI flag.

For example:

type DatabaseOptions struct {
    Host string `arg:"required"`
    Port string `arg:"help:Port to connect to the database"`
}

type Options struct {
    Verbose bool `arg:"help:Be noisy"`
    DatabaseOptions
}
fooApp --host bar --port 123

This allows us to have common DatabaseOptions throughout our codebase, and to be able to have go-arg parse them for us.

The following would be nice to have as well:

type Options struct {
    Verbose bool `arg:"help:Be noisy"`
    Database DatabaseOptions
}
fooApp --database-host bar --database-port 123

Placeholders not working

Placeholders are not working for me. I run the following example code from the README.

var args struct {
	Input    string   `arg:"positional" placeholder:"SRC"`
	Output   []string `arg:"positional" placeholder:"DST"`
	Optimize int      `arg:"-O" help:"optimization level" placeholder:"LEVEL"`
	MaxJobs  int      `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"`
}
arg.MustParse(&args)

and the output I get it as follows.

Usage: mattrax [--optimize OPTIMIZE] [--maxjobs MAXJOBS] INPUT [OUTPUT [OUTPUT ...]]

Positional arguments:
  INPUT
  OUTPUT

Options:
  --optimize OPTIMIZE, -O OPTIMIZE
                         optimization level
  --maxjobs MAXJOBS, -j MAXJOBS
                         maximum number of simultaneous jobs
  --help, -h             display this help and exit

My understanding is I should get the output

Usage: mattrax [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]

Positional arguments:
  SRC
  DST

Options:
  --optimize LEVEL, -O LEVEL
                         optimization level
  --maxjobs N, -j N      maximum number of simultaneous jobs
  --help, -h             display this help and exit

Attempts to use go-arg in tests: "error: unknown argument -test.timeout=10m0s"

I'm doing something wrong, but I don't know enough to spot the exact cause. Is this is bug with the package or something I'm doing due to my being new to Go testing?

Example:

package main

import (
	"os"
	"testing"

	"github.com/alexflint/go-arg"
)

type TestConfig struct {
	Option1 int  `arg:"--age,env:TESTING_OPTION1" help:"Placeholder for option 1"`
	Option2 bool `arg:"--keep-old,env:TESTING_OPTION2" help:"Placeholder for option 2"`
}

func TestGoArgReproduceProblemSimpler(t *testing.T) {
	envConfig := TestConfig{}
	os.Setenv("ELBOW_FILE_AGE", "3")
	arg.MustParse(&envConfig)
}

Error from failed test:

$ go test -v
=== RUN   TestGoArgReproduceProblemSimpler
Usage: goarg-issue97.test.exe [--age AGE] [--keep-old]
error: unknown argument -test.timeout=10m0s
exit status 4294967295
FAIL    github.com/atc0005/goarg-issue97        0.261s
$ cat go.mod
module github.com/atc0005/goarg-issue97

go 1.13

require github.com/alexflint/go-arg v1.2.0

I haven't published the code via a Git repo, but can do that if it would be helpful.

Automatically set the short name of a field to its first letter

It would be nice if go-arg automatically set a fields short name to its first letter.
Most command line apps let you specify flags this way. I know that I can do this manually, but it seems pointless if this could be made the default.
You could always add an option to turn this feature off if you don't want to use it.

When sending unknown parameter all parameters will be empty because of this line

https://github.com/alexflint/go-arg/blob/master/parse.go#L559
Maybe you can set config ignoring unknown params or just skip with continue
Example:

var args struct {
	FirstParam 		string `arg:"--first"`
	SecondParam 		string `arg:"--second"`
}
arg.MustParse(&args)

if i run like this that's good working

./command --first=1 --second=2
FirstParam = 1
SecondParam = 2

But like this all params even right setted params will be empty

./command --zero=0 --first=1 --second=2
FirstParam =
SecondParam =

Skip unexported fields

I have a configuration struct which uses your library to fill its values. My project uses a value which is computed from the arguments provided by the user. I wanted to have this computed value accessible wherever the configuration struct is parsed in the code so I added an unexported field to the struct. This causes the error below:

panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

I think unexported fields should be ignored by this library for this use case defined above.

Thanks for any help you can provide.

Inconsistencies in how multiple values are handled

There are a few inconsistencies between env variables and parameters when using this example code:

package main

import (
	"fmt"

	"github.com/alexflint/go-arg"
)

var args struct {
	Test []string `arg:"env:TEST,--test,required"`
}

func main() {
	arg.MustParse(&args)
	fmt.Printf("%+v\n", args.Test)
}
TEST="a,b" go run main.go
[a b]
go run main.go --test="a,b"
[a,b]

This was already discussed in #127 and it is possible to set multiple variables using --test="a" "b", however after having used this for a while I find it confuses end-users. I have to point out the different behaviour in every documentation.

Suggestion: Allow the use of --test="a,b" in addition to --test="a" "b"


go run main.go --test=""
[]
TEST="" go run main.go
Usage: main --test TEST
error: error reading a CSV string from environment variable TEST with multiple values: EOF
exit status 255

Basically one way allows empty values while the other one doesn't. Especially in the modern world with docker-compose, k8s, vault, terraform it can be quite tricky to conditionally set or unset environment variables. It would be easier if it was possible to set environment variables to empty. Please let me know if you need a more concrete example, I'm happy to elaborate on it.

Suggestion: Allow empty environment variables for multiple value fields


To be clear: All of these issues can be worked around relatively easily but I think overall it would be easier if things were more consistent.

Support for version flag (built in support)

I would like to add another flag the parser is aware of besides --help, and that is --version. This is to enable a simple interface for outputting the version information, which is pretty standard. Instead of every application reimplementing this and "obfuscating" their main functions this should be in the library. In my opinion.

If you thing this is a good idea I could submit a pull request with this functionality, with tests and documentation of course.

My idea is to add arg.SetVersion(version string) that should be optionally called before arg.MustParse.
Then parse the --version flag together with the help flag at https://github.com/alexflint/go-arg/blob/master/parse.go#L179
If it's not called no --version flag exists for the application.

What do you think of this idea?

Custom parsing with repeated args does not work

Slightly adapted example from the readme:

// Accepts command line arguments of the form "head.tail"
type NameDotName struct {
	Head, Tail string
}

func (n *NameDotName) UnmarshalText(b []byte) error {
	s := string(b)
	pos := strings.Index(s, ".")
	if pos == -1 {
		return fmt.Errorf("missing period in %s", s)
	}
	n.Head = s[:pos]
	n.Tail = s[pos+1:]
	return nil
}

// optional: implement in case you want to display a default value in the usage string
func (n *NameDotName) MarshalText() (text []byte, err error) {
	text = []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail))
	return
}

func main() {
	var args struct {
		Name []*NameDotName
	}
	arg.MustParse(&args)
	fmt.Printf("%#v\n", args.Name)
}

Leads to this error:

go build ;and ./app --name a.c
Usage: app [--name NAME]
error: error processing --name: cannot parse into main.NameDotName

Parse nested sctructs

Can you be interested in adding recursive struct parsing, like in:

var args struct {
	Db struct {
		User		string
		Password	string
	}
	Log struct {
		Enable	bool
		Level	string
	}
}

arg.MustParse(&args)
db := Db.Open(args.Db)
logger := Log.Open(args.Log)
$ app --db.user=guest --db.password=guest --log.level=debug

I am fiddling with the idea and would like to implement it, if such PR has chance to be merged.

[]string as non-positional argument

When using []string as the type for a non-positional argument, for example:

type MyArgs struct {
    Commands []string `arg:"-c,help:commands to execute"`
}

The syntax that is usable, is the following: -c "command1" "command2", however one would expect this to behave similar to how other standard command line apps do it: -c "command1" -c "command2".

Here is some example output from usql which is using go-arg and for which I'd like to accomplish the same style syntax as psql (which is also the same as bash and other standard cli programs):

ken@ken-desktop:~/src/go/src/github.com/knq/usql$ ./usql pg://booktest:booktest@localhost/ -c 'select * from books;' -c 'select * from authors'
2017/03/03 12:41:52 >>> commands: [select * from authors]
  author_id |      name       
+-----------+----------------+
          1 | Unknown Master  
(1 rows)

ken@ken-desktop:~/src/go/src/github.com/knq/usql$ ./usql pg://booktest:booktest@localhost/ -c='select * from books;' -c='select * from authors'
2017/03/03 12:42:00 >>> commands: [select * from authors]
  author_id |      name       
+-----------+----------------+
          1 | Unknown Master  
(1 rows)

ken@ken-desktop:~/src/go/src/github.com/knq/usql$ ./usql pg://booktest:booktest@localhost/ -c 'select * from books;' 'select * from authors'
2017/03/03 12:42:11 >>> commands: [select * from books; select * from authors]
  book_id | author_id | isbn | booktype |        title         | year |            available             |      tags        
+---------+-----------+------+----------+----------------------+------+----------------------------------+-----------------+
        1 |         1 |    1 | FICTION  | my book title        | 2016 | 2017-03-03T00:21:37.613263+07:00 | {}               
        2 |         1 |    2 | FICTION  | changed second title | 2016 | 2017-03-03T00:21:37.613263+07:00 | {cool,disastor}  
        3 |         1 |    3 | FICTION  | the third book       | 2001 | 2017-03-03T00:21:37.613263+07:00 | {cool}           
(3 rows)

  author_id |      name       
+-----------+----------------+
          1 | Unknown Master  
(1 rows)

As you can see above, the parsing for []string seems to be the same as a positional argument only. Ideally, one should be able to mix and match the named parameters, and positional arguments:

$ usql -c 'command1' pg://database -c 'command2'

# the above should work the same as the following:
$ usql -c 'command1' -c 'command2' pg://database

I'm happy to do the work for this, but would like approval first to know if I write the PR, it would be accepted. Thanks in advance!

Option Group Default or Is It a Default

This may already be possible and I'm just overlooking somehow but I was wondering if option groups are part of the spec. What I'm specifically looking for is to make certain options part of a group and be able to check if anything in the group was set at all. This would facilitate the ability to define a default for an option that is only active when nothing in the group was set. If that's not a thing is there a way to check if a variable was actually set and not just defaulted? That would also provide the functionality I'm looking for.

--foo=bar as positional

I have a launcher that take multi defined arg and 2 positional arg
the first positional arg is the name of the program and the second his parameters
the program can take parameter like --foo=bar
but i get error : "error: unknown argument --foo=bar"

example :

import (
	"os"
	"strings"
	
	"github.com/alexflint/go-arg"
)

func main() {
	var args struct {
		Name        string   `arg:"positional"`
		Parameters   []string `arg:"positional"`
	}
	
	os.Args = strings.Split("./launcher program --foo=bar", " ")
	arg.MustParse(&args)
}

https://play.golang.org/p/xzvCZhjL0Pn

there is a way to having --foo=bar in positional Arguments ?

Help Option Text

Thank you for this library! ๐Ÿ˜ƒ

I would like a way to be able to use my own text for the help flags though. Also to be able to specify my own short flag. Either by being able to specify my own explicit Help field

type args struct {
	Debug bool `arg:"-D" help:"Output debug info."`
	Help  bool `arg:"-H" help:"Display this help info."`
}

Or by method call such as:

arg.HelpOption("Display this help info.", "-H")

or something along the lines of

arg.SetHelpText("Display this help info.")
arg.SetHelpShort("-H")

Counter Options

I'd love it if there was support for multiple instance options that count their repetition instead of accepting multiple values. This is commonly used for verbosity where a setup, something along the lines of

type args struct {
	Debug   bool   `arg:"-D" help:"output debug info"`
	Verbose uint8 `arg:"-v,counter" help:"Increases verbosity"`
}

would allow for --verbose blah -v on the command line would set args.verbose == 2, -vvv would set args.verbose == 3, etc. And not specifying any verbosity options would set args.verbose == 0.

switch to the newer 'dep' depepdency tool

The dep tool is likely to become the standard versioning tool. It would be easy to drop it in in place of godep.

https://github.com/golang/dep

Also, IMHO, the vendored dependencies shouldn't be in the git repo of go-arg. Including them in git forces go-arg users to have multiple copies of dependencies in many cases. Instead they should be in .gitignore and fetched uniformly via dep ensure. (This question is broadly a matter of opinion, but for small apis such as go-arg I think the balance is in favour of relying on dep instead of using git.)

Positional arguments with dashes

Hey,

I was wondering wether it's possible to a []string as the destination of positional arguments that may container -prefixed strings. My use case is ./my-bin --debug my_first_pos_arg -something where I expect the []string{"my_first_pos_arg", "-something"} to be retrieved as the positional arguments given that I have something like

type Something struct {
    Debug      bool     `arg:"-d"`
    Positional []string `arg:"positional"`
}

Is this possible to be consumed by go-arg?

Also, thanks a lot for go-arg! It's my go-to every time I need something quick and simple ๐Ÿ‘

Thx!

Put coverage testing back

#110 broke test coverage using coveralls. We should get that working under github actions.

The .travis.yml file was:

language: go
go:
  - "1.12"
  - "1.13"
before_install:
  - go get github.com/axw/gocov/gocov
  - go get github.com/mattn/goveralls
  - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
  - $HOME/gopath/bin/goveralls -service=travis-ci
  - bash test/compile_with_go110.sh
  - bash test/compile_with_go111.sh
  - bash test/compile_with_go111_inside_gopath.sh

The coveralls badge in the README was like this:

-[![Coverage Status](https://coveralls.io/repos/alexflint/go-arg/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexflint/go-arg?branch=master)

Allow spaces after each comma in tags

With many args and lots of tag settings, the tags become really hard to read. It would be easy to allow spaces after each comma.

So, after parse.go line 170, add an extra line

    for _, key := range strings.Split(tag, ",") {
        key = strings.TrimLeft(key, " ")
        ...

And to test this, add

func TestSpacesAllowedInTags(t *testing.T) {
	var args struct {
		Foo []string `arg:"--foo, -f, separate, required, help:quite nice really"`
	}

	err := parse("--foo one -f=two --foo=three -f four", &args)
	require.NoError(t, err)
	assert.Equal(t, []string{"one", "two", "three", "four"}, args.Foo)
}

Subcommands don't display parent command arguments

Currently, subcommands inherit the arguments of their parent. That's great, but the help for those subcommands doesn't display the inherited arguments. From a UX standpoint, that's misleading - it looks like that those arguments aren't allowed on the subcommand.

Was this the intended behavior? I'd be happy to open a PR if not.

Support for environment variables?

I was wondering if parsing of environment variables is something worth adding? I thought of some method like AutomaticEnv in viper, where you can set an optional prefix and then parse Environment variables just like parameters. Is there interest in a push request?

Positional argument before positional,required argument doesn't work

I have the following code:

package main
import(
	"github.com/alexflint/go-arg"
)
type args struct {
	Name string `arg:"positional"`
	Age int `arg:"positional,required"`
}

func main() {
	a:=args{}
	arg.MustParse(&a)
}

This is a weird example, but it shows what I'd like to do.
When I run "./cli 15", I want it to leave name alone and set age to 15, but it seems to assume that I passed in the value for name, because I get this:
Usage: cli NAME AGE
error: age is required

Passing multiple values via CLI parameter doesn't work

Consider the following code:

package main

import (
	"fmt"

	"github.com/alexflint/go-arg"
)

var args struct {
	Test []string `arg:"env:TEST,--test,required"`
}

func main() {
	arg.MustParse(&args)
	fmt.Printf("%+v\n", args.Test)
}

If I pass multiple values by environment variable it works as expected:

$ TEST="foo,bar" go run main.go
[foo bar]

However if I try to use the cli parameter to do the same it doesn't split on the comma:

$ go run main.go --test="foo,bar"
[foo,bar]

Some cli tools allow for the same parameter key to be used multiple times to specify multiple values, that doesn't work either:

$ go run main.go --test="foo" --test="bar"
[bar]

Description() doesn't work for sub commands

Description() defined for subcommands is not used for help text.

I think only root arg is considered.

parse.go line 232:

if dest, ok := dest.(Described); ok {
	p.description = dest.Description()
}

Boolean arguments do not always need explicit values.

From README.md:

Boolean arguments must have explicit values, so ./prog -debug=1 sets debug to true but ./myprog -debug does not.

Not true. If you define

debug := flag.Bool("debug", false, "whatever")

Then passing -debug is enough to have the variable debug hold a true value.

Purposely call usage after argument validation,

I am trying to get the usage message when failing to validate arguments passed

arg.WriteHelp(os.Stdout)

does not work and is not really relevant without the &args struct.

What is the best way to purposely call usage?

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.