pry 0.9.8.4 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG +26 -0
  3. data/README.markdown +3 -3
  4. data/lib/pry.rb +9 -1
  5. data/lib/pry/code.rb +93 -0
  6. data/lib/pry/command.rb +35 -22
  7. data/lib/pry/command_set.rb +97 -67
  8. data/lib/pry/config.rb +63 -10
  9. data/lib/pry/core_extensions.rb +24 -18
  10. data/lib/pry/default_commands/context.rb +72 -12
  11. data/lib/pry/default_commands/easter_eggs.rb +4 -0
  12. data/lib/pry/default_commands/editing.rb +43 -15
  13. data/lib/pry/default_commands/find_method.rb +171 -0
  14. data/lib/pry/default_commands/hist.rb +10 -6
  15. data/lib/pry/default_commands/introspection.rb +183 -30
  16. data/lib/pry/default_commands/ls.rb +77 -7
  17. data/lib/pry/default_commands/misc.rb +1 -0
  18. data/lib/pry/default_commands/navigating_pry.rb +1 -8
  19. data/lib/pry/helpers/base_helpers.rb +10 -2
  20. data/lib/pry/helpers/command_helpers.rb +23 -40
  21. data/lib/pry/helpers/documentation_helpers.rb +65 -0
  22. data/lib/pry/indent.rb +17 -4
  23. data/lib/pry/method.rb +61 -45
  24. data/lib/pry/pry_class.rb +9 -3
  25. data/lib/pry/pry_instance.rb +99 -65
  26. data/lib/pry/rbx_method.rb +2 -9
  27. data/lib/pry/version.rb +1 -1
  28. data/lib/pry/wrapped_module.rb +236 -1
  29. data/pry.gemspec +5 -5
  30. data/test/helper.rb +22 -0
  31. data/test/test_command.rb +29 -0
  32. data/test/test_command_integration.rb +193 -10
  33. data/test/test_command_set.rb +82 -17
  34. data/test/test_default_commands/test_cd.rb +16 -0
  35. data/test/test_default_commands/test_context.rb +61 -0
  36. data/test/test_default_commands/test_documentation.rb +163 -43
  37. data/test/test_default_commands/test_find_method.rb +42 -0
  38. data/test/test_default_commands/test_input.rb +32 -0
  39. data/test/test_default_commands/test_introspection.rb +50 -197
  40. data/test/test_default_commands/test_ls.rb +22 -0
  41. data/test/test_default_commands/test_show_source.rb +306 -0
  42. data/test/test_pry.rb +3 -3
  43. data/test/test_pry_defaults.rb +21 -0
  44. data/test/test_sticky_locals.rb +81 -1
  45. data/test/test_syntax_checking.rb +7 -6
  46. metadata +22 -14
@@ -4,44 +4,82 @@ class Pry
4
4
  class Config < OpenStruct
5
5
 
6
6
  # Get/Set the object to use for input by default by all Pry instances.
7
+ # Pry.config.input is an option determining the input object - the object from
8
+ # which Pry retrieves its lines of input. Pry accepts any object that implements the readline method.
9
+ # This includes IO objects, StringIO, Readline, File and custom objects.
7
10
  # @return [#readline] The object to use for input by default by all
8
11
  # Pry instances.
12
+ # @example
13
+ # Pry.config.input = StringIO.new("@x = 10\nexit")
9
14
  attr_accessor :input
10
15
 
11
16
  # Get/Set the object to use for output by default by all Pry instances.
17
+ # Pry.config.output is an option determining the output object - the object to which
18
+ # Pry writes its output. Pry accepts any object that implements the puts method. This
19
+ # includes IO objects, StringIO, File and custom objects.
12
20
  # @return [#puts] The object to use for output by default by all
13
21
  # Pry instances.
22
+ # @example
23
+ # Pry.config.output = StringIO.new
14
24
  attr_accessor :output
15
25
 
