pry 0.9.7.4 → 0.9.8pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +1 -3
  2. data/README.markdown +3 -1
  3. data/Rakefile +48 -31
  4. data/bin/pry +2 -80
  5. data/lib/pry.rb +17 -20
  6. data/lib/pry/cli.rb +152 -0
  7. data/lib/pry/command_processor.rb +13 -0
  8. data/lib/pry/command_set.rb +102 -9
  9. data/lib/pry/config.rb +28 -6
  10. data/lib/pry/default_commands/context.rb +9 -8
  11. data/lib/pry/default_commands/documentation.rb +55 -13
  12. data/lib/pry/default_commands/easter_eggs.rb +1 -1
  13. data/lib/pry/default_commands/input.rb +25 -25
  14. data/lib/pry/default_commands/introspection.rb +19 -18
  15. data/lib/pry/default_commands/ls.rb +23 -38
  16. data/lib/pry/default_commands/shell.rb +47 -15
  17. data/lib/pry/helpers/command_helpers.rb +28 -6
  18. data/lib/pry/helpers/options_helpers.rb +7 -4
  19. data/lib/pry/helpers/text.rb +23 -3
  20. data/lib/pry/history.rb +55 -17
  21. data/lib/pry/history_array.rb +2 -0
  22. data/lib/pry/hooks.rb +108 -0
  23. data/lib/pry/indent.rb +9 -5
  24. data/lib/pry/method.rb +99 -50
  25. data/lib/pry/plugins.rb +10 -2
  26. data/lib/pry/pry_class.rb +48 -20
  27. data/lib/pry/pry_instance.rb +106 -91
  28. data/lib/pry/version.rb +1 -1
  29. data/lib/pry/wrapped_module.rb +73 -0
  30. data/man/pry.1 +195 -0
  31. data/man/pry.1.html +204 -0
  32. data/man/pry.1.ronn +141 -0
  33. data/pry.gemspec +21 -24
  34. data/test/helper.rb +12 -3
  35. data/test/test_cli.rb +78 -0
  36. data/test/test_command_set.rb +193 -1
  37. data/test/test_default_commands/test_context.rb +19 -4
  38. data/test/test_default_commands/test_input.rb +2 -2
  39. data/test/test_default_commands/test_introspection.rb +63 -6
  40. data/test/test_default_commands/test_ls.rb +8 -35
  41. data/test/test_default_commands/test_shell.rb +36 -1
  42. data/test/test_hooks.rb +175 -0
  43. data/test/test_indent.rb +2 -0
  44. data/test/test_method.rb +10 -0
  45. data/test/test_pry.rb +35 -34
  46. data/test/test_pry_history.rb +24 -24
  47. data/test/test_syntax_checking.rb +47 -0
  48. data/test/test_wrapped_module.rb +71 -0
  49. metadata +40 -34
data/lib/pry/history.rb CHANGED
@@ -1,61 +1,99 @@
1
1
  class Pry
2
2
  # The History class is responsible for maintaining the user's input history, both
3
- # internally and within Readline::HISTORY.
3
+ # internally and within Readline.
4
4
  class History
5
+ attr_accessor :loader, :saver, :pusher, :clearer
6
+
5
7
  def initialize
6
8
  @history = []
7
9
  @saved_lines = 0
10
+ restore_default_behavior
11
+ end
12
+
13
+ # Assign the default methods for loading, saving, pushing, and clearing.
14
+ def restore_default_behavior
15
+ @loader = method(:read_from_file)
16
+ @saver = method(:write_to_file)
17
+ @pusher = method(:push_to_readline)
18
+ @clearer = method(:clear_readline)
8
19
  end
9
20
 
10
- # Loads a file's contents into the input history.
11
- # @param [String] filename
21
+ # Load the input history using `History.loader`.
12
22
  # @return [Integer] The number of lines loaded
13
- def load(filename)
14
- File.foreach(filename) do |line|
15
- Readline::HISTORY << line.chomp
23
+ def load
24
+ @loader.call do |line|
25
+ @pusher.call(line.chomp)
16
26
  @history << line.chomp
17
27
  end
18
28
  @saved_lines = @history.length
19
29
  end
20
30
 
21
- # Appends input history from this session to a file.
22
- # @param [String] filename
31
+ # Write this session's history using `History.saver`.
23
32
  # @return [Integer] The number of lines saved
