rsql 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +7 -0
- data/bin/rsql +15 -2
- data/lib/rsql.rb +1 -1
- data/lib/rsql/eval_context.rb +152 -12
- data/lib/rsql/mysql_results.rb +12 -5
- data/test/test_eval_context.rb +53 -9
- metadata +63 -5
data/README.rdoc
CHANGED
@@ -29,6 +29,9 @@ RSQL is invoked from the comamnd line using:
|
|
29
29
|
-version::
|
30
30
|
Display the version of RSQL that is installed.
|
31
31
|
|
32
|
+
-verbose::
|
33
|
+
Display details on SSH connections and evaluation stack traces.
|
34
|
+
|
32
35
|
-rc _rcfile_::
|
33
36
|
Override loading the .rsqlrc file from the HOME directory for one in
|
34
37
|
a different location.
|
@@ -43,6 +46,10 @@ RSQL is invoked from the comamnd line using:
|
|
43
46
|
-ssh _ssh_host_::
|
44
47
|
Establish an SSH connection before connecting to the MySQL host.
|
45
48
|
|
49
|
+
-sshconfig _ssh_config_::
|
50
|
+
Use a specific SSH configuration file instead of the ones
|
51
|
+
dynamically determined.
|
52
|
+
|
46
53
|
-e [_query_]::
|
47
54
|
Run a query from the command line (i.e. not interactive). If a
|
48
55
|
_query_ is not provided, STDIN will be read. Multiple commands can
|
data/bin/rsql
CHANGED
@@ -121,6 +121,9 @@ if ARGV.delete('-version')
|
|
121
121
|
exit
|
122
122
|
end
|
123
123
|
|
124
|
+
verbose = ARGV.delete('-verbose')
|
125
|
+
eval_context.verbose = verbose
|
126
|
+
|
124
127
|
if i = ARGV.index('-maxrows')
|
125
128
|
ARGV.delete_at(i)
|
126
129
|
MySQLResults.max_rows = ARGV.delete_at(i).to_i
|
@@ -139,6 +142,11 @@ if i = ARGV.index('-ssh')
|
|
139
142
|
(ssh_host, ssh_user, ssh_password) = split_login(ARGV.delete_at(i))
|
140
143
|
end
|
141
144
|
|
145
|
+
if i = ARGV.index('-sshconfig')
|
146
|
+
ARGV.delete_at(i)
|
147
|
+
ssh_config = ARGV.delete_at(i)
|
148
|
+
end
|
149
|
+
|
142
150
|
if i = ARGV.index('-e')
|
143
151
|
ARGV.delete_at(i)
|
144
152
|
batch_input = ''
|
@@ -155,9 +163,9 @@ if ARGV.size < 1
|
|
155
163
|
prefix = ' ' << ' ' * bn.size
|
156
164
|
$stderr.puts <<USAGE
|
157
165
|
|
158
|
-
usage: #{bn} [-version] [-help]
|
166
|
+
usage: #{bn} [-version] [-help] [-verbose]
|
159
167
|
#{prefix}[-rc <rcfile>] [-maxrows <max>] [-batch <field_separator>]
|
160
|
-
#{prefix}[-ssh [<ssh_user>[:<ssh_password>]@]<ssh_host>]
|
168
|
+
#{prefix}[-ssh [<ssh_user>[:<ssh_password>]@]<ssh_host>] [-sshconfig <ssh_config>]
|
161
169
|
#{prefix}[<mysql_user>[:<mysql_password>]@]<mysql_host>
|
162
170
|
#{prefix}[<database>] [-e <remaining_args_as_input>]
|
163
171
|
|
@@ -234,6 +242,11 @@ if ssh_host
|
|
234
242
|
ssh = nil
|
235
243
|
ssh_thread = Thread.new do
|
236
244
|
opts = {:timeout => 15}
|
245
|
+
opts.merge!(Net::SSH::Config.load(ssh_config, ssh_host)) if ssh_config
|
246
|
+
if verbose
|
247
|
+
opts[:verbose] = :debug
|
248
|
+
puts "SSH options: #{opts.inspect}"
|
249
|
+
end
|
237
250
|
begin
|
238
251
|
opts[:password] = ssh_password if ssh_password
|
239
252
|
ssh = Net::SSH.start(ssh_host, ssh_user, opts)
|
data/lib/rsql.rb
CHANGED
data/lib/rsql/eval_context.rb
CHANGED
@@ -29,18 +29,19 @@ module RSQL
|
|
29
29
|
#
|
30
30
|
class EvalContext
|
31
31
|
|
32
|
-
Registration = Struct.new(:name, :args, :bangs, :block, :usage, :desc)
|
32
|
+
Registration = Struct.new(:name, :args, :bangs, :block, :usage, :desc, :source)
|
33
33
|
|
34
34
|
HEXSTR_LIMIT = 32
|
35
35
|
|
36
|
-
def initialize
|
36
|
+
def initialize(verbose=false)
|
37
|
+
@verbose = verbose
|
37
38
|
@hexstr_limit = HEXSTR_LIMIT
|
38
|
-
@results
|
39
|
-
@last_query
|
39
|
+
@results = nil
|
40
|
+
@last_query = nil
|
40
41
|
|
41
|
-
@loaded_fns
|
42
|
+
@loaded_fns = []
|
42
43
|
@init_registrations = []
|
43
|
-
@bangs
|
44
|
+
@bangs = {}
|
44
45
|
|
45
46
|
@registrations = {
|
46
47
|
:version => Registration.new('version', [], {},
|
@@ -51,6 +52,10 @@ module RSQL
|
|
51
52
|
method(:reload),
|
52
53
|
'reload',
|
53
54
|
'Reload the rsqlrc file.'),
|
55
|
+
:desc => Registration.new('desc', [], {},
|
56
|
+
method(:desc),
|
57
|
+
'desc',
|
58
|
+
'Describe the content of a recipe.'),
|
54
59
|
:last_query => Registration.new('last_query', [], {},
|
55
60
|
Proc.new{puts(@last_query)},
|
56
61
|
'last_query',
|
@@ -66,7 +71,7 @@ module RSQL
|
|
66
71
|
}
|
67
72
|
end
|
68
73
|
|
69
|
-
attr_accessor :bangs
|
74
|
+
attr_accessor :bangs, :verbose
|
70
75
|
|
71
76
|
def call_init_registrations
|
72
77
|
@init_registrations.each do |sym|
|
@@ -125,11 +130,18 @@ module RSQL
|
|
125
130
|
end
|
126
131
|
|
127
132
|
# same relaxed call to load too
|
128
|
-
if m = /^\s*load\s+'(.+)'\s
|
133
|
+
if m = content.match(/^\s*load\s+'(.+)'\s*$/)
|
129
134
|
self.load(m[1])
|
130
135
|
return
|
131
136
|
end
|
132
137
|
|
138
|
+
# help out the poor user and fix up any describes
|
139
|
+
# requested so they don't need to remember that it needs
|
140
|
+
# to be a symbol passed in
|
141
|
+
if m = content.match(/^\s*desc\s+([^:]\S+)\s*$/)
|
142
|
+
content = "desc :#{m[1]}"
|
143
|
+
end
|
144
|
+
|
133
145
|
if stdout
|
134
146
|
# capture stdout
|
135
147
|
orig_stdout = $stdout
|
@@ -139,7 +151,11 @@ module RSQL
|
|
139
151
|
begin
|
140
152
|
value = Thread.new{ eval('$SAFE=2;' + content) }.value
|
141
153
|
rescue Exception => ex
|
142
|
-
|
154
|
+
if @verbose
|
155
|
+
$stderr.puts("#{ex.class}: #{ex.message}", ex.backtrace)
|
156
|
+
else
|
157
|
+
$stderr.puts(ex.message.gsub(/\(eval\):\d+:/,''))
|
158
|
+
end
|
143
159
|
ensure
|
144
160
|
$stdout = orig_stdout if stdout
|
145
161
|
end
|
@@ -229,6 +245,128 @@ module RSQL
|
|
229
245
|
return nil
|
230
246
|
end
|
231
247
|
|
248
|
+
def params(block)
|
249
|
+
params = ''
|
250
|
+
|
251
|
+
if block.arity != 0 && block.arity != -1 &&
|
252
|
+
block.inspect.match(/@(.+):(\d+)>$/)
|
253
|
+
fn = $1
|
254
|
+
lineno = $2.to_i
|
255
|
+
|
256
|
+
if fn == '(eval)'
|
257
|
+
$stderr.puts 'refusing to search an eval block'
|
258
|
+
return params
|
259
|
+
end
|
260
|
+
|
261
|
+
File.open(fn) do |f|
|
262
|
+
i = 0
|
263
|
+
found = false
|
264
|
+
while line = f.gets
|
265
|
+
i += 1
|
266
|
+
next if i < lineno
|
267
|
+
|
268
|
+
unless found
|
269
|
+
# give up if no start found within 20
|
270
|
+
# lines
|
271
|
+
break if lineno + 20 < i
|
272
|
+
if m = line.match(/(\{|do)(.*)$/)
|
273
|
+
# adjust line to be the remainder
|
274
|
+
# after the start
|
275
|
+
line = m[2]
|
276
|
+
found = true
|
277
|
+
else
|
278
|
+
next
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
if m = line.match(/^\s*\|([^\|]*)\|/)
|
283
|
+
params = "(#{m[1]})"
|
284
|
+
break
|
285
|
+
end
|
286
|
+
|
287
|
+
# if the params aren't here then we'd
|
288
|
+
# better only have whitespace otherwise
|
289
|
+
# this block doesn't have params...even
|
290
|
+
# though arity says it should
|
291
|
+
next if line.match(/^\s*$/)
|
292
|
+
$stderr.puts 'unable to locate params'
|
293
|
+
break
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
return params
|
299
|
+
end
|
300
|
+
|
301
|
+
def desc(sym)
|
302
|
+
unless Symbol === sym
|
303
|
+
$stderr.puts("must provide a Symbol--try prefixing it with a colon (:)")
|
304
|
+
return
|
305
|
+
end
|
306
|
+
|
307
|
+
unless reg = @registrations[sym]
|
308
|
+
$stderr.puts "nothing registered as #{sym}"
|
309
|
+
return
|
310
|
+
end
|
311
|
+
|
312
|
+
if Method === reg.block
|
313
|
+
$stderr.puts "refusing to describe the #{sym} method"
|
314
|
+
return
|
315
|
+
end
|
316
|
+
|
317
|
+
if !reg.source && reg.block.inspect.match(/@(.+):(\d+)>$/)
|
318
|
+
fn = $1
|
319
|
+
lineno = $2.to_i
|
320
|
+
|
321
|
+
if fn == __FILE__
|
322
|
+
$stderr.puts "refusing to describe EvalContext##{sym}"
|
323
|
+
return
|
324
|
+
end
|
325
|
+
|
326
|
+
if fn == '(eval)'
|
327
|
+
$stderr.puts 'unable to describe body for an eval block'
|
328
|
+
return
|
329
|
+
end
|
330
|
+
|
331
|
+
File.open(fn) do |f|
|
332
|
+
source = ''
|
333
|
+
i = 0
|
334
|
+
ending = nil
|
335
|
+
found = false
|
336
|
+
|
337
|
+
while line = f.gets
|
338
|
+
i += 1
|
339
|
+
next unless ending || i == lineno
|
340
|
+
source << line
|
341
|
+
unless ending
|
342
|
+
unless m = line.match(/\{|do/)
|
343
|
+
$stderr.puts "unable to locate block beginning at #{fn}:#{lineno}"
|
344
|
+
return
|
345
|
+
end
|
346
|
+
ending = m[0] == '{' ? '\}' : 'end'
|
347
|
+
next
|
348
|
+
end
|
349
|
+
if m = line.match(/^#{ending}/)
|
350
|
+
found = true
|
351
|
+
break
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
if found
|
356
|
+
reg.source = source
|
357
|
+
else
|
358
|
+
reg.source = ''
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
if reg.source && reg.source.any?
|
364
|
+
puts reg.source
|
365
|
+
else
|
366
|
+
$stderr.puts "unable to locate body for #{sym}"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
232
370
|
# Show all the pertinent version data we have about our
|
233
371
|
# software and the mysql connection.
|
234
372
|
#
|
@@ -270,7 +408,8 @@ module RSQL
|
|
270
408
|
desc = '' unless desc
|
271
409
|
|
272
410
|
if block.nil?
|
273
|
-
|
411
|
+
source = args.pop
|
412
|
+
sql = sqeeze!(source.dup)
|
274
413
|
|
275
414
|
argstr = args.join(',')
|
276
415
|
usage << "(#{argstr})" unless argstr.empty?
|
@@ -279,10 +418,11 @@ module RSQL
|
|
279
418
|
block = Thread.new{ eval(blockstr) }.value
|
280
419
|
args = []
|
281
420
|
else
|
282
|
-
|
421
|
+
source = nil
|
422
|
+
usage << params(block)
|
283
423
|
end
|
284
424
|
|
285
|
-
@registrations[sym] = Registration.new(name, args, bangs, block, usage, desc)
|
425
|
+
@registrations[sym] = Registration.new(name, args, bangs, block, usage, desc, source)
|
286
426
|
end
|
287
427
|
|
288
428
|
# Convert a list of values into a comma-delimited string,
|
data/lib/rsql/mysql_results.rb
CHANGED
@@ -226,6 +226,10 @@ module RSQL
|
|
226
226
|
#
|
227
227
|
attr_reader :affected_rows
|
228
228
|
|
229
|
+
# Get the amount of elapsed time taken by the query.
|
230
|
+
#
|
231
|
+
attr_reader :elapsed
|
232
|
+
|
229
233
|
# Determine if there are any results.
|
230
234
|
#
|
231
235
|
def any?
|
@@ -247,13 +251,16 @@ module RSQL
|
|
247
251
|
# Get a row from the table hashed with the field names.
|
248
252
|
#
|
249
253
|
def [](index)
|
250
|
-
if
|
254
|
+
if !@fields || !@table
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
if row = @table[index]
|
258
|
+
hash = {}
|
259
|
+
@fields.each_with_index {|f,i| hash[f.name] = row[i]}
|
260
|
+
return hash
|
261
|
+
else
|
251
262
|
return nil
|
252
263
|
end
|
253
|
-
hash = {}
|
254
|
-
row = @table[index]
|
255
|
-
@fields.each_with_index {|f,i| hash[f.name] = row[i]}
|
256
|
-
return hash
|
257
264
|
end
|
258
265
|
|
259
266
|
# Iterate through each row of the table hashed with the field
|
data/test/test_eval_context.rb
CHANGED
@@ -19,8 +19,6 @@ class TestEvalContext < Test::Unit::TestCase
|
|
19
19
|
include RSQL
|
20
20
|
|
21
21
|
def setup
|
22
|
-
@orig_stdout = $stdout
|
23
|
-
$stdout = @strout = StringIO.new
|
24
22
|
@conn = MySQLResults.conn = mock('Mysql')
|
25
23
|
@conn.expects(:query).with(instance_of(String)).returns(nil)
|
26
24
|
@conn.expects(:affected_rows).returns(0)
|
@@ -28,13 +26,13 @@ class TestEvalContext < Test::Unit::TestCase
|
|
28
26
|
@ctx.load(File.join(File.dirname(__FILE__),'..','example.rsqlrc'))
|
29
27
|
end
|
30
28
|
|
31
|
-
def teardown
|
32
|
-
$stdout = @orig_stdout
|
33
|
-
end
|
34
|
-
|
35
29
|
def test_load
|
36
|
-
|
37
|
-
|
30
|
+
orig = $stdout
|
31
|
+
$stdout = out = StringIO.new
|
32
|
+
@ctx.safe_eval('reload', nil, out)
|
33
|
+
assert_match(/loaded: .+?example.rsqlrc/, out.string)
|
34
|
+
ensure
|
35
|
+
$stdout = orig
|
38
36
|
end
|
39
37
|
|
40
38
|
def test_eval
|
@@ -64,9 +62,55 @@ class TestEvalContext < Test::Unit::TestCase
|
|
64
62
|
assert_match(/usage\s+description/, out.string)
|
65
63
|
end
|
66
64
|
|
65
|
+
def test_params
|
66
|
+
val = @ctx.safe_eval('params(@registrations[:fill_table].block)', nil, nil)
|
67
|
+
assert_equal('', val)
|
68
|
+
val = @ctx.safe_eval('params(@registrations[:save_values].block)', nil, nil)
|
69
|
+
assert_equal('(fn)', val)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_desc
|
73
|
+
out = StringIO.new
|
74
|
+
err = StringIO.new
|
75
|
+
orig_err = $stderr
|
76
|
+
|
77
|
+
$stderr = err
|
78
|
+
val = @ctx.safe_eval('desc max_rows', nil, out)
|
79
|
+
$sterr = orig_err
|
80
|
+
assert_equal('', out.string)
|
81
|
+
assert_equal('refusing to describe EvalContext#max_rows',
|
82
|
+
err.string.chomp)
|
83
|
+
|
84
|
+
err.string = ''
|
85
|
+
$stderr = err
|
86
|
+
val = @ctx.safe_eval('desc :sldkfjas', nil, out)
|
87
|
+
$sterr = orig_err
|
88
|
+
assert_equal('', out.string)
|
89
|
+
assert_equal('nothing registered as sldkfjas', err.string.chomp)
|
90
|
+
|
91
|
+
err.string = ''
|
92
|
+
$stderr = err
|
93
|
+
val = @ctx.safe_eval('desc :version', nil, out)
|
94
|
+
$sterr = orig_err
|
95
|
+
assert_equal('', out.string)
|
96
|
+
assert_equal('refusing to describe the version method', err.string.chomp)
|
97
|
+
|
98
|
+
out.string = ''
|
99
|
+
val = @ctx.safe_eval('desc :cleanup_example', nil, out)
|
100
|
+
assert_equal('DROP TEMPORARY TABLE IF EXISTS #{@rsql_table}', out.string.strip)
|
101
|
+
|
102
|
+
out.string = ''
|
103
|
+
val = @ctx.safe_eval('desc :to_report', nil, out)
|
104
|
+
lines = out.string.split($/)
|
105
|
+
assert_match(/^register .+ do$/, lines[0])
|
106
|
+
assert_match(/^\s+puts/, lines[-2])
|
107
|
+
assert_match(/^end$/, lines[-1])
|
108
|
+
assert_equal(12, lines.size)
|
109
|
+
end
|
110
|
+
|
67
111
|
def test_complete
|
68
112
|
@conn.expects(:list_dbs).returns([])
|
69
|
-
assert_equal(
|
113
|
+
assert_equal(18, @ctx.complete('').size)
|
70
114
|
assert_equal(['version'], @ctx.complete('v'))
|
71
115
|
assert_equal(['.version'], @ctx.complete('.v'))
|
72
116
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 8
|
10
|
+
version: 0.1.8
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brad Robel-Forrest
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-06-03 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: net-ssh
|
@@ -33,6 +33,64 @@ dependencies:
|
|
33
33
|
version: 2.1.0
|
34
34
|
type: :runtime
|
35
35
|
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mocha
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 35
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 9
|
48
|
+
- 12
|
49
|
+
version: 0.9.12
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rake
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
type: :development
|
65
|
+
version_requirements: *id003
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: rdoc
|
68
|
+
prerelease: false
|
69
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
type: :development
|
79
|
+
version_requirements: *id004
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rcov
|
82
|
+
prerelease: false
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
type: :development
|
93
|
+
version_requirements: *id005
|
36
94
|
description: |
|
37
95
|
RSQL makes working with a MySQL command line more convenient through
|
38
96
|
the use of recipes and embedding the common operation of using a SSH
|
@@ -95,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
153
|
requirements: []
|
96
154
|
|
97
155
|
rubyforge_project:
|
98
|
-
rubygems_version: 1.8.
|
156
|
+
rubygems_version: 1.8.5
|
99
157
|
signing_key:
|
100
158
|
specification_version: 3
|
101
159
|
summary: Ruby based MySQL command line with recipes.
|