16
26
  # Get/Set the object to use for commands by default by all Pry instances.
17
27
  # @return [Pry::CommandBase] The object to use for commands by default by all
18
28
  # Pry instances.
29
+ # @example
30
+ # Pry.config.commands = Pry::CommandSet.new do
31
+ # import_from Pry::Commands, "ls"
32
+ #
33
+ # command "greet" do |name|
34
+ # output.puts "hello #{name}"
35
+ # end
36
+ # end
19
37
  attr_accessor :commands
20
38
 
21
39
  # Get/Set the Proc to use for printing by default by all Pry
22
40
  # instances.
41
+ # Two parameters are passed to the print Proc: these are (1) the
42
+ # output object for the current session and (2) the expression value to print. It is important
43
+ # that you write to the output object instead of just stdout so that all Pry output can be redirected if necessary.
23
44
  # This is the 'print' component of the REPL.
24
45
  # @return [Proc] The Proc to use for printing by default by all
25
46
  # Pry instances.
47
+ # @example
48
+ # Pry.config.print = proc { |output, value| output.puts "=> #{value.inspect}" }
26
49
  attr_accessor :print
27
50
 
51
+ # Pry.config.exception_handler is an option determining the exception handler object - the
52
+ # Proc responsible for dealing with exceptions raised by user input to the REPL.
53
+ # Three parameters are passed to the exception handler Proc: these
54
+ # are (1) the output object for the current session, (2) the
55
+ # exception object that was raised inside the Pry session, and (3)
56
+ # a reference to the associated Pry instance.
28
57
  # @return [Proc] The Proc to use for printing exceptions by default by all
29
58
  # Pry instances.
59
+ # @example
60
+ # Pry.config.exception_handler = proc do |output, exception, _|
61
+ # output.puts "#{exception.class}: #{exception.message}"
62
+ # output.puts "from #{exception.backtrace.first}"
63
+ # end
30
64
  attr_accessor :exception_handler
31
65
 
32
66
  # @return [Array] The classes of exception that will not be caught by Pry.
67
+ # @example
68
+ # Pry.config.exception_whitelist = [SystemExit, SignalException]
33
69
  attr_accessor :exception_whitelist
34
70
 
35
71
  # @return [Fixnum] The number of lines of context to show before and after
36
72
  # exceptions, etc.
73
+ # @example
74
+ # Pry.config.default_window_size = 10
37
75
  attr_accessor :default_window_size
38
76
 
39
- # Get/Set the Hash that defines Pry hooks used by default by all Pry
77
+ # Get/Set the `Pry::Hooks` instance that defines Pry hooks used by default by all Pry
40
78
  # instances.
41
- # @return [Hash] The hooks used by default by all Pry instances.
79
+ # @return [Pry::Hooks] The hooks used by default by all Pry instances.
42
80
  # @example
43
- # Pry.hooks :before_session => proc { puts "hello" },
44
- # :after_session => proc { puts "goodbye" }
81
+ # Pry.config.hooks = Pry::Hooks.new.add_hook(:before_session,
82
+ # :default) { |output, target, _pry_| output.puts "Good morning!" }
45
83
  attr_reader :hooks
46
84
 
47
85
  # FIXME:
@@ -63,23 +101,32 @@ class Pry
63
101
  # Pry.config.input_stack = [StringIO.new("puts 'hello world'\nexit")]
64
102
  attr_accessor :input_stack
65
103
 
66
- # Get the array of Procs to be used for the prompts by default by
104
+ # Get the array of Procs (or single Proc) to be used for the prompts by default by
67
105
  # all Pry instances.
68
- # @return [Array<Proc>] The array of Procs to be used for the
106
+ # Three parameters are passed into the prompt procs, (1) the
107
+ # object that is the target of the session, (2) the current
108
+ # nesting level, and (3) a reference to the associated Pry instance. These objects can be used in the prompt, if desired.
109
+ # @return [Array<Proc>, Proc] The array of Procs to be used for the
69
110
  # prompts by default by all Pry instances.
