Browse Source

Merge pull request #1541 from bcwaldon/client-peers

Use `-peers` in `etcdctl members` commands
Brian Waldon 11 years ago
parent
commit
38250d3fac

+ 2 - 2
Godeps/Godeps.json

@@ -16,8 +16,8 @@
 		},
 		},
 		{
 		{
 			"ImportPath": "github.com/codegangsta/cli",
 			"ImportPath": "github.com/codegangsta/cli",
-			"Comment": "1.0.0-72-gbb91895",
-			"Rev": "bb9189510af1f49580c073c9e59e8bf288f0df27"
+			"Comment": "1.2.0-26-gf7ebb76",
+			"Rev": "f7ebb761e83e21225d1d8954fde853bf8edd46c4"
 		},
 		},
 		{
 		{
 			"ImportPath": "github.com/coreos/go-etcd/etcd",
 			"ImportPath": "github.com/coreos/go-etcd/etcd",

+ 4 - 0
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml

@@ -1,2 +1,6 @@
 language: go
 language: go
 go: 1.1
 go: 1.1
+
+script:
+- go vet ./...
+- go test -v ./...

+ 42 - 12
Godeps/_workspace/src/github.com/codegangsta/cli/README.md

@@ -9,17 +9,17 @@ http://godoc.org/github.com/codegangsta/cli
 ## Overview
 ## Overview
 Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
 Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
 
 
-This is where cli.go comes into play. cli.go makes command line programming fun, organized, and expressive!
+**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive!
 
 
 ## Installation
 ## Installation
 Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html).
 Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html).
 
 
-To install cli.go, simply run:
+To install `cli.go`, simply run:
 ```
 ```
 $ go get github.com/codegangsta/cli
 $ go get github.com/codegangsta/cli
 ```
 ```
 
 
-Make sure your PATH includes to the `$GOPATH/bin` directory so your commands can be easily used:
+Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
 ```
 ```
 export PATH=$PATH:$GOPATH/bin
 export PATH=$PATH:$GOPATH/bin
 ```
 ```
@@ -122,7 +122,7 @@ GLOBAL OPTIONS
 ```
 ```
 
 
 ### Arguments
 ### Arguments
-You can lookup arguments by calling the `Args` function on cli.Context.
+You can lookup arguments by calling the `Args` function on `cli.Context`.
 
 
 ``` go
 ``` go
 ...
 ...
@@ -137,7 +137,11 @@ Setting and querying flags is simple.
 ``` go
 ``` go
 ...
 ...
 app.Flags = []cli.Flag {
 app.Flags = []cli.Flag {
-  cli.StringFlag{"lang", "english", "language for the greeting"},
+  cli.StringFlag{
+    Name: "lang",
+    Value: "english",
+    Usage: "language for the greeting",
+  },
 }
 }
 app.Action = func(c *cli.Context) {
 app.Action = func(c *cli.Context) {
   name := "someone"
   name := "someone"
@@ -155,11 +159,30 @@ app.Action = func(c *cli.Context) {
 
 
 #### Alternate Names
 #### Alternate Names
 
 
-You can set alternate (or short) names for flags by providing a comma-delimited list for the Name. e.g.
+You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
 
 
 ``` go
 ``` go
 app.Flags = []cli.Flag {
 app.Flags = []cli.Flag {
-  cli.StringFlag{"lang, l", "english", "language for the greeting"},
+  cli.StringFlag{
+    Name: "lang, l",
+    Value: "english",
+    Usage: "language for the greeting",
+  },
+}
+```
+
+#### Values from the Environment
+
+You can also have the default value set from the environment via `EnvVar`.  e.g.
+
+``` go
+app.Flags = []cli.Flag {
+  cli.StringFlag{
+    Name: "lang, l",
+    Value: "english",
+    Usage: "language for the greeting",
+    EnvVar: "APP_LANG",
+  },
 }
 }
 ```
 ```
 
 
@@ -214,8 +237,8 @@ app.Commands = []cli.Command{
 
 
 ### Bash Completion
 ### Bash Completion
 
 
-You can enable completion commands by setting the EnableBashCompletion
-flag on the App object.  By default, this setting will only auto-complete to
+You can enable completion commands by setting the `EnableBashCompletion`
+flag on the `App` object.  By default, this setting will only auto-complete to
 show an app's subcommands, but you can write your own completion methods for
 show an app's subcommands, but you can write your own completion methods for
 the App or its subcommands.
 the App or its subcommands.
 ```go
 ```go
@@ -237,7 +260,7 @@ app.Commands = []cli.Command{
         return
         return
       }
       }
       for _, t := range tasks {
       for _, t := range tasks {
-        println(t)
+        fmt.Println(t)
       }
       }
     },
     },
   }
   }
