pry 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,9 +9,11 @@ class Pry
9
9
  target = target()
10
10
 
11
11
  opts = Slop.parse!(args) do |opt|
12
- opt.banner "Usage: show-method [OPTIONS] [METH 1] [METH 2] [METH N]\n" \
13
- "Show the source for method METH. Tries instance methods first and then methods by default.\n" \
14
- "e.g: show-method hello_method"
12
+ opt.banner unindent <<-USAGE
13
+ Usage: show-method [OPTIONS] [METH 1] [METH 2] [METH N]
14
+ Show the source for method METH. Tries instance methods first and then methods by default.
15
+ e.g: show-method hello_method
16
+ USAGE
15
17
 
16
18
  opt.on :l, "line-numbers", "Show line numbers."
17
19
  opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)."
@@ -58,16 +60,18 @@ class Pry
58
60
  end
59
61
  end
60
62
 
61
- alias_command "show-source", "show-method", ""
62
- alias_command "$", "show-method", ""
63
+ alias_command "show-source", "show-method"
64
+ alias_command "$", "show-method"
63
65
 
64
66
  command "show-command", "Show the source for CMD. Type `show-command --help` for more info." do |*args|
65
67
  target = target()
66
68
 
67
69
  opts = Slop.parse!(args) do |opt|
68
- opt.banner = "Usage: show-command [OPTIONS] [CMD]\n" \
69
- "Show the source for command CMD.\n" \
70
- "e.g: show-command show-method"
70
+ opt.banner unindent <<-USAGE
71
+ Usage: show-command [OPTIONS] [CMD]
72
+ Show the source for command CMD.
73
+ e.g: show-command show-method
74
+ USAGE
71
75
 
72
76
  opt.on :l, "line-numbers", "Show line numbers."
73
77
  opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
@@ -110,70 +114,86 @@ class Pry
110
114
 
111
115
  command "edit", "Invoke the default editor on a file. Type `edit --help` for more info" do |*args|
112
116
  opts = Slop.parse!(args) do |opt|
113
- opt.banner "Usage: edit [OPTIONS] [FILE]\n" \
114
- "Edit the method FILE in an editor.\nWhen no file given, opens editor with contents of input buffer and evals after closing." \
115
- "\nEnsure #{text.bold("Pry.config.editor")} is set to your editor of choice.\n" \
116
- "e.g: edit sample.rb"
117
-
118
- opt.on :r, "reload", "Eval file content after editing (evals at top level)"
119
- opt.on :n, "no-reload", "Do not automatically reload the file after editing (only applies to --ex and -t)."
120
- opt.on :ex, "Open an editor at the line and file that generated the most recent Exception, reloads file after editing."
121
- opt.on :t, "temp", "Open a temporary file in an editor with contents of input buffer and eval it in current context after closing (same as `edit` with no args)"
122
- opt.on :p, "play", "Use the pry `play` command to eval the file content after editing."
123
- opt.on :l, "line", "Specify line number to jump to in file", true, :as => Integer
117
+ opt.banner unindent <<-USAGE
118
+ Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]]
119
+ Open a text editor. When no FILE is given, edits the pry input buffer.
120
+ Ensure #{text.bold("Pry.config.editor")} is set to your editor of choice.
121
+ e.g: edit sample.rb
122
+ USAGE
123
+
124
+ opt.on :e, :ex, "Open the file that raised the most recent exception (_ex_.file)", :optional => true, :as => Integer
125
+ opt.on :t, :temp, "Open an empty temporary file"
126
+ opt.on :l, :line, "Jump to this line in the opened file", true, :as => Integer
127
+ opt.on :n, :"no-reload", "Don't automatically reload the edited code"
128
+ opt.on :r, :reload, "Reload the edited code immediately (default for ruby files)"
124
129
  opt.on :h, :help, "This message." do
125
130
  output.puts opt
126
131
  end
127
132
  end
128
133
  next if opts.h?
129
134
 
130
- should_reload_at_top_level = opts[:r]
131
- should_reload_locally = false
132
-
133
- if opts.ex?
134
- next output.puts "No Exception found." if _pry_.last_exception.nil?
135
+ if [opts.ex? || nil, opts.t? || nil, !args.empty? || nil].compact.size > 1
136
+ next output.puts "Only one of --ex, --temp, and FILE may be specified"
137
+ end
135
138
 