111
+ # @example
112
+ # Pry.config.prompt = proc { |obj, nest_level, _pry_| "#{obj}:#{nest_level}> " }
70
113
  attr_accessor :prompt
71
114
 
72
- # The default editor to use. Defaults to $EDITOR or nano if
73
- # $EDITOR is not defined.
115
+ # The default editor to use. Defaults to $VISUAL, $EDITOR, or a sensible fallback
116
+ # for the platform.
74
117
  # If `editor` is a String then that string is used as the shell
75
118
  # command to invoke the editor. If `editor` is callable (e.g a
76
- # Proc) then `file` and `line` are passed in as parameters and the
119
+ # Proc) then `file`, `line`, and `reloading` are passed in as parameters and the
77
120
  # return value of that callable invocation is used as the exact
78
- # shell command to invoke the editor.
121
+ # shell command to invoke the editor. `reloading` indicates whether Pry will be
122
+ # reloading code after the shell command returns. Any or all of these parameters
123
+ # can be omitted from the callable's signature.
79
124
  # @example String
80
125
  # Pry.config.editor = "emacsclient"
81
126
  # @example Callable
82
127
  # Pry.config.editor = proc { |file, line| "emacsclient #{file} +#{line}" }
128
+ # @example Callable waiting only if reloading
129
+ # Pry.config.editor = proc { |file, line, reloading| "subl #{'--wait' if reloading} #{file}:#{line}" }
83
130
  # @return [String, #call]
84
131
  attr_accessor :editor
85
132
 
@@ -171,6 +218,12 @@ class Pry
171
218
  # Pry.config.gist.inspecter = proc &:inspect
172
219
  # @return [OpenStruct]
173
220
  attr_accessor :gist
221
+
222
+ # @return [Hash] Additional sticky locals (to the standard ones) to use in Pry sessions.
223
+ # @example Inject `random_number` sticky local into Pry session
224
+ # Pry.config.extra_sticky_locals = { :random_number => proc {
225
+ # rand(10) } }
226
+ attr_accessor :extra_sticky_locals
174
227
  end
175
228
  end
176
229
 
@@ -1,23 +1,29 @@
1
1
  class Object
2
- # Start a Pry REPL.
3
- # This method differs from `Pry.start` in that it does not
4
- # support an options hash. Also, when no parameter is provided, the Pry
5
- # session will start on the implied receiver rather than on
6
- # top-level (as in the case of `Pry.start`).
7
- # It has two forms of invocation. In the first form no parameter
8
- # should be provided and it will start a pry session on the
9
- # receiver. In the second form it should be invoked without an
10
- # explicit receiver and one parameter; this will start a Pry
11
- # session on the parameter.
12
- # @param [Object, Binding] target The receiver of the Pry session.
13
- # @example First form
2
+ # Start a Pry REPL. This method only differs from Pry.start in that it
3
+ # assumes that the target is `self`. It also accepts and passses the same
4
+ # exact options hash that Pry.start accepts. POSSIBLE DEPRICATION WARNING:
5
+ # In the future the backwards compatibility with pry(binding) could be
6
+ # removed so please properly use Object.pry or if you use pry(binding)
7
+ # switch Pry.start(binding).
8
+
9
+ # @param [Binding] the binding or options hash if no binding needed.
10
+ # @param [Hash] the options hash.
11
+ # @example First
14
12
  # "dummy".pry
15
- # @example Second form
16
- # pry "dummy"
17
- # @example Start a Pry session on current self (whatever that is)
18
- # pry
19
- def pry(target=self)
20
- Pry.start(target)
13
+ # @example Second
14
+ # binding.pry
15
+ # @example An example with options
16
+ # def my_method
17
+ # binding.pry :quiet => true
18
+ # end
19
+ # my_method()
20
+
21
+ def pry(*args)
22
+ if args.first.is_a?(Hash) || args.length == 0
23
+ args.unshift(self)
24
+ end
25
+
26
+ Pry.start(*args)
21
27
  end
