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 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.