136
- if is_core_rbx_path?(_pry_.last_exception.file)
137
- file_name = rbx_convert_path_to_full(_pry_.last_exception.file)
138
- else
139
- file_name = _pry_.last_exception.file
139
+ # edit of local code, eval'd within pry.
140
+ if !opts.ex? && args.empty?
141
+
142
+ content = if opts.t?
143
+ ""
144
+ elsif eval_string.strip != ""
145
+ eval_string
146
+ else
147
+ _pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || ""
148
+ end
149
+
150
+ line = content.lines.count
151
+
152
+ temp_file do |f|
153
+ f.puts(content)
154
+ f.flush
155
+ invoke_editor(f.path, line)
156
+ if !opts.n?
157
+ silence_warnings do
158
+ eval_string.replace(File.read(f.path))
159
+ end
160
+ end
140
161
  end
141
162
 
142
- line = _pry_.last_exception.line
143
- next output.puts "Exception has no associated file." if file_name.nil?
144
- next output.puts "Cannot edit exceptions raised in REPL." if Pry.eval_path == file_name
163
+ # edit of remote code, eval'd at top-level
164
+ else
165
+ if opts.ex?
166
+ next output.puts "No Exception found." if _pry_.last_exception.nil?
167
+ ex = _pry_.last_exception
168
+ bt_index = opts[:ex].to_i
169
+
170
+ ex_file, ex_line = ex.bt_source_location_for(bt_index)
171
+ if ex_file && is_core_rbx_path?(ex_file)
172
+ file_name = rbx_convert_path_to_full(ex_file)
173
+ else
174
+ file_name = ex_file
175
+ end
176
+
177
+ line = ex_line
178
+ next output.puts "Exception has no associated file." if file_name.nil?
179
+ next output.puts "Cannot edit exceptions raised in REPL." if Pry.eval_path == file_name
145
180
 
146
- should_reload_at_top_level = opts[:n] ? false : true
181
+ else
182
+ # break up into file:line
183
+ file_name = File.expand_path(args.first)
147
184
 
148
- elsif opts.t? || args.first.nil?
149
- file_name = temp_file do |f|
150
- f.puts eval_string if !eval_string.empty?
185
+ line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1
151
186
  end
152
- line = eval_string.lines.count + 1
153
- should_reload_locally = opts[:n] ? false : true
154
- else
155
- # break up into file:line
156
- /(:(\d+))?$/ =~ File.expand_path(args.first)
157
187
 