22
28
 
23
29
  # Return a binding object for the receiver.
@@ -1,5 +1,6 @@
1
1
  require "pry/default_commands/ls"
2
2
  require "pry/default_commands/cd"
3
+ require "pry/default_commands/find_method"
3
4
 
4
5
  class Pry
5
6
  module DefaultCommands
@@ -7,24 +8,62 @@ class Pry
7
8
  Context = Pry::CommandSet.new do
8
9
  import Ls
9
10
  import Cd
11
+ import FindMethod
10
12
 
11
- command "whereami", "Show the code context for the session. (whereami <n> shows <n> extra lines of code around the invocation line. Default: 5)" do |num|
12
- file, line_num = file_and_line_from_binding(target)
13
- i_num = num ? num.to_i : 5
13
+ create_command "whereami" do
14
+ description "Show code surrounding the current context."
15
+ banner <<-BANNER
16
+ Usage: whereami [OPTIONS]
17
+ BANNER
14
18
 
15
- if file != Pry.eval_path && (file =~ /(\(.*\))|<.*>/ || file == "" || file == "-e")
16
- raise CommandError, "Cannot find local context. Did you use binding.pry?"
19
+ def setup
20
+ @method = Pry::Method.from_binding(target)
17
21
  end
18
22
 
19
- set_file_and_dir_locals(file)
23
+ def process
24
+ if show_method?
25
+ file = @method.source_file
26
+ start = @method.source_range.begin
27
+ finish = @method.source_range.end
28
+ marker = target.eval("__LINE__")
29
+ else
30
+ file = target.eval("__FILE__")
31
+ start = target.eval("__LINE__")
32
+ finish = (args.first && args.first.to_i) || 5
33
+ marker = start
34
+ end
35
+
36
+ if invalid_file?(file)
37
+ raise Pry::CommandError,
38
+ "Cannot find local context. Did you use binding.pry?"
39
+ end
20
40
 
21
- method = Pry::Method.from_binding(target)
22
- method_description = method ? " in #{method.name_with_owner}" : ""
23
- output.puts "\n#{text.bold('From:')} #{file} @ line #{line_num}#{method_description}:\n\n"
41
+ # TODO: refactor.
42
+ if show_method?
43
+ code = Pry::Code.from_file(file).between(start, finish)
44
+ else
45
+ code = Pry::Code.from_file(file).around(start, finish)
46
+ end
47
+
48
+ desc = (@method && @method.name_with_owner) || ""
49
+
50
+ if !code.empty?
51
+ output.puts "\n#{text.bold('From:')} #{file} @ line #{start} #{desc}:\n\n"
52
+ output.puts code.with_line_numbers.with_marker(marker)
53
+ output.puts
54
+ end
55
+ end
24
56
 
25
- code = Pry::Code.from_file(file).around(line_num, i_num)
26
- output.puts code.with_line_numbers.with_marker(line_num)
27
- output.puts
57
+ private
58
+
59
+ def show_method?
60
+ args.empty? && @method && !@method.instance_of?(Pry::Method::Disowned) && @method.source_range.count < 20
61
+ end
62
+
63
+ def invalid_file?(file)
64
+ file != Pry.eval_path &&
65
+ (file =~ /(\(.*\))|<.*>/ || file == "" || file == "-e")
66
+ end
28
67
  end
29
68
 
30
69
  create_command "pry-backtrace", "Show the backtrace for the Pry session." do
@@ -87,6 +126,27 @@ class Pry
87
126
  end
88
127
  end
89
128
 