24
- def save(filename)
33
+ def save
25
34
  history_to_save = @history[@saved_lines..-1]
26
- File.open(filename, 'a') do |f|
27
- history_to_save.each { |ln| f.puts ln }
28
- end
35
+ @saver.call(history_to_save)
29
36
  @saved_lines = @history.length
30
37
  history_to_save.length
31
38
  end
32
39
 
33
- # Adds a line to the input history, ignoring blank and duplicate lines.
40
+ # Add a line to the input history, ignoring blank and duplicate lines.
34
41
  # @param [String] line
35
42
  # @return [String] The same line that was passed in
36
43
  def push(line)
37
44
  unless line.empty? || (@history.last && line == @history.last)
38
- Readline::HISTORY << line
45
+ @pusher.call(line)
39
46
  @history << line
40
47
  end
41
48
  line
42
49
  end
43
50
  alias << push
44
51
 
45
- # Clears all history. Anything the user entered before this point won't be
52
+ # Clear all history. Anything the user entered before this point won't be
46
53
  # saved, but anything they put in afterwards will still be appended to the
47
54
  # history file on exit.
48
55
  def clear
49
- Readline::HISTORY.shift until Readline::HISTORY.empty?
56
+ @clearer.call
50
57
  @history = []
51
58
  @saved_lines = 0
52
59
  end
53
60
 
54
- # Returns an Array containing all stored history.
61
+ # Return an Array containing all stored history.
55
62
  # @return [Array<String>] An Array containing all lines of history loaded
56
63
  # or entered by the user in the current session.
57
64
  def to_a
58
65
  @history.dup
59
66
  end
67
+
68
+ private
69
+ # The default loader. Yields lines from `Pry.history.config.file`.
70
+ def read_from_file
71
+ history_file = File.expand_path(Pry.config.history.file)
72
+
73
+ if File.exists?(history_file)
74
+ File.foreach(history_file) { |line| yield(line) }
75
+ end
76
+ end
77
+
78
+ # The default saver. Appends the given lines to `Pry.history.config.file`.
79
+ # @param [Array<String>] lines
80
+ def write_to_file(lines)
81
+ history_file = File.expand_path(Pry.config.history.file)
82
+
83
+ File.open(history_file, 'a') do |f|
84
+ lines.each { |ln| f.puts ln }
85
+ end
86
+ end
87
+
88
+ # The default pusher. Appends the given line to Readline::HISTORY.
89
+ # @param [String] line
90
+ def push_to_readline(line)
91
+ Readline::HISTORY << line
92
+ end
93
+
94
+ # The default clearer. Clears Readline::HISTORY.
95
+ def clear_readline
96
+ Readline::HISTORY.shift until Readline::HISTORY.empty?
97
+ end
60
98
  end
61
99
  end
@@ -72,6 +72,8 @@ class Pry
72
72
  def size
73
73
  @count
74
74
  end
75
+ alias count size
76
+ alias length size
75
77
 
76
78
  def empty?
77
79
  size == 0