158
- # $` is pre-match
159
- file_name, line = [$`, $2]
160
- line = line ? line.to_i : opts[:l].to_i
161
- end
188
+ line = opts[:l].to_i if opts.l?
162
189
 
163
- invoke_editor(file_name, line)
164
- set_file_and_dir_locals(file_name)
190
+ invoke_editor(file_name, line)
191
+ set_file_and_dir_locals(file_name)
165
192
 
166
- if opts[:p]
167
- silence_warnings do
168
- _pry_.input = StringIO.new(File.readlines(file_name).join)
169
- end
170
- elsif should_reload_locally
171
- silence_warnings do
172
- eval_string.replace(File.read(file_name))
173
- end
174
- elsif should_reload_at_top_level
175
- silence_warnings do
176
- TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
193
+ if opts.r? || ((opts.ex? || file_name.end_with?(".rb")) && !opts.n?)
194
+ silence_warnings do
195
+ TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
196
+ end
177
197
  end
178
198
  end
179
199
  end
@@ -182,15 +202,18 @@ class Pry
182
202
  target = target()
183
203
 
184
204
  opts = Slop.parse!(args) do |opt|
185
- opt.banner "Usage: edit-method [OPTIONS] [METH]\n" \
186
- "Edit the method METH in an editor.\n" \
187
- "Ensure #{text.bold("Pry.config.editor")} is set to your editor of choice.\n" \
188
- "e.g: edit-method hello_method"
205
+ opt.banner unindent <<-USAGE
206
+ Usage: edit-method [OPTIONS] [METH]
207
+ Edit the method METH in an editor.
208
+ Ensure #{text.bold("Pry.config.editor")} is set to your editor of choice.
209
+ e.g: edit-method hello_method
210
+ USAGE
189
211
 
190
212
  opt.on :M, "instance-methods", "Operate on instance methods."
191
213
  opt.on :m, :methods, "Operate on methods."
192
214
  opt.on :n, "no-reload", "Do not automatically reload the method's file after editing."
193
215
  opt.on "no-jump", "Do not fast forward editor to first line of method."
216
+ opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch."
194
217
  opt.on :c, :context, "Select object context to run under.", true do |context|
195
218
  target = Pry.binding_for(target.eval(context))
196
219
  end
@@ -201,20 +224,42 @@ class Pry
201
224
 
202
225
  next if opts.help?
203
226
 
227
+ if !Pry.config.editor
228
+ output.puts "Error: No editor set!"
229
+ output.puts "Ensure that #{text.bold("Pry.config.editor")} is set to your editor of choice."
230
+ next
231
+ end
232
+
204
233
  meth_name = args.shift
205
- if (meth = get_method_object(meth_name, target, opts.to_hash(true))).nil?
234
+ meth_name, target, type = get_method_attributes(meth_name, target, opts.to_hash(true))
235
+ meth = get_method_object_from_target(meth_name, target, type)
236
+
237
+ if meth.nil?
206
238
  output.puts "Invalid method name: #{meth_name}."
207
239
  next
208
240
  end
209
241
 
210
- next output.puts "Error: No editor set!\nEnsure that #{text.bold("Pry.config.editor")} is set to your editor of choice." if !Pry.config.editor
242
+ if opts.p? || is_a_dynamically_defined_method?(meth)
243
+ code, _ = code_and_code_type_for(meth)
244
+
245
+ lines = code.lines.to_a
246
+ if lines[0] =~ /^def [^( \n]+/
247
+ lines[0] = "def #{meth_name}#{$'}"
248
+ else
249
+ next output.puts "Error: Pry can only patch methods created with the `def` keyword."
250
+ end
251
+
252
+ temp_file do |f|
253
+ f.puts lines.join
254
+ f.flush
255
+ invoke_editor(f.path, 0)
256
+ Pry.new(:input => StringIO.new(File.read(f.path))).rep(meth.owner)
257
+ end
258
+ next
259
+ end
211
260
 
212
261
  if is_a_c_method?(meth)
213
262
  output.puts "Error: Can't edit a C method."
214
- elsif is_a_dynamically_defined_method?(meth)
215
- output.puts "Error: Can't edit an eval method."
216
-
217
- # editor is invoked here
218
263
  else
219
264
  file, line = path_line_for(meth)
220
265
  set_file_and_dir_locals(file)
@@ -11,11 +11,8 @@ class Pry
11
11
  rescue Errno::ENOENT
12
12
  output.puts "No such directory: #{dest}"
13
13
  end
14
-
15
14
  else
16
- if !system(cmd)
17
- output.puts "Error: there was a problem executing system command: #{cmd}"
18
- end
15
+ Pry.config.system.call(output, cmd, _pry_)
19
16
  end
20
17
  end
21
18
 
@@ -32,12 +29,13 @@ class Pry
32
29
  end
33
30
  end
34
31
 
35
- alias_command "file-mode", "shell-mode", ""
32
+ alias_command "file-mode", "shell-mode"
36
33
 
37
34
  command "cat", "Show output of file FILE. Type `cat --help` for more information." do |*args|
38
35
  start_line = 0
39
36
  end_line = -1
40
37
  file_name = nil
38
+ bt_index = 0
41
39
 
42
40
  opts = Slop.parse!(args) do |opt|
43
41
  opt.on :s, :start, "Start line (defaults to start of file)Line 1 is the first line.", true, :as => Integer do |line|
@@ -48,17 +46,25 @@ class Pry
48
46
  end_line = line - 1
49
47
  end
50
48
 
51
- opt.on :ex, "Show a window of N lines either side of the last exception (defaults to 5).", :optional => true, :as => Integer do |window_size|
52
- window_size ||= 5
49
+ opt.on :ex, "Show a window of N lines either side of the last exception (defaults to 5).", :optional => true, :as => Integer do |bt_index_arg|
50
+ window_size = Pry.config.exception_window_size || 5
53
51
  ex = _pry_.last_exception
54
52
  next if !ex
55
- start_line = (ex.line - 1) - window_size
53
+ if bt_index_arg
54
+ bt_index = bt_index_arg
55
+ else
56
+ bt_index = ex.bt_index
57
+ end
58
+ ex.bt_index = (bt_index + 1) % ex.backtrace.size
59
+
60
+ ex_file, ex_line = ex.bt_source_location_for(bt_index)
61
+ start_line = (ex_line - 1) - window_size
56
62
  start_line = start_line < 0 ? 0 : start_line
57
- end_line = (ex.line - 1) + window_size
58
- if is_core_rbx_path?(ex.file)
59
- file_name = rbx_convert_path_to_full(ex.file)
63
+ end_line = (ex_line - 1) + window_size
64
+ if ex_file && is_core_rbx_path?(ex_file)
65
+ file_name = rbx_convert_path_to_full(ex_file)
60
66
  else
61
- file_name = ex.file
67
+ file_name = ex_file
62
68
  end
63
69
  end
64
70
 
@@ -74,7 +80,6 @@ class Pry
74
80
 
75
81
  if opts.ex?
76
82
  next output.puts "No Exception or Exception has no associated file." if file_name.nil?
77
- next output.puts "Cannot cat exceptions raised in REPL." if Pry.eval_path == file_name
78
83
  else
79
84
  file_name = args.shift
80
85
  end
@@ -84,8 +89,13 @@ class Pry
84
89
  next
85
90
  end
86
91
 
87
- contents, _, _ = read_between_the_lines(file_name, start_line, end_line)
88
- contents = syntax_highlight_by_file_type_or_specified(contents, file_name, opts[:type])
92
+ begin
93
+ contents, _, _ = read_between_the_lines(file_name, start_line, end_line)
94
+ contents = syntax_highlight_by_file_type_or_specified(contents, file_name, opts[:type])
95
+ rescue Errno::ENOENT
96
+ output.puts "Could not find file: #{file_name}"
97
+ next
98
+ end
89
99
 
90
100
  if opts.l?
91
101
  contents = text.with_line_numbers contents, start_line + 1
@@ -93,11 +103,12 @@ class Pry
93
103
 
94
104
  # add the arrow pointing to line that caused the exception
95
105
  if opts.ex?
106
+ ex_file, ex_line = _pry_.last_exception.bt_source_location_for(bt_index)
96
107
  contents = text.with_line_numbers contents, start_line + 1, :bright_red
97
108
 
98
109
  contents = contents.lines.each_with_index.map do |line, idx|
99
110
  l = idx + start_line
100
- if l == (_pry_.last_exception.line - 1)
111
+ if l == (ex_line - 1)
101
112
  " =>#{line}"
102
113
  else
103
114
  " #{line}"
@@ -106,7 +117,7 @@ class Pry
106
117
 
107
118
  # header for exceptions
108
119
  output.puts "\n#{Pry::Helpers::Text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception.message}\n--"
109
- output.puts "#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{_pry_.last_exception.line}\n\n"
120
+ output.puts "#{Pry::Helpers::Text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold('level: ')} #{bt_index} of backtrace (of #{_pry_.last_exception.backtrace.size - 1}).\n\n"
110
121
  end
111
122
 
112
123
  set_file_and_dir_locals(file_name)
@@ -44,7 +44,6 @@ class Pry
44
44
  def temp_file
45
45
  file = Tempfile.new(["tmp", ".rb"])
46
46
  yield file
47
- file.path
48
47
  ensure
49
48
  file.close
50
49
  end
@@ -178,37 +177,43 @@ class Pry
178
177
  [doc, code_type]
179
178
  end
180
179
 
181
- def get_method_object(meth_name, target, options)
180
+ def get_method_object(meth_name, target=nil, options={})
181
+ get_method_object_from_target(*get_method_attributes(meth_name, target, options)) rescue nil
182
+ end
183
+
184
+ def get_method_attributes(meth_name, target=nil, options={})
182
185
  if meth_name
183
186
  if meth_name =~ /(\S+)\#(\S+)\Z/
184
187
  context, meth_name = $1, $2
185
188
  target = Pry.binding_for(target.eval(context))
186
- options["instance-methods"] = true
187
- options[:methods] = false
189
+ type = :instance
188
190
  elsif meth_name =~ /(\S+)\.(\S+)\Z/
189
191
  context, meth_name = $1, $2
190
192
  target = Pry.binding_for(target.eval(context))
191
- options["instance-methods"] = false
192
- options[:methods] = true
193
+ type = :singleton
194
+ elsif options["instance_methods"]
195
+ type = :instance
196
+ elsif options[:methods]
197
+ type = :singleton
198
+ else
199
+ type = nil
193
200
  end
194
201
  else
195
202
  meth_name = meth_name_from_binding(target)
203
+ type = nil
196
204
  end
205
+ [meth_name, target, type]
206
+ end
197
207
 
198
- if !meth_name
199
- return nil
200
- end
201
-
202
- if options["instance-methods"]
208
+ def get_method_object_from_target(meth_name, target, type=nil)
209
+ case type
210
+ when :instance
203
211
  target.eval("instance_method(:#{meth_name})") rescue nil
204
- elsif options[:methods]
212
+ when :singleton
205
213
  target.eval("method(:#{meth_name})") rescue nil
206
214
  else
207
- begin
208
- target.eval("instance_method(:#{meth_name})")
209
- rescue
210
- target.eval("method(:#{meth_name})") rescue nil
211
- end
215
+ get_method_object_from_target(meth_name, target, :instance) ||
216
+ get_method_object_from_target(meth_name, target, :singleton)
212
217
  end
213
218
  end
214
219
 
@@ -305,7 +310,11 @@ class Pry
305
310
  # returns the file content between the lines and the normalized
306
311
  # start and end line numbers.
307
312
  def read_between_the_lines(file_name, start_line, end_line)
308
- content = File.read(File.expand_path(file_name))
313
+ if file_name == Pry.eval_path
314
+ content = Pry.line_buffer.drop(1).join
315
+ else
316
+ content = File.read(File.expand_path(file_name))
317
+ end
309
318
  lines_array = content.each_line.to_a
310
319
 
311
320
  [lines_array[start_line..end_line].join, normalized_line_number(start_line, lines_array.size),
@@ -372,6 +381,7 @@ class Pry
372
381
  else
373
382
  editor_invocation = "#{Pry.config.editor} #{start_line_syntax_for_editor(file, line)}"
374
383
  end
384
+ return nil unless editor_invocation
375
385
 
376
386
  if jruby?
377
387
  begin
@@ -379,18 +389,23 @@ class Pry
379
389
  pid = Spoon.spawnp(*editor_invocation.split)
380
390
  Process.waitpid(pid)
381
391
  rescue FFI::NotFoundError
382
- run ".#{editor_invocation}"
392
+ system(editor_invocation)
383
393
  end
384
394
  else
385
- run ".#{editor_invocation}"
395
+ # Note we dont want to use Pry.config.system here as that
396
+ # may be invoked non-interactively (i.e via Open4), whereas we want to
397
+ # ensure the editor is always interactive
398
+ system(editor_invocation)
386
399
  end
387
400
  end
388
401
 
402
+ # Return the syntax for a given editor for starting the editor
403
+ # and moving to a particular line within that file
389
404
  def start_line_syntax_for_editor(file_name, line_number)
390
405
  file_name = file_name.gsub(/\//, '\\') if RUBY_PLATFORM =~ /mswin|mingw/
391
406
 
392
- # special case 0th line
393
- return file_name if line_number <= 0
407
+ # special case for 1st line
408
+ return file_name if line_number <= 1
394
409
 
395
410
  case Pry.config.editor
396
411
  when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
@@ -410,27 +425,45 @@ class Pry
410
425
  end
411
426
  end
412
427
 
413
- def prompt(message, options="Yn")
414
- opts = options.scan(/./)
415
- optstring = opts.join("/") # case maintained
416
- defaults = opts.select{|o| o.upcase == o }
417
- opts = opts.map{|o| o.downcase}
418
-
419
- raise "Error: Too many default values for the prompt: #{default.inspect}" if defaults.size > 1
420
-
421
- default = defaults.first
422
-
423
- loop do
424
- response = Pry.input.readline("#{message} (#{optstring}) ").downcase
425
- case response
426
- when *opts
427
- return response
428
- when ""
429
- return default.downcase
428
+ # Remove any common leading whitespace from every line in `text`.
429
+ #
430
+ # This can be used to make a HEREDOC line up with the left margin, without
431
+ # sacrificing the indentation level of the source code.
432
+ #
433
+ # e.g.
434
+ # opt.banner unindent <<-USAGE
435
+ # Lorem ipsum dolor sit amet, consectetur adipisicing elit,
436
+ # sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
437
+ # "Ut enim ad minim veniam."
438
+ # USAGE
439
+ #
440
+ # @param [String] The text from which to remove indentation
441
+ # @return [String], The text with indentation stripped.
442
+ #
443
+ # @copyright Heavily based on textwrap.dedent from Python, which is:
444
+ # Copyright (C) 1999-2001 Gregory P. Ward.
445
+ # Copyright (C) 2002, 2003 Python Software Foundation.
446
+ # Written by Greg Ward <gward@python.net>
447
+ #
448
+ # Licensed under <http://docs.python.org/license.html>
449
+ # From <http://hg.python.org/cpython/file/6b9f0a6efaeb/Lib/textwrap.py>
450
+ #
451
+ def unindent(text)
452
+ # Empty blank lines
453
+ text = text.sub(/^[ \t]+$/, '')
454
+
455
+ # Find the longest common whitespace to all indented lines
456
+ margin = text.scan(/^[ \t]*(?=[^ \t\n])/).inject do |current_margin, next_indent|
457
+ if next_indent.start_with?(current_margin)
458
+ current_margin
459
+ elsif current_margin.start_with?(next_indent)
460
+ next_indent
430
461
  else
431
- output.puts " |_ Invalid option: #{response.inspect}. Try again."
462
+ ""
432
463
  end
433
464
  end
465
+
466
+ text.gsub(/^#{margin}/, '')
434
467
  end
435
468
 
436
469
  end