├── .gitignore ├── migr8 ├── dump.rdb ├── go.mod ├── circle.yml ├── vendor ├── github.com │ ├── codegangsta │ │ └── cli │ │ │ ├── .travis.yml │ │ │ ├── cli.go │ │ │ ├── LICENSE │ │ │ ├── command.go │ │ │ ├── help.go │ │ │ ├── app.go │ │ │ ├── README.md │ │ │ ├── context.go │ │ │ └── flag.go │ └── garyburd │ │ └── redigo │ │ ├── internal │ │ └── commandinfo.go │ │ ├── redis │ │ ├── redis.go │ │ ├── script.go │ │ ├── log.go │ │ ├── pubsub.go │ │ ├── doc.go │ │ ├── pool.go │ │ ├── reply.go │ │ ├── conn.go │ │ └── scan.go │ │ └── LICENSE └── modules.txt ├── Makefile ├── go.sum ├── delete.go ├── LICENSE ├── scanner.go ├── README.md ├── main_test.go ├── delete_test.go ├── migrate.go ├── migrate_test.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | migrate 2 | bin/ 3 | pkg/ 4 | -------------------------------------------------------------------------------- /migr8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reverbdotcom/migr8/HEAD/migr8 -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reverbdotcom/migr8/HEAD/dump.rdb -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module reverbdotcom/migr8 2 | 3 | go 1.21.4 4 | 5 | require ( 6 | github.com/codegangsta/cli v1.10.2 7 | github.com/garyburd/redigo v0.0.0-20151029235527-6ece6e0a09f2 8 | ) 9 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | pre: 3 | - sudo apt-get install -y redis-server 4 | override: 5 | - go get github.com/constabulary/gb/... 6 | test: 7 | override: 8 | - make 9 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | go: 5 | - 1.0.3 6 | - 1.1.2 7 | - 1.2.2 8 | - 1.3.3 9 | - 1.4.2 10 | 11 | script: 12 | - go vet ./... 13 | - go test -v ./... 14 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/codegangsta/cli v1.10.2 2 | ## explicit 3 | github.com/codegangsta/cli 4 | # github.com/garyburd/redigo v0.0.0-20151029235527-6ece6e0a09f2 5 | ## explicit 6 | github.com/garyburd/redigo/internal 7 | github.com/garyburd/redigo/redis 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test build linux 2 | 3 | all: clean vet test build 4 | clean: 5 | @rm -rf bin/* 6 | 7 | build: clean 8 | @go build 9 | 10 | test: 11 | @go test 12 | 13 | vet: 14 | @go vet ./... 15 | 16 | linux: clean 17 | @GOOS=linux go build 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/codegangsta/cli v1.10.2 h1:2XP7LrElYXmnuJpfKCJf11ENt1qXJGt/6vNI3kIaEaw= 2 | github.com/codegangsta/cli v1.10.2/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= 3 | github.com/garyburd/redigo v0.0.0-20151029235527-6ece6e0a09f2 h1:oPANGfJj1kUNthxd8FT6mvg/a1/P4IupFWjzxcTukjY= 4 | github.com/garyburd/redigo v0.0.0-20151029235527-6ece6e0a09f2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= 5 | -------------------------------------------------------------------------------- /delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | 7 | "github.com/garyburd/redigo/redis" 8 | ) 9 | 10 | func deleteKeys(queue chan Task, wg *sync.WaitGroup) { 11 | sourceConn := sourceConnection(config.Source) 12 | for task := range queue { 13 | for _, key := range task.list { 14 | if config.DryRun { 15 | log.Printf("Would have deleted %s", key) 16 | continue 17 | } 18 | 19 | if _, err := redis.String(sourceConn.Do("del", key)); err != nil { 20 | log.Printf("Deleted %s \n", key) 21 | } else { 22 | log.Printf("Could not deleted %s: %s\n", key, err) 23 | } 24 | } 25 | } 26 | 27 | wg.Done() 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Package cli provides a minimal framework for creating and organizing command line 2 | // Go applications. cli is designed to be easy to understand and write, the most simple 3 | // cli application can be written as follows: 4 | // func main() { 5 | // cli.NewApp().Run(os.Args) 6 | // } 7 | // 8 | // Of course this application does not do much, so let's make this an actual application: 9 | // func main() { 10 | // app := cli.NewApp() 11 | // app.Name = "greet" 12 | // app.Usage = "say a greeting" 13 | // app.Action = func(c *cli.Context) { 14 | // println("Greetings") 15 | // } 16 | // 17 | // app.Run(os.Args) 18 | // } 19 | package cli 20 | 21 | import ( 22 | "strings" 23 | ) 24 | 25 | type MultiError struct { 26 | Errors []error 27 | } 28 | 29 | func NewMultiError(err ...error) MultiError { 30 | return MultiError{Errors: err} 31 | } 32 | 33 | func (m MultiError) Error() string { 34 | errs := make([]string, len(m.Errors)) 35 | for i, err := range m.Errors { 36 | errs[i] = err.Error() 37 | } 38 | 39 | return strings.Join(errs, "\n") 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2015] [Reverb.com, LLC] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Jeremy Saenz 2 | All Rights Reserved. 3 | 4 | MIT LICENSE 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/garyburd/redigo/redis" 10 | ) 11 | 12 | var keysProcessed uint64 = 0 13 | var startedAt time.Time 14 | 15 | func keyProcessed() { 16 | // there is no mutex here, but I don't care as this is just information and does not need 17 | // to be accurate 18 | keysProcessed += 1 19 | var duration time.Duration = time.Now().Sub(startedAt) 20 | kps := float64(keysProcessed) / float64(duration.Seconds()) 21 | log.Printf("\r%v keys processd in %v KPS", keysProcessed, kps) 22 | } 23 | 24 | func scanKeys(queue chan Task, wg *sync.WaitGroup) { 25 | cursor := 0 26 | conn := sourceConnection(config.Source) 27 | 28 | key_search := fmt.Sprintf("%s*", config.Prefix) 29 | log.Println("Starting Scan with keys", key_search) 30 | 31 | for { 32 | // we scan with our cursor offset, starting at 0 33 | reply, _ := redis.Values(conn.Do("scan", cursor, "match", key_search, "count", config.Batch)) 34 | 35 | var tmp_keys []string 36 | // this func name is confusing...it actually just converts array returns to Go values 37 | redis.Scan(reply, &cursor, &tmp_keys) 38 | 39 | // put this thing in the queue 40 | queue <- Task{list: tmp_keys} 41 | // check if we need to stop... 42 | if cursor == 0 { 43 | log.Println("Finished!") 44 | 45 | // close the channel 46 | close(queue) 47 | wg.Done() 48 | break 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | migr8 2 | --- 3 | 4 | Redis Migration Utility written in Go 5 | 6 | ## Build 7 | migr8 uses [gb](http://getgb.io) to vendor dependencies. 8 | 9 | To install it run, `go get github.com/constabulary/gb/...` 10 | 11 | Tests require that `redis-server` is somewhere in your $PATH. 12 | 13 | `make` To run tests and create a binary 14 | 15 | ## Usage 16 | ``` 17 | NAME: 18 | migr8 - It's time to move some redis 19 | 20 | USAGE: 21 | migr8 [global options] command [command options] [arguments...] 22 | 23 | VERSION: 24 | 0.0.0 25 | 26 | COMMANDS: 27 | migrate Migrate one redis to a new redis 28 | delete Delete all keys with the given prefix 29 | help, h Shows a list of commands or help for one command 30 | 31 | GLOBAL OPTIONS: 32 | --dry-run, --dr Run in dry-run mode 33 | --source, -s "127.0.0.1:6379" The redis server to pull data from 34 | --dest, -d "127.0.0.1:6379" The destination redis server 35 | --workers, -w "2" The count of workers to spin up 36 | --batch, -b "10" The batch size 37 | --prefix, -p The key prefix to act on 38 | --clear-dest, -c Clear the destination of all it's keys and values 39 | --help, -h show help 40 | --version, -v print the version 41 | ``` 42 | 43 | #### Cross Compile for Linux: 44 | *Note:* You will need the Go cross compile tools. If you're using homebrew: `brew install go --cross-compile-common` 45 | 46 | `make linux` will build a linux binary in bin/ 47 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "syscall" 8 | "testing" 9 | "time" 10 | 11 | "github.com/garyburd/redigo/redis" 12 | ) 13 | 14 | var sourceServer *RedisTestServer 15 | var destServer *RedisTestServer 16 | 17 | func ClearRedis() { 18 | sourceServer.conn.Do("flushdb") 19 | destServer.conn.Do("flushdb") 20 | } 21 | 22 | func StartTestServers() { 23 | fmt.Println("Starting redis...") 24 | sourceServer = NewRedisTestServer("6377") 25 | destServer = NewRedisTestServer("6277") 26 | } 27 | 28 | func StopTestServers() { 29 | fmt.Println("Stopping redis...") 30 | sourceServer.Stop() 31 | destServer.Stop() 32 | } 33 | 34 | func NewRedisTestServer(port string) *RedisTestServer { 35 | srv := &RedisTestServer{ 36 | port: port, 37 | url: fmt.Sprintf("127.0.0.1:%s", port), 38 | } 39 | 40 | srv.Start() 41 | 42 | return srv 43 | } 44 | 45 | type RedisTestServer struct { 46 | cmd *exec.Cmd 47 | port string 48 | url string 49 | conn redis.Conn 50 | } 51 | 52 | func (s *RedisTestServer) Start() { 53 | args := fmt.Sprintf("--port %s", s.port) 54 | s.cmd = exec.Command("redis-server", args) 55 | 56 | err := s.cmd.Start() 57 | time.Sleep(2 * time.Second) 58 | 59 | conn, err := redis.Dial("tcp", s.url) 60 | s.conn = conn 61 | 62 | if err != nil { 63 | panic("Could not start redis") 64 | } 65 | } 66 | 67 | func (s *RedisTestServer) Stop() { 68 | s.cmd.Process.Signal(syscall.SIGTERM) 69 | s.cmd.Process.Wait() 70 | } 71 | 72 | func TestMain(m *testing.M) { 73 | StartTestServers() 74 | 75 | result := m.Run() 76 | 77 | StopTestServers() 78 | os.Exit(result) 79 | } 80 | -------------------------------------------------------------------------------- /delete_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/garyburd/redigo/redis" 8 | ) 9 | 10 | func Test_DeleteAllKeysWithPrefix(t *testing.T) { 11 | ClearRedis() 12 | 13 | config = Config{ 14 | Source: sourceServer.url, 15 | Workers: 1, 16 | Batch: 10, 17 | Prefix: "bar", 18 | } 19 | 20 | for i := 0; i < 100; i++ { 21 | key := fmt.Sprintf("bar:%d", i) 22 | sourceServer.conn.Do("SET", key, i) 23 | } 24 | 25 | sourceServer.conn.Do("SET", "baz:foo", "yolo") 26 | 27 | RunAction(deleteKeys) 28 | 29 | for i := 0; i < 100; i++ { 30 | key := fmt.Sprintf("bar:%d", i) 31 | exists, _ := redis.Bool(sourceServer.conn.Do("EXISTS", key)) 32 | 33 | if exists { 34 | t.Errorf("Found a key %s that should have been deleted", key) 35 | } 36 | } 37 | 38 | exists, _ := redis.Bool(sourceServer.conn.Do("EXISTS", "baz:foo")) 39 | 40 | if !exists { 41 | t.Errorf("Deleted a key %s that should not have been deleted", "baz:foo") 42 | } 43 | } 44 | 45 | func Test_DoesNothingInDryRunModeForDelete(t *testing.T) { 46 | ClearRedis() 47 | 48 | config = Config{ 49 | Source: sourceServer.url, 50 | Workers: 1, 51 | Batch: 10, 52 | Prefix: "bar", 53 | DryRun: true, 54 | } 55 | 56 | for i := 0; i < 100; i++ { 57 | key := fmt.Sprintf("bar:%d", i) 58 | sourceServer.conn.Do("SET", key, i) 59 | } 60 | 61 | RunAction(deleteKeys) 62 | 63 | for i := 0; i < 100; i++ { 64 | key := fmt.Sprintf("bar:%d", i) 65 | exists, _ := redis.Bool(sourceServer.conn.Do("EXISTS", key)) 66 | 67 | if !exists { 68 | t.Errorf("In DryRun mode, but found a key %s that was actually deleted", key) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package internal // import "github.com/garyburd/redigo/internal" 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func init() { 44 | for n, ci := range commandInfos { 45 | commandInfos[strings.ToLower(n)] = ci 46 | } 47 | } 48 | 49 | func LookupCommandInfo(commandName string) CommandInfo { 50 | if ci, ok := commandInfos[commandName]; ok { 51 | return ci 52 | } 53 | return commandInfos[strings.ToUpper(commandName)] 54 | } 55 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | // Error represents an error returned in a command reply. 18 | type Error string 19 | 20 | func (err Error) Error() string { return string(err) } 21 | 22 | // Conn represents a connection to a Redis server. 23 | type Conn interface { 24 | // Close closes the connection. 25 | Close() error 26 | 27 | // Err returns a non-nil value if the connection is broken. The returned 28 | // value is either the first non-nil value returned from the underlying 29 | // network connection or a protocol parsing error. Applications should 30 | // close broken connections. 31 | Err() error 32 | 33 | // Do sends a command to the server and returns the received reply. 34 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | 36 | // Send writes the command to the client's output buffer. 37 | Send(commandName string, args ...interface{}) error 38 | 39 | // Flush flushes the output buffer to the Redis server. 40 | Flush() error 41 | 42 | // Receive receives a single reply from the Redis server 43 | Receive() (reply interface{}, err error) 44 | } 45 | -------------------------------------------------------------------------------- /migrate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | 8 | "github.com/garyburd/redigo/redis" 9 | ) 10 | 11 | func dumpKeyAndTTL(key string, sourceConn redis.Conn) (string, int64, error) { 12 | var err error 13 | var dumpedKey string 14 | var ttl int64 15 | 16 | if dumpedKey, err = redis.String(sourceConn.Do("dump", key)); err != nil { 17 | return dumpedKey, ttl, err 18 | } 19 | 20 | if ttl, err = redis.Int64(sourceConn.Do("pttl", key)); err != nil { 21 | return dumpedKey, ttl, err 22 | } 23 | 24 | return dumpedKey, ttl, err 25 | } 26 | 27 | func dumpAndRestore(sourceConn redis.Conn, destConn redis.Conn, key string) { 28 | dumpedKey, dumpedKeyTTL, err := dumpKeyAndTTL(key, sourceConn) 29 | 30 | if err != nil { 31 | log.Println(err) 32 | return 33 | } 34 | 35 | // when doing pttl, -1 means no expiration 36 | // when doing restore, 0 means no expiration 37 | if dumpedKeyTTL == -1 { 38 | dumpedKeyTTL = 0 39 | } 40 | 41 | if config.DryRun { 42 | log.Printf("Would have restored %s with ttl %d", key, dumpedKeyTTL) 43 | return 44 | } 45 | _, err = destConn.Do("restore", key, dumpedKeyTTL, dumpedKey) 46 | 47 | if err != nil { 48 | log.Printf("error: %s\n", err) 49 | return 50 | } 51 | 52 | keyProcessed() 53 | } 54 | 55 | func migrateKeys(queue chan Task, wg *sync.WaitGroup) { 56 | sourceConn := sourceConnection(config.Source) 57 | destConn := destConnection(config.Dest) 58 | 59 | for task := range queue { 60 | for _, key := range task.list { 61 | dumpAndRestore(sourceConn, destConn, key) 62 | } 63 | } 64 | 65 | wg.Done() 66 | } 67 | 68 | func shouldClearAllKeys(dest string) bool { 69 | fmt.Println("Are you sure you want to delete all keys at", dest, "? . Please type Y or N.") 70 | 71 | var response string 72 | if _, err := fmt.Scanln(&response); err == nil { 73 | return response == "Y" 74 | } 75 | 76 | return false 77 | } 78 | 79 | func clearDestination(dest string) { 80 | if shouldClearAllKeys(dest) { 81 | log.Println("Deleting all keys of destination") 82 | destConn := destConnection(dest) 83 | 84 | if _, err := destConn.Do("flushall"); err != nil { 85 | log.Printf("error in flushing: %s\n", err) 86 | } 87 | } else { 88 | log.Println("Skipping key deletion") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /migrate_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/garyburd/redigo/redis" 8 | ) 9 | 10 | func Test_MigrateAllKeysWithAPrefix(t *testing.T) { 11 | ClearRedis() 12 | 13 | config = Config{ 14 | Source: sourceServer.url, 15 | Dest: destServer.url, 16 | Workers: 1, 17 | Batch: 10, 18 | Prefix: "bar", 19 | } 20 | 21 | for i := 0; i < 100; i++ { 22 | key := fmt.Sprintf("bar:%d", i) 23 | sourceServer.conn.Do("SET", key, i) 24 | } 25 | 26 | sourceServer.conn.Do("SET", "baz:foo", "yolo") 27 | 28 | RunAction(migrateKeys) 29 | 30 | for i := 0; i < 100; i++ { 31 | key := fmt.Sprintf("bar:%d", i) 32 | exists, _ := redis.Bool(destServer.conn.Do("EXISTS", key)) 33 | 34 | if !exists { 35 | t.Errorf("Could not find a key %s that should have been migrated", key) 36 | } 37 | } 38 | 39 | exists, _ := redis.Bool(destServer.conn.Do("EXISTS", "baz:foo")) 40 | 41 | if exists { 42 | t.Errorf("Found a key %s that should not have been migrated", "baz:foo") 43 | } 44 | } 45 | 46 | func Test_MigrateAllKeysWithTTLs(t *testing.T) { 47 | ClearRedis() 48 | 49 | config = Config{ 50 | Source: sourceServer.url, 51 | Dest: destServer.url, 52 | Workers: 1, 53 | Batch: 10, 54 | Prefix: "bar", 55 | } 56 | 57 | for i := 0; i < 100; i++ { 58 | key := fmt.Sprintf("bar:%d", i) 59 | sourceServer.conn.Do("SET", key, i, "EX", 600) 60 | } 61 | 62 | RunAction(migrateKeys) 63 | 64 | for i := 0; i < 100; i++ { 65 | key := fmt.Sprintf("bar:%d", i) 66 | exists, _ := redis.Bool(destServer.conn.Do("EXISTS", key)) 67 | 68 | if !exists { 69 | t.Errorf("Could not find a key %s that should have been migrated", key) 70 | } 71 | 72 | ttl, _ := redis.Int64(destServer.conn.Do("PTTL", key)) 73 | 74 | if ttl < 1 || ttl > 600000 { 75 | t.Errorf("Could not find a TTL for key %s that should have been migrated", key) 76 | } 77 | } 78 | } 79 | 80 | func Test_DoesNothingInDryRunModeForMigrate(t *testing.T) { 81 | ClearRedis() 82 | 83 | config = Config{ 84 | Source: sourceServer.url, 85 | Workers: 1, 86 | Batch: 10, 87 | Prefix: "bar", 88 | DryRun: true, 89 | Dest: destServer.url, 90 | } 91 | 92 | for i := 0; i < 100; i++ { 93 | key := fmt.Sprintf("bar:%d", i) 94 | sourceServer.conn.Do("SET", key, i) 95 | } 96 | 97 | RunAction(migrateKeys) 98 | 99 | for i := 0; i < 100; i++ { 100 | key := fmt.Sprintf("bar:%d", i) 101 | exists, _ := redis.Bool(destServer.conn.Do("EXISTS", key)) 102 | 103 | if exists { 104 | t.Errorf("In DryRun mode, but found a key %s that was actually migrated", key) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 59 | // script using the EVALSHA command. If the command fails because the script is 60 | // not loaded, then Do evaluates the script using the EVAL command (thus 61 | // causing the script to load). 62 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 63 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 64 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 65 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 66 | } 67 | return v, err 68 | } 69 | 70 | // SendHash evaluates the script without waiting for the reply. The script is 71 | // evaluated with the EVALSHA command. The application must ensure that the 72 | // script is loaded by a previous call to Send, Do or Load methods. 73 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 74 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 75 | } 76 | 77 | // Send evaluates the script without waiting for the reply. 78 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 80 | } 81 | 82 | // Load loads the script without evaluating it. 83 | func (s *Script) Load(c Conn) error { 84 | _, err := c.Do("SCRIPT", "LOAD", s.src) 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | ) 22 | 23 | // NewLoggingConn returns a logging wrapper around a connection. 24 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 25 | if prefix != "" { 26 | prefix = prefix + "." 27 | } 28 | return &loggingConn{conn, logger, prefix} 29 | } 30 | 31 | type loggingConn struct { 32 | Conn 33 | logger *log.Logger 34 | prefix string 35 | } 36 | 37 | func (c *loggingConn) Close() error { 38 | err := c.Conn.Close() 39 | var buf bytes.Buffer 40 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 41 | c.logger.Output(2, buf.String()) 42 | return err 43 | } 44 | 45 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 46 | const chop = 32 47 | switch v := v.(type) { 48 | case []byte: 49 | if len(v) > chop { 50 | fmt.Fprintf(buf, "%q...", v[:chop]) 51 | } else { 52 | fmt.Fprintf(buf, "%q", v) 53 | } 54 | case string: 55 | if len(v) > chop { 56 | fmt.Fprintf(buf, "%q...", v[:chop]) 57 | } else { 58 | fmt.Fprintf(buf, "%q", v) 59 | } 60 | case []interface{}: 61 | if len(v) == 0 { 62 | buf.WriteString("[]") 63 | } else { 64 | sep := "[" 65 | fin := "]" 66 | if len(v) > chop { 67 | v = v[:chop] 68 | fin = "...]" 69 | } 70 | for _, vv := range v { 71 | buf.WriteString(sep) 72 | c.printValue(buf, vv) 73 | sep = ", " 74 | } 75 | buf.WriteString(fin) 76 | } 77 | default: 78 | fmt.Fprint(buf, v) 79 | } 80 | } 81 | 82 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 83 | var buf bytes.Buffer 84 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 85 | if method != "Receive" { 86 | buf.WriteString(commandName) 87 | for _, arg := range args { 88 | buf.WriteString(", ") 89 | c.printValue(&buf, arg) 90 | } 91 | } 92 | buf.WriteString(") -> (") 93 | if method != "Send" { 94 | c.printValue(&buf, reply) 95 | buf.WriteString(", ") 96 | } 97 | fmt.Fprintf(&buf, "%v)", err) 98 | c.logger.Output(3, buf.String()) 99 | } 100 | 101 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 102 | reply, err := c.Conn.Do(commandName, args...) 103 | c.print("Do", commandName, args, reply, err) 104 | return reply, err 105 | } 106 | 107 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 108 | err := c.Conn.Send(commandName, args...) 109 | c.print("Send", commandName, args, nil, err) 110 | return err 111 | } 112 | 113 | func (c *loggingConn) Receive() (interface{}, error) { 114 | reply, err := c.Conn.Receive() 115 | c.print("Receive", "", nil, reply, err) 116 | return reply, err 117 | } 118 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "sync" 7 | "time" 8 | 9 | "github.com/codegangsta/cli" 10 | "github.com/garyburd/redigo/redis" 11 | ) 12 | 13 | type Task struct { 14 | list []string 15 | } 16 | 17 | type Worker func(queue chan Task, wg *sync.WaitGroup) 18 | 19 | type Config struct { 20 | Dest string 21 | Source string 22 | Workers int 23 | Batch int 24 | Prefix string 25 | ClearDest bool 26 | DryRun bool 27 | } 28 | 29 | var config Config 30 | 31 | func main() { 32 | app := cli.NewApp() 33 | app.Name = "migr8" 34 | app.Usage = "It's time to move some redis" 35 | app.Commands = []cli.Command{ 36 | { 37 | Name: "migrate", 38 | Usage: "Migrate one redis to a new redis", 39 | Action: Migrate, 40 | }, 41 | { 42 | Name: "delete", 43 | Usage: "Delete all keys with the given prefix", 44 | Action: Delete, 45 | }, 46 | } 47 | app.Flags = []cli.Flag{ 48 | cli.BoolFlag{ 49 | Name: "dry-run, n", 50 | Usage: "Run in dry-run mode", 51 | }, 52 | cli.StringFlag{ 53 | Name: "source, s", 54 | Usage: "The redis server to pull data from", 55 | Value: "127.0.0.1:6379", 56 | }, 57 | cli.StringFlag{ 58 | Name: "dest, d", 59 | Usage: "The destination redis server", 60 | Value: "127.0.0.1:6379", 61 | }, 62 | cli.IntFlag{ 63 | Name: "workers, w", 64 | Usage: "The count of workers to spin up", 65 | Value: 2, 66 | }, 67 | cli.IntFlag{ 68 | Name: "batch, b", 69 | Usage: "The batch size", 70 | Value: 10, 71 | }, 72 | cli.StringFlag{ 73 | Name: "prefix, p", 74 | Usage: "The key prefix to act on", 75 | }, 76 | cli.BoolFlag{ 77 | Name: "clear-dest, c", 78 | Usage: "Clear the destination of all it's keys and values", 79 | }, 80 | } 81 | 82 | app.Run(os.Args) 83 | } 84 | 85 | func ParseConfig(c *cli.Context) { 86 | config = Config{ 87 | Source: c.GlobalString("source"), 88 | Dest: c.GlobalString("dest"), 89 | Workers: c.GlobalInt("workers"), 90 | Batch: c.GlobalInt("batch"), 91 | Prefix: c.GlobalString("prefix"), 92 | ClearDest: c.GlobalBool("clear-dest"), 93 | DryRun: c.GlobalBool("dry-run"), 94 | } 95 | } 96 | 97 | func sourceConnection(source string) redis.Conn { 98 | // attempt to connect to source server 99 | sourceConn, err := redis.Dial("tcp", source) 100 | if err != nil { 101 | panic(err) 102 | } 103 | 104 | return sourceConn 105 | } 106 | 107 | func destConnection(dest string) redis.Conn { 108 | // attempt to connect to source server 109 | destConn, err := redis.Dial("tcp", dest) 110 | if err != nil { 111 | panic(err) 112 | } 113 | 114 | return destConn 115 | } 116 | 117 | func RunAction(action Worker) { 118 | wg := &sync.WaitGroup{} 119 | workQueue := make(chan Task, config.Workers) 120 | startedAt = time.Now() 121 | 122 | wg.Add(1) 123 | go scanKeys(workQueue, wg) 124 | 125 | for i := 0; i <= config.Workers; i++ { 126 | wg.Add(1) 127 | go action(workQueue, wg) 128 | } 129 | 130 | wg.Wait() 131 | } 132 | 133 | func Migrate(c *cli.Context) { 134 | ParseConfig(c) 135 | log.Printf("Running migrate with config: %+v\n", config) 136 | log.SetPrefix("migrate - ") 137 | 138 | if config.ClearDest { 139 | clearDestination(c.String("dest")) 140 | } 141 | 142 | RunAction(migrateKeys) 143 | } 144 | 145 | func Delete(c *cli.Context) { 146 | ParseConfig(c) 147 | log.Printf("Running delete with config: %+v\n", config) 148 | log.SetPrefix("delete - ") 149 | 150 | RunAction(deleteKeys) 151 | } 152 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import "errors" 18 | 19 | // Subscription represents a subscribe or unsubscribe notification. 20 | type Subscription struct { 21 | 22 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 23 | Kind string 24 | 25 | // The channel that was changed. 26 | Channel string 27 | 28 | // The current number of subscriptions for connection. 29 | Count int 30 | } 31 | 32 | // Message represents a message notification. 33 | type Message struct { 34 | 35 | // The originating channel. 36 | Channel string 37 | 38 | // The message data. 39 | Data []byte 40 | } 41 | 42 | // PMessage represents a pmessage notification. 43 | type PMessage struct { 44 | 45 | // The matched pattern. 46 | Pattern string 47 | 48 | // The originating channel. 49 | Channel string 50 | 51 | // The message data. 52 | Data []byte 53 | } 54 | 55 | // Pong represents a pubsub pong notification. 56 | type Pong struct { 57 | Data string 58 | } 59 | 60 | // PubSubConn wraps a Conn with convenience methods for subscribers. 61 | type PubSubConn struct { 62 | Conn Conn 63 | } 64 | 65 | // Close closes the connection. 66 | func (c PubSubConn) Close() error { 67 | return c.Conn.Close() 68 | } 69 | 70 | // Subscribe subscribes the connection to the specified channels. 71 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 72 | c.Conn.Send("SUBSCRIBE", channel...) 73 | return c.Conn.Flush() 74 | } 75 | 76 | // PSubscribe subscribes the connection to the given patterns. 77 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 78 | c.Conn.Send("PSUBSCRIBE", channel...) 79 | return c.Conn.Flush() 80 | } 81 | 82 | // Unsubscribe unsubscribes the connection from the given channels, or from all 83 | // of them if none is given. 84 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 85 | c.Conn.Send("UNSUBSCRIBE", channel...) 86 | return c.Conn.Flush() 87 | } 88 | 89 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 90 | // of them if none is given. 91 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 92 | c.Conn.Send("PUNSUBSCRIBE", channel...) 93 | return c.Conn.Flush() 94 | } 95 | 96 | // Ping sends a PING to the server with the specified data. 97 | func (c PubSubConn) Ping(data string) error { 98 | c.Conn.Send("PING", data) 99 | return c.Conn.Flush() 100 | } 101 | 102 | // Receive returns a pushed message as a Subscription, Message, PMessage, Pong 103 | // or error. The return value is intended to be used directly in a type switch 104 | // as illustrated in the PubSubConn example. 105 | func (c PubSubConn) Receive() interface{} { 106 | reply, err := Values(c.Conn.Receive()) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | var kind string 112 | reply, err = Scan(reply, &kind) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | switch kind { 118 | case "message": 119 | var m Message 120 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 121 | return err 122 | } 123 | return m 124 | case "pmessage": 125 | var pm PMessage 126 | if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { 127 | return err 128 | } 129 | return pm 130 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 131 | s := Subscription{Kind: kind} 132 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 133 | return err 134 | } 135 | return s 136 | case "pong": 137 | var p Pong 138 | if _, err := Scan(reply, &p.Data); err != nil { 139 | return err 140 | } 141 | return p 142 | } 143 | return errors.New("redigo: unknown pubsub notification") 144 | } 145 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/command.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | ) 8 | 9 | // Command is a subcommand for a cli.App. 10 | type Command struct { 11 | // The name of the command 12 | Name string 13 | // short name of the command. Typically one character (deprecated, use `Aliases`) 14 | ShortName string 15 | // A list of aliases for the command 16 | Aliases []string 17 | // A short description of the usage of this command 18 | Usage string 19 | // A longer explanation of how the command works 20 | Description string 21 | // A short description of the arguments of this command 22 | ArgsUsage string 23 | // The function to call when checking for bash command completions 24 | BashComplete func(context *Context) 25 | // An action to execute before any sub-subcommands are run, but after the context is ready 26 | // If a non-nil error is returned, no sub-subcommands are run 27 | Before func(context *Context) error 28 | // An action to execute after any subcommands are run, but after the subcommand has finished 29 | // It is run even if Action() panics 30 | After func(context *Context) error 31 | // The function to call when this command is invoked 32 | Action func(context *Context) 33 | // List of child commands 34 | Subcommands []Command 35 | // List of flags to parse 36 | Flags []Flag 37 | // Treat all flags as normal arguments if true 38 | SkipFlagParsing bool 39 | // Boolean to hide built-in help command 40 | HideHelp bool 41 | 42 | // Full name of command for help, defaults to full command name, including parent commands. 43 | HelpName string 44 | commandNamePath []string 45 | } 46 | 47 | // Returns the full name of the command. 48 | // For subcommands this ensures that parent commands are part of the command path 49 | func (c Command) FullName() string { 50 | if c.commandNamePath == nil { 51 | return c.Name 52 | } 53 | return strings.Join(c.commandNamePath, " ") 54 | } 55 | 56 | // Invokes the command given the context, parses ctx.Args() to generate command-specific flags 57 | func (c Command) Run(ctx *Context) error { 58 | if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { 59 | return c.startApp(ctx) 60 | } 61 | 62 | if !c.HideHelp && (HelpFlag != BoolFlag{}) { 63 | // append help to flags 64 | c.Flags = append( 65 | c.Flags, 66 | HelpFlag, 67 | ) 68 | } 69 | 70 | if ctx.App.EnableBashCompletion { 71 | c.Flags = append(c.Flags, BashCompletionFlag) 72 | } 73 | 74 | set := flagSet(c.Name, c.Flags) 75 | set.SetOutput(ioutil.Discard) 76 | 77 | firstFlagIndex := -1 78 | terminatorIndex := -1 79 | for index, arg := range ctx.Args() { 80 | if arg == "--" { 81 | terminatorIndex = index 82 | break 83 | } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { 84 | firstFlagIndex = index 85 | } 86 | } 87 | 88 | var err error 89 | if firstFlagIndex > -1 && !c.SkipFlagParsing { 90 | args := ctx.Args() 91 | regularArgs := make([]string, len(args[1:firstFlagIndex])) 92 | copy(regularArgs, args[1:firstFlagIndex]) 93 | 94 | var flagArgs []string 95 | if terminatorIndex > -1 { 96 | flagArgs = args[firstFlagIndex:terminatorIndex] 97 | regularArgs = append(regularArgs, args[terminatorIndex:]...) 98 | } else { 99 | flagArgs = args[firstFlagIndex:] 100 | } 101 | 102 | err = set.Parse(append(flagArgs, regularArgs...)) 103 | } else { 104 | err = set.Parse(ctx.Args().Tail()) 105 | } 106 | 107 | if err != nil { 108 | fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") 109 | fmt.Fprintln(ctx.App.Writer) 110 | ShowCommandHelp(ctx, c.Name) 111 | return err 112 | } 113 | 114 | nerr := normalizeFlags(c.Flags, set) 115 | if nerr != nil { 116 | fmt.Fprintln(ctx.App.Writer, nerr) 117 | fmt.Fprintln(ctx.App.Writer) 118 | ShowCommandHelp(ctx, c.Name) 119 | return nerr 120 | } 121 | context := NewContext(ctx.App, set, ctx) 122 | 123 | if checkCommandCompletions(context, c.Name) { 124 | return nil 125 | } 126 | 127 | if checkCommandHelp(context, c.Name) { 128 | return nil 129 | } 130 | context.Command = c 131 | c.Action(context) 132 | return nil 133 | } 134 | 135 | func (c Command) Names() []string { 136 | names := []string{c.Name} 137 | 138 | if c.ShortName != "" { 139 | names = append(names, c.ShortName) 140 | } 141 | 142 | return append(names, c.Aliases...) 143 | } 144 | 145 | // Returns true if Command.Name or Command.ShortName matches given name 146 | func (c Command) HasName(name string) bool { 147 | for _, n := range c.Names() { 148 | if n == name { 149 | return true 150 | } 151 | } 152 | return false 153 | } 154 | 155 | func (c Command) startApp(ctx *Context) error { 156 | app := NewApp() 157 | 158 | // set the name and usage 159 | app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 160 | if c.HelpName == "" { 161 | app.HelpName = c.HelpName 162 | } else { 163 | app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 164 | } 165 | 166 | if c.Description != "" { 167 | app.Usage = c.Description 168 | } else { 169 | app.Usage = c.Usage 170 | } 171 | 172 | // set CommandNotFound 173 | app.CommandNotFound = ctx.App.CommandNotFound 174 | 175 | // set the flags and commands 176 | app.Commands = c.Subcommands 177 | app.Flags = c.Flags 178 | app.HideHelp = c.HideHelp 179 | 180 | app.Version = ctx.App.Version 181 | app.HideVersion = ctx.App.HideVersion 182 | app.Compiled = ctx.App.Compiled 183 | app.Author = ctx.App.Author 184 | app.Email = ctx.App.Email 185 | app.Writer = ctx.App.Writer 186 | 187 | // bash completion 188 | app.EnableBashCompletion = ctx.App.EnableBashCompletion 189 | if c.BashComplete != nil { 190 | app.BashComplete = c.BashComplete 191 | } 192 | 193 | // set the actions 194 | app.Before = c.Before 195 | app.After = c.After 196 | if c.Action != nil { 197 | app.Action = c.Action 198 | } else { 199 | app.Action = helpSubcommand.Action 200 | } 201 | 202 | var newCmds []Command 203 | for _, cc := range app.Commands { 204 | cc.commandNamePath = []string{c.Name, cc.Name} 205 | newCmds = append(newCmds, cc) 206 | } 207 | app.Commands = newCmds 208 | 209 | return app.RunAsSubcommand(ctx) 210 | } 211 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/help.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/tabwriter" 8 | "text/template" 9 | ) 10 | 11 | // The text template for the Default help topic. 12 | // cli.go uses text/template to render templates. You can 13 | // render custom help text by setting this variable. 14 | var AppHelpTemplate = `NAME: 15 | {{.Name}} - {{.Usage}} 16 | 17 | USAGE: 18 | {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} 19 | {{if .Version}} 20 | VERSION: 21 | {{.Version}} 22 | {{end}}{{if len .Authors}} 23 | AUTHOR(S): 24 | {{range .Authors}}{{ . }}{{end}} 25 | {{end}}{{if .Commands}} 26 | COMMANDS: 27 | {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 28 | {{end}}{{end}}{{if .Flags}} 29 | GLOBAL OPTIONS: 30 | {{range .Flags}}{{.}} 31 | {{end}}{{end}}{{if .Copyright }} 32 | COPYRIGHT: 33 | {{.Copyright}} 34 | {{end}} 35 | ` 36 | 37 | // The text template for the command help topic. 38 | // cli.go uses text/template to render templates. You can 39 | // render custom help text by setting this variable. 40 | var CommandHelpTemplate = `NAME: 41 | {{.HelpName}} - {{.Usage}} 42 | 43 | USAGE: 44 | {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} 45 | 46 | DESCRIPTION: 47 | {{.Description}}{{end}}{{if .Flags}} 48 | 49 | OPTIONS: 50 | {{range .Flags}}{{.}} 51 | {{end}}{{ end }} 52 | ` 53 | 54 | // The text template for the subcommand help topic. 55 | // cli.go uses text/template to render templates. You can 56 | // render custom help text by setting this variable. 57 | var SubcommandHelpTemplate = `NAME: 58 | {{.HelpName}} - {{.Usage}} 59 | 60 | USAGE: 61 | {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} 62 | 63 | COMMANDS: 64 | {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 65 | {{end}}{{if .Flags}} 66 | OPTIONS: 67 | {{range .Flags}}{{.}} 68 | {{end}}{{end}} 69 | ` 70 | 71 | var helpCommand = Command{ 72 | Name: "help", 73 | Aliases: []string{"h"}, 74 | Usage: "Shows a list of commands or help for one command", 75 | ArgsUsage: "[command]", 76 | Action: func(c *Context) { 77 | args := c.Args() 78 | if args.Present() { 79 | ShowCommandHelp(c, args.First()) 80 | } else { 81 | ShowAppHelp(c) 82 | } 83 | }, 84 | } 85 | 86 | var helpSubcommand = Command{ 87 | Name: "help", 88 | Aliases: []string{"h"}, 89 | Usage: "Shows a list of commands or help for one command", 90 | ArgsUsage: "[command]", 91 | Action: func(c *Context) { 92 | args := c.Args() 93 | if args.Present() { 94 | ShowCommandHelp(c, args.First()) 95 | } else { 96 | ShowSubcommandHelp(c) 97 | } 98 | }, 99 | } 100 | 101 | // Prints help for the App or Command 102 | type helpPrinter func(w io.Writer, templ string, data interface{}) 103 | 104 | var HelpPrinter helpPrinter = printHelp 105 | 106 | // Prints version for the App 107 | var VersionPrinter = printVersion 108 | 109 | func ShowAppHelp(c *Context) { 110 | HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) 111 | } 112 | 113 | // Prints the list of subcommands as the default app completion method 114 | func DefaultAppComplete(c *Context) { 115 | for _, command := range c.App.Commands { 116 | for _, name := range command.Names() { 117 | fmt.Fprintln(c.App.Writer, name) 118 | } 119 | } 120 | } 121 | 122 | // Prints help for the given command 123 | func ShowCommandHelp(ctx *Context, command string) { 124 | // show the subcommand help for a command with subcommands 125 | if command == "" { 126 | HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) 127 | return 128 | } 129 | 130 | for _, c := range ctx.App.Commands { 131 | if c.HasName(command) { 132 | HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) 133 | return 134 | } 135 | } 136 | 137 | if ctx.App.CommandNotFound != nil { 138 | ctx.App.CommandNotFound(ctx, command) 139 | } else { 140 | fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) 141 | } 142 | } 143 | 144 | // Prints help for the given subcommand 145 | func ShowSubcommandHelp(c *Context) { 146 | ShowCommandHelp(c, c.Command.Name) 147 | } 148 | 149 | // Prints the version number of the App 150 | func ShowVersion(c *Context) { 151 | VersionPrinter(c) 152 | } 153 | 154 | func printVersion(c *Context) { 155 | fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) 156 | } 157 | 158 | // Prints the lists of commands within a given context 159 | func ShowCompletions(c *Context) { 160 | a := c.App 161 | if a != nil && a.BashComplete != nil { 162 | a.BashComplete(c) 163 | } 164 | } 165 | 166 | // Prints the custom completions for a given command 167 | func ShowCommandCompletions(ctx *Context, command string) { 168 | c := ctx.App.Command(command) 169 | if c != nil && c.BashComplete != nil { 170 | c.BashComplete(ctx) 171 | } 172 | } 173 | 174 | func printHelp(out io.Writer, templ string, data interface{}) { 175 | funcMap := template.FuncMap{ 176 | "join": strings.Join, 177 | } 178 | 179 | w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) 180 | t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) 181 | err := t.Execute(w, data) 182 | if err != nil { 183 | panic(err) 184 | } 185 | w.Flush() 186 | } 187 | 188 | func checkVersion(c *Context) bool { 189 | found := false 190 | if VersionFlag.Name != "" { 191 | eachName(VersionFlag.Name, func(name string) { 192 | if c.GlobalBool(name) || c.Bool(name) { 193 | found = true 194 | } 195 | }) 196 | } 197 | return found 198 | } 199 | 200 | func checkHelp(c *Context) bool { 201 | found := false 202 | if HelpFlag.Name != "" { 203 | eachName(HelpFlag.Name, func(name string) { 204 | if c.GlobalBool(name) || c.Bool(name) { 205 | found = true 206 | } 207 | }) 208 | } 209 | return found 210 | } 211 | 212 | func checkCommandHelp(c *Context, name string) bool { 213 | if c.Bool("h") || c.Bool("help") { 214 | ShowCommandHelp(c, name) 215 | return true 216 | } 217 | 218 | return false 219 | } 220 | 221 | func checkSubcommandHelp(c *Context) bool { 222 | if c.GlobalBool("h") || c.GlobalBool("help") { 223 | ShowSubcommandHelp(c) 224 | return true 225 | } 226 | 227 | return false 228 | } 229 | 230 | func checkCompletions(c *Context) bool { 231 | if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { 232 | ShowCompletions(c) 233 | return true 234 | } 235 | 236 | return false 237 | } 238 | 239 | func checkCommandCompletions(c *Context, name string) bool { 240 | if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { 241 | ShowCommandCompletions(c, name) 242 | return true 243 | } 244 | 245 | return false 246 | } 247 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redis is a client for the Redis database. 16 | // 17 | // The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more 18 | // documentation about this package. 19 | // 20 | // Connections 21 | // 22 | // The Conn interface is the primary interface for working with Redis. 23 | // Applications create connections by calling the Dial, DialWithTimeout or 24 | // NewConn functions. In the future, functions will be added for creating 25 | // sharded and other types of connections. 26 | // 27 | // The application must call the connection Close method when the application 28 | // is done with the connection. 29 | // 30 | // Executing Commands 31 | // 32 | // The Conn interface has a generic method for executing Redis commands: 33 | // 34 | // Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | // 36 | // The Redis command reference (http://redis.io/commands) lists the available 37 | // commands. An example of using the Redis APPEND command is: 38 | // 39 | // n, err := conn.Do("APPEND", "key", "value") 40 | // 41 | // The Do method converts command arguments to binary strings for transmission 42 | // to the server as follows: 43 | // 44 | // Go Type Conversion 45 | // []byte Sent as is 46 | // string Sent as is 47 | // int, int64 strconv.FormatInt(v) 48 | // float64 strconv.FormatFloat(v, 'g', -1, 64) 49 | // bool true -> "1", false -> "0" 50 | // nil "" 51 | // all other types fmt.Print(v) 52 | // 53 | // Redis command reply types are represented using the following Go types: 54 | // 55 | // Redis type Go type 56 | // error redis.Error 57 | // integer int64 58 | // simple string string 59 | // bulk string []byte or nil if value not present. 60 | // array []interface{} or nil if value not present. 61 | // 62 | // Use type assertions or the reply helper functions to convert from 63 | // interface{} to the specific Go type for the command result. 64 | // 65 | // Pipelining 66 | // 67 | // Connections support pipelining using the Send, Flush and Receive methods. 68 | // 69 | // Send(commandName string, args ...interface{}) error 70 | // Flush() error 71 | // Receive() (reply interface{}, err error) 72 | // 73 | // Send writes the command to the connection's output buffer. Flush flushes the 74 | // connection's output buffer to the server. Receive reads a single reply from 75 | // the server. The following example shows a simple pipeline. 76 | // 77 | // c.Send("SET", "foo", "bar") 78 | // c.Send("GET", "foo") 79 | // c.Flush() 80 | // c.Receive() // reply from SET 81 | // v, err = c.Receive() // reply from GET 82 | // 83 | // The Do method combines the functionality of the Send, Flush and Receive 84 | // methods. The Do method starts by writing the command and flushing the output 85 | // buffer. Next, the Do method receives all pending replies including the reply 86 | // for the command just sent by Do. If any of the received replies is an error, 87 | // then Do returns the error. If there are no errors, then Do returns the last 88 | // reply. If the command argument to the Do method is "", then the Do method 89 | // will flush the output buffer and receive pending replies without sending a 90 | // command. 91 | // 92 | // Use the Send and Do methods to implement pipelined transactions. 93 | // 94 | // c.Send("MULTI") 95 | // c.Send("INCR", "foo") 96 | // c.Send("INCR", "bar") 97 | // r, err := c.Do("EXEC") 98 | // fmt.Println(r) // prints [1, 1] 99 | // 100 | // Concurrency 101 | // 102 | // Connections do not support concurrent calls to the write methods (Send, 103 | // Flush) or concurrent calls to the read method (Receive). Connections do 104 | // allow a concurrent reader and writer. 105 | // 106 | // Because the Do method combines the functionality of Send, Flush and Receive, 107 | // the Do method cannot be called concurrently with the other methods. 108 | // 109 | // For full concurrent access to Redis, use the thread-safe Pool to get and 110 | // release connections from within a goroutine. 111 | // 112 | // Publish and Subscribe 113 | // 114 | // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. 115 | // 116 | // c.Send("SUBSCRIBE", "example") 117 | // c.Flush() 118 | // for { 119 | // reply, err := c.Receive() 120 | // if err != nil { 121 | // return err 122 | // } 123 | // // process pushed message 124 | // } 125 | // 126 | // The PubSubConn type wraps a Conn with convenience methods for implementing 127 | // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods 128 | // send and flush a subscription management command. The receive method 129 | // converts a pushed message to convenient types for use in a type switch. 130 | // 131 | // psc := redis.PubSubConn{c} 132 | // psc.Subscribe("example") 133 | // for { 134 | // switch v := psc.Receive().(type) { 135 | // case redis.Message: 136 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 137 | // case redis.Subscription: 138 | // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 139 | // case error: 140 | // return v 141 | // } 142 | // } 143 | // 144 | // Reply Helpers 145 | // 146 | // The Bool, Int, Bytes, String, Strings and Values functions convert a reply 147 | // to a value of a specific type. To allow convenient wrapping of calls to the 148 | // connection Do and Receive methods, the functions take a second argument of 149 | // type error. If the error is non-nil, then the helper function returns the 150 | // error. If the error is nil, the function converts the reply to the specified 151 | // type: 152 | // 153 | // exists, err := redis.Bool(c.Do("EXISTS", "foo")) 154 | // if err != nil { 155 | // // handle error return from c.Do or type conversion error. 156 | // } 157 | // 158 | // The Scan function converts elements of a array reply to Go types: 159 | // 160 | // var value1 int 161 | // var value2 string 162 | // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) 163 | // if err != nil { 164 | // // handle error 165 | // } 166 | // if _, err := redis.Scan(reply, &value1, &value2); err != nil { 167 | // // handle error 168 | // } 169 | package redis // import "github.com/garyburd/redigo/redis" 170 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/app.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // App is the main structure of a cli application. It is recomended that 12 | // an app be created with the cli.NewApp() function 13 | type App struct { 14 | // The name of the program. Defaults to os.Args[0] 15 | Name string 16 | // Full name of command for help, defaults to Name 17 | HelpName string 18 | // Description of the program. 19 | Usage string 20 | // Description of the program argument format. 21 | ArgsUsage string 22 | // Version of the program 23 | Version string 24 | // List of commands to execute 25 | Commands []Command 26 | // List of flags to parse 27 | Flags []Flag 28 | // Boolean to enable bash completion commands 29 | EnableBashCompletion bool 30 | // Boolean to hide built-in help command 31 | HideHelp bool 32 | // Boolean to hide built-in version flag 33 | HideVersion bool 34 | // An action to execute when the bash-completion flag is set 35 | BashComplete func(context *Context) 36 | // An action to execute before any subcommands are run, but after the context is ready 37 | // If a non-nil error is returned, no subcommands are run 38 | Before func(context *Context) error 39 | // An action to execute after any subcommands are run, but after the subcommand has finished 40 | // It is run even if Action() panics 41 | After func(context *Context) error 42 | // The action to execute when no subcommands are specified 43 | Action func(context *Context) 44 | // Execute this function if the proper command cannot be found 45 | CommandNotFound func(context *Context, command string) 46 | // Compilation date 47 | Compiled time.Time 48 | // List of all authors who contributed 49 | Authors []Author 50 | // Copyright of the binary if any 51 | Copyright string 52 | // Name of Author (Note: Use App.Authors, this is deprecated) 53 | Author string 54 | // Email of Author (Note: Use App.Authors, this is deprecated) 55 | Email string 56 | // Writer writer to write output to 57 | Writer io.Writer 58 | } 59 | 60 | // Tries to find out when this binary was compiled. 61 | // Returns the current time if it fails to find it. 62 | func compileTime() time.Time { 63 | info, err := os.Stat(os.Args[0]) 64 | if err != nil { 65 | return time.Now() 66 | } 67 | return info.ModTime() 68 | } 69 | 70 | // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. 71 | func NewApp() *App { 72 | return &App{ 73 | Name: os.Args[0], 74 | HelpName: os.Args[0], 75 | Usage: "A new cli application", 76 | Version: "0.0.0", 77 | BashComplete: DefaultAppComplete, 78 | Action: helpCommand.Action, 79 | Compiled: compileTime(), 80 | Writer: os.Stdout, 81 | } 82 | } 83 | 84 | // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination 85 | func (a *App) Run(arguments []string) (err error) { 86 | if a.Author != "" || a.Email != "" { 87 | a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) 88 | } 89 | 90 | newCmds := []Command{} 91 | for _, c := range a.Commands { 92 | if c.HelpName == "" { 93 | c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 94 | } 95 | newCmds = append(newCmds, c) 96 | } 97 | a.Commands = newCmds 98 | 99 | // append help to commands 100 | if a.Command(helpCommand.Name) == nil && !a.HideHelp { 101 | a.Commands = append(a.Commands, helpCommand) 102 | if (HelpFlag != BoolFlag{}) { 103 | a.appendFlag(HelpFlag) 104 | } 105 | } 106 | 107 | //append version/help flags 108 | if a.EnableBashCompletion { 109 | a.appendFlag(BashCompletionFlag) 110 | } 111 | 112 | if !a.HideVersion { 113 | a.appendFlag(VersionFlag) 114 | } 115 | 116 | // parse flags 117 | set := flagSet(a.Name, a.Flags) 118 | set.SetOutput(ioutil.Discard) 119 | err = set.Parse(arguments[1:]) 120 | nerr := normalizeFlags(a.Flags, set) 121 | if nerr != nil { 122 | fmt.Fprintln(a.Writer, nerr) 123 | context := NewContext(a, set, nil) 124 | ShowAppHelp(context) 125 | return nerr 126 | } 127 | context := NewContext(a, set, nil) 128 | 129 | if err != nil { 130 | fmt.Fprintln(a.Writer, "Incorrect Usage.") 131 | fmt.Fprintln(a.Writer) 132 | ShowAppHelp(context) 133 | return err 134 | } 135 | 136 | if checkCompletions(context) { 137 | return nil 138 | } 139 | 140 | if !a.HideHelp && checkHelp(context) { 141 | ShowAppHelp(context) 142 | return nil 143 | } 144 | 145 | if !a.HideVersion && checkVersion(context) { 146 | ShowVersion(context) 147 | return nil 148 | } 149 | 150 | if a.After != nil { 151 | defer func() { 152 | afterErr := a.After(context) 153 | if afterErr != nil { 154 | if err != nil { 155 | err = NewMultiError(err, afterErr) 156 | } else { 157 | err = afterErr 158 | } 159 | } 160 | }() 161 | } 162 | 163 | if a.Before != nil { 164 | err := a.Before(context) 165 | if err != nil { 166 | return err 167 | } 168 | } 169 | 170 | args := context.Args() 171 | if args.Present() { 172 | name := args.First() 173 | c := a.Command(name) 174 | if c != nil { 175 | return c.Run(context) 176 | } 177 | } 178 | 179 | // Run default Action 180 | a.Action(context) 181 | return nil 182 | } 183 | 184 | // Another entry point to the cli app, takes care of passing arguments and error handling 185 | func (a *App) RunAndExitOnError() { 186 | if err := a.Run(os.Args); err != nil { 187 | fmt.Fprintln(os.Stderr, err) 188 | os.Exit(1) 189 | } 190 | } 191 | 192 | // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags 193 | func (a *App) RunAsSubcommand(ctx *Context) (err error) { 194 | // append help to commands 195 | if len(a.Commands) > 0 { 196 | if a.Command(helpCommand.Name) == nil && !a.HideHelp { 197 | a.Commands = append(a.Commands, helpCommand) 198 | if (HelpFlag != BoolFlag{}) { 199 | a.appendFlag(HelpFlag) 200 | } 201 | } 202 | } 203 | 204 | newCmds := []Command{} 205 | for _, c := range a.Commands { 206 | if c.HelpName == "" { 207 | c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 208 | } 209 | newCmds = append(newCmds, c) 210 | } 211 | a.Commands = newCmds 212 | 213 | // append flags 214 | if a.EnableBashCompletion { 215 | a.appendFlag(BashCompletionFlag) 216 | } 217 | 218 | // parse flags 219 | set := flagSet(a.Name, a.Flags) 220 | set.SetOutput(ioutil.Discard) 221 | err = set.Parse(ctx.Args().Tail()) 222 | nerr := normalizeFlags(a.Flags, set) 223 | context := NewContext(a, set, ctx) 224 | 225 | if nerr != nil { 226 | fmt.Fprintln(a.Writer, nerr) 227 | fmt.Fprintln(a.Writer) 228 | if len(a.Commands) > 0 { 229 | ShowSubcommandHelp(context) 230 | } else { 231 | ShowCommandHelp(ctx, context.Args().First()) 232 | } 233 | return nerr 234 | } 235 | 236 | if err != nil { 237 | fmt.Fprintln(a.Writer, "Incorrect Usage.") 238 | fmt.Fprintln(a.Writer) 239 | ShowSubcommandHelp(context) 240 | return err 241 | } 242 | 243 | if checkCompletions(context) { 244 | return nil 245 | } 246 | 247 | if len(a.Commands) > 0 { 248 | if checkSubcommandHelp(context) { 249 | return nil 250 | } 251 | } else { 252 | if checkCommandHelp(ctx, context.Args().First()) { 253 | return nil 254 | } 255 | } 256 | 257 | if a.After != nil { 258 | defer func() { 259 | afterErr := a.After(context) 260 | if afterErr != nil { 261 | if err != nil { 262 | err = NewMultiError(err, afterErr) 263 | } else { 264 | err = afterErr 265 | } 266 | } 267 | }() 268 | } 269 | 270 | if a.Before != nil { 271 | err := a.Before(context) 272 | if err != nil { 273 | return err 274 | } 275 | } 276 | 277 | args := context.Args() 278 | if args.Present() { 279 | name := args.First() 280 | c := a.Command(name) 281 | if c != nil { 282 | return c.Run(context) 283 | } 284 | } 285 | 286 | // Run default Action 287 | a.Action(context) 288 | 289 | return nil 290 | } 291 | 292 | // Returns the named command on App. Returns nil if the command does not exist 293 | func (a *App) Command(name string) *Command { 294 | for _, c := range a.Commands { 295 | if c.HasName(name) { 296 | return &c 297 | } 298 | } 299 | 300 | return nil 301 | } 302 | 303 | func (a *App) hasFlag(flag Flag) bool { 304 | for _, f := range a.Flags { 305 | if flag == f { 306 | return true 307 | } 308 | } 309 | 310 | return false 311 | } 312 | 313 | func (a *App) appendFlag(flag Flag) { 314 | if !a.hasFlag(flag) { 315 | a.Flags = append(a.Flags, flag) 316 | } 317 | } 318 | 319 | // Author represents someone who has contributed to a cli project. 320 | type Author struct { 321 | Name string // The Authors name 322 | Email string // The Authors email 323 | } 324 | 325 | // String makes Author comply to the Stringer interface, to allow an easy print in the templating process 326 | func (a Author) String() string { 327 | e := "" 328 | if a.Email != "" { 329 | e = "<" + a.Email + "> " 330 | } 331 | 332 | return fmt.Sprintf("%v %v", a.Name, e) 333 | } 334 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/README.md: -------------------------------------------------------------------------------- 1 | [![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) 2 | [![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) 3 | [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) 4 | 5 | # cli.go 6 | `cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. 7 | 8 | ## Overview 9 | 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. 10 | 11 | **This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! 12 | 13 | ## Installation 14 | Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). 15 | 16 | To install `cli.go`, simply run: 17 | ``` 18 | $ go get github.com/codegangsta/cli 19 | ``` 20 | 21 | Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: 22 | ``` 23 | export PATH=$PATH:$GOPATH/bin 24 | ``` 25 | 26 | ## Getting Started 27 | One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. 28 | 29 | ``` go 30 | package main 31 | 32 | import ( 33 | "os" 34 | "github.com/codegangsta/cli" 35 | ) 36 | 37 | func main() { 38 | cli.NewApp().Run(os.Args) 39 | } 40 | ``` 41 | 42 | This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: 43 | 44 | ``` go 45 | package main 46 | 47 | import ( 48 | "os" 49 | "github.com/codegangsta/cli" 50 | ) 51 | 52 | func main() { 53 | app := cli.NewApp() 54 | app.Name = "boom" 55 | app.Usage = "make an explosive entrance" 56 | app.Action = func(c *cli.Context) { 57 | println("boom! I say!") 58 | } 59 | 60 | app.Run(os.Args) 61 | } 62 | ``` 63 | 64 | Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below. 65 | 66 | ## Example 67 | 68 | Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! 69 | 70 | Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: 71 | 72 | ``` go 73 | package main 74 | 75 | import ( 76 | "os" 77 | "github.com/codegangsta/cli" 78 | ) 79 | 80 | func main() { 81 | app := cli.NewApp() 82 | app.Name = "greet" 83 | app.Usage = "fight the loneliness!" 84 | app.Action = func(c *cli.Context) { 85 | println("Hello friend!") 86 | } 87 | 88 | app.Run(os.Args) 89 | } 90 | ``` 91 | 92 | Install our command to the `$GOPATH/bin` directory: 93 | 94 | ``` 95 | $ go install 96 | ``` 97 | 98 | Finally run our new command: 99 | 100 | ``` 101 | $ greet 102 | Hello friend! 103 | ``` 104 | 105 | `cli.go` also generates neat help text: 106 | 107 | ``` 108 | $ greet help 109 | NAME: 110 | greet - fight the loneliness! 111 | 112 | USAGE: 113 | greet [global options] command [command options] [arguments...] 114 | 115 | VERSION: 116 | 0.0.0 117 | 118 | COMMANDS: 119 | help, h Shows a list of commands or help for one command 120 | 121 | GLOBAL OPTIONS 122 | --version Shows version information 123 | ``` 124 | 125 | ### Arguments 126 | You can lookup arguments by calling the `Args` function on `cli.Context`. 127 | 128 | ``` go 129 | ... 130 | app.Action = func(c *cli.Context) { 131 | println("Hello", c.Args()[0]) 132 | } 133 | ... 134 | ``` 135 | 136 | ### Flags 137 | Setting and querying flags is simple. 138 | ``` go 139 | ... 140 | app.Flags = []cli.Flag { 141 | cli.StringFlag{ 142 | Name: "lang", 143 | Value: "english", 144 | Usage: "language for the greeting", 145 | }, 146 | } 147 | app.Action = func(c *cli.Context) { 148 | name := "someone" 149 | if len(c.Args()) > 0 { 150 | name = c.Args()[0] 151 | } 152 | if c.String("lang") == "spanish" { 153 | println("Hola", name) 154 | } else { 155 | println("Hello", name) 156 | } 157 | } 158 | ... 159 | ``` 160 | 161 | See full list of flags at http://godoc.org/github.com/codegangsta/cli 162 | 163 | #### Alternate Names 164 | 165 | You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. 166 | 167 | ``` go 168 | app.Flags = []cli.Flag { 169 | cli.StringFlag{ 170 | Name: "lang, l", 171 | Value: "english", 172 | Usage: "language for the greeting", 173 | }, 174 | } 175 | ``` 176 | 177 | That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. 178 | 179 | #### Values from the Environment 180 | 181 | You can also have the default value set from the environment via `EnvVar`. e.g. 182 | 183 | ``` go 184 | app.Flags = []cli.Flag { 185 | cli.StringFlag{ 186 | Name: "lang, l", 187 | Value: "english", 188 | Usage: "language for the greeting", 189 | EnvVar: "APP_LANG", 190 | }, 191 | } 192 | ``` 193 | 194 | The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. 195 | 196 | ``` go 197 | app.Flags = []cli.Flag { 198 | cli.StringFlag{ 199 | Name: "lang, l", 200 | Value: "english", 201 | Usage: "language for the greeting", 202 | EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", 203 | }, 204 | } 205 | ``` 206 | 207 | ### Subcommands 208 | 209 | Subcommands can be defined for a more git-like command line app. 210 | ```go 211 | ... 212 | app.Commands = []cli.Command{ 213 | { 214 | Name: "add", 215 | Aliases: []string{"a"}, 216 | Usage: "add a task to the list", 217 | Action: func(c *cli.Context) { 218 | println("added task: ", c.Args().First()) 219 | }, 220 | }, 221 | { 222 | Name: "complete", 223 | Aliases: []string{"c"}, 224 | Usage: "complete a task on the list", 225 | Action: func(c *cli.Context) { 226 | println("completed task: ", c.Args().First()) 227 | }, 228 | }, 229 | { 230 | Name: "template", 231 | Aliases: []string{"r"}, 232 | Usage: "options for task templates", 233 | Subcommands: []cli.Command{ 234 | { 235 | Name: "add", 236 | Usage: "add a new template", 237 | Action: func(c *cli.Context) { 238 | println("new task template: ", c.Args().First()) 239 | }, 240 | }, 241 | { 242 | Name: "remove", 243 | Usage: "remove an existing template", 244 | Action: func(c *cli.Context) { 245 | println("removed task template: ", c.Args().First()) 246 | }, 247 | }, 248 | }, 249 | }, 250 | } 251 | ... 252 | ``` 253 | 254 | ### Bash Completion 255 | 256 | You can enable completion commands by setting the `EnableBashCompletion` 257 | flag on the `App` object. By default, this setting will only auto-complete to 258 | show an app's subcommands, but you can write your own completion methods for 259 | the App or its subcommands. 260 | ```go 261 | ... 262 | var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} 263 | app := cli.NewApp() 264 | app.EnableBashCompletion = true 265 | app.Commands = []cli.Command{ 266 | { 267 | Name: "complete", 268 | Aliases: []string{"c"}, 269 | Usage: "complete a task on the list", 270 | Action: func(c *cli.Context) { 271 | println("completed task: ", c.Args().First()) 272 | }, 273 | BashComplete: func(c *cli.Context) { 274 | // This will complete if no args are passed 275 | if len(c.Args()) > 0 { 276 | return 277 | } 278 | for _, t := range tasks { 279 | fmt.Println(t) 280 | } 281 | }, 282 | } 283 | } 284 | ... 285 | ``` 286 | 287 | #### To Enable 288 | 289 | Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while 290 | setting the `PROG` variable to the name of your program: 291 | 292 | `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` 293 | 294 | #### To Distribute 295 | 296 | Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename 297 | it to the name of the program you wish to add autocomplete support for (or 298 | automatically install it there if you are distributing a package). Don't forget 299 | to source the file to make it active in the current shell. 300 | 301 | ``` 302 | sudo cp src/bash_autocomplete /etc/bash_completion.d/ 303 | source /etc/bash_completion.d/ 304 | ``` 305 | 306 | Alternatively, you can just document that users should source the generic 307 | `autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set 308 | to the name of their program (as above). 309 | 310 | ## Contribution Guidelines 311 | 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. 312 | 313 | If you 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. 314 | 315 | 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. 316 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/context.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // Context is a type that is passed through to 12 | // each Handler action in a cli application. Context 13 | // can be used to retrieve context-specific Args and 14 | // parsed command-line options. 15 | type Context struct { 16 | App *App 17 | Command Command 18 | flagSet *flag.FlagSet 19 | setFlags map[string]bool 20 | globalSetFlags map[string]bool 21 | parentContext *Context 22 | } 23 | 24 | // Creates a new context. For use in when invoking an App or Command action. 25 | func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { 26 | return &Context{App: app, flagSet: set, parentContext: parentCtx} 27 | } 28 | 29 | // Looks up the value of a local int flag, returns 0 if no int flag exists 30 | func (c *Context) Int(name string) int { 31 | return lookupInt(name, c.flagSet) 32 | } 33 | 34 | // Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists 35 | func (c *Context) Duration(name string) time.Duration { 36 | return lookupDuration(name, c.flagSet) 37 | } 38 | 39 | // Looks up the value of a local float64 flag, returns 0 if no float64 flag exists 40 | func (c *Context) Float64(name string) float64 { 41 | return lookupFloat64(name, c.flagSet) 42 | } 43 | 44 | // Looks up the value of a local bool flag, returns false if no bool flag exists 45 | func (c *Context) Bool(name string) bool { 46 | return lookupBool(name, c.flagSet) 47 | } 48 | 49 | // Looks up the value of a local boolT flag, returns false if no bool flag exists 50 | func (c *Context) BoolT(name string) bool { 51 | return lookupBoolT(name, c.flagSet) 52 | } 53 | 54 | // Looks up the value of a local string flag, returns "" if no string flag exists 55 | func (c *Context) String(name string) string { 56 | return lookupString(name, c.flagSet) 57 | } 58 | 59 | // Looks up the value of a local string slice flag, returns nil if no string slice flag exists 60 | func (c *Context) StringSlice(name string) []string { 61 | return lookupStringSlice(name, c.flagSet) 62 | } 63 | 64 | // Looks up the value of a local int slice flag, returns nil if no int slice flag exists 65 | func (c *Context) IntSlice(name string) []int { 66 | return lookupIntSlice(name, c.flagSet) 67 | } 68 | 69 | // Looks up the value of a local generic flag, returns nil if no generic flag exists 70 | func (c *Context) Generic(name string) interface{} { 71 | return lookupGeneric(name, c.flagSet) 72 | } 73 | 74 | // Looks up the value of a global int flag, returns 0 if no int flag exists 75 | func (c *Context) GlobalInt(name string) int { 76 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 77 | return lookupInt(name, fs) 78 | } 79 | return 0 80 | } 81 | 82 | // Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists 83 | func (c *Context) GlobalDuration(name string) time.Duration { 84 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 85 | return lookupDuration(name, fs) 86 | } 87 | return 0 88 | } 89 | 90 | // Looks up the value of a global bool flag, returns false if no bool flag exists 91 | func (c *Context) GlobalBool(name string) bool { 92 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 93 | return lookupBool(name, fs) 94 | } 95 | return false 96 | } 97 | 98 | // Looks up the value of a global string flag, returns "" if no string flag exists 99 | func (c *Context) GlobalString(name string) string { 100 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 101 | return lookupString(name, fs) 102 | } 103 | return "" 104 | } 105 | 106 | // Looks up the value of a global string slice flag, returns nil if no string slice flag exists 107 | func (c *Context) GlobalStringSlice(name string) []string { 108 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 109 | return lookupStringSlice(name, fs) 110 | } 111 | return nil 112 | } 113 | 114 | // Looks up the value of a global int slice flag, returns nil if no int slice flag exists 115 | func (c *Context) GlobalIntSlice(name string) []int { 116 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 117 | return lookupIntSlice(name, fs) 118 | } 119 | return nil 120 | } 121 | 122 | // Looks up the value of a global generic flag, returns nil if no generic flag exists 123 | func (c *Context) GlobalGeneric(name string) interface{} { 124 | if fs := lookupGlobalFlagSet(name, c); fs != nil { 125 | return lookupGeneric(name, fs) 126 | } 127 | return nil 128 | } 129 | 130 | // Returns the number of flags set 131 | func (c *Context) NumFlags() int { 132 | return c.flagSet.NFlag() 133 | } 134 | 135 | // Determines if the flag was actually set 136 | func (c *Context) IsSet(name string) bool { 137 | if c.setFlags == nil { 138 | c.setFlags = make(map[string]bool) 139 | c.flagSet.Visit(func(f *flag.Flag) { 140 | c.setFlags[f.Name] = true 141 | }) 142 | } 143 | return c.setFlags[name] == true 144 | } 145 | 146 | // Determines if the global flag was actually set 147 | func (c *Context) GlobalIsSet(name string) bool { 148 | if c.globalSetFlags == nil { 149 | c.globalSetFlags = make(map[string]bool) 150 | ctx := c 151 | if ctx.parentContext != nil { 152 | ctx = ctx.parentContext 153 | } 154 | for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { 155 | ctx.flagSet.Visit(func(f *flag.Flag) { 156 | c.globalSetFlags[f.Name] = true 157 | }) 158 | } 159 | } 160 | return c.globalSetFlags[name] 161 | } 162 | 163 | // Returns a slice of flag names used in this context. 164 | func (c *Context) FlagNames() (names []string) { 165 | for _, flag := range c.Command.Flags { 166 | name := strings.Split(flag.getName(), ",")[0] 167 | if name == "help" { 168 | continue 169 | } 170 | names = append(names, name) 171 | } 172 | return 173 | } 174 | 175 | // Returns a slice of global flag names used by the app. 176 | func (c *Context) GlobalFlagNames() (names []string) { 177 | for _, flag := range c.App.Flags { 178 | name := strings.Split(flag.getName(), ",")[0] 179 | if name == "help" || name == "version" { 180 | continue 181 | } 182 | names = append(names, name) 183 | } 184 | return 185 | } 186 | 187 | // Returns the parent context, if any 188 | func (c *Context) Parent() *Context { 189 | return c.parentContext 190 | } 191 | 192 | type Args []string 193 | 194 | // Returns the command line arguments associated with the context. 195 | func (c *Context) Args() Args { 196 | args := Args(c.flagSet.Args()) 197 | return args 198 | } 199 | 200 | // Returns the nth argument, or else a blank string 201 | func (a Args) Get(n int) string { 202 | if len(a) > n { 203 | return a[n] 204 | } 205 | return "" 206 | } 207 | 208 | // Returns the first argument, or else a blank string 209 | func (a Args) First() string { 210 | return a.Get(0) 211 | } 212 | 213 | // Return the rest of the arguments (not the first one) 214 | // or else an empty string slice 215 | func (a Args) Tail() []string { 216 | if len(a) >= 2 { 217 | return []string(a)[1:] 218 | } 219 | return []string{} 220 | } 221 | 222 | // Checks if there are any arguments present 223 | func (a Args) Present() bool { 224 | return len(a) != 0 225 | } 226 | 227 | // Swaps arguments at the given indexes 228 | func (a Args) Swap(from, to int) error { 229 | if from >= len(a) || to >= len(a) { 230 | return errors.New("index out of range") 231 | } 232 | a[from], a[to] = a[to], a[from] 233 | return nil 234 | } 235 | 236 | func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { 237 | if ctx.parentContext != nil { 238 | ctx = ctx.parentContext 239 | } 240 | for ; ctx != nil; ctx = ctx.parentContext { 241 | if f := ctx.flagSet.Lookup(name); f != nil { 242 | return ctx.flagSet 243 | } 244 | } 245 | return nil 246 | } 247 | 248 | func lookupInt(name string, set *flag.FlagSet) int { 249 | f := set.Lookup(name) 250 | if f != nil { 251 | val, err := strconv.Atoi(f.Value.String()) 252 | if err != nil { 253 | return 0 254 | } 255 | return val 256 | } 257 | 258 | return 0 259 | } 260 | 261 | func lookupDuration(name string, set *flag.FlagSet) time.Duration { 262 | f := set.Lookup(name) 263 | if f != nil { 264 | val, err := time.ParseDuration(f.Value.String()) 265 | if err == nil { 266 | return val 267 | } 268 | } 269 | 270 | return 0 271 | } 272 | 273 | func lookupFloat64(name string, set *flag.FlagSet) float64 { 274 | f := set.Lookup(name) 275 | if f != nil { 276 | val, err := strconv.ParseFloat(f.Value.String(), 64) 277 | if err != nil { 278 | return 0 279 | } 280 | return val 281 | } 282 | 283 | return 0 284 | } 285 | 286 | func lookupString(name string, set *flag.FlagSet) string { 287 | f := set.Lookup(name) 288 | if f != nil { 289 | return f.Value.String() 290 | } 291 | 292 | return "" 293 | } 294 | 295 | func lookupStringSlice(name string, set *flag.FlagSet) []string { 296 | f := set.Lookup(name) 297 | if f != nil { 298 | return (f.Value.(*StringSlice)).Value() 299 | 300 | } 301 | 302 | return nil 303 | } 304 | 305 | func lookupIntSlice(name string, set *flag.FlagSet) []int { 306 | f := set.Lookup(name) 307 | if f != nil { 308 | return (f.Value.(*IntSlice)).Value() 309 | 310 | } 311 | 312 | return nil 313 | } 314 | 315 | func lookupGeneric(name string, set *flag.FlagSet) interface{} { 316 | f := set.Lookup(name) 317 | if f != nil { 318 | return f.Value 319 | } 320 | return nil 321 | } 322 | 323 | func lookupBool(name string, set *flag.FlagSet) bool { 324 | f := set.Lookup(name) 325 | if f != nil { 326 | val, err := strconv.ParseBool(f.Value.String()) 327 | if err != nil { 328 | return false 329 | } 330 | return val 331 | } 332 | 333 | return false 334 | } 335 | 336 | func lookupBoolT(name string, set *flag.FlagSet) bool { 337 | f := set.Lookup(name) 338 | if f != nil { 339 | val, err := strconv.ParseBool(f.Value.String()) 340 | if err != nil { 341 | return true 342 | } 343 | return val 344 | } 345 | 346 | return false 347 | } 348 | 349 | func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { 350 | switch ff.Value.(type) { 351 | case *StringSlice: 352 | default: 353 | set.Set(name, ff.Value.String()) 354 | } 355 | } 356 | 357 | func normalizeFlags(flags []Flag, set *flag.FlagSet) error { 358 | visited := make(map[string]bool) 359 | set.Visit(func(f *flag.Flag) { 360 | visited[f.Name] = true 361 | }) 362 | for _, f := range flags { 363 | parts := strings.Split(f.getName(), ",") 364 | if len(parts) == 1 { 365 | continue 366 | } 367 | var ff *flag.Flag 368 | for _, name := range parts { 369 | name = strings.Trim(name, " ") 370 | if visited[name] { 371 | if ff != nil { 372 | return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) 373 | } 374 | ff = set.Lookup(name) 375 | } 376 | } 377 | if ff == nil { 378 | continue 379 | } 380 | for _, name := range parts { 381 | name = strings.Trim(name, " ") 382 | if !visited[name] { 383 | copyFlag(name, ff, set) 384 | } 385 | } 386 | } 387 | return nil 388 | } 389 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "container/list" 20 | "crypto/rand" 21 | "crypto/sha1" 22 | "errors" 23 | "io" 24 | "strconv" 25 | "sync" 26 | "time" 27 | 28 | "github.com/garyburd/redigo/internal" 29 | ) 30 | 31 | var nowFunc = time.Now // for testing 32 | 33 | // ErrPoolExhausted is returned from a pool connection method (Do, Send, 34 | // Receive, Flush, Err) when the maximum number of database connections in the 35 | // pool has been reached. 36 | var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") 37 | 38 | var ( 39 | errPoolClosed = errors.New("redigo: connection pool closed") 40 | errConnClosed = errors.New("redigo: connection closed") 41 | ) 42 | 43 | // Pool maintains a pool of connections. The application calls the Get method 44 | // to get a connection from the pool and the connection's Close method to 45 | // return the connection's resources to the pool. 46 | // 47 | // The following example shows how to use a pool in a web application. The 48 | // application creates a pool at application startup and makes it available to 49 | // request handlers using a global variable. 50 | // 51 | // func newPool(server, password string) *redis.Pool { 52 | // return &redis.Pool{ 53 | // MaxIdle: 3, 54 | // IdleTimeout: 240 * time.Second, 55 | // Dial: func () (redis.Conn, error) { 56 | // c, err := redis.Dial("tcp", server) 57 | // if err != nil { 58 | // return nil, err 59 | // } 60 | // if _, err := c.Do("AUTH", password); err != nil { 61 | // c.Close() 62 | // return nil, err 63 | // } 64 | // return c, err 65 | // }, 66 | // TestOnBorrow: func(c redis.Conn, t time.Time) error { 67 | // _, err := c.Do("PING") 68 | // return err 69 | // }, 70 | // } 71 | // } 72 | // 73 | // var ( 74 | // pool *redis.Pool 75 | // redisServer = flag.String("redisServer", ":6379", "") 76 | // redisPassword = flag.String("redisPassword", "", "") 77 | // ) 78 | // 79 | // func main() { 80 | // flag.Parse() 81 | // pool = newPool(*redisServer, *redisPassword) 82 | // ... 83 | // } 84 | // 85 | // A request handler gets a connection from the pool and closes the connection 86 | // when the handler is done: 87 | // 88 | // func serveHome(w http.ResponseWriter, r *http.Request) { 89 | // conn := pool.Get() 90 | // defer conn.Close() 91 | // .... 92 | // } 93 | // 94 | type Pool struct { 95 | 96 | // Dial is an application supplied function for creating and configuring a 97 | // connection. 98 | // 99 | // The connection returned from Dial must not be in a special state 100 | // (subscribed to pubsub channel, transaction started, ...). 101 | Dial func() (Conn, error) 102 | 103 | // TestOnBorrow is an optional application supplied function for checking 104 | // the health of an idle connection before the connection is used again by 105 | // the application. Argument t is the time that the connection was returned 106 | // to the pool. If the function returns an error, then the connection is 107 | // closed. 108 | TestOnBorrow func(c Conn, t time.Time) error 109 | 110 | // Maximum number of idle connections in the pool. 111 | MaxIdle int 112 | 113 | // Maximum number of connections allocated by the pool at a given time. 114 | // When zero, there is no limit on the number of connections in the pool. 115 | MaxActive int 116 | 117 | // Close connections after remaining idle for this duration. If the value 118 | // is zero, then idle connections are not closed. Applications should set 119 | // the timeout to a value less than the server's timeout. 120 | IdleTimeout time.Duration 121 | 122 | // If Wait is true and the pool is at the MaxActive limit, then Get() waits 123 | // for a connection to be returned to the pool before returning. 124 | Wait bool 125 | 126 | // mu protects fields defined below. 127 | mu sync.Mutex 128 | cond *sync.Cond 129 | closed bool 130 | active int 131 | 132 | // Stack of idleConn with most recently used at the front. 133 | idle list.List 134 | } 135 | 136 | type idleConn struct { 137 | c Conn 138 | t time.Time 139 | } 140 | 141 | // NewPool creates a new pool. 142 | // 143 | // Deprecated: Initialize the Pool directory as shown in the example. 144 | func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { 145 | return &Pool{Dial: newFn, MaxIdle: maxIdle} 146 | } 147 | 148 | // Get gets a connection. The application must close the returned connection. 149 | // This method always returns a valid connection so that applications can defer 150 | // error handling to the first use of the connection. If there is an error 151 | // getting an underlying connection, then the connection Err, Do, Send, Flush 152 | // and Receive methods return that error. 153 | func (p *Pool) Get() Conn { 154 | c, err := p.get() 155 | if err != nil { 156 | return errorConnection{err} 157 | } 158 | return &pooledConnection{p: p, c: c} 159 | } 160 | 161 | // ActiveCount returns the number of active connections in the pool. 162 | func (p *Pool) ActiveCount() int { 163 | p.mu.Lock() 164 | active := p.active 165 | p.mu.Unlock() 166 | return active 167 | } 168 | 169 | // Close releases the resources used by the pool. 170 | func (p *Pool) Close() error { 171 | p.mu.Lock() 172 | idle := p.idle 173 | p.idle.Init() 174 | p.closed = true 175 | p.active -= idle.Len() 176 | if p.cond != nil { 177 | p.cond.Broadcast() 178 | } 179 | p.mu.Unlock() 180 | for e := idle.Front(); e != nil; e = e.Next() { 181 | e.Value.(idleConn).c.Close() 182 | } 183 | return nil 184 | } 185 | 186 | // release decrements the active count and signals waiters. The caller must 187 | // hold p.mu during the call. 188 | func (p *Pool) release() { 189 | p.active -= 1 190 | if p.cond != nil { 191 | p.cond.Signal() 192 | } 193 | } 194 | 195 | // get prunes stale connections and returns a connection from the idle list or 196 | // creates a new connection. 197 | func (p *Pool) get() (Conn, error) { 198 | p.mu.Lock() 199 | 200 | // Prune stale connections. 201 | 202 | if timeout := p.IdleTimeout; timeout > 0 { 203 | for i, n := 0, p.idle.Len(); i < n; i++ { 204 | e := p.idle.Back() 205 | if e == nil { 206 | break 207 | } 208 | ic := e.Value.(idleConn) 209 | if ic.t.Add(timeout).After(nowFunc()) { 210 | break 211 | } 212 | p.idle.Remove(e) 213 | p.release() 214 | p.mu.Unlock() 215 | ic.c.Close() 216 | p.mu.Lock() 217 | } 218 | } 219 | 220 | for { 221 | 222 | // Get idle connection. 223 | 224 | for i, n := 0, p.idle.Len(); i < n; i++ { 225 | e := p.idle.Front() 226 | if e == nil { 227 | break 228 | } 229 | ic := e.Value.(idleConn) 230 | p.idle.Remove(e) 231 | test := p.TestOnBorrow 232 | p.mu.Unlock() 233 | if test == nil || test(ic.c, ic.t) == nil { 234 | return ic.c, nil 235 | } 236 | ic.c.Close() 237 | p.mu.Lock() 238 | p.release() 239 | } 240 | 241 | // Check for pool closed before dialing a new connection. 242 | 243 | if p.closed { 244 | p.mu.Unlock() 245 | return nil, errors.New("redigo: get on closed pool") 246 | } 247 | 248 | // Dial new connection if under limit. 249 | 250 | if p.MaxActive == 0 || p.active < p.MaxActive { 251 | dial := p.Dial 252 | p.active += 1 253 | p.mu.Unlock() 254 | c, err := dial() 255 | if err != nil { 256 | p.mu.Lock() 257 | p.release() 258 | p.mu.Unlock() 259 | c = nil 260 | } 261 | return c, err 262 | } 263 | 264 | if !p.Wait { 265 | p.mu.Unlock() 266 | return nil, ErrPoolExhausted 267 | } 268 | 269 | if p.cond == nil { 270 | p.cond = sync.NewCond(&p.mu) 271 | } 272 | p.cond.Wait() 273 | } 274 | } 275 | 276 | func (p *Pool) put(c Conn, forceClose bool) error { 277 | err := c.Err() 278 | p.mu.Lock() 279 | if !p.closed && err == nil && !forceClose { 280 | p.idle.PushFront(idleConn{t: nowFunc(), c: c}) 281 | if p.idle.Len() > p.MaxIdle { 282 | c = p.idle.Remove(p.idle.Back()).(idleConn).c 283 | } else { 284 | c = nil 285 | } 286 | } 287 | 288 | if c == nil { 289 | if p.cond != nil { 290 | p.cond.Signal() 291 | } 292 | p.mu.Unlock() 293 | return nil 294 | } 295 | 296 | p.release() 297 | p.mu.Unlock() 298 | return c.Close() 299 | } 300 | 301 | type pooledConnection struct { 302 | p *Pool 303 | c Conn 304 | state int 305 | } 306 | 307 | var ( 308 | sentinel []byte 309 | sentinelOnce sync.Once 310 | ) 311 | 312 | func initSentinel() { 313 | p := make([]byte, 64) 314 | if _, err := rand.Read(p); err == nil { 315 | sentinel = p 316 | } else { 317 | h := sha1.New() 318 | io.WriteString(h, "Oops, rand failed. Use time instead.") 319 | io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) 320 | sentinel = h.Sum(nil) 321 | } 322 | } 323 | 324 | func (pc *pooledConnection) Close() error { 325 | c := pc.c 326 | if _, ok := c.(errorConnection); ok { 327 | return nil 328 | } 329 | pc.c = errorConnection{errConnClosed} 330 | 331 | if pc.state&internal.MultiState != 0 { 332 | c.Send("DISCARD") 333 | pc.state &^= (internal.MultiState | internal.WatchState) 334 | } else if pc.state&internal.WatchState != 0 { 335 | c.Send("UNWATCH") 336 | pc.state &^= internal.WatchState 337 | } 338 | if pc.state&internal.SubscribeState != 0 { 339 | c.Send("UNSUBSCRIBE") 340 | c.Send("PUNSUBSCRIBE") 341 | // To detect the end of the message stream, ask the server to echo 342 | // a sentinel value and read until we see that value. 343 | sentinelOnce.Do(initSentinel) 344 | c.Send("ECHO", sentinel) 345 | c.Flush() 346 | for { 347 | p, err := c.Receive() 348 | if err != nil { 349 | break 350 | } 351 | if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { 352 | pc.state &^= internal.SubscribeState 353 | break 354 | } 355 | } 356 | } 357 | c.Do("") 358 | pc.p.put(c, pc.state != 0) 359 | return nil 360 | } 361 | 362 | func (pc *pooledConnection) Err() error { 363 | return pc.c.Err() 364 | } 365 | 366 | func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { 367 | ci := internal.LookupCommandInfo(commandName) 368 | pc.state = (pc.state | ci.Set) &^ ci.Clear 369 | return pc.c.Do(commandName, args...) 370 | } 371 | 372 | func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { 373 | ci := internal.LookupCommandInfo(commandName) 374 | pc.state = (pc.state | ci.Set) &^ ci.Clear 375 | return pc.c.Send(commandName, args...) 376 | } 377 | 378 | func (pc *pooledConnection) Flush() error { 379 | return pc.c.Flush() 380 | } 381 | 382 | func (pc *pooledConnection) Receive() (reply interface{}, err error) { 383 | return pc.c.Receive() 384 | } 385 | 386 | type errorConnection struct{ err error } 387 | 388 | func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } 389 | func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } 390 | func (ec errorConnection) Err() error { return ec.err } 391 | func (ec errorConnection) Close() error { return ec.err } 392 | func (ec errorConnection) Flush() error { return ec.err } 393 | func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } 394 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/reply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | ) 22 | 23 | // ErrNil indicates that a reply value is nil. 24 | var ErrNil = errors.New("redigo: nil returned") 25 | 26 | // Int is a helper that converts a command reply to an integer. If err is not 27 | // equal to nil, then Int returns 0, err. Otherwise, Int converts the 28 | // reply to an int as follows: 29 | // 30 | // Reply type Result 31 | // integer int(reply), nil 32 | // bulk string parsed reply, nil 33 | // nil 0, ErrNil 34 | // other 0, error 35 | func Int(reply interface{}, err error) (int, error) { 36 | if err != nil { 37 | return 0, err 38 | } 39 | switch reply := reply.(type) { 40 | case int64: 41 | x := int(reply) 42 | if int64(x) != reply { 43 | return 0, strconv.ErrRange 44 | } 45 | return x, nil 46 | case []byte: 47 | n, err := strconv.ParseInt(string(reply), 10, 0) 48 | return int(n), err 49 | case nil: 50 | return 0, ErrNil 51 | case Error: 52 | return 0, reply 53 | } 54 | return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) 55 | } 56 | 57 | // Int64 is a helper that converts a command reply to 64 bit integer. If err is 58 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 59 | // reply to an int64 as follows: 60 | // 61 | // Reply type Result 62 | // integer reply, nil 63 | // bulk string parsed reply, nil 64 | // nil 0, ErrNil 65 | // other 0, error 66 | func Int64(reply interface{}, err error) (int64, error) { 67 | if err != nil { 68 | return 0, err 69 | } 70 | switch reply := reply.(type) { 71 | case int64: 72 | return reply, nil 73 | case []byte: 74 | n, err := strconv.ParseInt(string(reply), 10, 64) 75 | return n, err 76 | case nil: 77 | return 0, ErrNil 78 | case Error: 79 | return 0, reply 80 | } 81 | return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) 82 | } 83 | 84 | var errNegativeInt = errors.New("redigo: unexpected value for Uint64") 85 | 86 | // Uint64 is a helper that converts a command reply to 64 bit integer. If err is 87 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 88 | // reply to an int64 as follows: 89 | // 90 | // Reply type Result 91 | // integer reply, nil 92 | // bulk string parsed reply, nil 93 | // nil 0, ErrNil 94 | // other 0, error 95 | func Uint64(reply interface{}, err error) (uint64, error) { 96 | if err != nil { 97 | return 0, err 98 | } 99 | switch reply := reply.(type) { 100 | case int64: 101 | if reply < 0 { 102 | return 0, errNegativeInt 103 | } 104 | return uint64(reply), nil 105 | case []byte: 106 | n, err := strconv.ParseUint(string(reply), 10, 64) 107 | return n, err 108 | case nil: 109 | return 0, ErrNil 110 | case Error: 111 | return 0, reply 112 | } 113 | return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) 114 | } 115 | 116 | // Float64 is a helper that converts a command reply to 64 bit float. If err is 117 | // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts 118 | // the reply to an int as follows: 119 | // 120 | // Reply type Result 121 | // bulk string parsed reply, nil 122 | // nil 0, ErrNil 123 | // other 0, error 124 | func Float64(reply interface{}, err error) (float64, error) { 125 | if err != nil { 126 | return 0, err 127 | } 128 | switch reply := reply.(type) { 129 | case []byte: 130 | n, err := strconv.ParseFloat(string(reply), 64) 131 | return n, err 132 | case nil: 133 | return 0, ErrNil 134 | case Error: 135 | return 0, reply 136 | } 137 | return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) 138 | } 139 | 140 | // String is a helper that converts a command reply to a string. If err is not 141 | // equal to nil, then String returns "", err. Otherwise String converts the 142 | // reply to a string as follows: 143 | // 144 | // Reply type Result 145 | // bulk string string(reply), nil 146 | // simple string reply, nil 147 | // nil "", ErrNil 148 | // other "", error 149 | func String(reply interface{}, err error) (string, error) { 150 | if err != nil { 151 | return "", err 152 | } 153 | switch reply := reply.(type) { 154 | case []byte: 155 | return string(reply), nil 156 | case string: 157 | return reply, nil 158 | case nil: 159 | return "", ErrNil 160 | case Error: 161 | return "", reply 162 | } 163 | return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) 164 | } 165 | 166 | // Bytes is a helper that converts a command reply to a slice of bytes. If err 167 | // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts 168 | // the reply to a slice of bytes as follows: 169 | // 170 | // Reply type Result 171 | // bulk string reply, nil 172 | // simple string []byte(reply), nil 173 | // nil nil, ErrNil 174 | // other nil, error 175 | func Bytes(reply interface{}, err error) ([]byte, error) { 176 | if err != nil { 177 | return nil, err 178 | } 179 | switch reply := reply.(type) { 180 | case []byte: 181 | return reply, nil 182 | case string: 183 | return []byte(reply), nil 184 | case nil: 185 | return nil, ErrNil 186 | case Error: 187 | return nil, reply 188 | } 189 | return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) 190 | } 191 | 192 | // Bool is a helper that converts a command reply to a boolean. If err is not 193 | // equal to nil, then Bool returns false, err. Otherwise Bool converts the 194 | // reply to boolean as follows: 195 | // 196 | // Reply type Result 197 | // integer value != 0, nil 198 | // bulk string strconv.ParseBool(reply) 199 | // nil false, ErrNil 200 | // other false, error 201 | func Bool(reply interface{}, err error) (bool, error) { 202 | if err != nil { 203 | return false, err 204 | } 205 | switch reply := reply.(type) { 206 | case int64: 207 | return reply != 0, nil 208 | case []byte: 209 | return strconv.ParseBool(string(reply)) 210 | case nil: 211 | return false, ErrNil 212 | case Error: 213 | return false, reply 214 | } 215 | return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) 216 | } 217 | 218 | // MultiBulk is a helper that converts an array command reply to a []interface{}. 219 | // 220 | // Deprecated: Use Values instead. 221 | func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } 222 | 223 | // Values is a helper that converts an array command reply to a []interface{}. 224 | // If err is not equal to nil, then Values returns nil, err. Otherwise, Values 225 | // converts the reply as follows: 226 | // 227 | // Reply type Result 228 | // array reply, nil 229 | // nil nil, ErrNil 230 | // other nil, error 231 | func Values(reply interface{}, err error) ([]interface{}, error) { 232 | if err != nil { 233 | return nil, err 234 | } 235 | switch reply := reply.(type) { 236 | case []interface{}: 237 | return reply, nil 238 | case nil: 239 | return nil, ErrNil 240 | case Error: 241 | return nil, reply 242 | } 243 | return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) 244 | } 245 | 246 | // Strings is a helper that converts an array command reply to a []string. If 247 | // err is not equal to nil, then Strings returns nil, err. Nil array items are 248 | // converted to "" in the output slice. Strings returns an error if an array 249 | // item is not a bulk string or nil. 250 | func Strings(reply interface{}, err error) ([]string, error) { 251 | if err != nil { 252 | return nil, err 253 | } 254 | switch reply := reply.(type) { 255 | case []interface{}: 256 | result := make([]string, len(reply)) 257 | for i := range reply { 258 | if reply[i] == nil { 259 | continue 260 | } 261 | p, ok := reply[i].([]byte) 262 | if !ok { 263 | return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) 264 | } 265 | result[i] = string(p) 266 | } 267 | return result, nil 268 | case nil: 269 | return nil, ErrNil 270 | case Error: 271 | return nil, reply 272 | } 273 | return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) 274 | } 275 | 276 | // ByteSlices is a helper that converts an array command reply to a [][]byte. 277 | // If err is not equal to nil, then ByteSlices returns nil, err. Nil array 278 | // items are stay nil. ByteSlices returns an error if an array item is not a 279 | // bulk string or nil. 280 | func ByteSlices(reply interface{}, err error) ([][]byte, error) { 281 | if err != nil { 282 | return nil, err 283 | } 284 | switch reply := reply.(type) { 285 | case []interface{}: 286 | result := make([][]byte, len(reply)) 287 | for i := range reply { 288 | if reply[i] == nil { 289 | continue 290 | } 291 | p, ok := reply[i].([]byte) 292 | if !ok { 293 | return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i]) 294 | } 295 | result[i] = p 296 | } 297 | return result, nil 298 | case nil: 299 | return nil, ErrNil 300 | case Error: 301 | return nil, reply 302 | } 303 | return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply) 304 | } 305 | 306 | // Ints is a helper that converts an array command reply to a []int. If 307 | // err is not equal to nil, then Ints returns nil, err. 308 | func Ints(reply interface{}, err error) ([]int, error) { 309 | var ints []int 310 | values, err := Values(reply, err) 311 | if err != nil { 312 | return ints, err 313 | } 314 | if err := ScanSlice(values, &ints); err != nil { 315 | return ints, err 316 | } 317 | return ints, nil 318 | } 319 | 320 | // StringMap is a helper that converts an array of strings (alternating key, value) 321 | // into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. 322 | // Requires an even number of values in result. 323 | func StringMap(result interface{}, err error) (map[string]string, error) { 324 | values, err := Values(result, err) 325 | if err != nil { 326 | return nil, err 327 | } 328 | if len(values)%2 != 0 { 329 | return nil, errors.New("redigo: StringMap expects even number of values result") 330 | } 331 | m := make(map[string]string, len(values)/2) 332 | for i := 0; i < len(values); i += 2 { 333 | key, okKey := values[i].([]byte) 334 | value, okValue := values[i+1].([]byte) 335 | if !okKey || !okValue { 336 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 337 | } 338 | m[string(key)] = string(value) 339 | } 340 | return m, nil 341 | } 342 | 343 | // IntMap is a helper that converts an array of strings (alternating key, value) 344 | // into a map[string]int. The HGETALL commands return replies in this format. 345 | // Requires an even number of values in result. 346 | func IntMap(result interface{}, err error) (map[string]int, error) { 347 | values, err := Values(result, err) 348 | if err != nil { 349 | return nil, err 350 | } 351 | if len(values)%2 != 0 { 352 | return nil, errors.New("redigo: IntMap expects even number of values result") 353 | } 354 | m := make(map[string]int, len(values)/2) 355 | for i := 0; i < len(values); i += 2 { 356 | key, ok := values[i].([]byte) 357 | if !ok { 358 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 359 | } 360 | value, err := Int(values[i+1], nil) 361 | if err != nil { 362 | return nil, err 363 | } 364 | m[string(key)] = value 365 | } 366 | return m, nil 367 | } 368 | 369 | // Int64Map is a helper that converts an array of strings (alternating key, value) 370 | // into a map[string]int64. The HGETALL commands return replies in this format. 371 | // Requires an even number of values in result. 372 | func Int64Map(result interface{}, err error) (map[string]int64, error) { 373 | values, err := Values(result, err) 374 | if err != nil { 375 | return nil, err 376 | } 377 | if len(values)%2 != 0 { 378 | return nil, errors.New("redigo: Int64Map expects even number of values result") 379 | } 380 | m := make(map[string]int64, len(values)/2) 381 | for i := 0; i < len(values); i += 2 { 382 | key, ok := values[i].([]byte) 383 | if !ok { 384 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 385 | } 386 | value, err := Int64(values[i+1], nil) 387 | if err != nil { 388 | return nil, err 389 | } 390 | m[string(key)] = value 391 | } 392 | return m, nil 393 | } 394 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/flag.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // This flag enables bash-completion for all commands and subcommands 13 | var BashCompletionFlag = BoolFlag{ 14 | Name: "generate-bash-completion", 15 | } 16 | 17 | // This flag prints the version for the application 18 | var VersionFlag = BoolFlag{ 19 | Name: "version, v", 20 | Usage: "print the version", 21 | } 22 | 23 | // This flag prints the help for all commands and subcommands 24 | // Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand 25 | // unless HideHelp is set to true) 26 | var HelpFlag = BoolFlag{ 27 | Name: "help, h", 28 | Usage: "show help", 29 | } 30 | 31 | // Flag is a common interface related to parsing flags in cli. 32 | // For more advanced flag parsing techniques, it is recomended that 33 | // this interface be implemented. 34 | type Flag interface { 35 | fmt.Stringer 36 | // Apply Flag settings to the given flag set 37 | Apply(*flag.FlagSet) 38 | getName() string 39 | } 40 | 41 | func flagSet(name string, flags []Flag) *flag.FlagSet { 42 | set := flag.NewFlagSet(name, flag.ContinueOnError) 43 | 44 | for _, f := range flags { 45 | f.Apply(set) 46 | } 47 | return set 48 | } 49 | 50 | func eachName(longName string, fn func(string)) { 51 | parts := strings.Split(longName, ",") 52 | for _, name := range parts { 53 | name = strings.Trim(name, " ") 54 | fn(name) 55 | } 56 | } 57 | 58 | // Generic is a generic parseable type identified by a specific flag 59 | type Generic interface { 60 | Set(value string) error 61 | String() string 62 | } 63 | 64 | // GenericFlag is the flag type for types implementing Generic 65 | type GenericFlag struct { 66 | Name string 67 | Value Generic 68 | Usage string 69 | EnvVar string 70 | } 71 | 72 | // String returns the string representation of the generic flag to display the 73 | // help text to the user (uses the String() method of the generic flag to show 74 | // the value) 75 | func (f GenericFlag) String() string { 76 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) 77 | } 78 | 79 | // Apply takes the flagset and calls Set on the generic flag with the value 80 | // provided by the user for parsing by the flag 81 | func (f GenericFlag) Apply(set *flag.FlagSet) { 82 | val := f.Value 83 | if f.EnvVar != "" { 84 | for _, envVar := range strings.Split(f.EnvVar, ",") { 85 | envVar = strings.TrimSpace(envVar) 86 | if envVal := os.Getenv(envVar); envVal != "" { 87 | val.Set(envVal) 88 | break 89 | } 90 | } 91 | } 92 | 93 | eachName(f.Name, func(name string) { 94 | set.Var(f.Value, name, f.Usage) 95 | }) 96 | } 97 | 98 | func (f GenericFlag) getName() string { 99 | return f.Name 100 | } 101 | 102 | // StringSlice is an opaque type for []string to satisfy flag.Value 103 | type StringSlice []string 104 | 105 | // Set appends the string value to the list of values 106 | func (f *StringSlice) Set(value string) error { 107 | *f = append(*f, value) 108 | return nil 109 | } 110 | 111 | // String returns a readable representation of this value (for usage defaults) 112 | func (f *StringSlice) String() string { 113 | return fmt.Sprintf("%s", *f) 114 | } 115 | 116 | // Value returns the slice of strings set by this flag 117 | func (f *StringSlice) Value() []string { 118 | return *f 119 | } 120 | 121 | // StringSlice is a string flag that can be specified multiple times on the 122 | // command-line 123 | type StringSliceFlag struct { 124 | Name string 125 | Value *StringSlice 126 | Usage string 127 | EnvVar string 128 | } 129 | 130 | // String returns the usage 131 | func (f StringSliceFlag) String() string { 132 | firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") 133 | pref := prefixFor(firstName) 134 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) 135 | } 136 | 137 | // Apply populates the flag given the flag set and environment 138 | func (f StringSliceFlag) Apply(set *flag.FlagSet) { 139 | if f.EnvVar != "" { 140 | for _, envVar := range strings.Split(f.EnvVar, ",") { 141 | envVar = strings.TrimSpace(envVar) 142 | if envVal := os.Getenv(envVar); envVal != "" { 143 | newVal := &StringSlice{} 144 | for _, s := range strings.Split(envVal, ",") { 145 | s = strings.TrimSpace(s) 146 | newVal.Set(s) 147 | } 148 | f.Value = newVal 149 | break 150 | } 151 | } 152 | } 153 | 154 | eachName(f.Name, func(name string) { 155 | if f.Value == nil { 156 | f.Value = &StringSlice{} 157 | } 158 | set.Var(f.Value, name, f.Usage) 159 | }) 160 | } 161 | 162 | func (f StringSliceFlag) getName() string { 163 | return f.Name 164 | } 165 | 166 | // StringSlice is an opaque type for []int to satisfy flag.Value 167 | type IntSlice []int 168 | 169 | // Set parses the value into an integer and appends it to the list of values 170 | func (f *IntSlice) Set(value string) error { 171 | tmp, err := strconv.Atoi(value) 172 | if err != nil { 173 | return err 174 | } else { 175 | *f = append(*f, tmp) 176 | } 177 | return nil 178 | } 179 | 180 | // String returns a readable representation of this value (for usage defaults) 181 | func (f *IntSlice) String() string { 182 | return fmt.Sprintf("%d", *f) 183 | } 184 | 185 | // Value returns the slice of ints set by this flag 186 | func (f *IntSlice) Value() []int { 187 | return *f 188 | } 189 | 190 | // IntSliceFlag is an int flag that can be specified multiple times on the 191 | // command-line 192 | type IntSliceFlag struct { 193 | Name string 194 | Value *IntSlice 195 | Usage string 196 | EnvVar string 197 | } 198 | 199 | // String returns the usage 200 | func (f IntSliceFlag) String() string { 201 | firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") 202 | pref := prefixFor(firstName) 203 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) 204 | } 205 | 206 | // Apply populates the flag given the flag set and environment 207 | func (f IntSliceFlag) Apply(set *flag.FlagSet) { 208 | if f.EnvVar != "" { 209 | for _, envVar := range strings.Split(f.EnvVar, ",") { 210 | envVar = strings.TrimSpace(envVar) 211 | if envVal := os.Getenv(envVar); envVal != "" { 212 | newVal := &IntSlice{} 213 | for _, s := range strings.Split(envVal, ",") { 214 | s = strings.TrimSpace(s) 215 | err := newVal.Set(s) 216 | if err != nil { 217 | fmt.Fprintf(os.Stderr, err.Error()) 218 | } 219 | } 220 | f.Value = newVal 221 | break 222 | } 223 | } 224 | } 225 | 226 | eachName(f.Name, func(name string) { 227 | if f.Value == nil { 228 | f.Value = &IntSlice{} 229 | } 230 | set.Var(f.Value, name, f.Usage) 231 | }) 232 | } 233 | 234 | func (f IntSliceFlag) getName() string { 235 | return f.Name 236 | } 237 | 238 | // BoolFlag is a switch that defaults to false 239 | type BoolFlag struct { 240 | Name string 241 | Usage string 242 | EnvVar string 243 | } 244 | 245 | // String returns a readable representation of this value (for usage defaults) 246 | func (f BoolFlag) String() string { 247 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) 248 | } 249 | 250 | // Apply populates the flag given the flag set and environment 251 | func (f BoolFlag) Apply(set *flag.FlagSet) { 252 | val := false 253 | if f.EnvVar != "" { 254 | for _, envVar := range strings.Split(f.EnvVar, ",") { 255 | envVar = strings.TrimSpace(envVar) 256 | if envVal := os.Getenv(envVar); envVal != "" { 257 | envValBool, err := strconv.ParseBool(envVal) 258 | if err == nil { 259 | val = envValBool 260 | } 261 | break 262 | } 263 | } 264 | } 265 | 266 | eachName(f.Name, func(name string) { 267 | set.Bool(name, val, f.Usage) 268 | }) 269 | } 270 | 271 | func (f BoolFlag) getName() string { 272 | return f.Name 273 | } 274 | 275 | // BoolTFlag this represents a boolean flag that is true by default, but can 276 | // still be set to false by --some-flag=false 277 | type BoolTFlag struct { 278 | Name string 279 | Usage string 280 | EnvVar string 281 | } 282 | 283 | // String returns a readable representation of this value (for usage defaults) 284 | func (f BoolTFlag) String() string { 285 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) 286 | } 287 | 288 | // Apply populates the flag given the flag set and environment 289 | func (f BoolTFlag) Apply(set *flag.FlagSet) { 290 | val := true 291 | if f.EnvVar != "" { 292 | for _, envVar := range strings.Split(f.EnvVar, ",") { 293 | envVar = strings.TrimSpace(envVar) 294 | if envVal := os.Getenv(envVar); envVal != "" { 295 | envValBool, err := strconv.ParseBool(envVal) 296 | if err == nil { 297 | val = envValBool 298 | break 299 | } 300 | } 301 | } 302 | } 303 | 304 | eachName(f.Name, func(name string) { 305 | set.Bool(name, val, f.Usage) 306 | }) 307 | } 308 | 309 | func (f BoolTFlag) getName() string { 310 | return f.Name 311 | } 312 | 313 | // StringFlag represents a flag that takes as string value 314 | type StringFlag struct { 315 | Name string 316 | Value string 317 | Usage string 318 | EnvVar string 319 | } 320 | 321 | // String returns the usage 322 | func (f StringFlag) String() string { 323 | var fmtString string 324 | fmtString = "%s %v\t%v" 325 | 326 | if len(f.Value) > 0 { 327 | fmtString = "%s \"%v\"\t%v" 328 | } else { 329 | fmtString = "%s %v\t%v" 330 | } 331 | 332 | return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) 333 | } 334 | 335 | // Apply populates the flag given the flag set and environment 336 | func (f StringFlag) Apply(set *flag.FlagSet) { 337 | if f.EnvVar != "" { 338 | for _, envVar := range strings.Split(f.EnvVar, ",") { 339 | envVar = strings.TrimSpace(envVar) 340 | if envVal := os.Getenv(envVar); envVal != "" { 341 | f.Value = envVal 342 | break 343 | } 344 | } 345 | } 346 | 347 | eachName(f.Name, func(name string) { 348 | set.String(name, f.Value, f.Usage) 349 | }) 350 | } 351 | 352 | func (f StringFlag) getName() string { 353 | return f.Name 354 | } 355 | 356 | // IntFlag is a flag that takes an integer 357 | // Errors if the value provided cannot be parsed 358 | type IntFlag struct { 359 | Name string 360 | Value int 361 | Usage string 362 | EnvVar string 363 | } 364 | 365 | // String returns the usage 366 | func (f IntFlag) String() string { 367 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) 368 | } 369 | 370 | // Apply populates the flag given the flag set and environment 371 | func (f IntFlag) Apply(set *flag.FlagSet) { 372 | if f.EnvVar != "" { 373 | for _, envVar := range strings.Split(f.EnvVar, ",") { 374 | envVar = strings.TrimSpace(envVar) 375 | if envVal := os.Getenv(envVar); envVal != "" { 376 | envValInt, err := strconv.ParseInt(envVal, 0, 64) 377 | if err == nil { 378 | f.Value = int(envValInt) 379 | break 380 | } 381 | } 382 | } 383 | } 384 | 385 | eachName(f.Name, func(name string) { 386 | set.Int(name, f.Value, f.Usage) 387 | }) 388 | } 389 | 390 | func (f IntFlag) getName() string { 391 | return f.Name 392 | } 393 | 394 | // DurationFlag is a flag that takes a duration specified in Go's duration 395 | // format: https://golang.org/pkg/time/#ParseDuration 396 | type DurationFlag struct { 397 | Name string 398 | Value time.Duration 399 | Usage string 400 | EnvVar string 401 | } 402 | 403 | // String returns a readable representation of this value (for usage defaults) 404 | func (f DurationFlag) String() string { 405 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) 406 | } 407 | 408 | // Apply populates the flag given the flag set and environment 409 | func (f DurationFlag) Apply(set *flag.FlagSet) { 410 | if f.EnvVar != "" { 411 | for _, envVar := range strings.Split(f.EnvVar, ",") { 412 | envVar = strings.TrimSpace(envVar) 413 | if envVal := os.Getenv(envVar); envVal != "" { 414 | envValDuration, err := time.ParseDuration(envVal) 415 | if err == nil { 416 | f.Value = envValDuration 417 | break 418 | } 419 | } 420 | } 421 | } 422 | 423 | eachName(f.Name, func(name string) { 424 | set.Duration(name, f.Value, f.Usage) 425 | }) 426 | } 427 | 428 | func (f DurationFlag) getName() string { 429 | return f.Name 430 | } 431 | 432 | // Float64Flag is a flag that takes an float value 433 | // Errors if the value provided cannot be parsed 434 | type Float64Flag struct { 435 | Name string 436 | Value float64 437 | Usage string 438 | EnvVar string 439 | } 440 | 441 | // String returns the usage 442 | func (f Float64Flag) String() string { 443 | return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) 444 | } 445 | 446 | // Apply populates the flag given the flag set and environment 447 | func (f Float64Flag) Apply(set *flag.FlagSet) { 448 | if f.EnvVar != "" { 449 | for _, envVar := range strings.Split(f.EnvVar, ",") { 450 | envVar = strings.TrimSpace(envVar) 451 | if envVal := os.Getenv(envVar); envVal != "" { 452 | envValFloat, err := strconv.ParseFloat(envVal, 10) 453 | if err == nil { 454 | f.Value = float64(envValFloat) 455 | } 456 | } 457 | } 458 | } 459 | 460 | eachName(f.Name, func(name string) { 461 | set.Float64(name, f.Value, f.Usage) 462 | }) 463 | } 464 | 465 | func (f Float64Flag) getName() string { 466 | return f.Name 467 | } 468 | 469 | func prefixFor(name string) (prefix string) { 470 | if len(name) == 1 { 471 | prefix = "-" 472 | } else { 473 | prefix = "--" 474 | } 475 | 476 | return 477 | } 478 | 479 | func prefixedNames(fullName string) (prefixed string) { 480 | parts := strings.Split(fullName, ",") 481 | for i, name := range parts { 482 | name = strings.Trim(name, " ") 483 | prefixed += prefixFor(name) + name 484 | if i < len(parts)-1 { 485 | prefixed += ", " 486 | } 487 | } 488 | return 489 | } 490 | 491 | func withEnvHint(envVar, str string) string { 492 | envText := "" 493 | if envVar != "" { 494 | envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) 495 | } 496 | return str + envText 497 | } 498 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "net" 24 | "net/url" 25 | "regexp" 26 | "strconv" 27 | "sync" 28 | "time" 29 | ) 30 | 31 | // conn is the low-level implementation of Conn 32 | type conn struct { 33 | 34 | // Shared 35 | mu sync.Mutex 36 | pending int 37 | err error 38 | conn net.Conn 39 | 40 | // Read 41 | readTimeout time.Duration 42 | br *bufio.Reader 43 | 44 | // Write 45 | writeTimeout time.Duration 46 | bw *bufio.Writer 47 | 48 | // Scratch space for formatting argument length. 49 | // '*' or '$', length, "\r\n" 50 | lenScratch [32]byte 51 | 52 | // Scratch space for formatting integers and floats. 53 | numScratch [40]byte 54 | } 55 | 56 | // DialTimeout acts like Dial but takes timeouts for establishing the 57 | // connection to the server, writing a command and reading a reply. 58 | // 59 | // Deprecated: Use Dial with options instead. 60 | func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { 61 | return Dial(network, address, 62 | DialConnectTimeout(connectTimeout), 63 | DialReadTimeout(readTimeout), 64 | DialWriteTimeout(writeTimeout)) 65 | } 66 | 67 | // DialOption specifies an option for dialing a Redis server. 68 | type DialOption struct { 69 | f func(*dialOptions) 70 | } 71 | 72 | type dialOptions struct { 73 | readTimeout time.Duration 74 | writeTimeout time.Duration 75 | dial func(network, addr string) (net.Conn, error) 76 | db int 77 | password string 78 | } 79 | 80 | // DialReadTimeout specifies the timeout for reading a single command reply. 81 | func DialReadTimeout(d time.Duration) DialOption { 82 | return DialOption{func(do *dialOptions) { 83 | do.readTimeout = d 84 | }} 85 | } 86 | 87 | // DialWriteTimeout specifies the timeout for writing a single command. 88 | func DialWriteTimeout(d time.Duration) DialOption { 89 | return DialOption{func(do *dialOptions) { 90 | do.writeTimeout = d 91 | }} 92 | } 93 | 94 | // DialConnectTimeout specifies the timeout for connecting to the Redis server. 95 | func DialConnectTimeout(d time.Duration) DialOption { 96 | return DialOption{func(do *dialOptions) { 97 | dialer := net.Dialer{Timeout: d} 98 | do.dial = dialer.Dial 99 | }} 100 | } 101 | 102 | // DialNetDial specifies a custom dial function for creating TCP 103 | // connections. If this option is left out, then net.Dial is 104 | // used. DialNetDial overrides DialConnectTimeout. 105 | func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { 106 | return DialOption{func(do *dialOptions) { 107 | do.dial = dial 108 | }} 109 | } 110 | 111 | // DialDatabase specifies the database to select when dialing a connection. 112 | func DialDatabase(db int) DialOption { 113 | return DialOption{func(do *dialOptions) { 114 | do.db = db 115 | }} 116 | } 117 | 118 | // DialPassword specifies the password to use when connecting to 119 | // the Redis server. 120 | func DialPassword(password string) DialOption { 121 | return DialOption{func(do *dialOptions) { 122 | do.password = password 123 | }} 124 | } 125 | 126 | // Dial connects to the Redis server at the given network and 127 | // address using the specified options. 128 | func Dial(network, address string, options ...DialOption) (Conn, error) { 129 | do := dialOptions{ 130 | dial: net.Dial, 131 | } 132 | for _, option := range options { 133 | option.f(&do) 134 | } 135 | 136 | netConn, err := do.dial(network, address) 137 | if err != nil { 138 | return nil, err 139 | } 140 | c := &conn{ 141 | conn: netConn, 142 | bw: bufio.NewWriter(netConn), 143 | br: bufio.NewReader(netConn), 144 | readTimeout: do.readTimeout, 145 | writeTimeout: do.writeTimeout, 146 | } 147 | 148 | if do.password != "" { 149 | if _, err := c.Do("AUTH", do.password); err != nil { 150 | netConn.Close() 151 | return nil, err 152 | } 153 | } 154 | 155 | if do.db != 0 { 156 | if _, err := c.Do("SELECT", do.db); err != nil { 157 | netConn.Close() 158 | return nil, err 159 | } 160 | } 161 | 162 | return c, nil 163 | } 164 | 165 | var pathDBRegexp = regexp.MustCompile(`/(\d+)\z`) 166 | 167 | // DialURL connects to a Redis server at the given URL using the Redis 168 | // URI scheme. URLs should follow the draft IANA specification for the 169 | // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). 170 | func DialURL(rawurl string, options ...DialOption) (Conn, error) { 171 | u, err := url.Parse(rawurl) 172 | if err != nil { 173 | return nil, err 174 | } 175 | 176 | if u.Scheme != "redis" { 177 | return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) 178 | } 179 | 180 | // As per the IANA draft spec, the host defaults to localhost and 181 | // the port defaults to 6379. 182 | host, port, err := net.SplitHostPort(u.Host) 183 | if err != nil { 184 | // assume port is missing 185 | host = u.Host 186 | port = "6379" 187 | } 188 | if host == "" { 189 | host = "localhost" 190 | } 191 | address := net.JoinHostPort(host, port) 192 | 193 | if u.User != nil { 194 | password, isSet := u.User.Password() 195 | if isSet { 196 | options = append(options, DialPassword(password)) 197 | } 198 | } 199 | 200 | match := pathDBRegexp.FindStringSubmatch(u.Path) 201 | if len(match) == 2 { 202 | db, err := strconv.Atoi(match[1]) 203 | if err != nil { 204 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 205 | } 206 | if db != 0 { 207 | options = append(options, DialDatabase(db)) 208 | } 209 | } else if u.Path != "" { 210 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 211 | } 212 | 213 | return Dial("tcp", address, options...) 214 | } 215 | 216 | // NewConn returns a new Redigo connection for the given net connection. 217 | func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { 218 | return &conn{ 219 | conn: netConn, 220 | bw: bufio.NewWriter(netConn), 221 | br: bufio.NewReader(netConn), 222 | readTimeout: readTimeout, 223 | writeTimeout: writeTimeout, 224 | } 225 | } 226 | 227 | func (c *conn) Close() error { 228 | c.mu.Lock() 229 | err := c.err 230 | if c.err == nil { 231 | c.err = errors.New("redigo: closed") 232 | err = c.conn.Close() 233 | } 234 | c.mu.Unlock() 235 | return err 236 | } 237 | 238 | func (c *conn) fatal(err error) error { 239 | c.mu.Lock() 240 | if c.err == nil { 241 | c.err = err 242 | // Close connection to force errors on subsequent calls and to unblock 243 | // other reader or writer. 244 | c.conn.Close() 245 | } 246 | c.mu.Unlock() 247 | return err 248 | } 249 | 250 | func (c *conn) Err() error { 251 | c.mu.Lock() 252 | err := c.err 253 | c.mu.Unlock() 254 | return err 255 | } 256 | 257 | func (c *conn) writeLen(prefix byte, n int) error { 258 | c.lenScratch[len(c.lenScratch)-1] = '\n' 259 | c.lenScratch[len(c.lenScratch)-2] = '\r' 260 | i := len(c.lenScratch) - 3 261 | for { 262 | c.lenScratch[i] = byte('0' + n%10) 263 | i -= 1 264 | n = n / 10 265 | if n == 0 { 266 | break 267 | } 268 | } 269 | c.lenScratch[i] = prefix 270 | _, err := c.bw.Write(c.lenScratch[i:]) 271 | return err 272 | } 273 | 274 | func (c *conn) writeString(s string) error { 275 | c.writeLen('$', len(s)) 276 | c.bw.WriteString(s) 277 | _, err := c.bw.WriteString("\r\n") 278 | return err 279 | } 280 | 281 | func (c *conn) writeBytes(p []byte) error { 282 | c.writeLen('$', len(p)) 283 | c.bw.Write(p) 284 | _, err := c.bw.WriteString("\r\n") 285 | return err 286 | } 287 | 288 | func (c *conn) writeInt64(n int64) error { 289 | return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) 290 | } 291 | 292 | func (c *conn) writeFloat64(n float64) error { 293 | return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) 294 | } 295 | 296 | func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { 297 | c.writeLen('*', 1+len(args)) 298 | err = c.writeString(cmd) 299 | for _, arg := range args { 300 | if err != nil { 301 | break 302 | } 303 | switch arg := arg.(type) { 304 | case string: 305 | err = c.writeString(arg) 306 | case []byte: 307 | err = c.writeBytes(arg) 308 | case int: 309 | err = c.writeInt64(int64(arg)) 310 | case int64: 311 | err = c.writeInt64(arg) 312 | case float64: 313 | err = c.writeFloat64(arg) 314 | case bool: 315 | if arg { 316 | err = c.writeString("1") 317 | } else { 318 | err = c.writeString("0") 319 | } 320 | case nil: 321 | err = c.writeString("") 322 | default: 323 | var buf bytes.Buffer 324 | fmt.Fprint(&buf, arg) 325 | err = c.writeBytes(buf.Bytes()) 326 | } 327 | } 328 | return err 329 | } 330 | 331 | type protocolError string 332 | 333 | func (pe protocolError) Error() string { 334 | return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) 335 | } 336 | 337 | func (c *conn) readLine() ([]byte, error) { 338 | p, err := c.br.ReadSlice('\n') 339 | if err == bufio.ErrBufferFull { 340 | return nil, protocolError("long response line") 341 | } 342 | if err != nil { 343 | return nil, err 344 | } 345 | i := len(p) - 2 346 | if i < 0 || p[i] != '\r' { 347 | return nil, protocolError("bad response line terminator") 348 | } 349 | return p[:i], nil 350 | } 351 | 352 | // parseLen parses bulk string and array lengths. 353 | func parseLen(p []byte) (int, error) { 354 | if len(p) == 0 { 355 | return -1, protocolError("malformed length") 356 | } 357 | 358 | if p[0] == '-' && len(p) == 2 && p[1] == '1' { 359 | // handle $-1 and $-1 null replies. 360 | return -1, nil 361 | } 362 | 363 | var n int 364 | for _, b := range p { 365 | n *= 10 366 | if b < '0' || b > '9' { 367 | return -1, protocolError("illegal bytes in length") 368 | } 369 | n += int(b - '0') 370 | } 371 | 372 | return n, nil 373 | } 374 | 375 | // parseInt parses an integer reply. 376 | func parseInt(p []byte) (interface{}, error) { 377 | if len(p) == 0 { 378 | return 0, protocolError("malformed integer") 379 | } 380 | 381 | var negate bool 382 | if p[0] == '-' { 383 | negate = true 384 | p = p[1:] 385 | if len(p) == 0 { 386 | return 0, protocolError("malformed integer") 387 | } 388 | } 389 | 390 | var n int64 391 | for _, b := range p { 392 | n *= 10 393 | if b < '0' || b > '9' { 394 | return 0, protocolError("illegal bytes in length") 395 | } 396 | n += int64(b - '0') 397 | } 398 | 399 | if negate { 400 | n = -n 401 | } 402 | return n, nil 403 | } 404 | 405 | var ( 406 | okReply interface{} = "OK" 407 | pongReply interface{} = "PONG" 408 | ) 409 | 410 | func (c *conn) readReply() (interface{}, error) { 411 | line, err := c.readLine() 412 | if err != nil { 413 | return nil, err 414 | } 415 | if len(line) == 0 { 416 | return nil, protocolError("short response line") 417 | } 418 | switch line[0] { 419 | case '+': 420 | switch { 421 | case len(line) == 3 && line[1] == 'O' && line[2] == 'K': 422 | // Avoid allocation for frequent "+OK" response. 423 | return okReply, nil 424 | case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': 425 | // Avoid allocation in PING command benchmarks :) 426 | return pongReply, nil 427 | default: 428 | return string(line[1:]), nil 429 | } 430 | case '-': 431 | return Error(string(line[1:])), nil 432 | case ':': 433 | return parseInt(line[1:]) 434 | case '$': 435 | n, err := parseLen(line[1:]) 436 | if n < 0 || err != nil { 437 | return nil, err 438 | } 439 | p := make([]byte, n) 440 | _, err = io.ReadFull(c.br, p) 441 | if err != nil { 442 | return nil, err 443 | } 444 | if line, err := c.readLine(); err != nil { 445 | return nil, err 446 | } else if len(line) != 0 { 447 | return nil, protocolError("bad bulk string format") 448 | } 449 | return p, nil 450 | case '*': 451 | n, err := parseLen(line[1:]) 452 | if n < 0 || err != nil { 453 | return nil, err 454 | } 455 | r := make([]interface{}, n) 456 | for i := range r { 457 | r[i], err = c.readReply() 458 | if err != nil { 459 | return nil, err 460 | } 461 | } 462 | return r, nil 463 | } 464 | return nil, protocolError("unexpected response line") 465 | } 466 | 467 | func (c *conn) Send(cmd string, args ...interface{}) error { 468 | c.mu.Lock() 469 | c.pending += 1 470 | c.mu.Unlock() 471 | if c.writeTimeout != 0 { 472 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 473 | } 474 | if err := c.writeCommand(cmd, args); err != nil { 475 | return c.fatal(err) 476 | } 477 | return nil 478 | } 479 | 480 | func (c *conn) Flush() error { 481 | if c.writeTimeout != 0 { 482 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 483 | } 484 | if err := c.bw.Flush(); err != nil { 485 | return c.fatal(err) 486 | } 487 | return nil 488 | } 489 | 490 | func (c *conn) Receive() (reply interface{}, err error) { 491 | if c.readTimeout != 0 { 492 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 493 | } 494 | if reply, err = c.readReply(); err != nil { 495 | return nil, c.fatal(err) 496 | } 497 | // When using pub/sub, the number of receives can be greater than the 498 | // number of sends. To enable normal use of the connection after 499 | // unsubscribing from all channels, we do not decrement pending to a 500 | // negative value. 501 | // 502 | // The pending field is decremented after the reply is read to handle the 503 | // case where Receive is called before Send. 504 | c.mu.Lock() 505 | if c.pending > 0 { 506 | c.pending -= 1 507 | } 508 | c.mu.Unlock() 509 | if err, ok := reply.(Error); ok { 510 | return nil, err 511 | } 512 | return 513 | } 514 | 515 | func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { 516 | c.mu.Lock() 517 | pending := c.pending 518 | c.pending = 0 519 | c.mu.Unlock() 520 | 521 | if cmd == "" && pending == 0 { 522 | return nil, nil 523 | } 524 | 525 | if c.writeTimeout != 0 { 526 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 527 | } 528 | 529 | if cmd != "" { 530 | if err := c.writeCommand(cmd, args); err != nil { 531 | return nil, c.fatal(err) 532 | } 533 | } 534 | 535 | if err := c.bw.Flush(); err != nil { 536 | return nil, c.fatal(err) 537 | } 538 | 539 | if c.readTimeout != 0 { 540 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 541 | } 542 | 543 | if cmd == "" { 544 | reply := make([]interface{}, pending) 545 | for i := range reply { 546 | r, e := c.readReply() 547 | if e != nil { 548 | return nil, c.fatal(e) 549 | } 550 | reply[i] = r 551 | } 552 | return reply, nil 553 | } 554 | 555 | var err error 556 | var reply interface{} 557 | for i := 0; i <= pending; i++ { 558 | var e error 559 | if reply, e = c.readReply(); e != nil { 560 | return nil, c.fatal(e) 561 | } 562 | if e, ok := reply.(Error); ok && err == nil { 563 | err = e 564 | } 565 | } 566 | return reply, err 567 | } 568 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/scan.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "reflect" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | ) 25 | 26 | func ensureLen(d reflect.Value, n int) { 27 | if n > d.Cap() { 28 | d.Set(reflect.MakeSlice(d.Type(), n, n)) 29 | } else { 30 | d.SetLen(n) 31 | } 32 | } 33 | 34 | func cannotConvert(d reflect.Value, s interface{}) error { 35 | var sname string 36 | switch s.(type) { 37 | case string: 38 | sname = "Redis simple string" 39 | case Error: 40 | sname = "Redis error" 41 | case int64: 42 | sname = "Redis integer" 43 | case []byte: 44 | sname = "Redis bulk string" 45 | case []interface{}: 46 | sname = "Redis array" 47 | default: 48 | sname = reflect.TypeOf(s).String() 49 | } 50 | return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) 51 | } 52 | 53 | func convertAssignBulkString(d reflect.Value, s []byte) (err error) { 54 | switch d.Type().Kind() { 55 | case reflect.Float32, reflect.Float64: 56 | var x float64 57 | x, err = strconv.ParseFloat(string(s), d.Type().Bits()) 58 | d.SetFloat(x) 59 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 60 | var x int64 61 | x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) 62 | d.SetInt(x) 63 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 64 | var x uint64 65 | x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) 66 | d.SetUint(x) 67 | case reflect.Bool: 68 | var x bool 69 | x, err = strconv.ParseBool(string(s)) 70 | d.SetBool(x) 71 | case reflect.String: 72 | d.SetString(string(s)) 73 | case reflect.Slice: 74 | if d.Type().Elem().Kind() != reflect.Uint8 { 75 | err = cannotConvert(d, s) 76 | } else { 77 | d.SetBytes(s) 78 | } 79 | default: 80 | err = cannotConvert(d, s) 81 | } 82 | return 83 | } 84 | 85 | func convertAssignInt(d reflect.Value, s int64) (err error) { 86 | switch d.Type().Kind() { 87 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 88 | d.SetInt(s) 89 | if d.Int() != s { 90 | err = strconv.ErrRange 91 | d.SetInt(0) 92 | } 93 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 94 | if s < 0 { 95 | err = strconv.ErrRange 96 | } else { 97 | x := uint64(s) 98 | d.SetUint(x) 99 | if d.Uint() != x { 100 | err = strconv.ErrRange 101 | d.SetUint(0) 102 | } 103 | } 104 | case reflect.Bool: 105 | d.SetBool(s != 0) 106 | default: 107 | err = cannotConvert(d, s) 108 | } 109 | return 110 | } 111 | 112 | func convertAssignValue(d reflect.Value, s interface{}) (err error) { 113 | switch s := s.(type) { 114 | case []byte: 115 | err = convertAssignBulkString(d, s) 116 | case int64: 117 | err = convertAssignInt(d, s) 118 | default: 119 | err = cannotConvert(d, s) 120 | } 121 | return err 122 | } 123 | 124 | func convertAssignArray(d reflect.Value, s []interface{}) error { 125 | if d.Type().Kind() != reflect.Slice { 126 | return cannotConvert(d, s) 127 | } 128 | ensureLen(d, len(s)) 129 | for i := 0; i < len(s); i++ { 130 | if err := convertAssignValue(d.Index(i), s[i]); err != nil { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func convertAssign(d interface{}, s interface{}) (err error) { 138 | // Handle the most common destination types using type switches and 139 | // fall back to reflection for all other types. 140 | switch s := s.(type) { 141 | case nil: 142 | // ingore 143 | case []byte: 144 | switch d := d.(type) { 145 | case *string: 146 | *d = string(s) 147 | case *int: 148 | *d, err = strconv.Atoi(string(s)) 149 | case *bool: 150 | *d, err = strconv.ParseBool(string(s)) 151 | case *[]byte: 152 | *d = s 153 | case *interface{}: 154 | *d = s 155 | case nil: 156 | // skip value 157 | default: 158 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 159 | err = cannotConvert(d, s) 160 | } else { 161 | err = convertAssignBulkString(d.Elem(), s) 162 | } 163 | } 164 | case int64: 165 | switch d := d.(type) { 166 | case *int: 167 | x := int(s) 168 | if int64(x) != s { 169 | err = strconv.ErrRange 170 | x = 0 171 | } 172 | *d = x 173 | case *bool: 174 | *d = s != 0 175 | case *interface{}: 176 | *d = s 177 | case nil: 178 | // skip value 179 | default: 180 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 181 | err = cannotConvert(d, s) 182 | } else { 183 | err = convertAssignInt(d.Elem(), s) 184 | } 185 | } 186 | case string: 187 | switch d := d.(type) { 188 | case *string: 189 | *d = string(s) 190 | default: 191 | err = cannotConvert(reflect.ValueOf(d), s) 192 | } 193 | case []interface{}: 194 | switch d := d.(type) { 195 | case *[]interface{}: 196 | *d = s 197 | case *interface{}: 198 | *d = s 199 | case nil: 200 | // skip value 201 | default: 202 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 203 | err = cannotConvert(d, s) 204 | } else { 205 | err = convertAssignArray(d.Elem(), s) 206 | } 207 | } 208 | case Error: 209 | err = s 210 | default: 211 | err = cannotConvert(reflect.ValueOf(d), s) 212 | } 213 | return 214 | } 215 | 216 | // Scan copies from src to the values pointed at by dest. 217 | // 218 | // The values pointed at by dest must be an integer, float, boolean, string, 219 | // []byte, interface{} or slices of these types. Scan uses the standard strconv 220 | // package to convert bulk strings to numeric and boolean types. 221 | // 222 | // If a dest value is nil, then the corresponding src value is skipped. 223 | // 224 | // If a src element is nil, then the corresponding dest value is not modified. 225 | // 226 | // To enable easy use of Scan in a loop, Scan returns the slice of src 227 | // following the copied values. 228 | func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { 229 | if len(src) < len(dest) { 230 | return nil, errors.New("redigo.Scan: array short") 231 | } 232 | var err error 233 | for i, d := range dest { 234 | err = convertAssign(d, src[i]) 235 | if err != nil { 236 | err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) 237 | break 238 | } 239 | } 240 | return src[len(dest):], err 241 | } 242 | 243 | type fieldSpec struct { 244 | name string 245 | index []int 246 | omitEmpty bool 247 | } 248 | 249 | type structSpec struct { 250 | m map[string]*fieldSpec 251 | l []*fieldSpec 252 | } 253 | 254 | func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { 255 | return ss.m[string(name)] 256 | } 257 | 258 | func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { 259 | for i := 0; i < t.NumField(); i++ { 260 | f := t.Field(i) 261 | switch { 262 | case f.PkgPath != "" && !f.Anonymous: 263 | // Ignore unexported fields. 264 | case f.Anonymous: 265 | // TODO: Handle pointers. Requires change to decoder and 266 | // protection against infinite recursion. 267 | if f.Type.Kind() == reflect.Struct { 268 | compileStructSpec(f.Type, depth, append(index, i), ss) 269 | } 270 | default: 271 | fs := &fieldSpec{name: f.Name} 272 | tag := f.Tag.Get("redis") 273 | p := strings.Split(tag, ",") 274 | if len(p) > 0 { 275 | if p[0] == "-" { 276 | continue 277 | } 278 | if len(p[0]) > 0 { 279 | fs.name = p[0] 280 | } 281 | for _, s := range p[1:] { 282 | switch s { 283 | case "omitempty": 284 | fs.omitEmpty = true 285 | default: 286 | panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) 287 | } 288 | } 289 | } 290 | d, found := depth[fs.name] 291 | if !found { 292 | d = 1 << 30 293 | } 294 | switch { 295 | case len(index) == d: 296 | // At same depth, remove from result. 297 | delete(ss.m, fs.name) 298 | j := 0 299 | for i := 0; i < len(ss.l); i++ { 300 | if fs.name != ss.l[i].name { 301 | ss.l[j] = ss.l[i] 302 | j += 1 303 | } 304 | } 305 | ss.l = ss.l[:j] 306 | case len(index) < d: 307 | fs.index = make([]int, len(index)+1) 308 | copy(fs.index, index) 309 | fs.index[len(index)] = i 310 | depth[fs.name] = len(index) 311 | ss.m[fs.name] = fs 312 | ss.l = append(ss.l, fs) 313 | } 314 | } 315 | } 316 | } 317 | 318 | var ( 319 | structSpecMutex sync.RWMutex 320 | structSpecCache = make(map[reflect.Type]*structSpec) 321 | defaultFieldSpec = &fieldSpec{} 322 | ) 323 | 324 | func structSpecForType(t reflect.Type) *structSpec { 325 | 326 | structSpecMutex.RLock() 327 | ss, found := structSpecCache[t] 328 | structSpecMutex.RUnlock() 329 | if found { 330 | return ss 331 | } 332 | 333 | structSpecMutex.Lock() 334 | defer structSpecMutex.Unlock() 335 | ss, found = structSpecCache[t] 336 | if found { 337 | return ss 338 | } 339 | 340 | ss = &structSpec{m: make(map[string]*fieldSpec)} 341 | compileStructSpec(t, make(map[string]int), nil, ss) 342 | structSpecCache[t] = ss 343 | return ss 344 | } 345 | 346 | var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") 347 | 348 | // ScanStruct scans alternating names and values from src to a struct. The 349 | // HGETALL and CONFIG GET commands return replies in this format. 350 | // 351 | // ScanStruct uses exported field names to match values in the response. Use 352 | // 'redis' field tag to override the name: 353 | // 354 | // Field int `redis:"myName"` 355 | // 356 | // Fields with the tag redis:"-" are ignored. 357 | // 358 | // Integer, float, boolean, string and []byte fields are supported. Scan uses the 359 | // standard strconv package to convert bulk string values to numeric and 360 | // boolean types. 361 | // 362 | // If a src element is nil, then the corresponding field is not modified. 363 | func ScanStruct(src []interface{}, dest interface{}) error { 364 | d := reflect.ValueOf(dest) 365 | if d.Kind() != reflect.Ptr || d.IsNil() { 366 | return errScanStructValue 367 | } 368 | d = d.Elem() 369 | if d.Kind() != reflect.Struct { 370 | return errScanStructValue 371 | } 372 | ss := structSpecForType(d.Type()) 373 | 374 | if len(src)%2 != 0 { 375 | return errors.New("redigo.ScanStruct: number of values not a multiple of 2") 376 | } 377 | 378 | for i := 0; i < len(src); i += 2 { 379 | s := src[i+1] 380 | if s == nil { 381 | continue 382 | } 383 | name, ok := src[i].([]byte) 384 | if !ok { 385 | return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) 386 | } 387 | fs := ss.fieldSpec(name) 388 | if fs == nil { 389 | continue 390 | } 391 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 392 | return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) 393 | } 394 | } 395 | return nil 396 | } 397 | 398 | var ( 399 | errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") 400 | ) 401 | 402 | // ScanSlice scans src to the slice pointed to by dest. The elements the dest 403 | // slice must be integer, float, boolean, string, struct or pointer to struct 404 | // values. 405 | // 406 | // Struct fields must be integer, float, boolean or string values. All struct 407 | // fields are used unless a subset is specified using fieldNames. 408 | func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { 409 | d := reflect.ValueOf(dest) 410 | if d.Kind() != reflect.Ptr || d.IsNil() { 411 | return errScanSliceValue 412 | } 413 | d = d.Elem() 414 | if d.Kind() != reflect.Slice { 415 | return errScanSliceValue 416 | } 417 | 418 | isPtr := false 419 | t := d.Type().Elem() 420 | if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 421 | isPtr = true 422 | t = t.Elem() 423 | } 424 | 425 | if t.Kind() != reflect.Struct { 426 | ensureLen(d, len(src)) 427 | for i, s := range src { 428 | if s == nil { 429 | continue 430 | } 431 | if err := convertAssignValue(d.Index(i), s); err != nil { 432 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) 433 | } 434 | } 435 | return nil 436 | } 437 | 438 | ss := structSpecForType(t) 439 | fss := ss.l 440 | if len(fieldNames) > 0 { 441 | fss = make([]*fieldSpec, len(fieldNames)) 442 | for i, name := range fieldNames { 443 | fss[i] = ss.m[name] 444 | if fss[i] == nil { 445 | return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) 446 | } 447 | } 448 | } 449 | 450 | if len(fss) == 0 { 451 | return errors.New("redigo.ScanSlice: no struct fields") 452 | } 453 | 454 | n := len(src) / len(fss) 455 | if n*len(fss) != len(src) { 456 | return errors.New("redigo.ScanSlice: length not a multiple of struct field count") 457 | } 458 | 459 | ensureLen(d, n) 460 | for i := 0; i < n; i++ { 461 | d := d.Index(i) 462 | if isPtr { 463 | if d.IsNil() { 464 | d.Set(reflect.New(t)) 465 | } 466 | d = d.Elem() 467 | } 468 | for j, fs := range fss { 469 | s := src[i*len(fss)+j] 470 | if s == nil { 471 | continue 472 | } 473 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 474 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) 475 | } 476 | } 477 | } 478 | return nil 479 | } 480 | 481 | // Args is a helper for constructing command arguments from structured values. 482 | type Args []interface{} 483 | 484 | // Add returns the result of appending value to args. 485 | func (args Args) Add(value ...interface{}) Args { 486 | return append(args, value...) 487 | } 488 | 489 | // AddFlat returns the result of appending the flattened value of v to args. 490 | // 491 | // Maps are flattened by appending the alternating keys and map values to args. 492 | // 493 | // Slices are flattened by appending the slice elements to args. 494 | // 495 | // Structs are flattened by appending the alternating names and values of 496 | // exported fields to args. If v is a nil struct pointer, then nothing is 497 | // appended. The 'redis' field tag overrides struct field names. See ScanStruct 498 | // for more information on the use of the 'redis' field tag. 499 | // 500 | // Other types are appended to args as is. 501 | func (args Args) AddFlat(v interface{}) Args { 502 | rv := reflect.ValueOf(v) 503 | switch rv.Kind() { 504 | case reflect.Struct: 505 | args = flattenStruct(args, rv) 506 | case reflect.Slice: 507 | for i := 0; i < rv.Len(); i++ { 508 | args = append(args, rv.Index(i).Interface()) 509 | } 510 | case reflect.Map: 511 | for _, k := range rv.MapKeys() { 512 | args = append(args, k.Interface(), rv.MapIndex(k).Interface()) 513 | } 514 | case reflect.Ptr: 515 | if rv.Type().Elem().Kind() == reflect.Struct { 516 | if !rv.IsNil() { 517 | args = flattenStruct(args, rv.Elem()) 518 | } 519 | } else { 520 | args = append(args, v) 521 | } 522 | default: 523 | args = append(args, v) 524 | } 525 | return args 526 | } 527 | 528 | func flattenStruct(args Args, v reflect.Value) Args { 529 | ss := structSpecForType(v.Type()) 530 | for _, fs := range ss.l { 531 | fv := v.FieldByIndex(fs.index) 532 | if fs.omitEmpty { 533 | var empty = false 534 | switch fv.Kind() { 535 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 536 | empty = fv.Len() == 0 537 | case reflect.Bool: 538 | empty = !fv.Bool() 539 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 540 | empty = fv.Int() == 0 541 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 542 | empty = fv.Uint() == 0 543 | case reflect.Float32, reflect.Float64: 544 | empty = fv.Float() == 0 545 | case reflect.Interface, reflect.Ptr: 546 | empty = fv.IsNil() 547 | } 548 | if empty { 549 | continue 550 | } 551 | } 552 | args = append(args, fs.name, fv.Interface()) 553 | } 554 | return args 555 | } 556 | --------------------------------------------------------------------------------