Skip to content

Commit

Permalink
Clean up Parse interface
Browse files Browse the repository at this point in the history
  • Loading branch information
gaal committed Feb 2, 2012
1 parent 0db6b8a commit 0c770aa
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 62 deletions.
11 changes: 8 additions & 3 deletions example/ex1/cat1.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ i,input-encoding= charset input is encoded in [utf-8]
o,output-encoding= charset output is encoded in [utf-8]
r,repeat= repeat every line some number of times [1]
v,verbose be verbose
author= authors you like (may be repeated)
`

func main() {
spec := options.NewOptions(mySpec)
opt, flags, extra := spec.Parse(os.Args[1:])
opt := spec.Parse(os.Args[1:])

fmt.Printf("I will concatenate the files: %q\n", extra)
fmt.Printf("I will concatenate the files: %q\n", opt.Extra)
if opt.GetBool("number") {
fmt.Println("I will number each line")
}
Expand All @@ -42,6 +43,10 @@ func main() {
}
fmt.Printf("Input charset: %s\n", opt.Get("input-encoding"))
fmt.Printf("Output charset: %s\n", opt.Get("output-encoding"))
authors := options.GetAll("--author", opt.Flags) // Note, you need "--".
if len(authors) > 0 {
fmt.Printf("You like these authors. I'll tell you if I see them: %q\n", authors)
}

fmt.Printf("For reference, here are the flags you gave me: %v\n", flags)
fmt.Printf("For reference, here are the flags you gave me: %v\n", opt.Flags)
}
24 changes: 20 additions & 4 deletions example/ex2/cat2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package main

import (
"math/big"
"fmt"
"os"
"crypto/rand"

"github.com/gaal/go-options/options"
)
Expand All @@ -14,6 +16,8 @@ var (
n, e bool
in, out string = "utf-8", "utf-8"
r, v int = 1, 0
c float32 = 0.1
cInt int64
)

const mySpec = `
Expand All @@ -28,9 +32,11 @@ i,input-encoding= charset input is encoded in [utf-8]
o,output-encoding= charset output is encoded in [utf-8]
r,repeat= repeat every line some number of times [1]
v,verbose be verbose
c,cookie-chance= chance to get a fortune cookie [0.1]
`

func myParseCallback(spec *options.OptionSpec, option string, argument *string) {
// Example custom option callback
func MyParseCallback(spec *options.OptionSpec, option string, argument *string) {
if argument != nil {
switch spec.GetCanonical(option) {
case "input-encoding":
Expand All @@ -39,6 +45,9 @@ func myParseCallback(spec *options.OptionSpec, option string, argument *string)
out = *argument
case "repeat":
fmt.Sscanf(*argument, "%d", &r)
case "cookie-chance":
fmt.Sscanf(*argument, "%f", &c)
cInt = int64(c * 1000)
default:
spec.PrintUsageAndExit("Unknown option: " + option)
}
Expand All @@ -61,10 +70,10 @@ func myParseCallback(spec *options.OptionSpec, option string, argument *string)
}

func main() {
spec := options.NewOptions(mySpec).SetParseCallback(myParseCallback)
_, _, extra := spec.Parse(os.Args[1:])
spec := options.NewOptions(mySpec).SetParseCallback(MyParseCallback)
opt := spec.Parse(os.Args[1:])

fmt.Printf("I will concatenate the files: %q\n", extra)
fmt.Printf("I will concatenate the files: %q\n", opt.Extra)
if n {
fmt.Println("I will number each line")
}
Expand All @@ -79,4 +88,11 @@ func main() {
}
fmt.Printf("Input charset: %s\n", in)
fmt.Printf("Output charset: %s\n", out)

fmt.Printf("Chance for a cookie: %.2f\n", c)
var max = big.NewInt(1000)
rnd, _ := rand.Int(rand.Reader, max)
if cInt > rnd.Int64() {
fmt.Println("*** You got a cookie! Yay! ***")
}
}
63 changes: 32 additions & 31 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ a line containing only two dashes, then a series of option specifications:
Then parse the command line:
opt, flags, extra := s.Parse(os.Args[1:])
opt := s.Parse(os.Args[1:])
(For another way to do this, see the secion "Callback interface" below.)
This return value is of Options type and has the parsed command line (for
an alternate way of parsing the command line, see the "Callback interface"
descibed below).
Options may have any number of aliases; the last one is the "canonical"
name and the one your program must use when reading values.
Expand All @@ -61,14 +63,14 @@ The user can say either "--foo=bar" or "--foo bar".
Parsing stops if "--" is given on the command line.
The "extra" return value of Parse contains all non-option command line
The "Extra" field of the returned Options contains all non-option command line
input. In the case of a cat command, this would be the filenames to concat.
By default, options permits such extra values. Setting UnknownValuesFatal
causes it to panic when it enconters them instead.
The "flags" return value of Parse contains the series of flags as given on
the command line, including repeated ones (which are suppressed in opt --
The "Flags" field of the returned Options contains the series of flags as given
on the command line, including repeated ones (which are suppressed in opt --
it only contains the last value). This allows you to do your own handling
of repeated options easily.
Expand Down Expand Up @@ -126,6 +128,8 @@ const EX_USAGE = 64 // Exit status for incorrect command lines.
type Options struct {
opts map[string]string
known map[string]bool
Flags [][]string // Original flags presented on the command line
Extra []string // Non-option command line arguments left on the command line
}

// Get returns the value of an option, which must be known to this parse.
Expand Down Expand Up @@ -315,15 +319,15 @@ func (s *OptionSpec) GetCanonical(option string) string {

// Parse performs the actual parsing of a command line according to an
// OptionSpec.
// It returns three values: opt, flags, extra; see the package description
// for an overview of what they mean and how they are used.
// It returns an Options value; see the package description for an overview
// of what it means and how to use it.
// In case of parse error, a panic is thrown.
// TODO(gaal): decide if gentler error signalling is more useful.
func (s *OptionSpec) Parse(args []string) (Options, [][]string, []string) {
func (s *OptionSpec) Parse(args []string) Options {
// TODO(gaal): extract to constant.
flagRe := regexp.MustCompile(`^((--?)([-\w]+))(=(.*))?$`)

opt := Options{}
opt := Options{opts: make(map[string]string), Flags: make([][]string, 0), Extra: make([]string, 0)}
opt.opts = make(map[string]string)
for flag, def := range s.defaults {
opt.opts[flag] = def
Expand All @@ -332,8 +336,6 @@ func (s *OptionSpec) Parse(args []string) (Options, [][]string, []string) {
for _, canonical := range s.aliases {
opt.known[canonical] = true
}
flags := make([][]string, 0)
extra := make([]string, 0)

for i := 0; i < len(args); i++ { // Can't use range because we may bump i.
val := args[i]
Expand All @@ -346,30 +348,14 @@ func (s *OptionSpec) Parse(args []string) (Options, [][]string, []string) {
if s.UnknownValuesFatal {
panic("Unexpected argument: " + val + "\n" + s.Usage)
}
extra = append(extra, val)
opt.Extra = append(opt.Extra, val)
continue
}
presentedFlag := flagParts[1] // "presented" by the user.
presentedFlagName := flagParts[3]
haveSelfValue := flagParts[4] != ""
selfValue := flagParts[5]
canonical, known := s.aliases[presentedFlagName]
var nextArg *string = nil
if i < len(args)-1 {
nextArg = &(args[i+1])
}
needsArg := known && s.requiresArg[canonical]
if !known && nextArg != nil && !strings.HasPrefix(*nextArg, "-") {
needsArg = true
}

arg := func() *string {
if haveSelfValue {
return &selfValue
}
i++
return nextArg
}

callback := s.ParseCallback
if callback == nil {
Expand Down Expand Up @@ -399,13 +385,28 @@ func (s *OptionSpec) Parse(args []string) (Options, [][]string, []string) {
}
}
if value != nil {
flags = append(flags, []string{presentedFlag, *value})
opt.Flags = append(opt.Flags, []string{presentedFlag, *value})
} else {
flags = append(flags, []string{presentedFlag})
opt.Flags = append(opt.Flags, []string{presentedFlag})
}
}
}

var nextArg *string = nil
if i < len(args)-1 {
nextArg = &(args[i+1])
}
needsArg := known && s.requiresArg[canonical]
if !known && nextArg != nil && !strings.HasPrefix(*nextArg, "-") {
needsArg = true
}
arg := func() *string {
if haveSelfValue {
return &selfValue
}
i++
return nextArg
}
if needsArg {
callback(s, presentedFlagName, arg())
} else {
Expand All @@ -414,7 +415,7 @@ func (s *OptionSpec) Parse(args []string) (Options, [][]string, []string) {

}

return opt, flags, extra
return opt
}

// PrintUsageAndExit writes the usage string and exits the program.
Expand Down
48 changes: 24 additions & 24 deletions options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import (
"testing"
)

func exitToPanic(code int) {
panic(fmt.Sprintf("exiting with code: %d", code))
}

func TestNewOptions_trivial(t *testing.T) {
s := NewOptions("TestNewOptions_trivial\n--\na,bbb,ccc= doc [def]")
s.Exit = exitToPanic
Expand All @@ -27,21 +23,21 @@ func TestNewOptions_trivial(t *testing.T) {
func TestParse_trivialDefault(t *testing.T) {
s := NewOptions("TestParse_trivialDefault\n--\na,bbb,ccc= doc [def]")
s.Exit = exitToPanic
opt, flags, extra := s.Parse([]string{})
opt := s.Parse([]string{})
ExpectEquals(t, "def", opt.Get("ccc"), "default (via canonical)")
ExpectEquals(t, [][]string{}, flags, "no flags specified")
ExpectEquals(t, []string{}, extra, "no extra args given")
ExpectEquals(t, [][]string{}, opt.Flags, "no flags specified")
ExpectEquals(t, []string{}, opt.Extra, "no extra args given")
}

func TestParse_trivial(t *testing.T) {
s := NewOptions("TestParse_trivial\n--\na,bbb,ccc= doc [def]")
s.Exit = exitToPanic
test := func(name string) {
opt, flags, extra := s.Parse([]string{name, "myval"})
opt := s.Parse([]string{name, "myval"})
ExpectEquals(t, "myval", opt.opts["ccc"], "canonical direct access - "+name)
ExpectEquals(t, "myval", opt.Get("ccc"), "Get - "+name)
ExpectEquals(t, [][]string{[]string{name, "myval"}}, flags, "flags specified - "+name)
ExpectEquals(t, []string{}, extra, "no extra args given")
ExpectEquals(t, [][]string{[]string{name, "myval"}}, opt.Flags, "flags specified - "+name)
ExpectEquals(t, []string{}, opt.Extra, "no extra args given")
}
test("--ccc")
test("--bbb")
Expand All @@ -52,11 +48,11 @@ func TestParse_trivialSelfVal(t *testing.T) {
s := NewOptions("TestParse_trivialSelfVal\n--\na,bbb,ccc= doc [def]")
s.Exit = exitToPanic
test := func(name string) {
opt, flags, extra := s.Parse([]string{name + "=myval"})
opt := s.Parse([]string{name + "=myval"})
ExpectEquals(t, "myval", opt.opts["ccc"], "canonical direct access - "+name)
ExpectEquals(t, "myval", opt.Get("ccc"), "Get - "+name)
ExpectEquals(t, [][]string{[]string{name, "myval"}}, flags, "flags specified - "+name)
ExpectEquals(t, []string{}, extra, "no extra args given")
ExpectEquals(t, [][]string{[]string{name, "myval"}}, opt.Flags, "flags specified - "+name)
ExpectEquals(t, []string{}, opt.Extra, "no extra args given")
}
test("--ccc")
test("--bbb")
Expand All @@ -73,10 +69,10 @@ func TestParse_missingArgument(t *testing.T) {
func TestParse_extra(t *testing.T) {
s := NewOptions("TestParse_extra\n--\nccc= doc [def]")
s.Exit = exitToPanic
opt, flags, extra := s.Parse([]string{"extra1", "--ccc", "myval", "extra2"})
opt := s.Parse([]string{"extra1", "--ccc", "myval", "extra2"})
ExpectEquals(t, "myval", opt.Get("ccc"), "Get")
ExpectEquals(t, [][]string{[]string{"--ccc", "myval"}}, flags, "flags specified")
ExpectEquals(t, []string{"extra1", "extra2"}, extra, "extra args given")
ExpectEquals(t, [][]string{[]string{"--ccc", "myval"}}, opt.Flags, "flags specified")
ExpectEquals(t, []string{"extra1", "extra2"}, opt.Extra, "extra args given")

s.SetUnknownValuesFatal(true)
ExpectDies(t, func() {
Expand All @@ -94,31 +90,31 @@ func TestParse_unknownFlags(t *testing.T) {
}, "dies on unknown options unless asked not to")

s.SetUnknownOptionsFatal(false)
opt, flags, extra := s.Parse([]string{"--unk1", "--ccc", "myval", "--unk2", "val2", "--unk3"})
opt := s.Parse([]string{"--unk1", "--ccc", "myval", "--unk2", "val2", "--unk3"})
ExpectEquals(t, "myval", opt.Get("ccc"), "Get")
ExpectEquals(t, [][]string{
[]string{"--unk1"},
[]string{"--ccc", "myval"},
[]string{"--unk2", "val2"},
[]string{"--unk3"}},
flags, "flags specified")
ExpectEquals(t, []string{}, extra, "no extra args given")
opt.Flags, "flags specified")
ExpectEquals(t, []string{}, opt.Extra, "no extra args given")
}

func TestParse_override(t *testing.T) {
s := NewOptions("TestParse_override\n--\na,bbb,ccc= doc [def]")
s.Exit = exitToPanic
opt, _, _ := s.Parse([]string{"--bbb", "111", "--ccc", "222", "-a", "333"})
opt := s.Parse([]string{"--bbb", "111", "--ccc", "222", "-a", "333"})
ExpectEquals(t, "333", opt.Get("ccc"), "last flag wins")
}

func TestParse_counting(t *testing.T) {
s := NewOptions("TestParse_counting\n--\na,bbb,ccc doc")
s.Exit = exitToPanic
opt, _, _ := s.Parse([]string{"-a"})
opt := s.Parse([]string{"-a"})
ExpectEquals(t, 1, opt.GetInt("ccc"), "implicit value")

opt, _, _ = s.Parse([]string{"-a", "-a", "--ccc"})
opt = s.Parse([]string{"-a", "-a", "--ccc"})
ExpectEquals(t, 3, opt.GetInt("ccc"), "implicit value - repetitions")
}

Expand Down Expand Up @@ -159,7 +155,7 @@ func TestCallbackInterface(t *testing.T) {
}
}
}
_, _, extra := s.Parse(
opt := s.Parse(
[]string{"--unk1", "--ccc", "myval", "--bbb=noooo", "hi", "-a", "myotherval",
"--unk2", "val2", "--ddd", "--unk3"})
ExpectEquals(t, "myotherval", ccc, "known option")
Expand All @@ -169,7 +165,11 @@ func TestCallbackInterface(t *testing.T) {
[][]string{[]string{"unk1"}, []string{"unk2", "val2"}, []string{"unk3"}},
unknown,
"unknown options, with and without arguments")
ExpectEquals(t, []string{"hi"}, extra, "extra")
ExpectEquals(t, []string{"hi"}, opt.Extra, "extra")
}

func exitToPanic(code int) {
panic(fmt.Sprintf("exiting with code: %d", code))
}

// These are little testing utilities that I like. May move to a separate module one day.
Expand Down

0 comments on commit 0c770aa

Please sign in to comment.