Ignore flags after the special option delimiter argument ('--') and
interprete them as non-flag, positional arguments as getopt(3) does.
Example:
type Foo struct {
Force bool `opt:"-f"`
Name string `opt:"name"`
}
Current behavior:
$ foo -- -f
main.Foo{Force:true, Name:"--"}
Expected behavior with this patch:
$ foo -- -f
main.Foo{Force:false, Name:"-f"}
and
$ foo -f -- -f
main.Foo{Force:true, Name:"-f"}
Signed-off-by: Koni Marti <koni.marti@gmail.com>
---
v1->v2:
* match getopt(3) behavior: end option-scanning after -- and treat rest
as positionals
README.md | 3 +++
spec.go | 14 ++++++++++++--
spec_test.go | 8 ++++++++
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index b1478f4..4652a95 100644
--- a/README.md
+++ b/README.md
@@ -277,3 +277,6 @@ Short flags can be combined like with `getopt(3)`:
* Flags with no value: `-abc` is equivalent to `-a -b -c`
* Flags with a value (options): `-j9` is equivalent to `-j 9`
+
+The special argument `--` forces an end to the flag parsing. The remaining
+arguments are interpreted as positional arguments (see `getopt(3)`).
diff --git a/spec.go b/spec.go
index 323dffa..d4960d1 100644
--- a/spec.go
+++ b/spec.go
@@ -82,6 +82,8 @@ var (
positionalRe = regexp.MustCompile(`^([a-zA-Z][\w-]*)$`)
)
+const optionDelim = "--"
+
// Interpret all struct fields to a list of option specs
func NewCmdSpec(name string, v any) *CmdSpec {
typ := reflect.TypeOf(v)
@@ -412,6 +414,7 @@ func (c *CmdSpec) parseArgs(args *Args) []*ArgError {
argErrors = append(argErrors, &ArgError{kind, s, detail})
}
positionals := c.positionals
+ ignoreFlags := false
c.seen = nil
args.Shift(1) // skip command name
i := 1
@@ -420,7 +423,7 @@ func (c *CmdSpec) parseArgs(args *Args) []*ArgError {
arg := args.Arg(0)
switch {
- case c.getLongFlag(arg) != nil:
+ case c.getLongFlag(arg) != nil && !ignoreFlags:
if cur != nil {
fail(missingValue, cur.spec, nil)
}
@@ -443,7 +446,7 @@ func (c *CmdSpec) parseArgs(args *Args) []*ArgError {
cur = nil
}
- case c.getShortFlag(arg) != nil:
+ case c.getShortFlag(arg) != nil && !ignoreFlags:
if cur != nil {
fail(missingValue, cur.spec, nil)
}
@@ -486,6 +489,7 @@ func (c *CmdSpec) parseArgs(args *Args) []*ArgError {
cur = nil
goto next
}
+
for len(positionals) > 0 {
spec := &c.opts[positionals[0]]
positionals = positionals[1:]
@@ -494,6 +498,12 @@ func (c *CmdSpec) parseArgs(args *Args) []*ArgError {
break
}
}
+
+ if arg == optionDelim {
+ ignoreFlags = true
+ goto next
+ }
+
if cur == nil {
fail(unexpectedArg, nil, errors.New(arg))
goto next
diff --git a/spec_test.go b/spec_test.go
index 1d0b6de..4673ed0 100644
--- a/spec_test.go
+++ b/spec_test.go
@@ -92,6 +92,14 @@ func TestArgsToStruct(t *testing.T) {
Name: "n a m e",
},
},
+ {
+ cmdline: `bar -j3 -- -j7`,
+ expected: OptionStruct{
+ Jobs: 3,
+ Delay: 0.5,
+ Name: "-j7",
+ },
+ },
}
for _, v := range vectors {
--
2.43.0
Koni Marti <koni.marti@gmail.com> wrote:
> Ignore flags after the special option delimiter argument ('--') and
> interprete them as non-flag, positional arguments as getopt(3) does.
>
> Example:
>
> type Foo struct {
> Force bool `opt:"-f"`
> Name string `opt:"name"`
> }
>
> Current behavior:
>
> $ foo -- -f
> main.Foo{Force:true, Name:"--"}
>
> Expected behavior with this patch:
>
> $ foo -- -f
> main.Foo{Force:false, Name:"-f"}
>
> and
>
> $ foo -f -- -f
> main.Foo{Force:true, Name:"-f"}
>
> Signed-off-by: Koni Marti <koni.marti@gmail.com>
> ---
> v1->v2:
>
> * match getopt(3) behavior: end option-scanning after -- and treat rest
> as positionals
Acked-by: Robin Jarry <robin@jarry.cc>
Applied, thanks.
To
cb392dfc9d62..4bd5fff1499e main -> main