readis 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2012 Matthew King, Lance Lakey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
data/Readme.md ADDED
@@ -0,0 +1,6 @@
1
+ Read-only Redis Utilities
2
+
3
+ $ readis -h
4
+ Usage: readis <command> [options]
5
+ Available commands: inspect, monitor, help
6
+
data/bin/readis ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # set up loadpath
4
+ here = File.dirname(__FILE__)
5
+ READIS_ROOT = File.expand_path("#{here}/..")
6
+ $LOAD_PATH.unshift("#{READIS_ROOT}/lib")
7
+
8
+ require "readis"
9
+
10
+ command_name = ARGV.shift
11
+
12
+ if command_name == "help"
13
+ actual_command = ARGV.shift
14
+ puts Readis.command_runner(actual_command).help
15
+ else
16
+ Readis.command_runner(command_name).run
17
+ end
18
+
@@ -0,0 +1,75 @@
1
+ class Readis
2
+ class Inspect < Readis
3
+
4
+ WHITELIST = %w[
5
+ keys
6
+ exists get hexists hget hgetall hkeys hlen hmget hvals
7
+ info lindex llen lrange mget randomkey scard sdiff
8
+ sinter sismember smembers srandmember strlen sunion
9
+ ttl type zcard zcount zrange zrangebyscore zrank
10
+ zrevrange zrevrangebyscore zrevrank zscore
11
+ ]
12
+
13
+ def initialize(*args)
14
+ super
15
+ @redis = Redis.new(:host => self.options[:host], :port => self.options[:port])
16
+ end
17
+
18
+ def parser
19
+ optparser = super
20
+ optparser.banner = <<-BANNER
21
+
22
+ Usage: readis inspect [options]
23
+
24
+ BANNER
25
+ optparser
26
+ end
27
+
28
+ def run
29
+ loop do
30
+ print "readis #{self.options[:host]}:#{self.options[:port]}> "
31
+ input_string = gets.chomp
32
+ # TODO: tab completion. Example implementation in automatthew/flipper
33
+ # https://github.com/automatthew/flipper/blob/master/lib/flipper.rb
34
+ # Inititally, we should only try to complete using the list of command
35
+ # names, but we may later consider adding keys, fields, member names, etc.
36
+ # discovered through commands issued.
37
+ begin
38
+ out = execute_command(input_string)
39
+ case out
40
+ when nil
41
+ # do nothing
42
+ else
43
+ # TODO: consider the formatting. Do we want to mimic
44
+ # the redis-cli output?
45
+ puts out.inspect
46
+ end
47
+ rescue => error
48
+ puts "Error: #{error.message}"
49
+ end
50
+ end
51
+ end
52
+
53
+ def execute_command(input_string)
54
+ parts = input_string.split(" ")
55
+ redis_command = parts.shift
56
+
57
+ if redis_command
58
+ redis_command.downcase!
59
+ end
60
+
61
+ case redis_command
62
+ when nil
63
+ # do nothing
64
+ when *WHITELIST
65
+ @redis.send(redis_command, *parts)
66
+ when "exit", "quit"
67
+ exit
68
+ else
69
+ raise ArgumentError, "Unknown or unsupported command"
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+
@@ -0,0 +1,199 @@
1
+ class Readis
2
+ class Monitor < Readis
3
+
4
+ def initialize(*args)
5
+ super
6
+ if options[:includes] && options[:excludes]
7
+ raise ArgumentError, "Define either --includes or --excludes, but not both."
8
+ end
9
+ end
10
+
11
+ def run
12
+ @redis.monitor do |line|
13
+ if formatted = format(line)
14
+ print formatted
15
+ puts
16
+ end
17
+ end
18
+ end
19
+
20
+ def options
21
+ @options ||= super.merge(
22
+ :colorized => false
23
+ )
24
+ end
25
+
26
+ def parser
27
+ optparser = super
28
+ optparser.banner = <<-BANNER
29
+
30
+ Usage: readis monitor [options]
31
+
32
+ BANNER
33
+ optparser.on("-c", "--color", "enable syntax coloring") do |color|
34
+ options[:colorized] = true
35
+ end
36
+ optparser.on("-i", "--include COMMANDS",
37
+ "comma separated list of Redis commands to include") do |includes|
38
+ options[:includes] = includes.split(",").map {|c| c.upcase}
39
+ end
40
+ optparser.on("-e", "--exclude COMMANDS",
41
+ "comma separated list of Redis commands to exclude") do |excludes|
42
+ options[:excludes] = excludes.split(",").map {|c| c.upcase}
43
+ end
44
+ # TODO: options for
45
+ # * compact vs. spacey
46
+ # * timestamp on/off
47
+ optparser
48
+ end
49
+
50
+
51
+ # 1325296232.698537 "set" "monkey" "shines"
52
+ def format(line)
53
+ if timestamp = line.slice!(/^[\d.]+ "/)
54
+ timestamp.chomp!(' "')
55
+ end
56
+ parts = line.split('" "').map {|p| p.chomp('"') }
57
+
58
+ command = parts[0].upcase
59
+ return if filtered?(command)
60
+
61
+ out = [ format_command(command) ]
62
+
63
+ one_channel_one_message = %w(PUBLISH)
64
+ one_key_one_value = %w(APPEND GETSET LPUSHX RPUSHX SET SETNX)
65
+ one_key_one_argument = %w(DECRBY EXPIRE EXPIREAT GETBIT INCRBY LINDEX MOVE RENAME RENAMENX)
66
+ one_key_one_field = %w(HEXISTS HGET)
67
+ one_key_one_field_one_value = %w(HSET HSETNX)
68
+
69
+ # TODO: add the rest of the redis commands
70
+ case command
71
+ when *one_channel_one_message
72
+ # one channel, one message
73
+ out << format_channel(parts[1])
74
+ out << format_message(parts[2])
75
+ when *one_key_one_value
76
+ # one key, one value
77
+ out << format_key(parts[1])
78
+ out << format_value(parts[2])
79
+ when *one_key_one_argument
80
+ # one key, one arg
81
+ out << format_key(parts[1])
82
+ out << format_argument(parts[2])
83
+ when "BLPOP"
84
+ # variable number of keys, last part is an arg
85
+ parts.slice(1..-2).each do |part|
86
+ out << format_key(part)
87
+ end
88
+ out << format_argument(parts[-1])
89
+ when "LPUSH", "RPUSH", "ZREM"
90
+ # one key, rest are values
91
+ out << format_key(parts[1])
92
+ parts.slice(2..-1).each do |part|
93
+ out << format_value(part)
94
+ end
95
+ when "ZRANGE", "ZRANGEBYSCORE", "ZREMRANGEBYSCORE",
96
+ "ZREMRANGEBYRANK", "ZCOUNT", "ZREVRANK", "ZREVRANGE"
97
+ # one key, rest are arguments
98
+ out << format_key(parts[1])
99
+ parts.slice(2..-1).each do |part|
100
+ out << format_argument(part)
101
+ end
102
+ when *one_key_one_field_one_value
103
+ # key, field, value
104
+ out << format_key(parts[1])
105
+ out << format_field(parts[2])
106
+ out << format_value(parts[3])
107
+ when *one_key_one_field
108
+ # key, field
109
+ out << format_key(parts[1])
110
+ out << format_field(parts[2])
111
+ when "ZADD", "ZINCRBY"
112
+ # ZADD score member [score] [member]
113
+ out << format_key(parts[1])
114
+ parts.slice(2..-1).each_slice(2) do |pair|
115
+ out << format_argument(pair.first)
116
+ out << format_value(pair.last)
117
+ end
118
+ else
119
+ # all keys
120
+ if rest = parts.slice(1..-1)
121
+ parts.slice(1..-1).each do |part|
122
+ out << format_key(part)
123
+ end
124
+ end
125
+ end
126
+ "#{colorize(:underscore, timestamp)} #{out}"
127
+ end
128
+
129
+ # Wraps the command in the appropriate ANSI color codes
130
+ def format_command(string)
131
+ case string
132
+ when "MULTI", "EXEC", "WATCH"
133
+ colorize(:red, string + " ")
134
+
135
+ when /SUBSCRIBE/, "PUBLISH"
136
+ colorize(:yellow, string + " ")
137
+ else
138
+ colorize(:green, string + " ")
139
+ end
140
+ end
141
+
142
+ def format_channel(string)
143
+ colorize(:cyan, string + " ")
144
+ end
145
+
146
+ def format_key(string)
147
+ colorize(:cyan, string + " ")
148
+ end
149
+
150
+ def format_argument(string)
151
+ colorize(:magenta, string + " ")
152
+ end
153
+
154
+ def format_field(string)
155
+ colorize(:blue, string + " ")
156
+ end
157
+
158
+ def format_message(string)
159
+ begin
160
+ string.gsub!(/\\/, "")
161
+ object = JSON.parse(string)
162
+ out = "\n" + JSON.pretty_generate(object)
163
+ rescue
164
+ out = string + " "
165
+ end
166
+ out
167
+ end
168
+
169
+ def format_value(string)
170
+ begin
171
+ string.gsub!(/\\/, "")
172
+ object = JSON.parse(string)
173
+ out = "\n" + JSON.pretty_generate(object)
174
+ rescue
175
+ out = string + " "
176
+ end
177
+ out
178
+ end
179
+
180
+ def filtered?(command)
181
+ if list = @options[:includes]
182
+ !list.include?(command)
183
+ elsif list = @options[:excludes]
184
+ list.include?(command)
185
+ end
186
+ end
187
+
188
+ def colorize(name, string)
189
+ if @options[:colorized]
190
+ [Term::ANSIColor.send(name), string, Term::ANSIColor.reset].join
191
+ else
192
+ string
193
+ end
194
+ end
195
+
196
+
197
+
198
+ end
199
+ end
data/lib/readis.rb ADDED
@@ -0,0 +1,58 @@
1
+ require "optparse"
2
+
3
+ require "rubygems"
4
+ require "redis"
5
+ require "json"
6
+ require "term/ansicolor"
7
+
8
+ class Readis
9
+
10
+ def self.command_runner(name)
11
+ case name
12
+ when "inspect"
13
+ Readis::Inspect.new
14
+ when "monitor"
15
+ Readis::Monitor.new
16
+ else
17
+ puts "Usage: readis <command> [options]"
18
+ puts "Usage: readis help <command>"
19
+ puts "Available commands: inspect, monitor"
20
+ exit
21
+ end
22
+ end
23
+
24
+ def initialize
25
+ self.parser.parse!
26
+ @redis = Redis.new(:host => self.options[:host], :port => self.options[:port])
27
+ end
28
+
29
+ def options
30
+ @options ||= {
31
+ :host => "127.0.0.1",
32
+ :port => "6379",
33
+ }
34
+ end
35
+
36
+ def parser
37
+ OptionParser.new do |parser|
38
+ parser.on("-h", "--host=HOST",
39
+ "redis host. Defaults to '127.0.0.1'") do |name|
40
+ self.options[:host] = name
41
+ end
42
+ parser.on("-p", "--port=PORT",
43
+ "redis port. Defaults to '6379'") do |name|
44
+ self.options[:port] = name
45
+ end
46
+ end
47
+ end
48
+
49
+ def help
50
+ parser.help
51
+ end
52
+
53
+ end
54
+
55
+
56
+ require "readis/inspect"
57
+ require "readis/monitor"
58
+
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: readis
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Matthew King
14
+ - Lance Lakey
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-08-01 00:00:00 -05:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: redis
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 2
33
+ - 2
34
+ - 2
35
+ version: 2.2.2
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: json
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 15
47
+ segments:
48
+ - 1
49
+ - 0
50
+ version: "1.0"
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: term-ansicolor
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 15
62
+ segments:
63
+ - 1
64
+ - 0
65
+ version: "1.0"
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ description:
69
+ email:
70
+ executables:
71
+ - readis
72
+ extensions: []
73
+
74
+ extra_rdoc_files: []
75
+
76
+ files:
77
+ - lib/readis/inspect.rb
78
+ - lib/readis/monitor.rb
79
+ - lib/readis.rb
80
+ - LICENSE
81
+ - Readme.md
82
+ - bin/readis
83
+ has_rdoc: true
84
+ homepage: https://github.com/lancelakey/readis
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options: []
89
+
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.6.2
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Read-only Redis Utilities
117
+ test_files: []
118
+