pry 0.9.6.2 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/CHANGELOG +19 -1
- data/CONTRIBUTORS +22 -16
- data/Rakefile +12 -6
- data/bin/pry +15 -12
- data/lib/pry.rb +39 -28
- data/lib/pry/command_context.rb +1 -0
- data/lib/pry/command_processor.rb +9 -2
- data/lib/pry/command_set.rb +7 -0
- data/lib/pry/config.rb +8 -0
- data/lib/pry/default_commands/basic.rb +7 -10
- data/lib/pry/default_commands/context.rb +36 -26
- data/lib/pry/default_commands/documentation.rb +31 -123
- data/lib/pry/default_commands/gems.rb +9 -4
- data/lib/pry/default_commands/input.rb +21 -11
- data/lib/pry/default_commands/introspection.rb +79 -88
- data/lib/pry/default_commands/ls.rb +165 -176
- data/lib/pry/default_commands/shell.rb +8 -8
- data/lib/pry/extended_commands/experimental.rb +1 -5
- data/lib/pry/extended_commands/user_command_api.rb +17 -5
- data/lib/pry/helpers.rb +1 -0
- data/lib/pry/helpers/base_helpers.rb +5 -1
- data/lib/pry/helpers/command_helpers.rb +22 -241
- data/lib/pry/helpers/options_helpers.rb +58 -0
- data/lib/pry/helpers/text.rb +10 -4
- data/lib/pry/history.rb +1 -1
- data/lib/pry/indent.rb +205 -0
- data/lib/pry/method.rb +412 -0
- data/lib/pry/pry_class.rb +56 -15
- data/lib/pry/pry_instance.rb +63 -16
- data/lib/pry/rbx_method.rb +20 -0
- data/lib/pry/rbx_path.rb +34 -0
- data/lib/pry/version.rb +1 -1
- data/pry.gemspec +16 -16
- data/test/helper.rb +8 -4
- data/test/test_command_helpers.rb +1 -69
- data/test/test_command_set.rb +29 -28
- data/test/test_default_commands/test_documentation.rb +23 -10
- data/test/test_default_commands/test_introspection.rb +58 -13
- data/test/test_default_commands/test_ls.rb +148 -0
- data/test/test_indent.rb +234 -0
- data/test/test_input_stack.rb +13 -0
- data/test/test_method.rb +291 -0
- data/test/test_pry.rb +10 -1
- metadata +82 -65
data/lib/pry/history.rb
CHANGED
@@ -34,7 +34,7 @@ class Pry
|
|
34
34
|
# @param [String] line
|
35
35
|
# @return [String] The same line that was passed in
|
36
36
|
def push(line)
|
37
|
-
unless line.empty? || (@history.last && line
|
37
|
+
unless line.empty? || (@history.last && line == @history.last)
|
38
38
|
Readline::HISTORY << line
|
39
39
|
@history << line
|
40
40
|
end
|
data/lib/pry/indent.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'coderay'
|
2
|
+
|
3
|
+
class Pry
|
4
|
+
##
|
5
|
+
# Pry::Indent is a class that can be used to indent a number of lines
|
6
|
+
# containing Ruby code similar as to how IRB does it (but better). The class
|
7
|
+
# works by tokenizing a string using CodeRay and then looping over those
|
8
|
+
# tokens. Based on the tokens in a line of code that line (or the next one)
|
9
|
+
# will be indented or un-indented by correctly.
|
10
|
+
#
|
11
|
+
class Indent
|
12
|
+
# String containing the spaces to be inserted before the next line.
|
13
|
+
attr_reader :indent_level
|
14
|
+
|
15
|
+
# The amount of spaces to insert for each indent level.
|
16
|
+
SPACES = ' '
|
17
|
+
|
18
|
+
# Hash containing all the tokens that should increase the indentation
|
19
|
+
# level. The keys of this hash are open tokens, the values the matching
|
20
|
+
# tokens that should prevent a line from being indented if they appear on
|
21
|
+
# the same line.
|
22
|
+
OPEN_TOKENS = {
|
23
|
+
'def' => 'end',
|
24
|
+
'class' => 'end',
|
25
|
+
'module' => 'end',
|
26
|
+
'do' => 'end',
|
27
|
+
'if' => 'end',
|
28
|
+
'unless' => 'end',
|
29
|
+
'while' => 'end',
|
30
|
+
'until' => 'end',
|
31
|
+
'for' => 'end',
|
32
|
+
'case' => 'end',
|
33
|
+
'begin' => 'end',
|
34
|
+
'[' => ']',
|
35
|
+
'{' => '}',
|
36
|
+
'(' => ')'
|
37
|
+
}
|
38
|
+
|
39
|
+
# Which tokens can either be open tokens, or appear as modifiers on
|
40
|
+
# a single-line.
|
41
|
+
SINGLELINE_TOKENS = %w(if while until unless rescue)
|
42
|
+
|
43
|
+
# Collection of token types that should be ignored. Without this list
|
44
|
+
# keywords such as "class" inside strings would cause the code to be
|
45
|
+
# indented incorrectly.
|
46
|
+
IGNORE_TOKENS = [:space, :content, :string, :delimiter, :method, :ident]
|
47
|
+
|
48
|
+
# Tokens that indicate the end of a statement (i.e. that, if they appear
|
49
|
+
# directly before an "if" indicates that that if applies to the same line,
|
50
|
+
# not the next line)
|
51
|
+
STATEMENT_END_TOKENS = IGNORE_TOKENS + [:regexp, :integer, :float]
|
52
|
+
|
53
|
+
# Collection of tokens that should appear dedented even though they
|
54
|
+
# don't affect the surrounding code.
|
55
|
+
MIDWAY_TOKENS = %w(when else elsif rescue)
|
56
|
+
|
57
|
+
def initialize
|
58
|
+
reset
|
59
|
+
end
|
60
|
+
|
61
|
+
# reset internal state
|
62
|
+
def reset
|
63
|
+
@stack = []
|
64
|
+
@indent_level = ''
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Indents a string and returns it. This string can either be a single line
|
69
|
+
# or multiple ones.
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# str = <<TXT
|
73
|
+
# class User
|
74
|
+
# attr_accessor :name
|
75
|
+
# end
|
76
|
+
# TXT
|
77
|
+
#
|
78
|
+
# # This would result in the following being displayed:
|
79
|
+
# #
|
80
|
+
# # class User
|
81
|
+
# # attr_accessor :name
|
82
|
+
# # end
|
83
|
+
# #
|
84
|
+
# puts Pry::Indent.new.indent(str)
|
85
|
+
#
|
86
|
+
# @param [String] input The input string to indent.
|
87
|
+
# @return [String] The indented version of +input+.
|
88
|
+
#
|
89
|
+
def indent(input)
|
90
|
+
output = ''
|
91
|
+
open_tokens = OPEN_TOKENS.keys
|
92
|
+
prefix = indent_level
|
93
|
+
|
94
|
+
input.lines.each do |line|
|
95
|
+
tokens = CodeRay.scan(line, :ruby)
|
96
|
+
tokens = tokens.tokens.each_slice(2) if tokens.respond_to?(:tokens) # Coderay 1.0.0
|
97
|
+
|
98
|
+
before, after = indentation_delta(tokens)
|
99
|
+
|
100
|
+
before.times{ prefix.sub! SPACES, '' }
|
101
|
+
output += prefix + line.strip + "\n"
|
102
|
+
prefix += SPACES * after
|
103
|
+
end
|
104
|
+
|
105
|
+
@indent_level = prefix
|
106
|
+
|
107
|
+
return output.gsub(/\s+$/, '')
|
108
|
+
end
|
109
|
+
|
110
|
+
# Get the change in indentation indicated by the line.
|
111
|
+
#
|
112
|
+
# By convention, you remove indent from the line containing end tokens,
|
113
|
+
# but add indent to the line *after* that which contains the start tokens.
|
114
|
+
#
|
115
|
+
# This method returns a pair, where the first number is the number of closings
|
116
|
+
# on this line (i.e. the number of indents to remove before the line) and the
|
117
|
+
# second is the number of openings (i.e. the number of indents to add after
|
118
|
+
# this line)
|
119
|
+
#
|
120
|
+
# @param [Array] tokens A list of tokens to scan.
|
121
|
+
# @return [Array[Integer]]
|
122
|
+
#
|
123
|
+
def indentation_delta(tokens)
|
124
|
+
|
125
|
+
# We need to keep track of whether we've seen a "for" on this line because
|
126
|
+
# if the line ends with "do" then that "do" should be discounted (i.e. we're
|
127
|
+
# only opening one level not two) To do this robustly we want to keep track
|
128
|
+
# of the indent level at which we saw the for, so we can differentiate
|
129
|
+
# between "for x in [1,2,3] do" and "for x in ([1,2,3].map do" properly
|
130
|
+
seen_for_at = []
|
131
|
+
|
132
|
+
# When deciding whether an "if" token is the start of a multiline statement,
|
133
|
+
# or just the middle of a single-line if statement, we just look at the
|
134
|
+
# preceding token, which is tracked here.
|
135
|
+
last_token, last_kind = [nil, nil]
|
136
|
+
|
137
|
+
# delta keeps track of the total difference from the start of each line after
|
138
|
+
# the given token, 0 is just the level at which the current line started for
|
139
|
+
# reference.
|
140
|
+
remove_before, add_after = [0, 0]
|
141
|
+
|
142
|
+
# If the list of tokens contains a matching closing token the line should
|
143
|
+
# not be indented (and thus we should return true).
|
144
|
+
tokens.each do |token, kind|
|
145
|
+
is_singleline_if = (SINGLELINE_TOKENS.include?(token)) && end_of_statement?(last_token, last_kind)
|
146
|
+
is_optional_do = (token == "do" && seen_for_at.include?(add_after - 1))
|
147
|
+
|
148
|
+
last_token, last_kind = token, kind unless kind == :space
|
149
|
+
next if IGNORE_TOKENS.include?(kind)
|
150
|
+
|
151
|
+
seen_for_at << add_after if token == "for"
|
152
|
+
|
153
|
+
if OPEN_TOKENS.keys.include?(token) && !is_optional_do && !is_singleline_if
|
154
|
+
@stack << token
|
155
|
+
add_after += 1
|
156
|
+
elsif token == OPEN_TOKENS[@stack.last]
|
157
|
+
@stack.pop
|
158
|
+
if add_after == 0
|
159
|
+
remove_before += 1
|
160
|
+
else
|
161
|
+
add_after -= 1
|
162
|
+
end
|
163
|
+
elsif MIDWAY_TOKENS.include?(token)
|
164
|
+
if add_after == 0
|
165
|
+
remove_before += 1
|
166
|
+
add_after += 1
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
return [remove_before, add_after]
|
172
|
+
end
|
173
|
+
|
174
|
+
# If the code just before an "if" or "while" token on a line looks like the end of a statement,
|
175
|
+
# then we want to treat that "if" as a singleline, not multiline statement.
|
176
|
+
def end_of_statement?(last_token, last_kind)
|
177
|
+
(last_token =~ /^[)\]}\/]$/ || STATEMENT_END_TOKENS.include?(last_kind))
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return a string which, when printed, will rewrite the previous line with
|
181
|
+
# the correct indentation. Mostly useful for fixing 'end'.
|
182
|
+
#
|
183
|
+
# @param [String] full_line The full line of input, including the prompt.
|
184
|
+
# @param [Fixnum] overhang (0) The number of chars to erase afterwards (i.e.,
|
185
|
+
# the difference in length between the old line and the new one).
|
186
|
+
# @return [String]
|
187
|
+
def correct_indentation(full_line, overhang=0)
|
188
|
+
if Readline.respond_to?(:get_screen_size)
|
189
|
+
_, cols = Readline.get_screen_size
|
190
|
+
lines = full_line.length / cols + 1
|
191
|
+
elsif ENV['COLUMNS'] && ENV['COLUMNS'] != ''
|
192
|
+
cols = ENV['COLUMNS'].to_i
|
193
|
+
lines = full_line.length / cols + 1
|
194
|
+
else
|
195
|
+
lines = 1
|
196
|
+
end
|
197
|
+
|
198
|
+
move_up = "\e[#{lines}F"
|
199
|
+
whitespace = ' ' * overhang
|
200
|
+
move_down = "\e[#{lines}E"
|
201
|
+
|
202
|
+
"#{move_up}#{full_line}#{whitespace}#{move_down}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/lib/pry/method.rb
ADDED
@@ -0,0 +1,412 @@
|
|
1
|
+
class Pry
|
2
|
+
class Method
|
3
|
+
include RbxMethod if Helpers::BaseHelpers.rbx?
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# Given a string representing a method name and optionally a binding to
|
7
|
+
# search in, find and return the requested method wrapped in a `Pry::Method`
|
8
|
+
# instance.
|
9
|
+
#
|
10
|
+
# @param [String, nil] name The name of the method to retrieve, or `nil` to
|
11
|
+
# delegate to `from_binding` instead.
|
12
|
+
# @param [Binding] target The context in which to search for the method.
|
13
|
+
# @param [Hash] options
|
14
|
+
# @option options [Boolean] :instance Look for an instance method if `name` doesn't
|
15
|
+
# contain any context.
|
16
|
+
# @option options [Boolean] :methods Look for a bound/singleton method if `name` doesn't
|
17
|
+
# contain any context.
|
18
|
+
# @return [Pry::Method, nil] A `Pry::Method` instance containing the requested
|
19
|
+
# method, or `nil` if no method could be located matching the parameters.
|
20
|
+
def from_str(name, target=TOPLEVEL_BINDING, options={})
|
21
|
+
if name.nil?
|
22
|
+
from_binding(target)
|
23
|
+
elsif name.to_s =~ /(.+)\#(\S+)\Z/
|
24
|
+
context, meth_name = $1, $2
|
25
|
+
from_module(target.eval(context), meth_name)
|
26
|
+
elsif name.to_s =~ /(.+)\.(\S+)\Z/
|
27
|
+
context, meth_name = $1, $2
|
28
|
+
from_obj(target.eval(context), meth_name)
|
29
|
+
elsif options[:instance]
|
30
|
+
from_module(target.eval("self"), name)
|
31
|
+
elsif options[:methods]
|
32
|
+
from_obj(target.eval("self"), name)
|
33
|
+
else
|
34
|
+
from_str(name, target, :instance => true) or
|
35
|
+
from_str(name, target, :methods => true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Given a `Binding`, try to extract the `::Method` it originated from and
|
40
|
+
# use it to instantiate a `Pry::Method`. Return `nil` if this isn't
|
41
|
+
# possible.
|
42
|
+
#
|
43
|
+
# @param [Binding] b
|
44
|
+
# @return [Pry::Method, nil]
|
45
|
+
#
|
46
|
+
def from_binding(b)
|
47
|
+
meth_name = b.eval('__method__')
|
48
|
+
if [:__script__, nil, :__binding__, :__binding_impl__].include?(meth_name)
|
49
|
+
nil
|
50
|
+
else
|
51
|
+
new(b.eval("method(:#{meth_name})"))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a `Class` or `Module` and the name of a method, try to
|
56
|
+
# instantiate a `Pry::Method` containing the instance method of
|
57
|
+
# that name. Return `nil` if no such method exists.
|
58
|
+
#
|
59
|
+
# @param [Class, Module] klass
|
60
|
+
# @param [String] name
|
61
|
+
# @return [Pry::Method, nil]
|
62
|
+
def from_class(klass, name)
|
63
|
+
new(klass.instance_method(name)) rescue nil
|
64
|
+
end
|
65
|
+
alias from_module from_class
|
66
|
+
|
67
|
+
# Given an object and the name of a method, try to instantiate
|
68
|
+
# a `Pry::Method` containing the method of that name bound to
|
69
|
+
# that object. Return `nil` if no such method exists.
|
70
|
+
#
|
71
|
+
# @param [Object] obj
|
72
|
+
# @param [String] name
|
73
|
+
# @return [Pry::Method, nil]
|
74
|
+
def from_obj(obj, name)
|
75
|
+
new(obj.method(name)) rescue nil
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get all of the instance methods of a `Class` or `Module`
|
79
|
+
# @param [Class,Module] klass
|
80
|
+
# @return [Array[Pry::Method]]
|
81
|
+
def all_from_class(klass)
|
82
|
+
all_from_common(klass, :instance_method)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get all of the methods on an `Object`
|
86
|
+
# @param [Object] obj
|
87
|
+
# @return [Array[Pry::Method]]
|
88
|
+
def all_from_obj(obj)
|
89
|
+
all_from_common(obj, :method)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get every `Class` and `Module`, in order, that will be checked when looking
|
93
|
+
# for an instance method to call on this object.
|
94
|
+
# @param [Object] obj
|
95
|
+
# @return [Array[Class, Module]]
|
96
|
+
def resolution_order(obj)
|
97
|
+
if Class === obj
|
98
|
+
singleton_class_resolution_order(obj) + instance_resolution_order(Class)
|
99
|
+
else
|
100
|
+
klass = singleton_class(obj) rescue obj.class
|
101
|
+
instance_resolution_order(klass)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get every `Class` and `Module`, in order, that will be checked when looking
|
106
|
+
# for methods on instances of the given `Class` or `Module`.
|
107
|
+
# This does not treat singleton classes of classes specially.
|
108
|
+
# @param [Class, Module] klass
|
109
|
+
# @return [Array[Class, Module]]
|
110
|
+
def instance_resolution_order(klass)
|
111
|
+
# include klass in case it is a singleton class,
|
112
|
+
([klass] + klass.ancestors).uniq
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# See all_from_class and all_from_obj.
|
118
|
+
# If method_type is :instance_method, obj must be a `Class` or a `Module`
|
119
|
+
# If method_type is :method, obj can be any `Object`
|
120
|
+
#
|
121
|
+
# N.B. we pre-cache the visibility here to avoid O(N²) behaviour in "ls".
|
122
|
+
def all_from_common(obj, method_type)
|
123
|
+
%w(public protected private).map do |visibility|
|
124
|
+
safe_send(obj, :"#{visibility}_#{method_type}s").map do |method_name|
|
125
|
+
new(safe_send(obj, method_type, method_name), :visibility => visibility.to_sym)
|
126
|
+
end
|
127
|
+
end.flatten(1)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Acts like send but ignores any methods defined below Object or Class in the
|
131
|
+
# inheritance heirarchy.
|
132
|
+
# This is required to introspect methods on objects like Net::HTTP::Get that
|
133
|
+
# have overridden the `method` method.
|
134
|
+
def safe_send(obj, method, *args, &block)
|
135
|
+
(Class === obj ? Class : Object).instance_method(method).bind(obj).call(*args, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the singleton classes of superclasses that could define methods on
|
139
|
+
# the given class object, and any modules they include.
|
140
|
+
# If a module is included at multiple points in the ancestry, only
|
141
|
+
# the lowest copy will be returned.
|
142
|
+
def singleton_class_resolution_order(klass)
|
143
|
+
resolution_order = klass.ancestors.map do |anc|
|
144
|
+
[singleton_class(anc)] + singleton_class(anc).included_modules if anc.is_a?(Class)
|
145
|
+
end.compact.flatten(1)
|
146
|
+
|
147
|
+
resolution_order.reverse.uniq.reverse - Class.included_modules
|
148
|
+
end
|
149
|
+
|
150
|
+
def singleton_class(obj); class << obj; self; end end
|
151
|
+
end
|
152
|
+
|
153
|
+
# A new instance of `Pry::Method` wrapping the given `::Method`, `UnboundMethod`, or `Proc`.
|
154
|
+
#
|
155
|
+
# @param [::Method, UnboundMethod, Proc] method
|
156
|
+
# @param [Hash] known_info, can be used to pre-cache expensive to compute stuff.
|
157
|
+
# @return [Pry::Method]
|
158
|
+
def initialize(method, known_info={})
|
159
|
+
@method = method
|
160
|
+
@visibility = known_info[:visibility]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Get the name of the method as a String, regardless of the underlying Method#name type.
|
164
|
+
# @return [String]
|
165
|
+
def name
|
166
|
+
@method.name.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
# @return [String, nil] The source code of the method, or `nil` if it's unavailable.
|
170
|
+
def source
|
171
|
+
@source ||= case source_type
|
172
|
+
when :c
|
173
|
+
info = pry_doc_info
|
174
|
+
if info and info.source
|
175
|
+
code = strip_comments_from_c_code(info.source)
|
176
|
+
end
|
177
|
+
when :rbx
|
178
|
+
strip_leading_whitespace(core_code)
|
179
|
+
when :ruby
|
180
|
+
if pry_method?
|
181
|
+
code = Pry.new(:input => StringIO.new(Pry.line_buffer[source_line..-1].join), :prompt => proc {""}, :hooks => {}).r
|
182
|
+
else
|
183
|
+
code = @method.source
|
184
|
+
end
|
185
|
+
strip_leading_whitespace(code)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [String, nil] The documentation for the method, or `nil` if it's
|
190
|
+
# unavailable.
|
191
|
+
# @raise [CommandError] Raises when the method was defined in the REPL.
|
192
|
+
def doc
|
193
|
+
@doc ||= case source_type
|
194
|
+
when :c
|
195
|
+
info = pry_doc_info
|
196
|
+
info.docstring if info
|
197
|
+
when :rbx
|
198
|
+
strip_leading_hash_and_whitespace_from_ruby_comments(core_doc)
|
199
|
+
when :ruby
|
200
|
+
if pry_method?
|
201
|
+
raise CommandError, "Can't view doc for a REPL-defined method."
|
202
|
+
else
|
203
|
+
strip_leading_hash_and_whitespace_from_ruby_comments(@method.comment)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @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.
|
211
|
+
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
|
217
|
+
end
|
218
|
+
|
219
|
+
# @return [String, nil] The name of the file the method is defined in, or
|
220
|
+
# `nil` if the filename is unavailable.
|
221
|
+
def source_file
|
222
|
+
if source_location.nil?
|
223
|
+
if !Helpers::BaseHelpers.rbx? and source_type == :c
|
224
|
+
info = pry_doc_info
|
225
|
+
info.file if info
|
226
|
+
end
|
227
|
+
else
|
228
|
+
source_location.first
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# @return [Fixnum, nil] The line of code in `source_file` which begins
|
233
|
+
# the method's definition, or `nil` if that information is unavailable.
|
234
|
+
def source_line
|
235
|
+
source_location.nil? ? nil : source_location.last
|
236
|
+
end
|
237
|
+
|
238
|
+
# @return [Symbol] The visibility of the method. May be `:public`,
|
239
|
+
# `:protected`, or `:private`.
|
240
|
+
def visibility
|
241
|
+
@visibility ||= if owner.public_instance_methods.any? { |m| m.to_s == name }
|
242
|
+
:public
|
243
|
+
elsif owner.protected_instance_methods.any? { |m| m.to_s == name }
|
244
|
+
:protected
|
245
|
+
elsif owner.private_instance_methods.any? { |m| m.to_s == name }
|
246
|
+
:private
|
247
|
+
else
|
248
|
+
:none
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# @return [String] A representation of the method's signature, including its
|
253
|
+
# name and parameters. Optional and "rest" parameters are marked with `*`
|
254
|
+
# and block parameters with `&`. If the parameter names are unavailable,
|
255
|
+
# they're given numbered names instead.
|
256
|
+
# Paraphrased from `awesome_print` gem.
|
257
|
+
def signature
|
258
|
+
if respond_to?(:parameters)
|
259
|
+
args = parameters.inject([]) do |arr, (type, name)|
|
260
|
+
name ||= (type == :block ? 'block' : "arg#{arr.size + 1}")
|
261
|
+
arr << case type
|
262
|
+
when :req then name.to_s
|
263
|
+
when :opt, :rest then "*#{name}"
|
264
|
+
when :block then "&#{name}"
|
265
|
+
else '?'
|
266
|
+
end
|
267
|
+
end
|
268
|
+
else
|
269
|
+
args = (1..arity.abs).map { |i| "arg#{i}" }
|
270
|
+
args[-1] = "*#{args[-1]}" if arity < 0
|
271
|
+
end
|
272
|
+
|
273
|
+
"#{name}(#{args.join(', ')})"
|
274
|
+
end
|
275
|
+
|
276
|
+
# @return [Pry::Method, nil] The wrapped method that is called when you
|
277
|
+
# use "super" in the body of this method.
|
278
|
+
def super(times=1)
|
279
|
+
if respond_to?(:receiver)
|
280
|
+
sup = super_using_ancestors(Pry::Method.resolution_order(receiver), times)
|
281
|
+
sup &&= sup.bind(receiver)
|
282
|
+
else
|
283
|
+
sup = super_using_ancestors(Pry::Method.instance_resolution_order(owner), times)
|
284
|
+
end
|
285
|
+
Pry::Method.new(sup) if sup
|
286
|
+
end
|
287
|
+
|
288
|
+
# @return [Symbol, nil] The original name the method was defined under,
|
289
|
+
# before any aliasing, or `nil` if it can't be determined.
|
290
|
+
def original_name
|
291
|
+
return nil if source_type != :ruby
|
292
|
+
|
293
|
+
first_line = source.lines.first
|
294
|
+
return nil if first_line.strip !~ /^def /
|
295
|
+
|
296
|
+
if RUBY_VERSION =~ /^1\.9/ && RUBY_ENGINE == "ruby"
|
297
|
+
require 'ripper'
|
298
|
+
|
299
|
+
# Ripper is ok with an extraneous end, so we don't need to worry about
|
300
|
+
# whether it's a one-liner.
|
301
|
+
tree = Ripper::SexpBuilder.new(first_line + ";end").parse
|
302
|
+
|
303
|
+
name = tree.flatten(2).each do |lst|
|
304
|
+
break lst[1] if lst[0] == :@ident
|
305
|
+
end
|
306
|
+
|
307
|
+
name.is_a?(String) ? name : nil
|
308
|
+
else
|
309
|
+
require 'ruby_parser'
|
310
|
+
|
311
|
+
# RubyParser breaks if there's an extra end, so we'll just rescue
|
312
|
+
# and try again.
|
313
|
+
tree = begin
|
314
|
+
RubyParser.new.parse(first_line + ";end")
|
315
|
+
rescue Racc::ParseError
|
316
|
+
RubyParser.new.parse(first_line)
|
317
|
+
end
|
318
|
+
|
319
|
+
name = tree.each_cons(2) do |a, b|
|
320
|
+
break a if b.is_a?(Array) && b.first == :args
|
321
|
+
end
|
322
|
+
|
323
|
+
name.is_a?(Symbol) ? name.to_s : nil
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# @return [Boolean] Was the method defined outside a source file?
|
328
|
+
def dynamically_defined?
|
329
|
+
!!(source_file and source_file =~ /(\(.*\))|<.*>/)
|
330
|
+
end
|
331
|
+
|
332
|
+
# @return [Boolean] Was the method defined within the Pry REPL?
|
333
|
+
def pry_method?
|
334
|
+
source_file == Pry.eval_path
|
335
|
+
end
|
336
|
+
|
337
|
+
# @return [Boolean] Is the method definitely an alias?
|
338
|
+
def alias?
|
339
|
+
name != original_name
|
340
|
+
end
|
341
|
+
|
342
|
+
# @return [Boolean]
|
343
|
+
def ==(obj)
|
344
|
+
if obj.is_a? Pry::Method
|
345
|
+
super
|
346
|
+
else
|
347
|
+
@method == obj
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# @param [Class] klass
|
352
|
+
# @return [Boolean]
|
353
|
+
def is_a?(klass)
|
354
|
+
klass == Pry::Method or @method.is_a?(klass)
|
355
|
+
end
|
356
|
+
alias kind_of? is_a?
|
357
|
+
|
358
|
+
# @param [String, Symbol] method_name
|
359
|
+
# @return [Boolean]
|
360
|
+
def respond_to?(method_name)
|
361
|
+
super or @method.respond_to?(method_name)
|
362
|
+
end
|
363
|
+
|
364
|
+
# Delegate any unknown calls to the wrapped method.
|
365
|
+
def method_missing(method_name, *args, &block)
|
366
|
+
@method.send(method_name, *args, &block)
|
367
|
+
end
|
368
|
+
|
369
|
+
private
|
370
|
+
# @return [YARD::CodeObjects::MethodObject]
|
371
|
+
# @raise [CommandError] Raises when the method can't be found or `pry-doc` isn't installed.
|
372
|
+
def pry_doc_info
|
373
|
+
if Pry.config.has_pry_doc
|
374
|
+
Pry::MethodInfo.info_for(@method) or raise CommandError, "Cannot locate this method: #{name}."
|
375
|
+
else
|
376
|
+
raise CommandError, "Cannot locate this method: #{name}. Try `gem install pry-doc` to get access to Ruby Core documentation."
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# @param [String] code
|
381
|
+
# @return [String]
|
382
|
+
def strip_comments_from_c_code(code)
|
383
|
+
code.sub(/\A\s*\/\*.*?\*\/\s*/m, '')
|
384
|
+
end
|
385
|
+
|
386
|
+
# @param [String] comment
|
387
|
+
# @return [String]
|
388
|
+
def strip_leading_hash_and_whitespace_from_ruby_comments(comment)
|
389
|
+
comment = comment.dup
|
390
|
+
comment.gsub!(/\A\#+?$/, '')
|
391
|
+
comment.gsub!(/^\s*#/, '')
|
392
|
+
strip_leading_whitespace(comment)
|
393
|
+
end
|
394
|
+
|
395
|
+
# @param [String] text
|
396
|
+
# @return [String]
|
397
|
+
def strip_leading_whitespace(text)
|
398
|
+
Pry::Helpers::CommandHelpers.unindent(text)
|
399
|
+
end
|
400
|
+
|
401
|
+
# @param [Class,Module] the ancestors to investigate
|
402
|
+
# @return [Method] the unwrapped super-method
|
403
|
+
def super_using_ancestors(ancestors, times=1)
|
404
|
+
next_owner = self.owner
|
405
|
+
times.times do
|
406
|
+
next_owner = ancestors[ancestors.index(next_owner) + 1]
|
407
|
+
return nil unless next_owner
|
408
|
+
end
|
409
|
+
next_owner.instance_method(name) rescue nil
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|