129
+ # N.B. using a regular expresion here so that "raise-up 'foo'" does the right thing.
130
+ create_command /raise-up(!?\b.*)/, :listing => 'raise-up' do
131
+ description "Raise an exception out of the current pry instance."
132
+ banner <<-BANNER
133
+ Raise up, like exit, allows you to quit pry. Instead of returning a value however, it raises an exception.
134
+ If you don't provide the exception to be raised, it will use the most recent exception (in pry _ex_).
135
+
136
+ e.g. `raise-up "get-me-out-of-here"` is equivalent to:
137
+ `raise "get-me-out-of-here"
138
+ raise-up`
139
+
140
+ When called as raise-up! (with an exclamation mark), this command raises the exception through
141
+ any nested prys you have created by "cd"ing into objects.
142
+ BANNER
143
+
144
+ def process
145
+ return stagger_output help if captures[0] =~ /(-h|--help)\b/
146
+ # Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble' in a rubyesque manner
147
+ target.eval("_pry_.raise_up#{captures[0]}")
148
+ end
149
+ end
90
150
  end
91
151
  end
92
152
  end
@@ -3,6 +3,10 @@ class Pry
3
3
 
4
4
  EasterEggs = Pry::CommandSet.new do
5
5
 
6
+ command "nyan-cat", "", :requires_gem => ["nyancat"] do
7
+ run ".nyancat"
8
+ end
9
+
6
10
  command(/!s\/(.*?)\/(.*?)/, "") do |source, dest|
7
11
  eval_string.gsub!(/#{source}/) { dest }
8
12
  run "show-input"
@@ -88,8 +88,9 @@ class Pry
88
88
  temp_file do |f|
89
89
  f.puts(content)
90
90
  f.flush
91
- invoke_editor(f.path, line)
92
- if !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
91
+ reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
92
+ invoke_editor(f.path, line, reload)
93
+ if reload
93
94
  silence_warnings do
94
95
  eval_string.replace(File.read(f.path))
95
96
  end
@@ -138,10 +139,11 @@ class Pry
138
139
 
139
140
  line = opts[:l].to_i if opts.present?(:line)
140
141
 
141
- invoke_editor(file_name, line)
142
+ reload = opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:'no-reload')) && !Pry.config.disable_auto_reload
143
+ invoke_editor(file_name, line, reload)
142
144
  set_file_and_dir_locals(file_name)
143
145
 
144
- if opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:'no-reload')) && !Pry.config.disable_auto_reload
146
+ if reload
145
147
  silence_warnings do
146
148
  TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
147
149
  end
@@ -202,17 +204,12 @@ class Pry
202
204
  def process_patch
203
205
  lines = @method.source.lines.to_a
204
206
 
