voxelbrain / goptions Goto Github PK
View Code? Open in Web Editor NEWA flexible parser for command line options
License: Other
A flexible parser for command line options
License: Other
It doesn't look currently possible to define verb-like flags which take a variable.
i.e. something like
myprog [global options] <template> <data>
Could this be supported in a similar way to verbs, by allowing a non-dashed flag name to represent a positional argument for the current verb?
goptions is really really great.
it is only missing the ability to assign default values to flags... (or did I miss it?)
-s
Hi Surma, the more I use goptions
, the more I like it. So I'm writing down my thought that will help to get it better. I.e.,
The thing I'm missing from goptions
is a program description entry, which briefly describes what the program is for, and a footer, for something to mention after listing all the options.
Most of the programs have them. I.e., the help output of rsync
is a good example.
Thanks
That would parse a comma delimited list of items.
For example:
options := struct {
AwsAccessKey string `goptions:"--aws-access-key, description='AWS Access Key'" json:"aws_access_key"`
Help goptions.Help `goptions:"-h, --help, description='Show this help'"`
Verb goptions.Verbs
Run struct {
Name string `goptions:"--name, mutexgroup='input', description='name'"`
Volume string `goptions:"-v, --volume, obligatory, description='volume'"`
Remainder goptions.Remainder
} `goptions:"run"`
If I run it with no parameters at all, ParseAndFail doesn't fail or print an error or anything.
I did not find the possibility to set default values for Verbs.
Maybe this is just missing in the documentation.
Does goptions
support handling none-option parameters?
For e.g., rsync
takes an awful lot of options, but besides them, at the end of command line, there needs to be source and option destination. Can goptions
handle cases like this?
Thanks
For example, cannot use -a and -b arguments in the same command line
I think I've found a Type os.File option bug when output to "-",
The https://godoc.org/github.com/voxelbrain/goptions says,
Type: *os.File The given string is interpreted as a path to a file. If the string is "-"
os.Stdin or os.Stdout will be used. os.Stdin will be returned, if the
rdonly
flag was set. os.Stdout will be returned, ifwronly
was set.
However, when I try it, I think it is not writing to Stdout. This is my parameter:
Fo *os.File `goptions:"-o, --output, description='The output', wronly"`
The whole program is available at
https://github.com/suntong/lang/blob/master/lang/Go/src/sys/CommandLineGoptionsBug.go
Here is how I prove there is a problem:
$ go run CommandLineGoptionsBug.go execute -c test -o -
Selected verb: execute
Execute.Command: test
with verbosity: 0
To output, Check str: ''
To os.Stdout, Check str: ''
Note the two "Check str" output line. Now,
$ go run CommandLineGoptionsBug.go execute -c test -o - > /dev/null
To output, Check str: ''
The print out to os.Stdout is redirected, while the output of "-o -" is not. This is the problem, and I found that it is not redirecting to stderr either:
$ go run CommandLineGoptionsBug.go execute -c test -o - > /dev/null 2>&1
To output, Check str: ''
Still there, even stderr is redirected.
Also, I wasn't able to give options.Execute.Check
a default value -- When I enable line 36,
https://github.com/suntong/lang/blob/master/lang/Go/src/sys/CommandLineGoptionsBug.go#L36
I got invalid field name Execute.Check in struct initializer
error.
Please help.
Thanks
package main
import (
"github.com/voxelbrain/goptions"
)
type A struct {
Foo string
bar string
}
func main() {
foo := struct {
goptions.Verbs
A A `goptions:"a"`
}{}
goptions.ParseAndFail(&foo)
}
renders
panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
goroutine 1 [running]:
reflect.valueInterface(0x522e60, 0xc200086380, 0x187, 0x5c8501, 0x588e40, ...)
/usr/lib/go/src/pkg/reflect/value.go:983 +0xbb
reflect.Value.Interface(0x522e60, 0xc200086380, 0x187, 0x0, 0x0, ...)
/usr/lib/go/src/pkg/reflect/value.go:972 +0x50
github.com/voxelbrain/goptions.parseStructField(0x522e60, 0xc200086380, 0x187, 0x588e40, 0x0, ...)
${HOME}/.local/go/twitterybot/src/github.com/voxelbrain/goptions/tagparser.go:24 +0x52
github.com/voxelbrain/goptions.newFlagset(0x59a19a, 0x1, 0x562500, 0xc200086370, 0x196, ...)
${HOME}/.local/go/twitterybot/src/github.com/voxelbrain/goptions/flagset.go:69 +0x298
github.com/voxelbrain/goptions.newFlagset(0x7fff791a42f2, 0x1, 0x55d820, 0xc200086360, 0x196, ...)
${HOME}/.local/go/twitterybot/src/github.com/voxelbrain/goptions/flagset.go:97 +0x634
github.com/voxelbrain/goptions.NewFlagSet(0x7fff791a42f2, 0x1, 0x519fe0, 0xc200086360, 0xc200086360, ...)
${HOME}/.local/go/twitterybot/src/github.com/voxelbrain/goptions/flagset.go:46 +0x184
github.com/voxelbrain/goptions.ParseAndFail(0x519fe0, 0xc200086360)
${HOME}/.local/go/twitterybot/src/github.com/voxelbrain/goptions/goptions.go:79 +0x6d
main.main()
${HOME}/a.go:19 +0x6c
upon execution.
If I remove the A.bar field, it works fine.
As far as I can tell there is currently no support for nesting verbs?
Is that something you could forsee wanting / adding?
I'm just getting started in Go and should probably NOT submit any code yet, however when using your excellent options library that I read about here (http://dave.cheney.net/2013/11/07/subcommand-handling-in-go). I ran into a problem. The solution might just be to amend the documentation, or perhaps there's an actual fix. But when I used Remainder in the top level struct with verbs, I got a nasty reflect error:
panic: reflect: NumField of non-struct type
goroutine 1 [running]:
reflect.(*rtype).NumField(0x5e5560, 0xc20802e530)
/images/opt/go/src/reflect/type.go:657 +0x7a
github.com/voxelbrain/goptions.newFlagset(0x0, 0x0, 0x5e5560, 0xc20802e530, 0xd7, 0xc2080117a0, 0x7ffb282ca7e8)
/home/jagipson/go/src/github.com/voxelbrain/goptions/flagset.go:66 +0x23c
github.com/voxelbrain/goptions.newFlagset(0x695710, 0x4, 0x664e00, 0xc20802e500, 0xd9, 0x0, 0x7ffb282bd000)
/home/jagipson/go/src/github.com/voxelbrain/goptions/flagset.go:102 +0x889
github.com/voxelbrain/goptions.NewFlagSet(0x695710, 0x4, 0x5d5be0, 0xc20802e500, 0x0)
/home/jagipson/go/src/github.com/voxelbrain/goptions/flagset.go:46 +0x1a6
main.main()
/home/jagipson/go/src/github.com/jagipson/goreps/cmd/reps/reps.go:55 +0x84
exit status 2
That happens for this:
options := struct {
DataStorePath string `goptions:"-d, --datastore, description='Specify DataStore path. (default: ~/.gorepsdata)'"`
Verbosity bool `goptions:"-v, --verbose", description='firehose verbosity'"`
Help goptions.Help `goptions:"-h, --help, description='Show this help'"`
goptions.Verbs
List struct {
Colorize bool `goptions:"-c, --color, description='Colorize the output (not implemented yet)'"`
Long bool `goptions:"-l, --long, description='extra data'"`
Help bool `goptions:"-h, --help, description='Print List Help'"`
goptions.Verbs
Models struct{} `goptions:"models"`
Items struct{} `goptions:"items"`
} `goptions:"list"`
New struct {
Foo bool `goptions:"-f"`
} `goptions:"new"`
Remainder goptions.Remainder
}{}
And goes away when I comment out the Remainder line
I'd like to add this global option:
Dry bool `goptions:"-d, --dry, description='do a dry run'"`
and set its default value to true
. However, I cannot override that in the command line: when I pass --dry=false
, I get Error: Invalid trailing arguments: [--dry=false ...
.
I tried the same with regular flags, and it was possible.
Is this something that can be fixed?
package main
import "github.com/voxelbrain/goptions"
import "os"
func main() {
var options struct {
Server string `goptions:"-s, --server, obligatory, description='Server to connect to'"`
Password string `goptions:"-p, --password, description='Don\\'t prompt for password'"`
Verbosity int `goptions:"-v, --verbose, accumulate, description='Set output threshold level'"`
goptions.Help `goptions:"-h, --help, description='Show this help'"`
goptions.Verbs
Create struct {
Name string `goptions:"-n, --name, obligatory, description='Name of the entity to be created'"`
Directory bool `goptions:"--directory, mutexgroup='type', description='Create a directory'"`
File bool `goptions:"--file, mutexgroup='type', description='Create a file'"`
} `goptions:"create"`
Delete struct {
Name string `goptions:"-n, --name, obligatory, description='Name of the entity to be deleted'"`
Directory bool `goptions:"--directory, mutexgroup='type', description='Delete a directory'"`
File bool `goptions:"--file, mutexgroup='type', description='Delete a file'"`
} `goptions:"delete"`
}
fs := goptions.NewFlagSet("goptions", &options)
err := fs.Parse(os.Args[1:])
if err != nil{
println(err.Error())
fs.PrintHelp(os.Stderr)
return
}
}
./main -p
panic: runtime error: index out of range
goroutine 1 [running]:
github.com/voxelbrain/goptions.(*FlagSet).parseShortFlagCluster(0xf840001b40, 0xf8400371c0, 0x0, 0x7fff00000001, 0x0, ...)
/home/nsf/waytogo/src/github.com/voxelbrain/goptions/flagset.go:235 +0x4a5
github.com/voxelbrain/goptions.(*FlagSet).parseNextItem(0xf840001b40, 0xf8400371b0, 0x100000001, 0xf84005b000, 0x4eb9c0, ...)
/home/nsf/waytogo/src/github.com/voxelbrain/goptions/flagset.go:180 +0x26c
github.com/voxelbrain/goptions.(*FlagSet).Parse(0xf840001b40, 0xf8400371b0, 0x100000001, 0xf840000e00, 0xf840001b40, ...)
/home/nsf/waytogo/src/github.com/voxelbrain/goptions/flagset.go:147 +0x5c
main.main()
/home/nsf/tmp/another/main.go:28 +0xaa
goroutine 2 [syscall]:
created by runtime.main
/home/nsf/go/src/pkg/runtime/proc.c:221
Looks pretty cool but it's not quite obvious that to get the chosen verb one needs just options.Verbs.
And I couldn't figure out how to define command that accepts nargs, aka
<command> 1*<arg>
Also I'm missing information on how to use the verbs. Like if the user is supposed to switch
on the options.Verbs
or if there is a way to define struct that can be run by goptions or if there is other project that implements this functionality on top of goptions (I think that would be preferred option for me)
So, I fiigured I can do
options := struct {
Help goptions.Help `goptions:"-h, --help, description='Show this help'"`
goptions.Verbs
My struct {
Command *MyCommand
} `goptions:"my"`
a := MyCommand{}
options.My.Command = &a
goptions.ParseAndFail(&options)
structValue := reflect.ValueOf(options)
verb_ := UpFirst(string(options.Verbs))
verb := structValue.FieldByName(verb_)
command := verb.FieldByName("Command").Interface().(VerbCommand)
command.Run()
if you pass in '-' as an possition option (eg - Remainder), you get a panic: runtime error: slice bounds our of range
on line 118 in flagset.go
basically, it assumes the next character is a short option. In this case, it is a "file" (stdin)
it would be really nice if it checked if there was a character after the dash before trying ot access it.
If you strive to reach the complete GNU style compatibility, instead of getting an error when a value-flag in a short flag cluster is not the last one, you should parse the rest of the arg as its value.
A typical example would be a -p parameter (let's say a port). And let's assume we have two boolean parameters -x and -y. That's the possible ways to specify flags and their values:
app -xyp22
yields -x -y -p=22
app -pxy22
yields -p=xy22
app -xy -p22
yields -x -y -p=22
That's a feature request.
I have the following in option in my file:
Fileo *os.File `goptions:"-o, --output, description='The dump output', wronly"`
And my main begins with:
func main() {
goptions.ParseAndFail(&options)
Howerver, when I invoke my program with
my_program -o /tmp/out
It errors out, with the following message:
Error: open /tmp/out: no such file or directory
Usage: ....
I think for wronly
option, that is meant solely for output, the package should create the file instead of error out.
Hi Surma, once again along the line of writing down my thought that will help to get goptions
better, (since I knew you are contemplating a new goptions
version)...
Before using goptions
, I use my own home-made solution, which gives me (beside the program description, usage line customization and the help footer), the ability to use environment variables to specify options.
Please refers to Showcasing the power of easygen with ffcvt if you like to see a concrete example.
Thanks
How do you license the library? Could you please add a LICENSE file?
Hi Surma,
Would there be any side-affects if I use a goptions variable as global (configuration) variable? I.e., using them for program configuration/behavior purpose, so that not to define another set of variables having the exact same purpose?
The question may seems strange. That's because I'm having an extremely strange problem using goptions variable as global (configuration) variable. Please take a look at the changes made in
--- a/wts-dump.go
+++ b/wts-dump.go
@@ -323,7 +323,7 @@ func treatWtsXml(w io.Writer, checkOnly bool, decoder *xml.Decoder) error {
}
func treatComment(w io.Writer, v string) {
- if options.Dump.Cnr {
+ if cnr {
debug("here", 1)
v = cmtRe.ReplaceAllString(v, "[]")
}
@@ -471,6 +471,7 @@ func minify(xs string) string {
return re.ReplaceAllString(xs, "")
}
+var cnr bool
var cmtRe *regexp.Regexp
var tmsRe *regexp.Regexp
@@ -485,10 +486,11 @@ func dumpCmd(options Options) error {
}
defer fileo.Close()
+ cnr = options.Dump.Cnr
if options.Dump.Raw {
- options.Dump.Cnr = true
+ cnr = true
}
- if options.Dump.Cnr {
+ if cnr {
cmtRe = regexp.MustCompile(`\[#\d+]`)
debug("here", 1)
}
The only thing that I did, was to replace the global variable options.Dump.Cnr
with global variable cnr
. and the if
statement in func treatComment
changed from not working to working.
Any idea why this is happening?
Here is a sample file, if you need to test it yourself.
<?xml version="1.0" encoding="utf-8"?>
<WebTest Name="Test" Id="a60ebdf8-..." Owner="" Priority="0" Enabled="True" CssProjectStructure="" CssIteration="" Timeout="0" WorkItemIds="" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010" Description="" CredentialUserName="" CredentialPassword="" PreAuthenticate="True" Proxy="default" StopOnError="False" RecordedResultFile="" ResultsLocale="">
<Items>
<Comment CommentText="[#1]" />
<Request Method="GET" Guid="5477c561-3508-43dc-af23-eb6b78ef8f81" Version="1.1" Url="http://my.site.com/" ThinkTime="7" Timeout="60" ParseDependentRequests="True" FollowRedirects="False" RecordResult="True" Cache="False" ResponseTimeGoal="0" Encoding="utf-8" ExpectedHttpStatusCode="0" ExpectedResponseUrl="" ReportingName="" IgnoreHttpStatusCode="False" />
<Comment CommentText="[#7]" />
</Items>
</WebTest>
To summary up, using a goptions variable as global configuration variable for program configuration/behavior purpose works, but only if not changing its values programmatically from outside voxelbrain/goptions.
Thanks
I'm not sure if your v3 will be using structs to define the layout of options as this version does, but I noticed that embedded structs were not implemented.
During the reflection/parsing of the struct, if you check the reflect.Value.Anonymous (bool) field and get true you could recurse into that single embedded struct. This allows user to 'reuse' subsets of field structs defined in separate structs but embedding them in the main field struct.
Long options with values after =
does not seems to be working:
$ go run main.go --env=dev
Error: Invalid trailing arguments: [--env=dev]
Usage: main [global options]
Global options:
-e, --env Application environment, defines config section (default: prod)
-r, --rebuild Rebuild index only, do not start web server
-h, --help Show this help
exit status 1
W/out equal sign works fine.
$ go run main.go --env dev
[LOG] 2014/12/23 - 04:11:39 | Loading config with env set to dev
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.