data/lib/pry/hooks.rb ADDED
@@ -0,0 +1,108 @@
1
+ class Pry
2
+ class Hooks
3
+
4
+ def initialize
5
+ @hooks = {}
6
+ end
7
+
8
+ # Add a new hook to be executed for the `name` even.
9
+ # @param [Symbol] event_name The name of the event.
10
+ # @param [Symbol] hook_name The name of the hook.
11
+ # @param [#call] callable The callable.
12
+ # @yield The block to use as the callable (if `callable` parameter not provided)
13
+ def add_hook(event_name, hook_name, callable=nil, &block)
14
+ @hooks[event_name] ||= []
15
+
16
+ # do not allow duplicates
17
+ raise ArgumentError, "Hook with name '#{hook_name}' already defined!" if hook_exists?(event_name, hook_name)
18
+
19
+ if block
20
+ @hooks[event_name] << [hook_name, block]
21
+ elsif callable
22
+ @hooks[event_name] << [hook_name, callable]
23
+ else
24
+ raise ArgumentError, "Must provide a block or callable."
25
+ end
26
+
27
+ self
28
+ end
29
+
30
+ # Execute the list of hooks for the `event_name` event.
31
+ # @param [Symbol] event_name The name of the event.
32
+ # @param [Array] args The arguments to pass to each hook function.
33
+ # @return [Object] The return value of the last executed hook.
34
+ def exec_hook(event_name, *args, &block)
35
+ @hooks[event_name] ||= []
36
+
37
+ # silence warnings to get rid of 1.8's "warning: multiple values
38
+ # for a block parameter" warnings
39
+ Pry::Helpers::BaseHelpers.silence_warnings do
40
+ @hooks[event_name].map { |hook_name, callable| callable.call(*args, &block) }.last
41
+ end
42
+ end
43
+
44
+ # Return the number of hook functions registered for the `event_name` event.
45
+ # @param [Symbol] event_name The name of the event.
46
+ # @return [Fixnum] The number of hook functions for `event_name`.
47
+ def hook_count(event_name)
48
+ @hooks[event_name] ||= []
49
+ @hooks[event_name].size
50
+ end
51
+
52
+ # Return a specific hook for a given event.
53
+ # @param [Symbol] event_name The name of the event.
54
+ # @param [Symbol[ hook_name The name of the hook
55
+ # @return [#call] The requested hook.
56
+ def get_hook(event_name, hook_name)
57
+ @hooks[event_name] ||= []
58
+ hook = @hooks[event_name].find { |current_hook_name, callable| current_hook_name == hook_name }
59
+ hook.last if hook
60
+ end
61
+
62
+ # Return the hash of hook names / hook functions for a
63
+ # given event. (Note that modifying the returned hash does not
64
+ # alter the hooks, use add_hook/delete_hook for that).
65
+ # @param [Symbol] event_name The name of the event.
66
+ # @return [Hash] The hash of hook names / hook functions.
67
+ def get_hooks(event_name)
68
+ @hooks[event_name] ||= []
69
+ Hash[@hooks[event_name]]
70
+ end
71
+
72
+ # Delete a hook for an event.
73
+ # @param [Symbol] event_name The name of the event.
74
+ # @param [Symbol] hook_name The name of the hook.
75
+ # to delete.
76
+ # @return [#call] The deleted hook.
77
+ def delete_hook(event_name, hook_name)
78
+ @hooks[event_name] ||= []
79
+ deleted_callable = nil
80
+
81
+ @hooks[event_name].delete_if do |current_hook_name, callable|
82
+ if current_hook_name == hook_name
83
+ deleted_callable = callable
84
+ true
85
+ else
86
+ false
87
+ end
88
+ end
89
+ deleted_callable
90
+ end
91
+
92
+ # Clear all hooks functions for a given event.
93
+ # @param [String] event_name The name of the event.
94
+ def clear(event_name)
95
+ @hooks[event_name] = []
96
+ end
97
+
98
+ # @param [Symbol] event_name Name of the event.
99
+ # @param [Symbol] hook_name Name of the hook.
100
+ # @return [Boolean] Whether the hook by the name `hook_name`
101
+ # is defined for the event.
102
+ def hook_exists?(event_name, hook_name)
103
+ !!@hooks[event_name].find { |name, _| name == hook_name }
104
+ end
105
+ private :hook_exists?
106
+
107
+ end
108
+ end
data/lib/pry/indent.rb CHANGED
@@ -43,7 +43,11 @@ class Pry
43
43
  # Collection of token types that should be ignored. Without this list
44
44
  # keywords such as "class" inside strings would cause the code to be
45
45
  # indented incorrectly.
46
- IGNORE_TOKENS = [:space, :content, :string, :delimiter, :method, :ident]
46
+ #
47
+ # :pre_constant and :preserved_constant are the CodeRay 0.9.8 and 1.0.0
48
+ # classifications of "true", "false", and "nil".
49
+ IGNORE_TOKENS = [:space, :content, :string, :delimiter, :method, :ident,
50
+ :constant, :pre_constant, :predefined_constant]
47
51
 
48
52
  # Tokens that indicate the end of a statement (i.e. that, if they appear
49
53
  # directly before an "if" indicates that that if applies to the same line,
@@ -199,11 +203,11 @@ class Pry
199
203
  end
200
204
 
201
205
  if defined?(Win32::Console)
202
- move_up = "\e[#{lines}F"
203
- move_down = "\e[#{lines}E"
206
+ move_up = "\e[#{lines}F"
207
+ move_down = "\e[#{lines}E"
204
208
  else