@@ -247,11 +270,18 @@ app.Commands = []cli.Command{
 
 
 #### To Enable
 #### To Enable
 
 
-Source the autocomplete/bash_autocomplete file in your .bashrc file while
-setting the PROG variable to the name of your program:
+Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
+setting the `PROG` variable to the name of your program:
 
 
 `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
 `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
 
 
 
 
+## Contribution Guidelines
+Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
+
+If you are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
+
+If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
+
 ## About
 ## About
 cli.go is written by none other than the [Code Gangsta](http://codegangsta.io)
 cli.go is written by none other than the [Code Gangsta](http://codegangsta.io)

+ 15 - 7
Godeps/_workspace/src/github.com/codegangsta/cli/app.go

@@ -22,6 +22,8 @@ type App struct {
 	Flags []Flag
 	Flags []Flag
 	// Boolean to enable bash completion commands
 	// Boolean to enable bash completion commands
 	EnableBashCompletion bool
 	EnableBashCompletion bool
+	// Boolean to hide built-in help command
+	HideHelp bool
 	// An action to execute when the bash-completion flag is set
 	// An action to execute when the bash-completion flag is set
 	BashComplete func(context *Context)
 	BashComplete func(context *Context)
 	// An action to execute before any subcommands are run, but after the context is ready
 	// An action to execute before any subcommands are run, but after the context is ready
@@ -58,16 +60,15 @@ func NewApp() *App {
 		BashComplete: DefaultAppComplete,
 		BashComplete: DefaultAppComplete,
 		Action:       helpCommand.Action,
 		Action:       helpCommand.Action,
 		Compiled:     compileTime(),
 		Compiled:     compileTime(),
-		Author:       "Author",
-		Email:        "unknown@email",
 	}
 	}
 }
 }
 
 
 // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
 // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
 func (a *App) Run(arguments []string) error {
 func (a *App) Run(arguments []string) error {
 	// append help to commands
 	// append help to commands
-	if a.Command(helpCommand.Name) == nil {
+	if a.Command(helpCommand.Name) == nil && !a.HideHelp {
 		a.Commands = append(a.Commands, helpCommand)
 		a.Commands = append(a.Commands, helpCommand)
+		a.appendFlag(HelpFlag)
 	}
 	}
 
 
 	//append version/help flags
 	//append version/help flags
@@ -75,7 +76,6 @@ func (a *App) Run(arguments []string) error {
 		a.appendFlag(BashCompletionFlag)
 		a.appendFlag(BashCompletionFlag)
 	}
 	}
 	a.appendFlag(VersionFlag)
 	a.appendFlag(VersionFlag)
-	a.appendFlag(HelpFlag)
 
 
 	// parse flags
 	// parse flags
 	set := flagSet(a.Name, a.Flags)
 	set := flagSet(a.Name, a.Flags)
@@ -131,12 +131,21 @@ func (a *App) Run(arguments []string) error {
 	return nil
 	return nil
 }
 }
 
 
+// Another entry point to the cli app, takes care of passing arguments and error handling
+func (a *App) RunAndExitOnError() {
+	if err := a.Run(os.Args); err != nil {
+		os.Stderr.WriteString(fmt.Sprintln(err))
+		os.Exit(1)
+	}
+}
+
 // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
 // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
 func (a *App) RunAsSubcommand(ctx *Context) error {
 func (a *App) RunAsSubcommand(ctx *Context) error {
 	// append help to commands
 	// append help to commands
 	if len(a.Commands) > 0 {
 	if len(a.Commands) > 0 {
-		if a.Command(helpCommand.Name) == nil {
+		if a.Command(helpCommand.Name) == nil && !a.HideHelp {
 			a.Commands = append(a.Commands, helpCommand)
 			a.Commands = append(a.Commands, helpCommand)
+			a.appendFlag(HelpFlag)
 		}
 		}
 	}
 	}
 
 
@@ -144,14 +153,13 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
 	if a.EnableBashCompletion {
 	if a.EnableBashCompletion {
 		a.appendFlag(BashCompletionFlag)
 		a.appendFlag(BashCompletionFlag)
 	}
 	}
-	a.appendFlag(HelpFlag)
 
 
 	// parse flags
 	// parse flags
 	set := flagSet(a.Name, a.Flags)
 	set := flagSet(a.Name, a.Flags)
 	set.SetOutput(ioutil.Discard)
 	set.SetOutput(ioutil.Discard)
 	err := set.Parse(ctx.Args().Tail())
 	err := set.Parse(ctx.Args().Tail())
 	nerr := normalizeFlags(a.Flags, set)
 	nerr := normalizeFlags(a.Flags, set)
-	context := NewContext(a, set, set)
+	context := NewContext(a, set, ctx.globalSet)
 
 
 	if nerr != nil {
 	if nerr != nil {
 		fmt.Println(nerr)
 		fmt.Println(nerr)

+ 59 - 7
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go

@@ -2,9 +2,10 @@ package cli_test
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
 	"os"
 	"os"
 	"testing"
 	"testing"
+
+	"github.com/codegangsta/cli"
 )
 )
 
 
 func ExampleApp() {
 func ExampleApp() {
@@ -42,7 +43,11 @@ func ExampleAppSubcommand() {
 					Usage:       "sends a greeting in english",
 					Usage:       "sends a greeting in english",
 					Description: "greets someone in english",
 					Description: "greets someone in english",
 					Flags: []cli.Flag{
 					Flags: []cli.Flag{
-						cli.StringFlag{"name", "Bob", "Name of the person to greet"},
+						cli.StringFlag{
+							Name:  "name",
+							Value: "Bob",
+							Usage: "Name of the person to greet",
+						},
 					},
 					},
 					Action: func(c *cli.Context) {
 					Action: func(c *cli.Context) {
 						fmt.Println("Hello,", c.String("name"))
 						fmt.Println("Hello,", c.String("name"))
@@ -83,12 +88,10 @@ func ExampleAppHelp() {
 	//    describeit - use it to see a description
 	//    describeit - use it to see a description
 	//
 	//
 	// USAGE:
 	// USAGE:
-	//    command describeit [command options] [arguments...]
+	//    command describeit [arguments...]
 	//
 	//
 	// DESCRIPTION:
 	// DESCRIPTION:
 	//    This is how we describe describeit the function
 	//    This is how we describe describeit the function
-	//
-	// OPTIONS:
 }
 }
 
 
 func ExampleAppBashComplete() {
 func ExampleAppBashComplete() {
@@ -254,11 +257,11 @@ func TestApp_ParseSliceFlags(t *testing.T) {
 	var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
 	var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
 
 
 	if !IntsEquals(parsedIntSlice, expectedIntSlice) {
 	if !IntsEquals(parsedIntSlice, expectedIntSlice) {
-		t.Errorf("%s does not match %s", parsedIntSlice, expectedIntSlice)
+		t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
 	}
 	}
 
 
 	if !StrsEquals(parsedStringSlice, expectedStringSlice) {
 	if !StrsEquals(parsedStringSlice, expectedStringSlice) {
-		t.Errorf("%s does not match %s", parsedStringSlice, expectedStringSlice)
+		t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
 	}
 	}
 }
 }
 
 
@@ -347,6 +350,26 @@ func TestAppHelpPrinter(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestAppVersionPrinter(t *testing.T) {
+	oldPrinter := cli.VersionPrinter
+	defer func() {
+		cli.VersionPrinter = oldPrinter
+	}()
+
+	var wasCalled = false
+	cli.VersionPrinter = func(c *cli.Context) {
+		wasCalled = true
+	}
+
+	app := cli.NewApp()
+	ctx := cli.NewContext(app, nil, nil)
+	cli.ShowVersion(ctx)
+
+	if wasCalled == false {
+		t.Errorf("Version printer expected to be called, but was not")
+	}
+}
+
 func TestAppCommandNotFound(t *testing.T) {
 func TestAppCommandNotFound(t *testing.T) {
 	beforeRun, subcommandRun := false, false
 	beforeRun, subcommandRun := false, false
 	app := cli.NewApp()
 	app := cli.NewApp()
@@ -369,3 +392,32 @@ func TestAppCommandNotFound(t *testing.T) {
 	expect(t, beforeRun, true)
 	expect(t, beforeRun, true)
 	expect(t, subcommandRun, false)
 	expect(t, subcommandRun, false)
 }
 }
+
+func TestGlobalFlagsInSubcommands(t *testing.T) {
+	subcommandRun := false
+	app := cli.NewApp()
+
+	app.Flags = []cli.Flag{
+		cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
+	}
+
+	app.Commands = []cli.Command{
+		cli.Command{
+			Name: "foo",
+			Subcommands: []cli.Command{
+				{
+					Name: "bar",
+					Action: func(c *cli.Context) {
+						if c.GlobalBool("debug") {
+							subcommandRun = true
+						}
+					},
+				},
+			},
+		},
+	}
+
+	app.Run([]string{"command", "-d", "foo", "bar"})
+
+	expect(t, subcommandRun, true)
+}

+ 1 - 1
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete

@@ -5,7 +5,7 @@ _cli_bash_autocomplete() {
      COMPREPLY=()
      COMPREPLY=()
      cur="${COMP_WORDS[COMP_CWORD]}"
      cur="${COMP_WORDS[COMP_CWORD]}"
      prev="${COMP_WORDS[COMP_CWORD-1]}"
      prev="${COMP_WORDS[COMP_CWORD-1]}"
-     opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion )
+     opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
      COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
      COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
      return 0
      return 0
  }
  }

+ 5 - 0
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete

@@ -0,0 +1,5 @@
+autoload -U compinit && compinit
+autoload -U bashcompinit && bashcompinit
+
+script_dir=$(dirname $0)
+source ${script_dir}/bash_autocomplete

+ 17 - 4
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go

@@ -1,8 +1,9 @@
 package cli_test
 package cli_test
 
 
 import (
 import (
-	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
 	"os"
 	"os"
+
+	"github.com/codegangsta/cli"
 )
 )
 
 
 func Example() {
 func Example() {
@@ -47,7 +48,11 @@ func ExampleSubcommand() {
 					Usage:       "sends a greeting in english",
 					Usage:       "sends a greeting in english",
 					Description: "greets someone in english",
 					Description: "greets someone in english",
 					Flags: []cli.Flag{
 					Flags: []cli.Flag{
-						cli.StringFlag{"name", "Bob", "Name of the person to greet"},
+						cli.StringFlag{
+							Name:  "name",
+							Value: "Bob",
+							Usage: "Name of the person to greet",
+						},
 					},
 					},
 					Action: func(c *cli.Context) {
 					Action: func(c *cli.Context) {
 						println("Hello, ", c.String("name"))
 						println("Hello, ", c.String("name"))
@@ -57,7 +62,11 @@ func ExampleSubcommand() {
 					ShortName: "sp",
 					ShortName: "sp",
 					Usage:     "sends a greeting in spanish",
 					Usage:     "sends a greeting in spanish",
 					Flags: []cli.Flag{
 					Flags: []cli.Flag{
-						cli.StringFlag{"surname", "Jones", "Surname of the person to greet"},
+						cli.StringFlag{
+							Name:  "surname",
+							Value: "Jones",
+							Usage: "Surname of the person to greet",
+						},
 					},
 					},
 					Action: func(c *cli.Context) {
 					Action: func(c *cli.Context) {
 						println("Hola, ", c.String("surname"))
 						println("Hola, ", c.String("surname"))
@@ -67,7 +76,11 @@ func ExampleSubcommand() {
 					ShortName: "fr",
 					ShortName: "fr",
 					Usage:     "sends a greeting in french",
 					Usage:     "sends a greeting in french",
 					Flags: []cli.Flag{
 					Flags: []cli.Flag{
-						cli.StringFlag{"nickname", "Stevie", "Nickname of the person to greet"},
+						cli.StringFlag{
+							Name:  "nickname",
+							Value: "Stevie",
+							Usage: "Nickname of the person to greet",
+						},
 					},
 					},
 					Action: func(c *cli.Context) {
 					Action: func(c *cli.Context) {
 						println("Bonjour, ", c.String("nickname"))
 						println("Bonjour, ", c.String("nickname"))

+ 13 - 5
Godeps/_workspace/src/github.com/codegangsta/cli/command.go

@@ -29,6 +29,8 @@ type Command struct {
 	Flags []Flag
 	Flags []Flag
 	// Treat all flags as normal arguments if true
 	// Treat all flags as normal arguments if true
 	SkipFlagParsing bool
 	SkipFlagParsing bool
+	// Boolean to hide built-in help command
+	HideHelp bool
 }
 }
 
 
 // Invokes the command given the context, parses ctx.Args() to generate command-specific flags
 // Invokes the command given the context, parses ctx.Args() to generate command-specific flags
@@ -38,11 +40,13 @@ func (c Command) Run(ctx *Context) error {
 		return c.startApp(ctx)
 		return c.startApp(ctx)
 	}
 	}
 
 
-	// append help to flags
-	c.Flags = append(
-		c.Flags,
-		HelpFlag,
-	)
+	if !c.HideHelp {
+		// append help to flags
+		c.Flags = append(
+			c.Flags,
+			HelpFlag,
+		)
+	}
 
 
 	if ctx.App.EnableBashCompletion {
 	if ctx.App.EnableBashCompletion {
 		c.Flags = append(c.Flags, BashCompletionFlag)
 		c.Flags = append(c.Flags, BashCompletionFlag)
@@ -114,9 +118,13 @@ func (c Command) startApp(ctx *Context) error {
 		app.Usage = c.Usage
 		app.Usage = c.Usage
 	}
 	}
 
 
+	// set CommandNotFound
+	app.CommandNotFound = ctx.App.CommandNotFound
+
 	// set the flags and commands
 	// set the flags and commands
 	app.Commands = c.Subcommands
 	app.Commands = c.Subcommands
 	app.Flags = c.Flags
 	app.Flags = c.Flags
+	app.HideHelp = c.HideHelp
 
 
 	// bash completion
 	// bash completion
 	app.EnableBashCompletion = ctx.App.EnableBashCompletion
 	app.EnableBashCompletion = ctx.App.EnableBashCompletion

+ 2 - 1
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go

@@ -2,8 +2,9 @@ package cli_test
 
 
 import (
 import (
 	"flag"
 	"flag"
-	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
 	"testing"
 	"testing"
+
+	"github.com/codegangsta/cli"
 )
 )
 
 
 func TestCommandDoNotIgnoreFlags(t *testing.T) {
 func TestCommandDoNotIgnoreFlags(t *testing.T) {

+ 44 - 0
Godeps/_workspace/src/github.com/codegangsta/cli/context.go

@@ -5,6 +5,7 @@ import (
 	"flag"
 	"flag"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"time"
 )
 )
 
 
 // Context is a type that is passed through to
 // Context is a type that is passed through to
@@ -29,6 +30,11 @@ func (c *Context) Int(name string) int {
 	return lookupInt(name, c.flagSet)
 	return lookupInt(name, c.flagSet)
 }
 }
 
 
+// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
+func (c *Context) Duration(name string) time.Duration {
+	return lookupDuration(name, c.flagSet)
+}
+
 // Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
 // Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
 func (c *Context) Float64(name string) float64 {
 func (c *Context) Float64(name string) float64 {
 	return lookupFloat64(name, c.flagSet)
 	return lookupFloat64(name, c.flagSet)
@@ -69,6 +75,11 @@ func (c *Context) GlobalInt(name string) int {
 	return lookupInt(name, c.globalSet)
 	return lookupInt(name, c.globalSet)
 }
 }
 
 
+// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
+func (c *Context) GlobalDuration(name string) time.Duration {
+	return lookupDuration(name, c.globalSet)
+}
+
 // Looks up the value of a global bool flag, returns false if no bool flag exists
 // Looks up the value of a global bool flag, returns false if no bool flag exists
 func (c *Context) GlobalBool(name string) bool {
 func (c *Context) GlobalBool(name string) bool {
 	return lookupBool(name, c.globalSet)
 	return lookupBool(name, c.globalSet)
@@ -105,6 +116,18 @@ func (c *Context) IsSet(name string) bool {
 	return c.setFlags[name] == true
 	return c.setFlags[name] == true
 }
 }
 
 
+// Returns a slice of flag names used in this context.
+func (c *Context) FlagNames() (names []string) {
+	for _, flag := range c.Command.Flags {
+		name := strings.Split(flag.getName(), ",")[0]
+		if name == "help" {
+			continue
+		}
+		names = append(names, name)
+	}
+	return
+}
+
 type Args []string
 type Args []string
 
 
 // Returns the command line arguments associated with the context.
 // Returns the command line arguments associated with the context.
@@ -140,6 +163,15 @@ func (a Args) Present() bool {
 	return len(a) != 0
 	return len(a) != 0
 }
 }
 
 
+// Swaps arguments at the given indexes
+func (a Args) Swap(from, to int) error {
+	if from >= len(a) || to >= len(a) {
+		return errors.New("index out of range")
+	}
+	a[from], a[to] = a[to], a[from]
+	return nil
+}
+
 func lookupInt(name string, set *flag.FlagSet) int {
 func lookupInt(name string, set *flag.FlagSet) int {
 	f := set.Lookup(name)
 	f := set.Lookup(name)
 	if f != nil {
 	if f != nil {
@@ -153,6 +185,18 @@ func lookupInt(name string, set *flag.FlagSet) int {
 	return 0
 	return 0
 }
 }
 
 
+func lookupDuration(name string, set *flag.FlagSet) time.Duration {
+	f := set.Lookup(name)
+	if f != nil {
+		val, err := time.ParseDuration(f.Value.String())
+		if err == nil {
+			return val
+		}
+	}
+
+	return 0
+}
+
 func lookupFloat64(name string, set *flag.FlagSet) float64 {
 func lookupFloat64(name string, set *flag.FlagSet) float64 {
 	f := set.Lookup(name)
 	f := set.Lookup(name)
 	if f != nil {
 	if f != nil {

+ 10 - 1
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go

@@ -2,8 +2,10 @@ package cli_test
 
 
 import (
 import (
 	"flag"
 	"flag"
-	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
 	"testing"
 	"testing"
+	"time"
+
+	"github.com/codegangsta/cli"
 )
 )
 
 
 func TestNewContext(t *testing.T) {
 func TestNewContext(t *testing.T) {
@@ -26,6 +28,13 @@ func TestContext_Int(t *testing.T) {
 	expect(t, c.Int("myflag"), 12)
 	expect(t, c.Int("myflag"), 12)
 }
 }
 
 
+func TestContext_Duration(t *testing.T) {
+	set := flag.NewFlagSet("test", 0)
+	set.Duration("myflag", time.Duration(12*time.Second), "doc")
+	c := cli.NewContext(nil, set, set)
+	expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
+}
+
 func TestContext_String(t *testing.T) {
 func TestContext_String(t *testing.T) {
 	set := flag.NewFlagSet("test", 0)
 	set := flag.NewFlagSet("test", 0)
 	set.String("myflag", "hello world", "doc")
 	set.String("myflag", "hello world", "doc")

+ 165 - 35
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go

@@ -3,18 +3,28 @@ package cli
 import (
 import (
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
+	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"time"
 )
 )
 
 
 // This flag enables bash-completion for all commands and subcommands
 // This flag enables bash-completion for all commands and subcommands
-var BashCompletionFlag = BoolFlag{"generate-bash-completion", ""}
+var BashCompletionFlag = BoolFlag{
+	Name: "generate-bash-completion",
+}
 
 
 // This flag prints the version for the application
 // This flag prints the version for the application
-var VersionFlag = BoolFlag{"version, v", "print the version"}
+var VersionFlag = BoolFlag{
+	Name:  "version, v",
+	Usage: "print the version",
+}
 
 
 // This flag prints the help for all commands and subcommands
 // This flag prints the help for all commands and subcommands
-var HelpFlag = BoolFlag{"help, h", "show help"}
+var HelpFlag = BoolFlag{
+	Name:  "help, h",
+	Usage: "show help",
+}
 
 
 // Flag is a common interface related to parsing flags in cli.
 // Flag is a common interface related to parsing flags in cli.
 // For more advanced flag parsing techniques, it is recomended that
 // For more advanced flag parsing techniques, it is recomended that
@@ -51,16 +61,24 @@ type Generic interface {
 
 
 // GenericFlag is the flag type for types implementing Generic
 // GenericFlag is the flag type for types implementing Generic
 type GenericFlag struct {
 type GenericFlag struct {
-	Name  string
-	Value Generic
-	Usage string
+	Name   string
+	Value  Generic
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f GenericFlag) String() string {
 func (f GenericFlag) String() string {
-	return fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage))
 }
 }
 
 
 func (f GenericFlag) Apply(set *flag.FlagSet) {
 func (f GenericFlag) Apply(set *flag.FlagSet) {
+	val := f.Value
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			val.Set(envVal)
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
 		set.Var(f.Value, name, f.Usage)
 		set.Var(f.Value, name, f.Usage)
 	})
 	})
@@ -86,18 +104,29 @@ func (f *StringSlice) Value() []string {
 }
 }
 
 
 type StringSliceFlag struct {
 type StringSliceFlag struct {
-	Name  string
-	Value *StringSlice
-	Usage string
+	Name   string
+	Value  *StringSlice
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f StringSliceFlag) String() string {
 func (f StringSliceFlag) String() string {
 	firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
 	firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
 	pref := prefixFor(firstName)
 	pref := prefixFor(firstName)
-	return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
 }
 }
 
 
 func (f StringSliceFlag) Apply(set *flag.FlagSet) {
 func (f StringSliceFlag) Apply(set *flag.FlagSet) {
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			newVal := &StringSlice{}
+			for _, s := range strings.Split(envVal, ",") {
+				newVal.Set(s)
+			}
+			f.Value = newVal
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
 		set.Var(f.Value, name, f.Usage)
 		set.Var(f.Value, name, f.Usage)
 	})
 	})
@@ -129,18 +158,32 @@ func (f *IntSlice) Value() []int {
 }
 }
 
 
 type IntSliceFlag struct {
 type IntSliceFlag struct {
-	Name  string
-	Value *IntSlice
-	Usage string
+	Name   string
+	Value  *IntSlice
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f IntSliceFlag) String() string {
 func (f IntSliceFlag) String() string {
 	firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
 	firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
 	pref := prefixFor(firstName)
 	pref := prefixFor(firstName)
-	return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
 }
 }
 
 
 func (f IntSliceFlag) Apply(set *flag.FlagSet) {
 func (f IntSliceFlag) Apply(set *flag.FlagSet) {
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			newVal := &IntSlice{}
+			for _, s := range strings.Split(envVal, ",") {
+				err := newVal.Set(s)
+				if err != nil {
+					fmt.Fprintf(os.Stderr, err.Error())
+				}
+			}
+			f.Value = newVal
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
 		set.Var(f.Value, name, f.Usage)
 		set.Var(f.Value, name, f.Usage)
 	})
 	})
@@ -151,17 +194,28 @@ func (f IntSliceFlag) getName() string {
 }
 }
 
 
 type BoolFlag struct {
 type BoolFlag struct {
-	Name  string
-	Usage string
+	Name   string
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f BoolFlag) String() string {
 func (f BoolFlag) String() string {
-	return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
 }
 }
 
 
 func (f BoolFlag) Apply(set *flag.FlagSet) {
 func (f BoolFlag) Apply(set *flag.FlagSet) {
+	val := false
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			envValBool, err := strconv.ParseBool(envVal)
+			if err == nil {
+				val = envValBool
+			}
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
-		set.Bool(name, false, f.Usage)
+		set.Bool(name, val, f.Usage)
 	})
 	})
 }
 }
 
 
@@ -170,17 +224,28 @@ func (f BoolFlag) getName() string {
 }
 }
 
 
 type BoolTFlag struct {
 type BoolTFlag struct {
-	Name  string
-	Usage string
+	Name   string
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f BoolTFlag) String() string {
 func (f BoolTFlag) String() string {
-	return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
 }
 }
 
 
 func (f BoolTFlag) Apply(set *flag.FlagSet) {
 func (f BoolTFlag) Apply(set *flag.FlagSet) {
+	val := true
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			envValBool, err := strconv.ParseBool(envVal)
+			if err == nil {
+				val = envValBool
+			}
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
-		set.Bool(name, true, f.Usage)
+		set.Bool(name, val, f.Usage)
 	})
 	})
 }
 }
 
 
@@ -189,9 +254,10 @@ func (f BoolTFlag) getName() string {
 }
 }
 
 
 type StringFlag struct {
 type StringFlag struct {
-	Name  string
-	Value string
-	Usage string
+	Name   string
+	Value  string
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f StringFlag) String() string {
 func (f StringFlag) String() string {
@@ -204,10 +270,16 @@ func (f StringFlag) String() string {
 		fmtString = "%s %v\t%v"
 		fmtString = "%s %v\t%v"
 	}
 	}
 
 
-	return fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
 }
 }
 
 
 func (f StringFlag) Apply(set *flag.FlagSet) {
 func (f StringFlag) Apply(set *flag.FlagSet) {
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			f.Value = envVal
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
 		set.String(name, f.Value, f.Usage)
 		set.String(name, f.Value, f.Usage)
 	})
 	})
@@ -218,16 +290,26 @@ func (f StringFlag) getName() string {
 }
 }
 
 
 type IntFlag struct {
 type IntFlag struct {
-	Name  string
-	Value int
-	Usage string
+	Name   string
+	Value  int
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f IntFlag) String() string {
 func (f IntFlag) String() string {
-	return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
 }
 }
 
 
 func (f IntFlag) Apply(set *flag.FlagSet) {
 func (f IntFlag) Apply(set *flag.FlagSet) {
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			envValInt, err := strconv.ParseUint(envVal, 10, 64)
+			if err == nil {
+				f.Value = int(envValInt)
+			}
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
 		set.Int(name, f.Value, f.Usage)
 		set.Int(name, f.Value, f.Usage)
 	})
 	})
@@ -237,17 +319,57 @@ func (f IntFlag) getName() string {
 	return f.Name
 	return f.Name
 }
 }
 
 
+type DurationFlag struct {
+	Name   string
+	Value  time.Duration
+	Usage  string
+	EnvVar string
+}
+
+func (f DurationFlag) String() string {
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
+}
+
+func (f DurationFlag) Apply(set *flag.FlagSet) {
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			envValDuration, err := time.ParseDuration(envVal)
+			if err == nil {
+				f.Value = envValDuration
+			}
+		}
+	}
+
+	eachName(f.Name, func(name string) {
+		set.Duration(name, f.Value, f.Usage)
+	})
+}
+
+func (f DurationFlag) getName() string {
+	return f.Name
+}
+
 type Float64Flag struct {
 type Float64Flag struct {
-	Name  string
-	Value float64
-	Usage string
+	Name   string
+	Value  float64
+	Usage  string
+	EnvVar string
 }
 }
 
 
 func (f Float64Flag) String() string {
 func (f Float64Flag) String() string {
-	return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)
+	return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
 }
 }
 
 
 func (f Float64Flag) Apply(set *flag.FlagSet) {
 func (f Float64Flag) Apply(set *flag.FlagSet) {
+	if f.EnvVar != "" {
+		if envVal := os.Getenv(f.EnvVar); envVal != "" {
+			envValFloat, err := strconv.ParseFloat(envVal, 10)
+			if err == nil {
+				f.Value = float64(envValFloat)
+			}
+		}
+	}
+
 	eachName(f.Name, func(name string) {
 	eachName(f.Name, func(name string) {
 		set.Float64(name, f.Value, f.Usage)
 		set.Float64(name, f.Value, f.Usage)
 	})
 	})
@@ -278,3 +400,11 @@ func prefixedNames(fullName string) (prefixed string) {
 	}
 	}
 	return
 	return
 }
 }
+
+func withEnvHint(envVar, str string) string {
+	envText := ""
+	if envVar != "" {
+		envText = fmt.Sprintf(" [$%s]", envVar)
+	}
+	return str + envText
+}

+ 395 - 2
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go

@@ -1,12 +1,13 @@
 package cli_test
 package cli_test
 
 
 import (
 import (
-	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
-
 	"fmt"
 	"fmt"
+	"os"
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
+
+	"github.com/codegangsta/cli"
 )
 )
 
 
 var boolFlagTests = []struct {
 var boolFlagTests = []struct {
@@ -52,6 +53,71 @@ func TestStringFlagHelpOutput(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_FOO", "derp")
+	for _, test := range stringFlagTests {
+		flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_FOO]") {
+			t.Errorf("%s does not end with [$APP_FOO]", output)
+		}
+	}
+}
+
+var stringSliceFlagTests = []struct {
+	name     string
+	value    *cli.StringSlice
+	expected string
+}{
+	{"help", func() *cli.StringSlice {
+		s := &cli.StringSlice{}
+		s.Set("")
+		return s
+	}(), "--help '--help option --help option'\t"},
+	{"h", func() *cli.StringSlice {
+		s := &cli.StringSlice{}
+		s.Set("")
+		return s
+	}(), "-h '-h option -h option'\t"},
+	{"h", func() *cli.StringSlice {
+		s := &cli.StringSlice{}
+		s.Set("")
+		return s
+	}(), "-h '-h option -h option'\t"},
+	{"test", func() *cli.StringSlice {
+		s := &cli.StringSlice{}
+		s.Set("Something")
+		return s
+	}(), "--test '--test option --test option'\t"},
+}
+
+func TestStringSliceFlagHelpOutput(t *testing.T) {
+
+	for _, test := range stringSliceFlagTests {
+		flag := cli.StringSliceFlag{Name: test.name, Value: test.value}
+		output := flag.String()
+
+		if output != test.expected {
+			t.Errorf("%q does not match %q", output, test.expected)
+		}
+	}
+}
+
+func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_QWWX", "11,4")
+	for _, test := range stringSliceFlagTests {
+		flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_QWWX]") {
+			t.Errorf("%q does not end with [$APP_QWWX]", output)
+		}
+	}
+}
+
 var intFlagTests = []struct {
 var intFlagTests = []struct {
 	name     string
 	name     string
 	expected string
 	expected string
@@ -72,6 +138,92 @@ func TestIntFlagHelpOutput(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_BAR", "2")
+	for _, test := range intFlagTests {
+		flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_BAR]") {
+			t.Errorf("%s does not end with [$APP_BAR]", output)
+		}
+	}
+}
+
+var durationFlagTests = []struct {
+	name     string
+	expected string
+}{
+	{"help", "--help '0'\t"},
+	{"h", "-h '0'\t"},
+}
+
+func TestDurationFlagHelpOutput(t *testing.T) {
+
+	for _, test := range durationFlagTests {
+		flag := cli.DurationFlag{Name: test.name}
+		output := flag.String()
+
+		if output != test.expected {
+			t.Errorf("%s does not match %s", output, test.expected)
+		}
+	}
+}
+
+func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_BAR", "2h3m6s")
+	for _, test := range durationFlagTests {
+		flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_BAR]") {
+			t.Errorf("%s does not end with [$APP_BAR]", output)
+		}
+	}
+}
+
+var intSliceFlagTests = []struct {
+	name     string
+	value    *cli.IntSlice
+	expected string
+}{
+	{"help", &cli.IntSlice{}, "--help '--help option --help option'\t"},
+	{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
+	{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
+	{"test", func() *cli.IntSlice {
+		i := &cli.IntSlice{}
+		i.Set("9")
+		return i
+	}(), "--test '--test option --test option'\t"},
+}
+
+func TestIntSliceFlagHelpOutput(t *testing.T) {
+
+	for _, test := range intSliceFlagTests {
+		flag := cli.IntSliceFlag{Name: test.name, Value: test.value}
+		output := flag.String()
+
+		if output != test.expected {
+			t.Errorf("%q does not match %q", output, test.expected)
+		}
+	}
+}
+
+func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_SMURF", "42,3")
+	for _, test := range intSliceFlagTests {
+		flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_SMURF]") {
+			t.Errorf("%q does not end with [$APP_SMURF]", output)
+		}
+	}
+}
+
 var float64FlagTests = []struct {
 var float64FlagTests = []struct {
 	name     string
 	name     string
 	expected string
 	expected string
@@ -92,6 +244,54 @@ func TestFloat64FlagHelpOutput(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_BAZ", "99.4")
+	for _, test := range float64FlagTests {
+		flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_BAZ]") {
+			t.Errorf("%s does not end with [$APP_BAZ]", output)
+		}
+	}
+}
+
+var genericFlagTests = []struct {
+	name     string
+	value    cli.Generic
+	expected string
+}{
+	{"help", &Parser{}, "--help <nil>\t`-help option -help option` "},
+	{"h", &Parser{}, "-h <nil>\t`-h option -h option` "},
+	{"test", &Parser{}, "--test <nil>\t`-test option -test option` "},
+}
+
+func TestGenericFlagHelpOutput(t *testing.T) {
+
+	for _, test := range genericFlagTests {
+		flag := cli.GenericFlag{Name: test.name}
+		output := flag.String()
+
+		if output != test.expected {
+			t.Errorf("%q does not match %q", output, test.expected)
+		}
+	}
+}
+
+func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
+
+	os.Setenv("APP_ZAP", "3")
+	for _, test := range genericFlagTests {
+		flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
+		output := flag.String()
+
+		if !strings.HasSuffix(output, " [$APP_ZAP]") {
+			t.Errorf("%s does not end with [$APP_ZAP]", output)
+		}
+	}
+}
+
 func TestParseMultiString(t *testing.T) {
 func TestParseMultiString(t *testing.T) {
 	(&cli.App{
 	(&cli.App{
 		Flags: []cli.Flag{
 		Flags: []cli.Flag{
@@ -108,6 +308,23 @@ func TestParseMultiString(t *testing.T) {
 	}).Run([]string{"run", "-s", "10"})
 	}).Run([]string{"run", "-s", "10"})
 }
 }
 
 
+func TestParseMultiStringFromEnv(t *testing.T) {
+	os.Setenv("APP_COUNT", "20")
+	(&cli.App{
+		Flags: []cli.Flag{
+			cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.String("count") != "20" {
+				t.Errorf("main name not set")
+			}
+			if ctx.String("c") != "20" {
+				t.Errorf("short name not set")
+			}
+		},
+	}).Run([]string{"run"})
+}
+
 func TestParseMultiStringSlice(t *testing.T) {
 func TestParseMultiStringSlice(t *testing.T) {
 	(&cli.App{
 	(&cli.App{
 		Flags: []cli.Flag{
 		Flags: []cli.Flag{
@@ -124,6 +341,24 @@ func TestParseMultiStringSlice(t *testing.T) {
 	}).Run([]string{"run", "-s", "10", "-s", "20"})
 	}).Run([]string{"run", "-s", "10", "-s", "20"})
 }
 }
 
 
+func TestParseMultiStringSliceFromEnv(t *testing.T) {
+	os.Setenv("APP_INTERVALS", "20,30,40")
+
+	(&cli.App{
+		Flags: []cli.Flag{
+			cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"},
+		},
+		Action: func(ctx *cli.Context) {
+			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+				t.Errorf("main name not set from env")
+			}
+			if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+				t.Errorf("short name not set from env")
+			}
+		},
+	}).Run([]string{"run"})
+}
+
 func TestParseMultiInt(t *testing.T) {
 func TestParseMultiInt(t *testing.T) {
 	a := cli.App{
 	a := cli.App{
 		Flags: []cli.Flag{
 		Flags: []cli.Flag{
@@ -141,6 +376,93 @@ func TestParseMultiInt(t *testing.T) {
 	a.Run([]string{"run", "-s", "10"})
 	a.Run([]string{"run", "-s", "10"})
 }
 }
 
 
+func TestParseMultiIntFromEnv(t *testing.T) {
+	os.Setenv("APP_TIMEOUT_SECONDS", "10")
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.Int("timeout") != 10 {
+				t.Errorf("main name not set")
+			}
+			if ctx.Int("t") != 10 {
+				t.Errorf("short name not set")
+			}
+		},
+	}
+	a.Run([]string{"run"})
+}
+
+func TestParseMultiIntSlice(t *testing.T) {
+	(&cli.App{
+		Flags: []cli.Flag{
+			cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}},
+		},
+		Action: func(ctx *cli.Context) {
+			if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
+				t.Errorf("main name not set")
+			}
+			if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
+				t.Errorf("short name not set")
+			}
+		},
+	}).Run([]string{"run", "-s", "10", "-s", "20"})
+}
+
+func TestParseMultiIntSliceFromEnv(t *testing.T) {
+	os.Setenv("APP_INTERVALS", "20,30,40")
+
+	(&cli.App{
+		Flags: []cli.Flag{
+			cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"},
+		},
+		Action: func(ctx *cli.Context) {
+			if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
+				t.Errorf("main name not set from env")
+			}
+			if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
+				t.Errorf("short name not set from env")
+			}
+		},
+	}).Run([]string{"run"})
+}
+
+func TestParseMultiFloat64(t *testing.T) {
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.Float64Flag{Name: "serve, s"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.Float64("serve") != 10.2 {
+				t.Errorf("main name not set")
+			}
+			if ctx.Float64("s") != 10.2 {
+				t.Errorf("short name not set")
+			}
+		},
+	}
+	a.Run([]string{"run", "-s", "10.2"})
+}
+
+func TestParseMultiFloat64FromEnv(t *testing.T) {
+	os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.Float64("timeout") != 15.5 {
+				t.Errorf("main name not set")
+			}
+			if ctx.Float64("t") != 15.5 {
+				t.Errorf("short name not set")
+			}
+		},
+	}
+	a.Run([]string{"run"})
+}
+
 func TestParseMultiBool(t *testing.T) {
 func TestParseMultiBool(t *testing.T) {
 	a := cli.App{
 	a := cli.App{
 		Flags: []cli.Flag{
 		Flags: []cli.Flag{
@@ -158,6 +480,59 @@ func TestParseMultiBool(t *testing.T) {
 	a.Run([]string{"run", "--serve"})
 	a.Run([]string{"run", "--serve"})
 }
 }
 
 
+func TestParseMultiBoolFromEnv(t *testing.T) {
+	os.Setenv("APP_DEBUG", "1")
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.Bool("debug") != true {
+				t.Errorf("main name not set from env")
+			}
+			if ctx.Bool("d") != true {
+				t.Errorf("short name not set from env")
+			}
+		},
+	}
+	a.Run([]string{"run"})
+}
+
+func TestParseMultiBoolT(t *testing.T) {
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.BoolTFlag{Name: "serve, s"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.BoolT("serve") != true {
+				t.Errorf("main name not set")
+			}
+			if ctx.BoolT("s") != true {
+				t.Errorf("short name not set")
+			}
+		},
+	}
+	a.Run([]string{"run", "--serve"})
+}
+
+func TestParseMultiBoolTFromEnv(t *testing.T) {
+	os.Setenv("APP_DEBUG", "0")
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
+		},
+		Action: func(ctx *cli.Context) {
+			if ctx.BoolT("debug") != false {
+				t.Errorf("main name not set from env")
+			}
+			if ctx.BoolT("d") != false {
+				t.Errorf("short name not set from env")
+			}
+		},
+	}
+	a.Run([]string{"run"})
+}
+
 type Parser [2]string
 type Parser [2]string
 
 
 func (p *Parser) Set(value string) error {
 func (p *Parser) Set(value string) error {
@@ -192,3 +567,21 @@ func TestParseGeneric(t *testing.T) {
 	}
 	}
 	a.Run([]string{"run", "-s", "10,20"})
 	a.Run([]string{"run", "-s", "10,20"})
 }
 }