205
- if ((original_name = @method.original_name) &&
206
- lines[0] =~ /^def (?:.*?\.)?#{original_name}(?=[\(\s;]|$)/)
207
- lines[0] = "def #{original_name}#{$'}"
208
- else
209
- raise CommandError, "Pry can only patch methods created with the `def` keyword."
210
- end
207
+ lines[0] = definition_line_for_owner(lines[0])
211
208
 
212
209
  temp_file do |f|
213
210
  f.puts lines.join
214
211
  f.flush
215
- invoke_editor(f.path, 0)
212
+ invoke_editor(f.path, 0, true)
216
213
 
217
214
  if @method.alias?
218
215
  with_method_transaction(original_name, @method.owner) do
@@ -228,9 +225,10 @@ class Pry
228
225
  def process_file
229
226
  file, line = extract_file_and_line
230
227
 
231
- invoke_editor(file, opts["no-jump"] ? 0 : line)
228
+ reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
229
+ invoke_editor(file, opts["no-jump"] ? 0 : line, reload)
232
230
  silence_warnings do
233
- load file unless opts.present?(:'no-reload') || Pry.config.disable_auto_reload
231
+ load file if reload
234
232
  end
235
233
  end
236
234
 
@@ -257,6 +255,34 @@ class Pry
257
255
  ensure
258
256
  target.eval("undef #{temp_name}") rescue nil
259
257
  end
258
+
259
+ # The original name of the method, if it's not present raise an error telling
260
+ # the user why we don't work.
261
+ #
262
+ def original_name
263
+ @method.original_name or raise CommandError, "Pry can only patch methods created with the `def` keyword."
264
+ end
265
+
266
+ # Update the definition line so that it can be eval'd directly on the Method's
267
+ # owner instead of from the original context.
268
+ #
269
+ # In particular this takes `def self.foo` and turns it into `def foo` so that we
270
+ # don't end up creating the method on the singleton class of the singleton class
271
+ # by accident.
272
+ #
273
+ # This is necessarily done by String manipulation because we can't find out what
274
+ # syntax is needed for the argument list by ruby-level introspection.
275
+ #
276
+ # @param String The original definition line. e.g. def self.foo(bar, baz=1)
277
+ # @return String The new definition line. e.g. def foo(bar, baz=1)
278
+ #
279
+ def definition_line_for_owner(line)
280
+ if line =~ /^def (?:.*?\.)?#{Regexp.escape(original_name)}(?=[\(\s;]|$)/
281
+ "def #{original_name}#{$'}"
282
+ else
283
+ raise CommandError, "Could not find original `def #{original_name}` line to patch."
284
+ end
285
+ end
260
286
  end
261
287
 
262
288
  create_command(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/) do
@@ -301,6 +327,8 @@ class Pry
301
327
  end
302
328
 
303
329
  create_command "play" do
330
+ include Helpers::DocumentationHelpers
331
+
304
332
  description "Play back a string variable or a method or a file as input."
305
333
 
306
334
  banner <<-BANNER
@@ -343,7 +371,7 @@ class Pry
343
371
  self.content << File.read(File.expand_path(file))
344
372
  end
345
373
  opt.on :l, :lines, "Only play a subset of lines.", :optional => true, :as => Range, :default => 1..-1
346
- opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range.", :optional => true,
374
+ opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range. Note this can only replay pure Ruby code, not Pry commands.", :optional => true,
347
375
  :as => Range, :default => -5..-1 do |range|
348
376
  input_expressions = _pry_.input_array[range] || []
349
377
  Array(input_expressions).each { |v| self.content << v }
@@ -353,7 +381,7 @@ class Pry
353
381
 
354
382
  def process
355
383
  perform_play
356
- run "show-input" unless _pry_.complete_expression?(eval_string)
384
+ run "show-input" unless Pry::Code.complete_expression?(eval_string)
357
385
  end
358
386
 
359
387
  def process_non_opt
@@ -0,0 +1,171 @@
1
+ class Pry
2
+ module DefaultCommands
3
+ FindMethod = Pry::CommandSet.new do
4
+
5
+ create_command "find-method" do
6
+ extend Helpers::BaseHelpers
7
+
8
+ group "Context"
9
+
10
+ options :requires_gem => "ruby18_source_location" if mri_18?
11
+
12
+ description "Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]"
13
+
14
+ banner <<-BANNER
15
+ Usage: find-method [-n | -c] METHOD [NAMESPACE]
16
+
17
+ Recursively search for a method within a Class/Module or the current namespace.
18
+ Use the `-n` switch (the default) to search for methods whose name matches the given regex.
19
+ Use the `-c` switch to search for methods that contain the given code.
20
+
21
+ e.g find re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc.
22
+ e.g find -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace.
23
+ BANNER
24
+
25
+ def setup
26
+ require 'ruby18_source_location' if mri_18?
27
+ end
28
+
29
+ def options(opti)
30
+ opti.on :n, :name, "Search for a method by name"
31
+ opti.on :c, :content, "Search for a method based on content in Regex form"
32
+ end
33
+
34
+ def process
35
+ return if args.size < 1
36
+ pattern = ::Regexp.new args[0]
37
+ if args[1]
38
+ klass = target.eval(args[1])
39
+ if !klass.is_a?(Module)
40
+ klass = klass.class
41
+ end
42
+ else
43
+ klass = (target_self.is_a?(Module)) ? target_self : target_self.class
44
+ end
45
+
46
+ matches = if opts.content?
47
+ content_search(pattern, klass)
48
+ else
49
+ name_search(pattern, klass)
50
+ end
51
+
52
+ if matches.empty?
53
+ output.puts text.bold("No Methods Matched")
54
+ else
55
+ print_matches(matches, pattern)
56
+ end
57
+
58
+ end
59
+
60
+ private
61
+
62
+ # pretty-print a list of matching methods.
63
+ #
64
+ # @param Array[Method]
65
+ def print_matches(matches, pattern)
66
+ grouped = matches.group_by(&:owner)
67
+ order = grouped.keys.sort_by{ |x| x.name || x.to_s }
68
+
69
+ order.each do |klass|
70
+ output.puts text.bold(klass.name)
71
+ grouped[klass].each do |method|
72
+ header = method.name_with_owner
73
+
74
+ extra = if opts.content?
75
+ header += ": "
76
+ colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}"))
77
+ else
78
+ ""
79
+ end
80
+
81
+ output.puts header + extra
82
+ end
83
+ end
84
+ end
85
+
86
+ # Run the given block against every constant in the provided namespace.
87
+ #
88
+ # @param Module The namespace in which to start the search.
89
+ # @param Hash[Module,Boolean] The namespaces we've already visited (private)
90
+ # @yieldparam klazz Each class/module in the namespace.
91
+ #
92
+ def recurse_namespace(klass, done={}, &block)
93
+ if done[klass] || !(Module === klass)
94
+ return
95
+ end
96
+
97
+ done[klass] = true
98
+
99
+ yield klass
100
+
101
+ klass.constants.each do |name|
102
+ next if klass.autoload?(name)
103
+ begin
104
+ const = klass.const_get(name)
105
+ rescue RescuableException => e
106
+ # constant loading is an inexact science at the best of times,
107
+ # this often happens when a constant was .autoload? but someone
108
+ # tried to load it. It's now not .autoload? but will still raise
109
+ # a NameError when you access it.
110
+ else
111
+ recurse_namespace(const, done, &block)
112
+ end
113
+ end
114
+ end
115
+
116
+ # Gather all the methods in a namespace that pass the given block.
117
+ #
118
+ # @param Module The namespace in which to search.
119
+ # @yieldparam Method The method to test
120
+ # @yieldreturn Boolean
121
+ # @return Array[Method]
122
+ #
123
+ def search_all_methods(namespace)
124
+ done = Hash.new{ |h,k| h[k] = {} }
125
+ matches = []
126
+
127
+ recurse_namespace(namespace) do |klass|
128
+ (Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).each do |method|
129
+ next if done[method.owner][method.name]
130
+ done[method.owner][method.name] = true
131
+
132
+ matches << method if yield method
133
+ end
134
+ end
135
+
136
+ matches
137
+ end
138
+
139
+ # Search for all methods with a name that matches the given regex
140
+ # within a namespace.
141
+ #
142
+ # @param Regex The regex to search for
143
+ # @param Module The namespace to search
144
+ # @return Array[Method]
145
+ #
146
+ def name_search(regex, namespace)
147
+ search_all_methods(namespace) do |meth|
148
+ meth.name =~ regex
149
+ end
150
+ end
151
+
152
+ # Search for all methods who's implementation matches the given regex
153
+ # within a namespace.
154
+ #
155
+ # @param Regex The regex to search for
156
+ # @param Module The namespace to search
157
+ # @return Array[Method]
158
+ #
159
+ def content_search(regex, namespace)
160
+ search_all_methods(namespace) do |meth|
161
+ begin
162
+ meth.source =~ regex
163
+ rescue RescuableException => e
164
+ false
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end