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 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
@@ -2,7 +2,7 @@
2
2
  # Commands using an EvalContext for handling recipes.
3
3
  #
4
4
  module RSQL
5
- VERSION = '0.1.7'
5
+ VERSION = '0.1.8'
6
6
 
7
7
  require 'rsql/mysql'
8
8
  require 'rsql/mysql_results'
@@ -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 = nil
39
- @last_query = nil
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*$/.match(content)
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
- $stderr.puts(ex.message.gsub(/\(eval\):\d+:/,''))
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
- sql = sqeeze!(args.pop)
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
- usage << "(#{block.arity})" unless 0 == block.arity
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,
@@ -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 index < 0 || !@fields || !@table || @table.size <= index
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
@@ -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
- @ctx.reload
37
- assert_match(/loaded: .+?example.rsqlrc/, @strout.string)
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(17, @ctx.complete('').size)
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: 21
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 7
10
- version: 0.1.7
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-05-23 00:00:00 Z
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.2
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.