pry 0.9.7.4 → 0.9.8pre2
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/.gitignore +1 -3
- data/README.markdown +3 -1
- data/Rakefile +48 -31
- data/bin/pry +2 -80
- data/lib/pry.rb +17 -20
- data/lib/pry/cli.rb +152 -0
- data/lib/pry/command_processor.rb +13 -0
- data/lib/pry/command_set.rb +102 -9
- data/lib/pry/config.rb +28 -6
- data/lib/pry/default_commands/context.rb +9 -8
- data/lib/pry/default_commands/documentation.rb +55 -13
- data/lib/pry/default_commands/easter_eggs.rb +1 -1
- data/lib/pry/default_commands/input.rb +25 -25
- data/lib/pry/default_commands/introspection.rb +19 -18
- data/lib/pry/default_commands/ls.rb +23 -38
- data/lib/pry/default_commands/shell.rb +47 -15
- data/lib/pry/helpers/command_helpers.rb +28 -6
- data/lib/pry/helpers/options_helpers.rb +7 -4
- data/lib/pry/helpers/text.rb +23 -3
- data/lib/pry/history.rb +55 -17
- data/lib/pry/history_array.rb +2 -0
- data/lib/pry/hooks.rb +108 -0
- data/lib/pry/indent.rb +9 -5
- data/lib/pry/method.rb +99 -50
- data/lib/pry/plugins.rb +10 -2
- data/lib/pry/pry_class.rb +48 -20
- data/lib/pry/pry_instance.rb +106 -91
- data/lib/pry/version.rb +1 -1
- data/lib/pry/wrapped_module.rb +73 -0
- data/man/pry.1 +195 -0
- data/man/pry.1.html +204 -0
- data/man/pry.1.ronn +141 -0
- data/pry.gemspec +21 -24
- data/test/helper.rb +12 -3
- data/test/test_cli.rb +78 -0
- data/test/test_command_set.rb +193 -1
- data/test/test_default_commands/test_context.rb +19 -4
- data/test/test_default_commands/test_input.rb +2 -2
- data/test/test_default_commands/test_introspection.rb +63 -6
- data/test/test_default_commands/test_ls.rb +8 -35
- data/test/test_default_commands/test_shell.rb +36 -1
- data/test/test_hooks.rb +175 -0
- data/test/test_indent.rb +2 -0
- data/test/test_method.rb +10 -0
- data/test/test_pry.rb +35 -34
- data/test/test_pry_history.rb +24 -24
- data/test/test_syntax_checking.rb +47 -0
- data/test/test_wrapped_module.rb +71 -0
- 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
|
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
|
-
#
|
11
|
-
# @param [String] filename
|
21
|
+
# Load the input history using `History.loader`.
|
12
22
|
# @return [Integer] The number of lines loaded
|
13
|
-
def load
|
14
|
-
|
15
|
-
|
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
|
-
#
|
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
|
33
|
+
def save
|
25
34
|
history_to_save = @history[@saved_lines..-1]
|
26
|
-
|
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
|
-
#
|
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
|
-
|
45
|
+
@pusher.call(line)
|
39
46
|
@history << line
|
40
47
|
end
|
41
48
|
line
|
42
49
|
end
|
43
50
|
alias << push
|
44
51
|
|
45
|
-
#
|
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
|
-
|
56
|
+
@clearer.call
|
50
57
|
@history = []
|
51
58
|
@saved_lines = 0
|
52
59
|
end
|
53
60
|
|
54
|
-
#
|
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
|
data/lib/pry/history_array.rb
CHANGED
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
|
-
|
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
|
203
|
-
move_down
|
206
|
+
move_up = "\e[#{lines}F"
|
207
|
+
move_down = "\e[#{lines}E"
|
204
208
|
else
|
205
|
-
move_up
|
206
|
-
move_down
|
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
|
-
|
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
|
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
|
181
|
-
code =
|
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
|
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
|
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
|
-
|
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 [
|
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
|