+
+func TestParseGenericFromEnv(t *testing.T) {
+	os.Setenv("APP_SERVE", "20,30")
+	a := cli.App{
+		Flags: []cli.Flag{
+			cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
+		},
+		Action: func(ctx *cli.Context) {
+			if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
+				t.Errorf("main name not set from env")
+			}
+			if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
+				t.Errorf("short name not set from env")
+			}
+		},
+	}
+	a.Run([]string{"run"})
+}

+ 21 - 10
Godeps/_workspace/src/github.com/codegangsta/cli/help.go

@@ -14,17 +14,21 @@ var AppHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}
    {{.Name}} - {{.Usage}}
 
 
 USAGE:
 USAGE:
-   {{.Name}} [global options] command [command options] [arguments...]
+   {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
 
 
 VERSION:
 VERSION:
-   {{.Version}}
+   {{.Version}}{{if or .Author .Email}}
+
+AUTHOR:{{if .Author}}
+  {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
+  {{.Email}}{{end}}{{end}}
 
 
 COMMANDS:
 COMMANDS:
    {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
    {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
-   {{end}}
+   {{end}}{{if .Flags}}
 GLOBAL OPTIONS:
 GLOBAL OPTIONS:
    {{range .Flags}}{{.}}
    {{range .Flags}}{{.}}
-   {{end}}
+   {{end}}{{end}}
 `
 `
 
 
 // The text template for the command help topic.
 // The text template for the command help topic.
@@ -34,14 +38,14 @@ var CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}
    {{.Name}} - {{.Usage}}
 
 
 USAGE:
 USAGE:
-   command {{.Name}} [command options] [arguments...]
+   command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
 
 
 DESCRIPTION:
 DESCRIPTION:
-   {{.Description}}
+   {{.Description}}{{end}}{{if .Flags}}
 
 
 OPTIONS:
 OPTIONS:
    {{range .Flags}}{{.}}
    {{range .Flags}}{{.}}
-   {{end}}
+   {{end}}{{ end }}
 `
 `
 
 
 // The text template for the subcommand help topic.
 // The text template for the subcommand help topic.
@@ -51,14 +55,14 @@ var SubcommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}
    {{.Name}} - {{.Usage}}
 
 
 USAGE:
 USAGE:
-   {{.Name}} [global options] command [command options] [arguments...]
+   {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
 
 
 COMMANDS:
 COMMANDS:
    {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
    {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
-   {{end}}
+   {{end}}{{if .Flags}}
 OPTIONS:
 OPTIONS:
    {{range .Flags}}{{.}}
    {{range .Flags}}{{.}}
-   {{end}}
+   {{end}}{{end}}
 `
 `
 
 
 var helpCommand = Command{
 var helpCommand = Command{
@@ -92,6 +96,9 @@ var helpSubcommand = Command{
 // Prints help for the App
 // Prints help for the App
 var HelpPrinter = printHelp
 var HelpPrinter = printHelp
 
 
+// Prints version for the App
+var VersionPrinter = printVersion
+
 func ShowAppHelp(c *Context) {
 func ShowAppHelp(c *Context) {
 	HelpPrinter(AppHelpTemplate, c.App)
 	HelpPrinter(AppHelpTemplate, c.App)
 }
 }
@@ -129,6 +136,10 @@ func ShowSubcommandHelp(c *Context) {
 
 
 // Prints the version number of the App
 // Prints the version number of the App
 func ShowVersion(c *Context) {
 func ShowVersion(c *Context) {
+	VersionPrinter(c)
+}
+
+func printVersion(c *Context) {
 	fmt.Printf("%v version %v\n", c.App.Name, c.App.Version)
 	fmt.Printf("%v version %v\n", c.App.Name, c.App.Version)
 }
 }
 
 

+ 53 - 0
client/cluster.go

@@ -0,0 +1,53 @@
+/*
+   Copyright 2014 CoreOS, Inc.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package client
+
+import (
+	"net/http"
+	"net/url"
+
+	"github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
+)
+
+func newHTTPClusterClient(tr *http.Transport, eps []string) (*httpClusterClient, error) {
+	c := httpClusterClient{
+		endpoints: make([]*httpClient, len(eps)),
+	}
+
+	for i, ep := range eps {
+		u, err := url.Parse(ep)
+		if err != nil {
+			return nil, err
+		}
+
+		c.endpoints[i] = &httpClient{
+			transport: tr,
+			endpoint:  *u,
+		}
+	}
+
+	return &c, nil
+}
+
+type httpClusterClient struct {
+	endpoints []*httpClient
+}
+
+func (c *httpClusterClient) do(ctx context.Context, act httpAction) (int, []byte, error) {
+	//TODO(bcwaldon): introduce retry logic so all endpoints are attempted
+	return c.endpoints[0].do(ctx, act)
+}

+ 4 - 6
client/http.go

@@ -42,6 +42,10 @@ type httpAction interface {
 	httpRequest(url.URL) *http.Request
 	httpRequest(url.URL) *http.Request
 }
 }
 
 
+type httpActionDo interface {
+	do(context.Context, httpAction) (int, []byte, error)
+}
+
 type roundTripResponse struct {
 type roundTripResponse struct {
 	resp *http.Response
 	resp *http.Response
 	err  error
 	err  error
@@ -53,12 +57,6 @@ type httpClient struct {
 	timeout   time.Duration
 	timeout   time.Duration
 }
 }
 
 
-func (c *httpClient) doWithTimeout(act httpAction) (int, []byte, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
-	defer cancel()
-	return c.do(ctx, act)
-}
-
 func (c *httpClient) do(ctx context.Context, act httpAction) (int, []byte, error) {
 func (c *httpClient) do(ctx context.Context, act httpAction) (int, []byte, error) {
 	req := act.httpRequest(c.endpoint)
 	req := act.httpRequest(c.endpoint)
 
 

+ 41 - 32
client/keys.go

@@ -41,30 +41,24 @@ var (
 	ErrKeyExists   = errors.New("client: key already exists")
 	ErrKeyExists   = errors.New("client: key already exists")
 )
 )
 
 
-func NewKeysAPI(tr *http.Transport, ep string, to time.Duration) (KeysAPI, error) {
-	return newHTTPKeysAPIWithPrefix(tr, ep, to, DefaultV2KeysPrefix)
+func NewKeysAPI(tr *http.Transport, eps []string, to time.Duration) (KeysAPI, error) {
+	return newHTTPKeysAPIWithPrefix(tr, eps, to, DefaultV2KeysPrefix)
 }
 }
 
 
-func NewDiscoveryKeysAPI(tr *http.Transport, ep string, to time.Duration) (KeysAPI, error) {
-	return newHTTPKeysAPIWithPrefix(tr, ep, to, "")
+func NewDiscoveryKeysAPI(tr *http.Transport, eps []string, to time.Duration) (KeysAPI, error) {
+	return newHTTPKeysAPIWithPrefix(tr, eps, to, "")
 }
 }
 
 
-func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, prefix string) (*httpKeysAPI, error) {
-	u, err := url.Parse(ep)
+func newHTTPKeysAPIWithPrefix(tr *http.Transport, eps []string, to time.Duration, prefix string) (*httpKeysAPI, error) {
+	c, err := newHTTPClusterClient(tr, eps)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	u.Path = path.Join(u.Path, prefix)
-
-	c := &httpClient{
-		transport: tr,
-		endpoint:  *u,
-		timeout:   to,
-	}
-
 	kAPI := httpKeysAPI{
 	kAPI := httpKeysAPI{
-		client: c,
+		client:  c,
+		prefix:  prefix,
+		timeout: to,
 	}
 	}
 
 
 	return &kAPI, nil
 	return &kAPI, nil
@@ -101,20 +95,25 @@ func (n *Node) String() string {
 }
 }
 
 
 type httpKeysAPI struct {
 type httpKeysAPI struct {
-	client *httpClient
+	client  httpActionDo
+	prefix  string
+	timeout time.Duration
 }
 }
 
 
 func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) {
 func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) {
 	create := &createAction{
 	create := &createAction{
-		Key:   key,
-		Value: val,
+		Prefix: k.prefix,
+		Key:    key,
+		Value:  val,
 	}
 	}
 	if ttl >= 0 {
 	if ttl >= 0 {
 		uttl := uint64(ttl.Seconds())
 		uttl := uint64(ttl.Seconds())
 		create.TTL = &uttl
 		create.TTL = &uttl
 	}
 	}
 
 
-	code, body, err := k.client.doWithTimeout(create)
+	ctx, cancel := context.WithTimeout(context.Background(), k.timeout)
+	code, body, err := k.client.do(ctx, create)
+	cancel()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -124,11 +123,14 @@ func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, err
 
 
 func (k *httpKeysAPI) Get(key string) (*Response, error) {
 func (k *httpKeysAPI) Get(key string) (*Response, error) {
 	get := &getAction{
 	get := &getAction{
+		Prefix:    k.prefix,
 		Key:       key,
 		Key:       key,
 		Recursive: false,
 		Recursive: false,
 	}
 	}
 
 
-	code, body, err := k.client.doWithTimeout(get)
+	ctx, cancel := context.WithTimeout(context.Background(), k.timeout)
+	code, body, err := k.client.do(ctx, get)
+	cancel()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -140,6 +142,7 @@ func (k *httpKeysAPI) Watch(key string, idx uint64) Watcher {
 	return &httpWatcher{
 	return &httpWatcher{
 		client: k.client,
 		client: k.client,
 		nextWait: waitAction{
 		nextWait: waitAction{
+			Prefix:    k.prefix,
 			Key:       key,
 			Key:       key,
 			WaitIndex: idx,
 			WaitIndex: idx,
 			Recursive: false,
 			Recursive: false,
@@ -151,6 +154,7 @@ func (k *httpKeysAPI) RecursiveWatch(key string, idx uint64) Watcher {
 	return &httpWatcher{
 	return &httpWatcher{
 		client: k.client,
 		client: k.client,
 		nextWait: waitAction{
 		nextWait: waitAction{
+			Prefix:    k.prefix,
 			Key:       key,
 			Key:       key,
 			WaitIndex: idx,
 			WaitIndex: idx,
 			Recursive: true,
 			Recursive: true,
@@ -159,7 +163,7 @@ func (k *httpKeysAPI) RecursiveWatch(key string, idx uint64) Watcher {
 }
 }
 
 
 type httpWatcher struct {
 type httpWatcher struct {
-	client   *httpClient
+	client   httpActionDo
 	nextWait waitAction
 	nextWait waitAction
 }
 }
 
 
@@ -179,21 +183,24 @@ func (hw *httpWatcher) Next() (*Response, error) {
 	return resp, nil
 	return resp, nil
 }
 }
 
 
-// v2KeysURL forms a URL representing the location of a key. The provided
-// endpoint must be the root of the etcd keys API. For example, a valid
-// endpoint probably has the path "/v2/keys".
-func v2KeysURL(ep url.URL, key string) *url.URL {
-	ep.Path = path.Join(ep.Path, key)
+// v2KeysURL forms a URL representing the location of a key.
+// The endpoint argument represents the base URL of an etcd
+// server. The prefix is the path needed to route from the
+// provided endpoint's path to the root of the keys API
+// (typically "/v2/keys").
+func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
+	ep.Path = path.Join(ep.Path, prefix, key)
 	return &ep
 	return &ep
 }
 }
 
 
 type getAction struct {
 type getAction struct {
+	Prefix    string
 	Key       string
 	Key       string
 	Recursive bool
 	Recursive bool
 }
 }
 
 
 func (g *getAction) httpRequest(ep url.URL) *http.Request {
 func (g *getAction) httpRequest(ep url.URL) *http.Request {
-	u := v2KeysURL(ep, g.Key)
+	u := v2KeysURL(ep, g.Prefix, g.Key)
 
 
 	params := u.Query()
 	params := u.Query()
 	params.Set("recursive", strconv.FormatBool(g.Recursive))
 	params.Set("recursive", strconv.FormatBool(g.Recursive))
@@ -204,13 +211,14 @@ func (g *getAction) httpRequest(ep url.URL) *http.Request {
 }
 }
 
 
 type waitAction struct {
 type waitAction struct {
+	Prefix    string
 	Key       string
 	Key       string
 	WaitIndex uint64
 	WaitIndex uint64
 	Recursive bool
 	Recursive bool
 }
 }
 
 
 func (w *waitAction) httpRequest(ep url.URL) *http.Request {
 func (w *waitAction) httpRequest(ep url.URL) *http.Request {
-	u := v2KeysURL(ep, w.Key)
+	u := v2KeysURL(ep, w.Prefix, w.Key)
 
 
 	params := u.Query()
 	params := u.Query()
 	params.Set("wait", "true")
 	params.Set("wait", "true")
@@ -223,13 +231,14 @@ func (w *waitAction) httpRequest(ep url.URL) *http.Request {
 }
 }
 
 
 type createAction struct {
 type createAction struct {
-	Key   string
-	Value string
-	TTL   *uint64
+	Prefix string
+	Key    string
+	Value  string
+	TTL    *uint64
 }
 }
 
 
 func (c *createAction) httpRequest(ep url.URL) *http.Request {
 func (c *createAction) httpRequest(ep url.URL) *http.Request {
-	u := v2KeysURL(ep, c.Key)
+	u := v2KeysURL(ep, c.Prefix, c.Key)
 
 
 	params := u.Query()
 	params := u.Query()
 	params.Set("prevExist", "false")
 	params.Set("prevExist", "false")

+ 14 - 1
client/keys_test.go

@@ -29,12 +29,14 @@ import (
 func TestV2KeysURLHelper(t *testing.T) {
 func TestV2KeysURLHelper(t *testing.T) {
 	tests := []struct {
 	tests := []struct {
 		endpoint url.URL
 		endpoint url.URL
+		prefix   string
 		key      string
 		key      string
 		want     url.URL
 		want     url.URL
 	}{
 	}{
 		// key is empty, no problem
 		// key is empty, no problem
 		{
 		{
 			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
 			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
+			prefix:   "",
 			key:      "",
 			key:      "",
 			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
 			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
 		},
 		},
@@ -42,6 +44,7 @@ func TestV2KeysURLHelper(t *testing.T) {
 		// key is joined to path
 		// key is joined to path
 		{
 		{
 			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
 			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
+			prefix:   "",
 			key:      "/foo/bar",
 			key:      "/foo/bar",
 			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
 			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
 		},
 		},
@@ -49,6 +52,7 @@ func TestV2KeysURLHelper(t *testing.T) {
 		// key is joined to path when path is empty
 		// key is joined to path when path is empty
 		{
 		{
 			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
 			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
+			prefix:   "",
 			key:      "/foo/bar",
 			key:      "/foo/bar",
 			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"},
 			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"},
 		},
 		},
@@ -56,6 +60,7 @@ func TestV2KeysURLHelper(t *testing.T) {
 		// Host field carries through with port
 		// Host field carries through with port
 		{
 		{
 			endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
 			endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
+			prefix:   "",
 			key:      "",
 			key:      "",
 			want:     url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
 			want:     url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
 		},
 		},
@@ -63,13 +68,21 @@ func TestV2KeysURLHelper(t *testing.T) {
 		// Scheme carries through
 		// Scheme carries through
 		{
 		{
 			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
 			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
+			prefix:   "",
 			key:      "",
 			key:      "",
 			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
 			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
 		},
 		},
+		// Prefix is applied
+		{
+			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
+			prefix:   "/bar",
+			key:      "/baz",
+			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"},
+		},
 	}
 	}
 
 
 	for i, tt := range tests {
 	for i, tt := range tests {
-		got := v2KeysURL(tt.endpoint, tt.key)
+		got := v2KeysURL(tt.endpoint, tt.prefix, tt.key)
 		if tt.want != *got {
 		if tt.want != *got {
 			t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
 			t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
 		}
 		}

+ 31 - 19
client/members.go

@@ -25,6 +25,7 @@ import (
 	"path"
 	"path"
 	"time"
 	"time"
 
 
+	"github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
 	"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
 	"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
 	"github.com/coreos/etcd/pkg/types"
 	"github.com/coreos/etcd/pkg/types"
 )
 )
@@ -33,22 +34,15 @@ var (
 	DefaultV2MembersPrefix = "/v2/members"
 	DefaultV2MembersPrefix = "/v2/members"
 )
 )
 
 
-func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, error) {
-	u, err := url.Parse(ep)
+func NewMembersAPI(tr *http.Transport, eps []string, to time.Duration) (MembersAPI, error) {
+	c, err := newHTTPClusterClient(tr, eps)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	u.Path = path.Join(u.Path, DefaultV2MembersPrefix)
-
-	c := &httpClient{
-		transport: tr,
-		endpoint:  *u,
-		timeout:   to,
-	}
-
 	mAPI := httpMembersAPI{
 	mAPI := httpMembersAPI{
-		client: c,
+		client:  c,
+		timeout: to,
 	}
 	}
 
 
 	return &mAPI, nil
 	return &mAPI, nil
@@ -61,11 +55,15 @@ type MembersAPI interface {
 }
 }
 
 
 type httpMembersAPI struct {
 type httpMembersAPI struct {
-	client *httpClient
+	client  httpActionDo
+	timeout time.Duration
 }
 }
 
 
 func (m *httpMembersAPI) List() ([]httptypes.Member, error) {
 func (m *httpMembersAPI) List() ([]httptypes.Member, error) {
-	code, body, err := m.client.doWithTimeout(&membersAPIActionList{})
+	req := &membersAPIActionList{}
+	ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
+	code, body, err := m.client.do(ctx, req)
+	cancel()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -89,7 +87,9 @@ func (m *httpMembersAPI) Add(peerURL string) (*httptypes.Member, error) {
 	}
 	}
 
 
 	req := &membersAPIActionAdd{peerURLs: urls}
 	req := &membersAPIActionAdd{peerURLs: urls}
-	code, body, err := m.client.doWithTimeout(req)
+	ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
+	code, body, err := m.client.do(ctx, req)
+	cancel()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -108,7 +108,9 @@ func (m *httpMembersAPI) Add(peerURL string) (*httptypes.Member, error) {
 
 
 func (m *httpMembersAPI) Remove(memberID string) error {
 func (m *httpMembersAPI) Remove(memberID string) error {
 	req := &membersAPIActionRemove{memberID: memberID}
 	req := &membersAPIActionRemove{memberID: memberID}
-	code, _, err := m.client.doWithTimeout(req)
+	ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
+	code, _, err := m.client.do(ctx, req)
+	cancel()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -119,7 +121,8 @@ func (m *httpMembersAPI) Remove(memberID string) error {
 type membersAPIActionList struct{}
 type membersAPIActionList struct{}
 
 
 func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request {
 func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request {
-	req, _ := http.NewRequest("GET", ep.String(), nil)
+	u := v2MembersURL(ep)
+	req, _ := http.NewRequest("GET", u.String(), nil)
 	return req
 	return req
 }
 }
 
 
@@ -128,8 +131,9 @@ type membersAPIActionRemove struct {
 }
 }
 
 
 func (d *membersAPIActionRemove) httpRequest(ep url.URL) *http.Request {
 func (d *membersAPIActionRemove) httpRequest(ep url.URL) *http.Request {
-	ep.Path = path.Join(ep.Path, d.memberID)
-	req, _ := http.NewRequest("DELETE", ep.String(), nil)
+	u := v2MembersURL(ep)
+	u.Path = path.Join(u.Path, d.memberID)
+	req, _ := http.NewRequest("DELETE", u.String(), nil)
 	return req
 	return req
 }
 }
 
 
@@ -138,9 +142,10 @@ type membersAPIActionAdd struct {
 }
 }
 
 
 func (a *membersAPIActionAdd) httpRequest(ep url.URL) *http.Request {
 func (a *membersAPIActionAdd) httpRequest(ep url.URL) *http.Request {
+	u := v2MembersURL(ep)
 	m := httptypes.MemberCreateRequest{PeerURLs: a.peerURLs}
 	m := httptypes.MemberCreateRequest{PeerURLs: a.peerURLs}
 	b, _ := json.Marshal(&m)
 	b, _ := json.Marshal(&m)
-	req, _ := http.NewRequest("POST", ep.String(), bytes.NewReader(b))
+	req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b))
 	req.Header.Set("Content-Type", "application/json")
 	req.Header.Set("Content-Type", "application/json")
 	return req
 	return req
 }
 }
@@ -151,3 +156,10 @@ func assertStatusCode(want, got int) (err error) {
 	}
 	}
 	return err
 	return err
 }
 }
+
+// v2MembersURL add the necessary path to the provided endpoint
+// to route requests to the default v2 members API.
+func v2MembersURL(ep url.URL) *url.URL {
+	ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix)
+	return &ep
+}

+ 22 - 4
client/members_test.go

@@ -19,13 +19,14 @@ package client
 import (
 import (
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"reflect"
 	"testing"
 	"testing"
 
 
 	"github.com/coreos/etcd/pkg/types"
 	"github.com/coreos/etcd/pkg/types"
 )
 )
 
 
 func TestMembersAPIActionList(t *testing.T) {
 func TestMembersAPIActionList(t *testing.T) {
-	ep := url.URL{Scheme: "http", Host: "example.com/v2/members"}
+	ep := url.URL{Scheme: "http", Host: "example.com"}
 	act := &membersAPIActionList{}
 	act := &membersAPIActionList{}
 
 
 	wantURL := &url.URL{
 	wantURL := &url.URL{
@@ -42,7 +43,7 @@ func TestMembersAPIActionList(t *testing.T) {
 }
 }
 
 
 func TestMembersAPIActionAdd(t *testing.T) {
 func TestMembersAPIActionAdd(t *testing.T) {
-	ep := url.URL{Scheme: "http", Host: "example.com/v2/admin/members"}
+	ep := url.URL{Scheme: "http", Host: "example.com"}
 	act := &membersAPIActionAdd{
 	act := &membersAPIActionAdd{
 		peerURLs: types.URLs([]url.URL{
 		peerURLs: types.URLs([]url.URL{
 			url.URL{Scheme: "https", Host: "127.0.0.1:8081"},
 			url.URL{Scheme: "https", Host: "127.0.0.1:8081"},
@@ -53,7 +54,7 @@ func TestMembersAPIActionAdd(t *testing.T) {
 	wantURL := &url.URL{
 	wantURL := &url.URL{
 		Scheme: "http",
 		Scheme: "http",
 		Host:   "example.com",
 		Host:   "example.com",
-		Path:   "/v2/admin/members",
+		Path:   "/v2/members",
 	}
 	}
 	wantHeader := http.Header{
 	wantHeader := http.Header{
 		"Content-Type": []string{"application/json"},
 		"Content-Type": []string{"application/json"},
@@ -68,7 +69,7 @@ func TestMembersAPIActionAdd(t *testing.T) {
 }
 }
 
 
 func TestMembersAPIActionRemove(t *testing.T) {
 func TestMembersAPIActionRemove(t *testing.T) {
-	ep := url.URL{Scheme: "http", Host: "example.com/v2/members"}
+	ep := url.URL{Scheme: "http", Host: "example.com"}
 	act := &membersAPIActionRemove{memberID: "XXX"}
 	act := &membersAPIActionRemove{memberID: "XXX"}
 
 
 	wantURL := &url.URL{
 	wantURL := &url.URL{
@@ -93,3 +94,20 @@ func TestAssertStatusCode(t *testing.T) {
 		t.Errorf("assertStatusCode found conflict in 400 vs 400: %v", err)
 		t.Errorf("assertStatusCode found conflict in 400 vs 400: %v", err)
 	}
 	}
 }
 }
+
+func TestV2MembersURL(t *testing.T) {
+	got := v2MembersURL(url.URL{
+		Scheme: "http",
+		Host:   "foo.example.com:4002",
+		Path:   "/pants",
+	})
+	want := &url.URL{
+		Scheme: "http",
+		Host:   "foo.example.com:4002",
+		Path:   "/pants/v2/members",
+	}
+
+	if !reflect.DeepEqual(want, got) {
+		t.Fatalf("v2MembersURL got %#v, want %#v", got, want)
+	}
+}

+ 1 - 1
discovery/discovery.go

@@ -106,7 +106,7 @@ func New(durl string, id types.ID, config string) (Discoverer, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	c, err := client.NewDiscoveryKeysAPI(&http.Transport{Proxy: pf}, u.String(), client.DefaultRequestTimeout)
+	c, err := client.NewDiscoveryKeysAPI(&http.Transport{Proxy: pf}, []string{u.String()}, client.DefaultRequestTimeout)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 10 - 6
etcdctl/command/handle.go

@@ -37,11 +37,7 @@ func createHttpPath(addr string) (string, error) {
 	return u.String(), nil
 	return u.String(), nil
 }
 }
 
 
-// rawhandle wraps the command function handlers and sets up the
-// environment but performs no output formatting.
-func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) {
-	sync := !c.GlobalBool("no-sync")
-
+func getPeersFlagValue(c *cli.Context) []string {
 	peerstr := c.GlobalString("peers")
 	peerstr := c.GlobalString("peers")
 
 
 	// Use an environment variable if nothing was supplied on the
 	// Use an environment variable if nothing was supplied on the
@@ -55,7 +51,15 @@ func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) {
 		peerstr = "127.0.0.1:4001"
 		peerstr = "127.0.0.1:4001"
 	}
 	}
 
 
-	peers := strings.Split(peerstr, ",")
+	return strings.Split(peerstr, ",")
+}
+
+// rawhandle wraps the command function handlers and sets up the
+// environment but performs no output formatting.
+func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) {
+	sync := !c.GlobalBool("no-sync")
+
+	peers := getPeersFlagValue(c)
 
 
 	// If no sync, create http path for each peer address
 	// If no sync, create http path for each peer address
 	if !sync {
 	if !sync {

+ 18 - 17
etcdctl/command/member_commands.go

@@ -33,18 +33,29 @@ func NewMemberCommand() cli.Command {
 	}
 	}
 }
 }
 
 
-func actionMemberList(c *cli.Context) {
-	if len(c.Args()) != 0 {
-		fmt.Fprintln(os.Stderr, "No arguments accepted")
-		os.Exit(1)
+func mustNewMembersAPI(c *cli.Context) client.MembersAPI {
+	peers := getPeersFlagValue(c)
+	for i, p := range peers {
+		if !strings.HasPrefix(p, "http") && !strings.HasPrefix(p, "https") {
+			peers[i] = fmt.Sprintf("http://%s", p)
+		}
 	}
 	}
 
 
-	mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout)
+	mAPI, err := client.NewMembersAPI(&http.Transport{}, peers, client.DefaultRequestTimeout)
 	if err != nil {
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		fmt.Fprintln(os.Stderr, err.Error())
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 
 
+	return mAPI
+}
+
+func actionMemberList(c *cli.Context) {
+	if len(c.Args()) != 0 {
+		fmt.Fprintln(os.Stderr, "No arguments accepted")
+		os.Exit(1)
+	}
+	mAPI := mustNewMembersAPI(c)
 	members, err := mAPI.List()
 	members, err := mAPI.List()
 	if err != nil {
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		fmt.Fprintln(os.Stderr, err.Error())
@@ -63,12 +74,7 @@ func actionMemberAdd(c *cli.Context) {
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 
 
-	mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(1)
-	}
-
+	mAPI := mustNewMembersAPI(c)
 	url := args[0]
 	url := args[0]
 	m, err := mAPI.Add(url)
 	m, err := mAPI.Add(url)
 	if err != nil {
 	if err != nil {
@@ -86,12 +92,7 @@ func actionMemberRemove(c *cli.Context) {
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
 
 
-	mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(1)
-	}
-
+	mAPI := mustNewMembersAPI(c)
 	mID := args[0]
 	mID := args[0]
 	if err := mAPI.Remove(mID); err != nil {
 	if err := mAPI.Remove(mID); err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		fmt.Fprintln(os.Stderr, err.Error())