Commit ed7c3f31 authored by Russ Cox's avatar Russ Cox

flag: handle multiple calls to flag.Parse

R=r
CC=golang-dev
https://golang.org/cl/3071041
parent 14eb03f6
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flag
import "os"
// Additional routines compiled into the package only during testing.
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will panic rather
// than exit the program.
func ResetForTesting(usage func()) {
flags = &allFlags{make(map[string]*Flag), make(map[string]*Flag), os.Args[1:]}
Usage = usage
panicOnError = true
}
// ParseForTesting parses the flag state using the provided arguments. It
// should be called after 1) ResetForTesting and 2) setting up the new flags.
// The return value reports whether the parse was error-free.
func ParseForTesting(args []string) (result bool) {
defer func() {
if recover() != nil {
result = false
}
}()
os.Args = args
Parse()
return true
}
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
Usage: Usage:
1) Define flags using flag.String(), Bool(), Int(), etc. Example: Define flags using flag.String(), Bool(), Int(), etc. Example:
import "flag" import "flag"
var ip *int = flag.Int("flagname", 1234, "help message for flagname") var ip *int = flag.Int("flagname", 1234, "help message for flagname")
If you like, you can bind the flag to a variable using the Var() functions. If you like, you can bind the flag to a variable using the Var() functions.
...@@ -20,17 +20,18 @@ ...@@ -20,17 +20,18 @@
flag.Var(&flagVal, "name", "help message for flagname") flag.Var(&flagVal, "name", "help message for flagname")
For such flags, the default value is just the initial value of the variable. For such flags, the default value is just the initial value of the variable.
2) After all flags are defined, call After all flags are defined, call
flag.Parse() flag.Parse()
to parse the command line into the defined flags. to parse the command line into the defined flags.
3) Flags may then be used directly. If you're using the flags themselves, Flags may then be used directly. If you're using the flags themselves,
they are all pointers; if you bind to variables, they're values. they are all pointers; if you bind to variables, they're values.
fmt.Println("ip has value ", *ip); fmt.Println("ip has value ", *ip);
fmt.Println("flagvar has value ", flagvar); fmt.Println("flagvar has value ", flagvar);
4) After parsing, flag.Arg(i) is the i'th argument after the flags. After parsing, the arguments after the flag are available as the
Args are indexed from 0 up to flag.NArg(). slice flag.Args() or individually as flag.Arg(i).
The arguments are indexed from 0 up to flag.NArg().
Command line flag syntax: Command line flag syntax:
-flag -flag
...@@ -48,6 +49,19 @@ ...@@ -48,6 +49,19 @@
Integer flags accept 1234, 0664, 0x1234 and may be negative. Integer flags accept 1234, 0664, 0x1234 and may be negative.
Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False. Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False.
It is safe to call flag.Parse multiple times, possibly after changing
os.Args. This makes it possible to implement command lines with
subcommands that enable additional flags, as in:
flag.Bool(...) // global options
flag.Parse() // parse leading command
subcmd := flag.Args(0)
switch subcmd {
// add per-subcommand options
}
os.Args = flag.Args()
flag.Parse()
*/ */
package flag package flag
...@@ -200,9 +214,9 @@ type Flag struct { ...@@ -200,9 +214,9 @@ type Flag struct {
} }
type allFlags struct { type allFlags struct {
actual map[string]*Flag actual map[string]*Flag
formal map[string]*Flag formal map[string]*Flag
first_arg int // 0 is the program name, 1 is first arg args []string // arguments after flags
} }
var flags *allFlags var flags *allFlags
...@@ -275,18 +289,17 @@ func NFlag() int { return len(flags.actual) } ...@@ -275,18 +289,17 @@ func NFlag() int { return len(flags.actual) }
// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument // Arg returns the i'th command-line argument. Arg(0) is the first remaining argument
// after flags have been processed. // after flags have been processed.
func Arg(i int) string { func Arg(i int) string {
i += flags.first_arg if i < 0 || i >= len(flags.args) {
if i < 0 || i >= len(os.Args) {
return "" return ""
} }
return os.Args[i] return flags.args[i]
} }
// NArg is the number of arguments remaining after flags have been processed. // NArg is the number of arguments remaining after flags have been processed.
func NArg() int { return len(os.Args) - flags.first_arg } func NArg() int { return len(flags.args) }
// Args returns the non-flag command-line arguments. // Args returns the non-flag command-line arguments.
func Args() []string { return os.Args[flags.first_arg:] } func Args() []string { return flags.args }
// BoolVar defines a bool flag with specified name, default value, and usage string. // BoolVar defines a bool flag with specified name, default value, and usage string.
// The argument p points to a bool variable in which to store the value of the flag. // The argument p points to a bool variable in which to store the value of the flag.
...@@ -414,23 +427,20 @@ func Var(value Value, name string, usage string) { ...@@ -414,23 +427,20 @@ func Var(value Value, name string, usage string) {
} }
func (f *allFlags) parseOne(index int) (ok bool, next int) { func (f *allFlags) parseOne() (ok bool) {
s := os.Args[index] if len(f.args) == 0 {
f.first_arg = index // until proven otherwise return false
if len(s) == 0 {
return false, -1
} }
if s[0] != '-' { s := f.args[0]
return false, -1 if len(s) == 0 || s[0] != '-' || len(s) == 1 {
return false
} }
num_minuses := 1 num_minuses := 1
if len(s) == 1 {
return false, index
}
if s[1] == '-' { if s[1] == '-' {
num_minuses++ num_minuses++
if len(s) == 2 { // "--" terminates the flags if len(s) == 2 { // "--" terminates the flags
return false, index + 1 f.args = f.args[1:]
return false
} }
} }
name := s[num_minuses:] name := s[num_minuses:]
...@@ -440,6 +450,7 @@ func (f *allFlags) parseOne(index int) (ok bool, next int) { ...@@ -440,6 +450,7 @@ func (f *allFlags) parseOne(index int) (ok bool, next int) {
} }
// it's a flag. does it have an argument? // it's a flag. does it have an argument?
f.args = f.args[1:]
has_value := false has_value := false
value := "" value := ""
for i := 1; i < len(name); i++ { // equals cannot be first for i := 1; i < len(name); i++ { // equals cannot be first
...@@ -456,22 +467,21 @@ func (f *allFlags) parseOne(index int) (ok bool, next int) { ...@@ -456,22 +467,21 @@ func (f *allFlags) parseOne(index int) (ok bool, next int) {
fmt.Fprintf(os.Stderr, "flag provided but not defined: -%s\n", name) fmt.Fprintf(os.Stderr, "flag provided but not defined: -%s\n", name)
fail() fail()
} }
if f, ok := flag.Value.(*boolValue); ok { // special case: doesn't need an arg if fv, ok := flag.Value.(*boolValue); ok { // special case: doesn't need an arg
if has_value { if has_value {
if !f.Set(value) { if !fv.Set(value) {
fmt.Fprintf(os.Stderr, "invalid boolean value %q for flag: -%s\n", value, name) fmt.Fprintf(os.Stderr, "invalid boolean value %q for flag: -%s\n", value, name)
fail() fail()
} }
} else { } else {
f.Set("true") fv.Set("true")
} }
} else { } else {
// It must have a value, which might be the next argument. // It must have a value, which might be the next argument.
if !has_value && index < len(os.Args)-1 { if !has_value && len(f.args) > 0 {
// value is the next arg // value is the next arg
has_value = true has_value = true
index++ value, f.args = f.args[0], f.args[1:]
value = os.Args[index]
} }
if !has_value { if !has_value {
fmt.Fprintf(os.Stderr, "flag needs an argument: -%s\n", name) fmt.Fprintf(os.Stderr, "flag needs an argument: -%s\n", name)
...@@ -484,49 +494,17 @@ func (f *allFlags) parseOne(index int) (ok bool, next int) { ...@@ -484,49 +494,17 @@ func (f *allFlags) parseOne(index int) (ok bool, next int) {
} }
} }
flags.actual[name] = flag flags.actual[name] = flag
return true, index + 1 return true
} }
// Parse parses the command-line flags. Must be called after all flags are defined // Parse parses the command-line flags. Must be called after all flags are defined
// and before any are accessed by the program. // and before any are accessed by the program.
func Parse() { func Parse() {
for i := 1; i < len(os.Args); { flags.args = os.Args[1:]
ok, next := flags.parseOne(i) for flags.parseOne() {
if next > 0 {
flags.first_arg = next
i = next
}
if !ok {
break
}
} }
} }
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will panic rather
// than exit the program.
// For testing only!
func ResetForTesting(usage func()) {
flags = &allFlags{make(map[string]*Flag), make(map[string]*Flag), 1}
Usage = usage
panicOnError = true
}
// ParseForTesting parses the flag state using the provided arguments. It
// should be called after 1) ResetForTesting and 2) setting up the new flags.
// The return value reports whether the parse was error-free.
// For testing only!
func ParseForTesting(args []string) (result bool) {
defer func() {
if recover() != nil {
result = false
}
}()
os.Args = args
Parse()
return true
}
func init() { func init() {
flags = &allFlags{make(map[string]*Flag), make(map[string]*Flag), 1} flags = &allFlags{make(map[string]*Flag), make(map[string]*Flag), os.Args[1:]}
} }
...@@ -7,6 +7,7 @@ package flag_test ...@@ -7,6 +7,7 @@ package flag_test
import ( import (
. "flag" . "flag"
"fmt" "fmt"
"os"
"testing" "testing"
) )
...@@ -180,3 +181,21 @@ func TestUserDefined(t *testing.T) { ...@@ -180,3 +181,21 @@ func TestUserDefined(t *testing.T) {
t.Errorf("expected value %q got %q", expect, v.String()) t.Errorf("expected value %q got %q", expect, v.String())
} }
} }
func TestChangingArgs(t *testing.T) {
ResetForTesting(func() { t.Fatal("bad parse") })
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
before := Bool("before", false, "")
Parse()
cmd := Arg(0)
os.Args = Args()
after := Bool("after", false, "")
Parse()
args := Args()
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
t.Fatal("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment