rsql 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|