├── LICENSE ├── README.md ├── cmd └── rp │ └── main.go └── rp.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Brian Hetro 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rp - Redis Pipe 2 | =============== 3 | 4 | Install 5 | ------- 6 | 7 | go get github.com/whee/rp/cmd/rp 8 | 9 | Usage 10 | ----- 11 | 12 | rp read ... 13 | rp [-p] write ... 14 | rp -n -a
read ... 15 | rp [-p] -n -a
write ... 16 | 17 | Options: 18 | -p, --passthrough Pass written data to standard output. 19 | -n, --network Network of the Redis server [default: tcp] 20 | -a, --address
Address of the Redis server [default: :6379] 21 | 22 | Basics 23 | ------ 24 | 25 | rp converts standard input and output to networked pipes via Redis Pub/Sub. 26 | This allows trivial construction of MIMO data flows for processing and analysis. 27 | 28 | For example: 29 | 30 | Terminal 1> $ rp read foo | jq --unbuffered '.c' | rp write cs 31 | Terminal 2> $ rp read foo | jq '.' 32 | Terminal 3> $ rp read cs 33 | Terminal 4> $ echo '{"a":1,"b":2,"c":"foo"}' | rp write foo 34 | Terminal 2> { 35 | Terminal 2> "a": 1, 36 | Terminal 2> "b": 2, 37 | Terminal 2> "c": "foo" 38 | Terminal 2> } 39 | Terminal 3> "foo" 40 | -------------------------------------------------------------------------------- /cmd/rp/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Brian Hetro 2 | // Use of this source code is governed by the ISC 3 | // license which can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "os" 11 | 12 | docopt "github.com/docopt/docopt-go" 13 | "github.com/whee/rp" 14 | ) 15 | 16 | type config struct { 17 | channels []string 18 | read bool 19 | write bool 20 | passthrough bool 21 | rpConfig rp.Config 22 | } 23 | 24 | func main() { 25 | usage := `Redis Pipe. 26 | 27 | Usage: 28 | rp read ... 29 | rp [-p] write ... 30 | rp -n -a
read ... 31 | rp [-p] -n -a
write ... 32 | 33 | Options: 34 | -p, --passthrough Pass written data to standard output. 35 | -n, --network Network of the Redis server [default: tcp] 36 | -a, --address
Address of the Redis server [default: :6379]` 37 | 38 | arguments, err := docopt.Parse(usage, nil, true, "Redis Pipe 0.9", false) 39 | if err != nil { 40 | panic(err) 41 | } 42 | conf := config{ 43 | channels: arguments[""].([]string), 44 | read: arguments["read"].(bool), 45 | write: arguments["write"].(bool), 46 | passthrough: arguments["--passthrough"].(bool), 47 | rpConfig: rp.Config{ 48 | Network: arguments["--network"].(string), 49 | Address: arguments["--address"].(string)}, 50 | } 51 | _, err = conf.do() 52 | if err != nil { 53 | fmt.Fprintln(os.Stderr, err) 54 | os.Exit(1) 55 | } 56 | os.Exit(0) 57 | } 58 | 59 | func (c *config) do() (n int64, err error) { 60 | if c.write { 61 | n, err = writeTo(c) 62 | } else if c.read { 63 | n, err = readFrom(c) 64 | } 65 | return 66 | } 67 | 68 | type ptWriter struct { 69 | w io.Writer 70 | } 71 | 72 | func (w ptWriter) Write(p []byte) (n int, err error) { 73 | n, err = os.Stdout.Write(p) 74 | if err != nil { 75 | return 76 | } 77 | return w.w.Write(p) 78 | } 79 | 80 | func writeTo(c *config) (int64, error) { 81 | t, err := rp.NewWriter(&c.rpConfig, c.channels...) 82 | if err != nil { 83 | return 0, err 84 | } 85 | defer t.Close() 86 | 87 | var w io.Writer 88 | if c.passthrough { 89 | w = ptWriter{t} 90 | } else { 91 | w = t 92 | } 93 | return io.Copy(w, os.Stdin) 94 | } 95 | 96 | func readFrom(c *config) (int64, error) { 97 | t, err := rp.NewReader(&c.rpConfig, c.channels...) 98 | if err != nil { 99 | return 0, err 100 | } 101 | defer t.Close() 102 | return io.Copy(os.Stdout, t) 103 | } 104 | -------------------------------------------------------------------------------- /rp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Brian Hetro 2 | // Use of this source code is governed by the ISC 3 | // license which can be found in the LICENSE file. 4 | 5 | // 6 | // Package rp implements Reader and Writer interfaces to Redis Pub/Sub channels. 7 | // 8 | package rp 9 | 10 | import redis "github.com/garyburd/redigo/redis" 11 | 12 | // Config specifies the network and address for the Redis server. 13 | type Config struct { 14 | Network, Address string 15 | } 16 | 17 | // DefaultConfig is the network and address used for the Redis connection 18 | // if none is specified. 19 | var DefaultConfig = &Config{Network: "tcp", Address: ":6379"} 20 | 21 | // GetNetwork returns the network used for the config. 22 | func (c *Config) GetNetwork() string { 23 | if c == nil { 24 | return DefaultConfig.Network 25 | } 26 | return c.Network 27 | } 28 | 29 | // GetAddress returns the address used for the config. 30 | func (c *Config) GetAddress() string { 31 | if c == nil { 32 | return DefaultConfig.Address 33 | } 34 | return c.Address 35 | } 36 | 37 | // Writer implements writing to a Redis Pub/Sub channel. 38 | type Writer struct { 39 | conn redis.Conn 40 | names []string 41 | } 42 | 43 | // NewWriter returns a new Writer backed by the named Redis Pub/Sub 44 | // channels. Writes are sent to each named channel. 45 | // If config is nil, DefaultConfig is used. 46 | // If unable to connect to Redis, the connection error is returned. 47 | func NewWriter(config *Config, names ...string) (*Writer, error) { 48 | conn, err := redis.Dial(config.GetNetwork(), config.GetAddress()) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &Writer{conn, names}, nil 53 | } 54 | 55 | // Close closes the Redis connection. 56 | func (w *Writer) Close() error { 57 | return w.conn.Close() 58 | } 59 | 60 | // Write writes the contents of p to the Redis Pub/Sub channel via PUBLISH. 61 | // It returns the number of bytes written. 62 | // If the PUBLISH command fails, it returns why. 63 | func (w *Writer) Write(p []byte) (n int, err error) { 64 | n = len(p) 65 | for _, c := range w.names { 66 | _, err = w.conn.Do("PUBLISH", c, p) 67 | if err != nil { 68 | return 69 | } 70 | } 71 | return 72 | } 73 | 74 | // Reader implements reading from a Redis Pub/Sub channel. 75 | type Reader struct { 76 | conn redis.Conn 77 | names []string 78 | psc redis.PubSubConn 79 | } 80 | 81 | // NewReader returns a new Reader backed by the named Redis Pub/Sub 82 | // channels. Reads may return content from any channel. 83 | // If config is nil, DefaultConfig is used. 84 | // If unable to connect to Redis or subscribe to the named channel, 85 | // the error is returned. 86 | func NewReader(config *Config, names ...string) (r *Reader, err error) { 87 | conn, err := redis.Dial(config.GetNetwork(), config.GetAddress()) 88 | if err != nil { 89 | return 90 | } 91 | psc := redis.PubSubConn{conn} 92 | r = &Reader{conn, names, psc} 93 | for _, c := range names { 94 | err = psc.Subscribe(c) 95 | if err != nil { 96 | return 97 | } 98 | } 99 | return 100 | } 101 | 102 | // Read reads data from the Redis Pub/Sub channel into p. 103 | // It returns the number of bytes read into p. 104 | // If unable to receive from the Redis Pub/Sub channel, the error is returned. 105 | func (r *Reader) Read(p []byte) (n int, err error) { 106 | switch no := r.psc.Receive().(type) { 107 | case redis.Message: 108 | n = len(no.Data) 109 | copy(p, no.Data) 110 | case redis.PMessage: 111 | case redis.Subscription: 112 | case error: 113 | err = no 114 | return 115 | } 116 | return 117 | } 118 | 119 | // Close unsubscribes from the Redis Pub/Sub channel and closes 120 | // the Redis connection. 121 | func (r *Reader) Close() error { 122 | err := r.psc.Unsubscribe() 123 | if err != nil { 124 | return err 125 | } 126 | return r.conn.Close() 127 | } 128 | --------------------------------------------------------------------------------