205
- move_up = "\e[#{lines}A\e[0G"
206
- move_down = "\e[#{lines}B\e[0G"
209
+ move_up = "\e[#{lines}A\e[0G"
210
+ move_down = "\e[#{lines}B\e[0G"
207
211
  end
208
212
  whitespace = ' ' * overhang
209
213
 
data/lib/pry/method.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  class Pry
2
3
  class Method
3
4
  include RbxMethod if Helpers::BaseHelpers.rbx?
@@ -48,7 +49,11 @@ class Pry
48
49
  if [:__script__, nil, :__binding__, :__binding_impl__].include?(meth_name)
49
50
  nil
50
51
  else
51
- new(b.eval("method(:#{meth_name})"))
52
+ begin
53
+ new(b.eval("method(#{meth_name.to_s.inspect})"))
54
+ rescue NameError, NoMethodError
55
+ Disowned.new(b.eval('self'), meth_name.to_s)
56
+ end
52
57
  end
53
58
  end
54
59
 
@@ -128,7 +133,7 @@ class Pry
128
133
  end
129
134
 
130
135
  # Acts like send but ignores any methods defined below Object or Class in the
131
- # inheritance heirarchy.
136
+ # inheritance hierarchy.
132
137
  # This is required to introspect methods on objects like Net::HTTP::Get that
133
138
  # have overridden the `method` method.
134
139
  def safe_send(obj, method, *args, &block)
@@ -166,6 +171,27 @@ class Pry
166
171
  @method.name.to_s
167
172
  end
168
173
 
174
+ # Get the owner of the method as a Pry::Module
175
+ # @return [Pry::Module]
176
+ def wrapped_owner
177
+ @wrapped_owner ||= Pry::WrappedModule.new(owner)
178
+ end
179
+
180
+ # Is the method undefined? (aka `Disowned`)
181
+ # @return [Boolean] false
182
+ def undefined?
183
+ false
184
+ end
185
+
186
+ # Get the name of the method including the class on which it was defined.
187
+ # @example
188
+ # method(:puts).method_name
189
+ # => "Kernel.puts"
190
+ # @return [String]
191
+ def name_with_owner
192
+ "#{wrapped_owner.method_prefix}#{name}"
193
+ end
194
+
169
195
  # @return [String, nil] The source code of the method, or `nil` if it's unavailable.
170
196
  def source
171
197
  @source ||= case source_type
@@ -174,11 +200,11 @@ class Pry
174
200
  if info and info.source
175
201
  code = strip_comments_from_c_code(info.source)
176
202
  end
177
- when :rbx
178
- strip_leading_whitespace(core_code)
179
203
  when :ruby
180
- if pry_method?
181
- code = Pry.new(:input => StringIO.new(Pry.line_buffer[source_line..-1].join), :prompt => proc {""}, :hooks => {}).r
204
+ if Helpers::BaseHelpers.rbx? && core?
205
+ code = core_code
206
+ elsif pry_method?
207
+ code = Pry.new(:input => StringIO.new(Pry.line_buffer[source_line..-1].join), :prompt => proc {""}, :hooks => Pry::Hooks.new).r
182
208
  else
183
209
  code = @method.source
184
210
  end
@@ -194,10 +220,10 @@ class Pry
194
220
  when :c
195
221
  info = pry_doc_info
196
222
  info.docstring if info
197
- when :rbx
198
- strip_leading_hash_and_whitespace_from_ruby_comments(core_doc)
199
223
  when :ruby
200
- if pry_method?
224
+ if Helpers::BaseHelpers.rbx? && core?
225
+ strip_leading_hash_and_whitespace_from_ruby_comments(core_doc)
226
+ elsif pry_method?
201
227
  raise CommandError, "Can't view doc for a REPL-defined method."
202
228
  else
203
229
  strip_leading_hash_and_whitespace_from_ruby_comments(@method.comment)
@@ -206,14 +232,9 @@ class Pry
206
232
  end
207
233
 
208
234
  # @return [Symbol] The source type of the method. The options are
209
- # `:ruby` for ordinary Ruby methods, `:c` for methods written in
210
- # C, or `:rbx` for Rubinius core methods written in Ruby.
235
+ # `:ruby` for Ruby methods or `:c` for methods written in C.
211
236
  def source_type
