etcdctlv3: use spf13/cobra for cli interface

This replaces codegansta/cli with spf13/cobra base on
this guideline: https://github.com/coreos/docs/blob/master/golang/README.md#cli.
This commit is contained in:
Gyu-Ho Lee 2015-11-25 14:06:52 -08:00
parent a6bb74e9ff
commit b7647e0b55
11 changed files with 421 additions and 116 deletions

View File

@ -15,38 +15,42 @@
package command package command
import ( import (
"fmt"
"strconv" "strconv"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
) )
// NewCompactionCommand returns the CLI command for "compaction". // NewCompactionCommand returns the cobra command for "compaction".
func NewCompactionCommand() cli.Command { func NewCompactionCommand() *cobra.Command {
return cli.Command{ return &cobra.Command{
Name: "compaction", Use: "compaction",
Action: func(c *cli.Context) { Short: "Compaction compacts the event history in etcd.",
compactionCommandFunc(c) Run: compactionCommandFunc,
},
} }
} }
// compactionCommandFunc executes the "compaction" command. // compactionCommandFunc executes the "compaction" command.
func compactionCommandFunc(c *cli.Context) { func compactionCommandFunc(cmd *cobra.Command, args []string) {
if len(c.Args()) != 1 { if len(args) != 1 {
panic("bad arg") ExitWithError(ExitBadArgs, fmt.Errorf("compaction command needs 1 argument."))
} }
rev, err := strconv.ParseInt(c.Args()[0], 10, 64) rev, err := strconv.ParseInt(args[0], 10, 64)
if err != nil { if err != nil {
panic("bad arg") ExitWithError(ExitError, err)
} }
conn, err := grpc.Dial(c.GlobalString("endpoint")) endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil { if err != nil {
panic(err) ExitWithError(ExitError, err)
}
conn, err := grpc.Dial(endpoint)
if err != nil {
ExitWithError(ExitBadConnection, err)
} }
kv := pb.NewKVClient(conn) kv := pb.NewKVClient(conn)
req := &pb.CompactionRequest{Revision: rev} req := &pb.CompactionRequest{Revision: rev}

View File

@ -17,36 +17,40 @@ package command
import ( import (
"fmt" "fmt"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
) )
// NewDeleteRangeCommand returns the CLI command for "deleteRange". // NewDeleteRangeCommand returns the cobra command for "deleteRange".
func NewDeleteRangeCommand() cli.Command { func NewDeleteRangeCommand() *cobra.Command {
return cli.Command{ return &cobra.Command{
Name: "delete-range", Use: "delete-range",
Action: func(c *cli.Context) { Short: "DeleteRange deletes the given range from the store.",
deleteRangeCommandFunc(c) Run: deleteRangeCommandFunc,
},
} }
} }
// deleteRangeCommandFunc executes the "delegeRange" command. // deleteRangeCommandFunc executes the "deleteRange" command.
func deleteRangeCommandFunc(c *cli.Context) { func deleteRangeCommandFunc(cmd *cobra.Command, args []string) {
if len(c.Args()) == 0 { if len(args) == 0 {
panic("bad arg") ExitWithError(ExitBadArgs, fmt.Errorf("delete-range command needs arguments."))
} }
var rangeEnd []byte var rangeEnd []byte
key := []byte(c.Args()[0]) key := []byte(args[0])
if len(c.Args()) > 1 { if len(args) > 1 {
rangeEnd = []byte(c.Args()[1]) rangeEnd = []byte(args[1])
} }
conn, err := grpc.Dial(c.GlobalString("endpoint"))
endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil { if err != nil {
panic(err) ExitWithError(ExitError, err)
}
conn, err := grpc.Dial(endpoint)
if err != nil {
ExitWithError(ExitBadConnection, err)
} }
kv := pb.NewKVClient(conn) kv := pb.NewKVClient(conn)
req := &pb.DeleteRangeRequest{Key: key, RangeEnd: rangeEnd} req := &pb.DeleteRangeRequest{Key: key, RangeEnd: rangeEnd}

View File

@ -0,0 +1,39 @@
// Copyright 2015 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 command
import (
"fmt"
"os"
"github.com/coreos/etcd/client"
)
const (
// http://tldp.org/LDP/abs/html/exitcodes.html
ExitSuccess = iota
ExitError
ExitBadConnection
ExitInvalidInput // for txn, watch command
ExitBadArgs = 128
)
func ExitWithError(code int, err error) {
fmt.Fprintln(os.Stderr, "Error: ", err)
if cerr, ok := err.(*client.ClusterError); ok {
fmt.Fprintln(os.Stderr, cerr.Detail())
}
os.Exit(code)
}

View File

@ -0,0 +1,21 @@
// Copyright 2015 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 command
// GlobalFlags are flags that defined globally
// and are inherited to all sub-commands.
type GlobalFlags struct {
Endpoints string
}

View File

@ -17,33 +17,37 @@ package command
import ( import (
"fmt" "fmt"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
) )
// NewPutCommand returns the CLI command for "put". // NewPutCommand returns the cobra command for "put".
func NewPutCommand() cli.Command { func NewPutCommand() *cobra.Command {
return cli.Command{ return &cobra.Command{
Name: "put", Use: "put",
Action: func(c *cli.Context) { Short: "Put puts the given key into the store.",
putCommandFunc(c) Run: putCommandFunc,
},
} }
} }
// putCommandFunc executes the "put" command. // putCommandFunc executes the "put" command.
func putCommandFunc(c *cli.Context) { func putCommandFunc(cmd *cobra.Command, args []string) {
if len(c.Args()) != 2 { if len(args) != 2 {
panic("bad arg") ExitWithError(ExitBadArgs, fmt.Errorf("put command needs 2 arguments."))
} }
key := []byte(c.Args()[0]) key := []byte(args[0])
value := []byte(c.Args()[1]) value := []byte(args[1])
conn, err := grpc.Dial(c.GlobalString("endpoint"))
endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil { if err != nil {
panic(err) ExitWithError(ExitError, err)
}
conn, err := grpc.Dial(endpoint)
if err != nil {
ExitWithError(ExitBadConnection, err)
} }
kv := pb.NewKVClient(conn) kv := pb.NewKVClient(conn)
req := &pb.PutRequest{Key: key, Value: value} req := &pb.PutRequest{Key: key, Value: value}

View File

@ -17,36 +17,40 @@ package command
import ( import (
"fmt" "fmt"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
) )
// NewRangeCommand returns the CLI command for "range". // NewRangeCommand returns the cobra command for "range".
func NewRangeCommand() cli.Command { func NewRangeCommand() *cobra.Command {
return cli.Command{ return &cobra.Command{
Name: "range", Use: "range",
Action: func(c *cli.Context) { Short: "Range gets the keys in the range from the store.",
rangeCommandFunc(c) Run: rangeCommandFunc,
},
} }
} }
// rangeCommandFunc executes the "range" command. // rangeCommandFunc executes the "range" command.
func rangeCommandFunc(c *cli.Context) { func rangeCommandFunc(cmd *cobra.Command, args []string) {
if len(c.Args()) == 0 { if len(args) == 0 {
panic("bad arg") ExitWithError(ExitBadArgs, fmt.Errorf("range command needs arguments."))
} }
var rangeEnd []byte var rangeEnd []byte
key := []byte(c.Args()[0]) key := []byte(args[0])
if len(c.Args()) > 1 { if len(args) > 1 {
rangeEnd = []byte(c.Args()[1]) rangeEnd = []byte(args[1])
} }
conn, err := grpc.Dial(c.GlobalString("endpoint"))
endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil { if err != nil {
panic(err) ExitWithError(ExitError, err)
}
conn, err := grpc.Dial(endpoint)
if err != nil {
ExitWithError(ExitBadConnection, err)
} }
kv := pb.NewKVClient(conn) kv := pb.NewKVClient(conn)
req := &pb.RangeRequest{Key: key, RangeEnd: rangeEnd} req := &pb.RangeRequest{Key: key, RangeEnd: rangeEnd}

View File

@ -21,26 +21,25 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
) )
// NewTxnCommand returns the CLI command for "txn". // NewTxnCommand returns the cobra command for "txn".
func NewTxnCommand() cli.Command { func NewTxnCommand() *cobra.Command {
return cli.Command{ return &cobra.Command{
Name: "txn", Use: "txn",
Action: func(c *cli.Context) { Short: "Txn processes all the requests in one transaction.",
txnCommandFunc(c) Run: txnCommandFunc,
},
} }
} }
// txnCommandFunc executes the "txn" command. // txnCommandFunc executes the "txn" command.
func txnCommandFunc(c *cli.Context) { func txnCommandFunc(cmd *cobra.Command, args []string) {
if len(c.Args()) != 0 { if len(args) != 0 {
panic("unexpected args") ExitWithError(ExitBadArgs, fmt.Errorf("txn command does not accept argument."))
} }
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
@ -51,15 +50,19 @@ func txnCommandFunc(c *cli.Context) {
next = next(txn, reader) next = next(txn, reader)
} }
conn, err := grpc.Dial(c.GlobalString("endpoint")) endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil { if err != nil {
panic(err) ExitWithError(ExitError, err)
}
conn, err := grpc.Dial(endpoint)
if err != nil {
ExitWithError(ExitBadConnection, err)
} }
kv := pb.NewKVClient(conn) kv := pb.NewKVClient(conn)
resp, err := kv.Txn(context.Background(), txn) resp, err := kv.Txn(context.Background(), txn)
if err != nil { if err != nil {
fmt.Println(err) ExitWithError(ExitError, err)
} }
if resp.Succeeded { if resp.Succeeded {
fmt.Println("executed success request list") fmt.Println("executed success request list")
@ -75,7 +78,7 @@ func compareState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc {
line, err := r.ReadString('\n') line, err := r.ReadString('\n')
if err != nil { if err != nil {
os.Exit(1) ExitWithError(ExitInvalidInput, err)
} }
if len(line) == 1 { if len(line) == 1 {
@ -86,8 +89,7 @@ func compareState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc {
line = line[:len(line)-1] line = line[:len(line)-1]
c, err := parseCompare(line) c, err := parseCompare(line)
if err != nil { if err != nil {
fmt.Println(err) ExitWithError(ExitInvalidInput, err)
os.Exit(1)
} }
txn.Compare = append(txn.Compare, c) txn.Compare = append(txn.Compare, c)
@ -100,7 +102,7 @@ func successState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc {
line, err := r.ReadString('\n') line, err := r.ReadString('\n')
if err != nil { if err != nil {
os.Exit(1) ExitWithError(ExitInvalidInput, err)
} }
if len(line) == 1 { if len(line) == 1 {
@ -111,8 +113,7 @@ func successState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc {
line = line[:len(line)-1] line = line[:len(line)-1]
ru, err := parseRequestUnion(line) ru, err := parseRequestUnion(line)
if err != nil { if err != nil {
fmt.Println(err) ExitWithError(ExitInvalidInput, err)
os.Exit(1)
} }
txn.Success = append(txn.Success, ru) txn.Success = append(txn.Success, ru)
@ -125,7 +126,7 @@ func failureState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc {
line, err := r.ReadString('\n') line, err := r.ReadString('\n')
if err != nil { if err != nil {
os.Exit(1) ExitWithError(ExitInvalidInput, err)
} }
if len(line) == 1 { if len(line) == 1 {
@ -136,8 +137,7 @@ func failureState(txn *pb.TxnRequest, r *bufio.Reader) stateFunc {
line = line[:len(line)-1] line = line[:len(line)-1]
ru, err := parseRequestUnion(line) ru, err := parseRequestUnion(line)
if err != nil { if err != nil {
fmt.Println(err) ExitWithError(ExitInvalidInput, err)
os.Exit(1)
} }
txn.Failure = append(txn.Failure, ru) txn.Failure = append(txn.Failure, ru)

View File

@ -0,0 +1,35 @@
// Copyright 2015 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 command
import (
"fmt"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/version"
)
// NewVersionCommand prints out the version of etcd.
func NewVersionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print the version of etcdctlv3.",
Run: versionCommandFunc,
}
}
func versionCommandFunc(cmd *cobra.Command, args []string) {
fmt.Println(version.Version)
}

View File

@ -21,33 +21,36 @@ import (
"os" "os"
"strings" "strings"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc" "github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
) )
// NewWatchCommand returns the CLI command for "watch". // NewWatchCommand returns the cobra command for "watch".
func NewWatchCommand() cli.Command { func NewWatchCommand() *cobra.Command {
return cli.Command{ return &cobra.Command{
Name: "watch", Use: "watch",
Action: func(c *cli.Context) { Short: "Watch watches the events happening or happened.",
watchCommandFunc(c) Run: watchCommandFunc,
},
} }
} }
// watchCommandFunc executes the "watch" command. // watchCommandFunc executes the "watch" command.
func watchCommandFunc(c *cli.Context) { func watchCommandFunc(cmd *cobra.Command, args []string) {
conn, err := grpc.Dial(c.GlobalString("endpoint")) endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil { if err != nil {
panic(err) ExitWithError(ExitInvalidInput, err)
}
conn, err := grpc.Dial(endpoint)
if err != nil {
ExitWithError(ExitBadConnection, err)
} }
wAPI := pb.NewWatchClient(conn) wAPI := pb.NewWatchClient(conn)
wStream, err := wAPI.Watch(context.TODO()) wStream, err := wAPI.Watch(context.TODO())
if err != nil { if err != nil {
panic(err) ExitWithError(ExitBadConnection, err)
} }
go recvLoop(wStream) go recvLoop(wStream)
@ -57,8 +60,7 @@ func watchCommandFunc(c *cli.Context) {
for { for {
l, err := reader.ReadString('\n') l, err := reader.ReadString('\n')
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error reading watch request line: %v", err) ExitWithError(ExitInvalidInput, fmt.Errorf("Error reading watch request line: %v", err))
os.Exit(1)
} }
l = strings.TrimSuffix(l, "\n") l = strings.TrimSuffix(l, "\n")
@ -91,10 +93,10 @@ func recvLoop(wStream pb.Watch_WatchClient) {
for { for {
resp, err := wStream.Recv() resp, err := wStream.Recv()
if err == io.EOF { if err == io.EOF {
os.Exit(0) os.Exit(ExitSuccess)
} }
if err != nil { if err != nil {
panic(err) ExitWithError(ExitError, err)
} }
fmt.Printf("%s: %s %s\n", resp.Event.Type, string(resp.Event.Kv.Key), string(resp.Event.Kv.Value)) fmt.Printf("%s: %s %s\n", resp.Event.Type, string(resp.Event.Kv.Key), string(resp.Event.Kv.Value))
} }

166
etcdctlv3/help.go Normal file
View File

@ -0,0 +1,166 @@
// Copyright 2015 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.
// copied from https://github.com/coreos/rkt/blob/master/rkt/help.go
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"text/template"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/pflag"
"github.com/coreos/etcd/version"
)
var (
commandUsageTemplate *template.Template
templFuncs = template.FuncMap{
"descToLines": func(s string) []string {
// trim leading/trailing whitespace and split into slice of lines
return strings.Split(strings.Trim(s, "\n\t "), "\n")
},
"cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string {
parts := []string{cmd.Name()}
for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() {
cmd = cmd.Parent()
parts = append([]string{cmd.Name()}, parts...)
}
return strings.Join(parts, " ")
},
}
)
func init() {
commandUsage := `
{{ $cmd := .Cmd }}\
{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
NAME:
{{ if not .Cmd.HasParent }}\
{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
{{else}}\
{{printf "\t%s - %s" $cmdname .Cmd.Short}}
{{end}}\
USAGE:
{{printf "\t%s" .Cmd.UseLine}}
{{ if not .Cmd.HasParent }}\
VERSION:
{{printf "\t%s" .Version}}
{{end}}\
{{if .Cmd.HasSubCommands}}\
COMMANDS:
{{range .SubCommands}}\
{{ $cmdname := cmdName . $cmd }}\
{{ if .Runnable }}\
{{printf "\t%s\t%s" $cmdname .Short}}
{{end}}\
{{end}}\
{{end}}\
{{ if .Cmd.Long }}\
DESCRIPTION:
{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
{{end}}\
{{end}}\
{{if .Cmd.HasLocalFlags}}\
OPTIONS:
{{.LocalFlags}}\
{{end}}\
{{if .Cmd.HasInheritedFlags}}\
GLOBAL OPTIONS:
{{.GlobalFlags}}\
{{end}}
`[1:]
commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage, "\\\n", "", -1)))
}
func etcdFlagUsages(flagSet *pflag.FlagSet) string {
x := new(bytes.Buffer)
flagSet.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 {
return
}
format := ""
if len(flag.Shorthand) > 0 {
format = " -%s, --%s"
} else {
format = " %s --%s"
}
if len(flag.NoOptDefVal) > 0 {
format = format + "["
}
if flag.Value.Type() == "string" {
// put quotes on the value
format = format + "=%q"
} else {
format = format + "=%s"
}
if len(flag.NoOptDefVal) > 0 {
format = format + "]"
}
format = format + "\t%s\n"
shorthand := flag.Shorthand
fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage)
})
return x.String()
}
func getSubCommands(cmd *cobra.Command) []*cobra.Command {
var subCommands []*cobra.Command
for _, subCmd := range cmd.Commands() {
subCommands = append(subCommands, subCmd)
subCommands = append(subCommands, getSubCommands(subCmd)...)
}
return subCommands
}
func usageFunc(cmd *cobra.Command) error {
subCommands := getSubCommands(cmd)
tabOut := getTabOutWithWriter(os.Stdout)
commandUsageTemplate.Execute(tabOut, struct {
Cmd *cobra.Command
LocalFlags string
GlobalFlags string
SubCommands []*cobra.Command
Version string
}{
cmd,
etcdFlagUsages(cmd.LocalFlags()),
etcdFlagUsages(cmd.InheritedFlags()),
subCommands,
version.Version,
})
tabOut.Flush()
return nil
}
func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer {
aTabOut := new(tabwriter.Writer)
aTabOut.Init(writer, 0, 8, 1, '\t', 0)
return aTabOut
}

View File

@ -16,29 +16,55 @@
package main package main
import ( import (
"os" "text/tabwriter"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
"github.com/coreos/etcd/etcdctlv3/command" "github.com/coreos/etcd/etcdctlv3/command"
"github.com/coreos/etcd/version"
) )
func main() { const (
app := cli.NewApp() cliName = "etcdctlv3"
app.Name = "etcdctlv3" cliDescription = "A simple command line client for etcd3."
app.Version = version.Version )
app.Usage = "A simple command line client for etcd3."
app.Flags = []cli.Flag{ var (
cli.StringFlag{Name: "endpoint", Value: "127.0.0.1:2378", Usage: "gRPC endpoint"}, tabOut *tabwriter.Writer
globalFlags = command.GlobalFlags{}
)
var (
rootCmd = &cobra.Command{
Use: cliName,
Short: cliDescription,
SuggestFor: []string{"etcctlv3", "etcdcltv3", "etlctlv3"},
} }
app.Commands = []cli.Command{ )
func init() {
rootCmd.PersistentFlags().StringVar(&globalFlags.Endpoints, "endpoint", "127.0.0.1:2378", "gRPC endpoint")
rootCmd.AddCommand(
command.NewRangeCommand(), command.NewRangeCommand(),
command.NewPutCommand(), command.NewPutCommand(),
command.NewDeleteRangeCommand(), command.NewDeleteRangeCommand(),
command.NewTxnCommand(), command.NewTxnCommand(),
command.NewCompactionCommand(), command.NewCompactionCommand(),
command.NewWatchCommand(), command.NewWatchCommand(),
} command.NewVersionCommand(),
)
app.Run(os.Args) }
func init() {
cobra.EnablePrefixMatching = true
}
func main() {
rootCmd.SetUsageFunc(usageFunc)
// Make help just show the usage
rootCmd.SetHelpTemplate(`{{.UsageString}}`)
if err := rootCmd.Execute(); err != nil {
command.ExitWithError(command.ExitError, err)
}
} }