212
- if Helpers::BaseHelpers.rbx?
213
- if core? then :rbx else :ruby end
214
- else
215
- if source_location.nil? then :c else :ruby end
216
- end
237
+ source_location.nil? ? :c : :ruby
217
238
  end
218
239
 
219
240
  # @return [String, nil] The name of the file the method is defined in, or
@@ -286,43 +307,11 @@ class Pry
286
307
  Pry::Method.new(sup) if sup
287
308
  end
288
309
 
289
- # @return [Symbol, nil] The original name the method was defined under,
310
+ # @return [String, nil] The original name the method was defined under,
290
311
  # before any aliasing, or `nil` if it can't be determined.
291
312
  def original_name
292
313
  return nil if source_type != :ruby
293
-
294
- first_line = source.lines.first
295
- return nil if first_line.strip !~ /^def /
296
-
297
- if RUBY_VERSION =~ /^1\.9/ && RUBY_ENGINE == "ruby"
298
- require 'ripper'
299
-
300
- # Ripper is ok with an extraneous end, so we don't need to worry about
301
- # whether it's a one-liner.
302
- tree = Ripper::SexpBuilder.new(first_line + ";end").parse
303
-
304
- name = tree.flatten(2).each do |lst|
305
- break lst[1] if lst[0] == :@ident
306
- end
307
-
308
- name.is_a?(String) ? name : nil
309
- else
310
- require 'ruby_parser'
311
-
312
- # RubyParser breaks if there's an extra end, so we'll just rescue
313
- # and try again.
314
- tree = begin
315
- RubyParser.new.parse(first_line + ";end")
316
- rescue Racc::ParseError
317
- RubyParser.new.parse(first_line)
318
- end
319
-
320
- name = tree.each_cons(2) do |a, b|
321
- break a if b.is_a?(Array) && b.first == :args
322
- end
323
-
324
- name.is_a?(Symbol) ? name.to_s : nil
325
- end
314
+ method_name_from_first_line(source.lines.first)
326
315
  end
327
316
 
328
317
  # @return [Boolean] Was the method defined outside a source file?
@@ -409,5 +398,65 @@ class Pry
409
398
  end
410
399
  next_owner.instance_method(name) rescue nil
411
400
  end
401
+
402
+ # @param [String] first_ln The first line of a method definition.
403
+ # @return [String, nil]
404
+ def method_name_from_first_line(first_ln)
405
+ return nil if first_ln.strip !~ /^def /
406
+
407
+ tokens = CodeRay.scan(first_ln, :ruby)
408
+ tokens = tokens.tokens.each_slice(2) if tokens.respond_to?(:tokens)
409
+ tokens.each_cons(2) do |t1, t2|
410
+ if t2.last == :method || t2.last == :ident && t1 == [".", :operator]
411
+ return t2.first
412
+ end
413
+ end
414
+
415
+ nil
416
+ end
417
+
418
+ # A Disowned Method is one that's been removed from the class on which it was defined.
419
+ #
420
+ # e.g.
421
+ # class C
422
+ # def foo
423
+ # C.send(:undefine_method, :foo)
424
+ # Pry::Method.from_binding(binding)
425
+ # end
426
+ # end
427
+ #
428
+ # In this case we assume that the "owner" is the singleton class of the receiver.
429
+ #
430
+ # This occurs mainly in Sinatra applications.
431
+ class Disowned < Method
432
+ attr_reader :receiver, :name
433
+
434
+ # Create a new Disowned method.
435
+ #
436
+ # @param [Object] receiver
437
+ # @param [String] method_name
438
+ def initialize(*args)
439
+ @receiver, @name = *args
440
+ end
441
+
442
+ # Is the method undefined? (aka `Disowned`)
443
+ # @return [Boolean] true
444
+ def undefined?
445
+ true
446
+ end
447
+
448
+ # Get the hypothesized owner of the method.
449
+ #
450
+ # @return [Object]
451
+ def owner
452
+ class << receiver; self; end
453
+ end
454
+
455
+ # Raise a more useful error message instead of trying to forward to nil.
456
+ def method_missing(meth_name, *args, &block)
457
+ raise "Cannot call '#{meth_name}' on an undef'd method." if method(:name).respond_to?(meth_name)
458
+ Object.instance_method(:method_missing).bind(self).call(meth_name, *args, &block)
459
+ end
460
+ end
412
461
  end
413
462
  end