pry 0.10.0.pre2-universal-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +702 -0
- data/LICENSE +25 -0
- data/README.md +406 -0
- data/bin/pry +16 -0
- data/lib/pry.rb +161 -0
- data/lib/pry/cli.rb +220 -0
- data/lib/pry/code.rb +346 -0
- data/lib/pry/code/code_file.rb +103 -0
- data/lib/pry/code/code_range.rb +71 -0
- data/lib/pry/code/loc.rb +92 -0
- data/lib/pry/code_object.rb +172 -0
- data/lib/pry/color_printer.rb +55 -0
- data/lib/pry/command.rb +692 -0
- data/lib/pry/command_set.rb +443 -0
- data/lib/pry/commands.rb +6 -0
- data/lib/pry/commands/amend_line.rb +99 -0
- data/lib/pry/commands/bang.rb +20 -0
- data/lib/pry/commands/bang_pry.rb +17 -0
- data/lib/pry/commands/cat.rb +62 -0
- data/lib/pry/commands/cat/abstract_formatter.rb +27 -0
- data/lib/pry/commands/cat/exception_formatter.rb +77 -0
- data/lib/pry/commands/cat/file_formatter.rb +67 -0
- data/lib/pry/commands/cat/input_expression_formatter.rb +43 -0
- data/lib/pry/commands/cd.rb +41 -0
- data/lib/pry/commands/change_inspector.rb +27 -0
- data/lib/pry/commands/change_prompt.rb +26 -0
- data/lib/pry/commands/code_collector.rb +165 -0
- data/lib/pry/commands/disable_pry.rb +27 -0
- data/lib/pry/commands/disabled_commands.rb +2 -0
- data/lib/pry/commands/easter_eggs.rb +112 -0
- data/lib/pry/commands/edit.rb +195 -0
- data/lib/pry/commands/edit/exception_patcher.rb +25 -0
- data/lib/pry/commands/edit/file_and_line_locator.rb +36 -0
- data/lib/pry/commands/exit.rb +42 -0
- data/lib/pry/commands/exit_all.rb +29 -0
- data/lib/pry/commands/exit_program.rb +23 -0
- data/lib/pry/commands/find_method.rb +193 -0
- data/lib/pry/commands/fix_indent.rb +19 -0
- data/lib/pry/commands/gem_cd.rb +26 -0
- data/lib/pry/commands/gem_install.rb +32 -0
- data/lib/pry/commands/gem_list.rb +33 -0
- data/lib/pry/commands/gem_open.rb +29 -0
- data/lib/pry/commands/gist.rb +101 -0
- data/lib/pry/commands/help.rb +164 -0
- data/lib/pry/commands/hist.rb +180 -0
- data/lib/pry/commands/import_set.rb +22 -0
- data/lib/pry/commands/install_command.rb +53 -0
- data/lib/pry/commands/jump_to.rb +29 -0
- data/lib/pry/commands/list_inspectors.rb +35 -0
- data/lib/pry/commands/list_prompts.rb +35 -0
- data/lib/pry/commands/ls.rb +114 -0
- data/lib/pry/commands/ls/constants.rb +47 -0
- data/lib/pry/commands/ls/formatter.rb +49 -0
- data/lib/pry/commands/ls/globals.rb +48 -0
- data/lib/pry/commands/ls/grep.rb +21 -0
- data/lib/pry/commands/ls/instance_vars.rb +39 -0
- data/lib/pry/commands/ls/interrogatable.rb +18 -0
- data/lib/pry/commands/ls/jruby_hacks.rb +49 -0
- data/lib/pry/commands/ls/local_names.rb +35 -0
- data/lib/pry/commands/ls/local_vars.rb +39 -0
- data/lib/pry/commands/ls/ls_entity.rb +70 -0
- data/lib/pry/commands/ls/methods.rb +57 -0
- data/lib/pry/commands/ls/methods_helper.rb +46 -0
- data/lib/pry/commands/ls/self_methods.rb +32 -0
- data/lib/pry/commands/nesting.rb +25 -0
- data/lib/pry/commands/play.rb +103 -0
- data/lib/pry/commands/pry_backtrace.rb +25 -0
- data/lib/pry/commands/pry_version.rb +17 -0
- data/lib/pry/commands/raise_up.rb +32 -0
- data/lib/pry/commands/reload_code.rb +62 -0
- data/lib/pry/commands/reset.rb +18 -0
- data/lib/pry/commands/ri.rb +60 -0
- data/lib/pry/commands/save_file.rb +61 -0
- data/lib/pry/commands/shell_command.rb +48 -0
- data/lib/pry/commands/shell_mode.rb +25 -0
- data/lib/pry/commands/show_doc.rb +83 -0
- data/lib/pry/commands/show_info.rb +195 -0
- data/lib/pry/commands/show_input.rb +17 -0
- data/lib/pry/commands/show_source.rb +50 -0
- data/lib/pry/commands/simple_prompt.rb +22 -0
- data/lib/pry/commands/stat.rb +40 -0
- data/lib/pry/commands/switch_to.rb +23 -0
- data/lib/pry/commands/toggle_color.rb +24 -0
- data/lib/pry/commands/watch_expression.rb +105 -0
- data/lib/pry/commands/watch_expression/expression.rb +38 -0
- data/lib/pry/commands/whereami.rb +190 -0
- data/lib/pry/commands/wtf.rb +57 -0
- data/lib/pry/config.rb +24 -0
- data/lib/pry/config/behavior.rb +139 -0
- data/lib/pry/config/convenience.rb +26 -0
- data/lib/pry/config/default.rb +165 -0
- data/lib/pry/core_extensions.rb +131 -0
- data/lib/pry/editor.rb +133 -0
- data/lib/pry/exceptions.rb +77 -0
- data/lib/pry/helpers.rb +5 -0
- data/lib/pry/helpers/base_helpers.rb +113 -0
- data/lib/pry/helpers/command_helpers.rb +156 -0
- data/lib/pry/helpers/documentation_helpers.rb +75 -0
- data/lib/pry/helpers/options_helpers.rb +27 -0
- data/lib/pry/helpers/table.rb +109 -0
- data/lib/pry/helpers/text.rb +107 -0
- data/lib/pry/history.rb +125 -0
- data/lib/pry/history_array.rb +121 -0
- data/lib/pry/hooks.rb +230 -0
- data/lib/pry/indent.rb +406 -0
- data/lib/pry/input_completer.rb +242 -0
- data/lib/pry/input_lock.rb +132 -0
- data/lib/pry/inspector.rb +27 -0
- data/lib/pry/last_exception.rb +61 -0
- data/lib/pry/method.rb +546 -0
- data/lib/pry/method/disowned.rb +53 -0
- data/lib/pry/method/patcher.rb +125 -0
- data/lib/pry/method/weird_method_locator.rb +186 -0
- data/lib/pry/module_candidate.rb +136 -0
- data/lib/pry/object_path.rb +82 -0
- data/lib/pry/output.rb +50 -0
- data/lib/pry/pager.rb +234 -0
- data/lib/pry/plugins.rb +103 -0
- data/lib/pry/prompt.rb +26 -0
- data/lib/pry/pry_class.rb +375 -0
- data/lib/pry/pry_instance.rb +654 -0
- data/lib/pry/rbx_path.rb +22 -0
- data/lib/pry/repl.rb +202 -0
- data/lib/pry/repl_file_loader.rb +74 -0
- data/lib/pry/rubygem.rb +82 -0
- data/lib/pry/terminal.rb +79 -0
- data/lib/pry/test/helper.rb +170 -0
- data/lib/pry/version.rb +3 -0
- data/lib/pry/wrapped_module.rb +373 -0
- metadata +248 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
class Pry
|
2
|
+
class Method
|
3
|
+
# A Disowned Method is one that's been removed from the class on which it was defined.
|
4
|
+
#
|
5
|
+
# e.g.
|
6
|
+
# class C
|
7
|
+
# def foo
|
8
|
+
# C.send(:undefine_method, :foo)
|
9
|
+
# Pry::Method.from_binding(binding)
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# In this case we assume that the "owner" is the singleton class of the receiver.
|
14
|
+
#
|
15
|
+
# This occurs mainly in Sinatra applications.
|
16
|
+
class Disowned < Method
|
17
|
+
attr_reader :receiver, :name
|
18
|
+
|
19
|
+
# Create a new Disowned method.
|
20
|
+
#
|
21
|
+
# @param [Object] receiver
|
22
|
+
# @param [String] method_name
|
23
|
+
def initialize(receiver, method_name, binding=nil)
|
24
|
+
@receiver, @name = receiver, method_name
|
25
|
+
end
|
26
|
+
|
27
|
+
# Is the method undefined? (aka `Disowned`)
|
28
|
+
# @return [Boolean] true
|
29
|
+
def undefined?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Can we get the source for this method?
|
34
|
+
# @return [Boolean] false
|
35
|
+
def source?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the hypothesized owner of the method.
|
40
|
+
#
|
41
|
+
# @return [Object]
|
42
|
+
def owner
|
43
|
+
class << receiver; self; end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Raise a more useful error message instead of trying to forward to nil.
|
47
|
+
def method_missing(meth_name, *args, &block)
|
48
|
+
raise "Cannot call '#{meth_name}' on an undef'd method." if method(:name).respond_to?(meth_name)
|
49
|
+
Object.instance_method(:method_missing).bind(self).call(meth_name, *args, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
class Pry
|
2
|
+
class Method
|
3
|
+
class Patcher
|
4
|
+
attr_accessor :method
|
5
|
+
|
6
|
+
@@source_cache = {}
|
7
|
+
|
8
|
+
def initialize(method)
|
9
|
+
@method = method
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.code_for(filename)
|
13
|
+
@@source_cache[filename]
|
14
|
+
end
|
15
|
+
|
16
|
+
# perform the patch
|
17
|
+
def patch_in_ram(source)
|
18
|
+
if method.alias?
|
19
|
+
with_method_transaction do
|
20
|
+
redefine source
|
21
|
+
end
|
22
|
+
else
|
23
|
+
redefine source
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def redefine(source)
|
30
|
+
@@source_cache[cache_key] = source
|
31
|
+
TOPLEVEL_BINDING.eval wrap(source), cache_key
|
32
|
+
end
|
33
|
+
|
34
|
+
def cache_key
|
35
|
+
"pry-redefined(0x#{method.owner.object_id.to_s(16)}##{method.name})"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Run some code ensuring that at the end target#meth_name will not have changed.
|
39
|
+
#
|
40
|
+
# When we're redefining aliased methods we will overwrite the method at the
|
41
|
+
# unaliased name (so that super continues to work). By wrapping that code in a
|
42
|
+
# transation we make that not happen, which means that alias_method_chains, etc.
|
43
|
+
# continue to work.
|
44
|
+
#
|
45
|
+
# @param [String] meth_name The method name before aliasing
|
46
|
+
# @param [Module] target The owner of the method
|
47
|
+
|
48
|
+
def with_method_transaction
|
49
|
+
temp_name = "__pry_#{method.original_name}__"
|
50
|
+
method = self.method
|
51
|
+
method.owner.class_eval do
|
52
|
+
alias_method temp_name, method.original_name
|
53
|
+
yield
|
54
|
+
alias_method method.name, method.original_name
|
55
|
+
alias_method method.original_name, temp_name
|
56
|
+
end
|
57
|
+
|
58
|
+
ensure
|
59
|
+
method.send(:remove_method, temp_name) rescue nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Update the definition line so that it can be eval'd directly on the Method's
|
63
|
+
# owner instead of from the original context.
|
64
|
+
#
|
65
|
+
# In particular this takes `def self.foo` and turns it into `def foo` so that we
|
66
|
+
# don't end up creating the method on the singleton class of the singleton class
|
67
|
+
# by accident.
|
68
|
+
#
|
69
|
+
# This is necessarily done by String manipulation because we can't find out what
|
70
|
+
# syntax is needed for the argument list by ruby-level introspection.
|
71
|
+
#
|
72
|
+
# @param [String] line The original definition line. e.g. def self.foo(bar, baz=1)
|
73
|
+
# @return [String] The new definition line. e.g. def foo(bar, baz=1)
|
74
|
+
def definition_for_owner(line)
|
75
|
+
if line =~ /\Adef (?:.*?\.)?#{Regexp.escape(method.original_name)}(?=[\(\s;]|$)/
|
76
|
+
"def #{method.original_name}#{$'}"
|
77
|
+
else
|
78
|
+
raise CommandError, "Could not find original `def #{method.original_name}` line to patch."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Apply wrap_for_owner and wrap_for_nesting successively to `source`
|
83
|
+
# @param [String] source
|
84
|
+
# @return [String] The wrapped source.
|
85
|
+
def wrap(source)
|
86
|
+
wrap_for_nesting(wrap_for_owner(source))
|
87
|
+
end
|
88
|
+
|
89
|
+
# Update the source code so that when it has the right owner when eval'd.
|
90
|
+
#
|
91
|
+
# This (combined with definition_for_owner) is backup for the case that
|
92
|
+
# wrap_for_nesting fails, to ensure that the method will stil be defined in
|
93
|
+
# the correct place.
|
94
|
+
#
|
95
|
+
# @param [String] source The source to wrap
|
96
|
+
# @return [String]
|
97
|
+
def wrap_for_owner(source)
|
98
|
+
Pry.current[:pry_owner] = method.owner
|
99
|
+
owner_source = definition_for_owner(source)
|
100
|
+
visibility_fix = "#{method.visibility.to_s} #{method.name.to_sym.inspect}"
|
101
|
+
"Pry.current[:pry_owner].class_eval do; #{owner_source}\n#{visibility_fix}\nend"
|
102
|
+
end
|
103
|
+
|
104
|
+
# Update the new source code to have the correct Module.nesting.
|
105
|
+
#
|
106
|
+
# This method uses syntactic analysis of the original source file to determine
|
107
|
+
# the new nesting, so that we can tell the difference between:
|
108
|
+
#
|
109
|
+
# class A; def self.b; end; end
|
110
|
+
# class << A; def b; end; end
|
111
|
+
#
|
112
|
+
# The resulting code should be evaluated in the TOPLEVEL_BINDING.
|
113
|
+
#
|
114
|
+
# @param [String] source The source to wrap.
|
115
|
+
# @return [String]
|
116
|
+
def wrap_for_nesting(source)
|
117
|
+
nesting = Pry::Code.from_file(method.source_file).nesting_at(method.source_line)
|
118
|
+
|
119
|
+
(nesting + [source] + nesting.map{ "end" } + [""]).join(";")
|
120
|
+
rescue Pry::Indent::UnparseableNestingError
|
121
|
+
source
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
class Pry
|
2
|
+
class Method
|
3
|
+
|
4
|
+
# This class is responsible for locating the *real* `Pry::Method`
|
5
|
+
# object captured by a binding.
|
6
|
+
#
|
7
|
+
# Given a `Binding` from inside a method and a 'seed' Pry::Method object,
|
8
|
+
# there are primarily two situations where the seed method doesn't match
|
9
|
+
# the Binding:
|
10
|
+
# 1. The Pry::Method is from a subclass 2. The Pry::Method represents a method of the same name
|
11
|
+
# while the original was renamed to something else. For 1. we
|
12
|
+
# search vertically up the inheritance chain,
|
13
|
+
# and for 2. we search laterally along the object's method table.
|
14
|
+
#
|
15
|
+
# When we locate the method that matches the Binding we wrap it in
|
16
|
+
# Pry::Method and return it, or return nil if we fail.
|
17
|
+
class WeirdMethodLocator
|
18
|
+
class << self
|
19
|
+
|
20
|
+
# Whether the given method object matches the associated binding.
|
21
|
+
# If the method object does not match the binding, then it's
|
22
|
+
# most likely not the method captured by the binding, and we
|
23
|
+
# must commence a search.
|
24
|
+
#
|
25
|
+
# @param [Pry::Method] method
|
26
|
+
# @param [Binding] b
|
27
|
+
# @return [Boolean]
|
28
|
+
def normal_method?(method, b)
|
29
|
+
method && (method.source_file && method.source_range rescue false) &&
|
30
|
+
File.expand_path(method.source_file) == File.expand_path(b.eval('__FILE__')) &&
|
31
|
+
method.source_range.include?(b.eval('__LINE__'))
|
32
|
+
end
|
33
|
+
|
34
|
+
def weird_method?(method, b)
|
35
|
+
!normal_method?(method, b)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_accessor :method
|
40
|
+
attr_accessor :target
|
41
|
+
|
42
|
+
# @param [Pry::Method] method The seed method.
|
43
|
+
# @param [Binding] target The Binding that captures the method
|
44
|
+
# we want to locate.
|
45
|
+
def initialize(method, target)
|
46
|
+
@method, @target = method, target
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Pry::Method, nil] The Pry::Method that matches the
|
50
|
+
# given binding.
|
51
|
+
def get_method
|
52
|
+
find_method_in_superclass || find_renamed_method
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Boolean] Whether the Pry::Method is unrecoverable
|
56
|
+
# This usually happens when the method captured by the Binding
|
57
|
+
# has been subsequently deleted.
|
58
|
+
def lost_method?
|
59
|
+
!!(get_method.nil? && renamed_method_source_location)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def normal_method?(method)
|
65
|
+
self.class.normal_method?(method, target)
|
66
|
+
end
|
67
|
+
|
68
|
+
def target_self
|
69
|
+
target.eval('self')
|
70
|
+
end
|
71
|
+
|
72
|
+
def target_file
|
73
|
+
pry_file? ? target.eval('__FILE__') : File.expand_path(target.eval('__FILE__'))
|
74
|
+
end
|
75
|
+
|
76
|
+
def target_line
|
77
|
+
target.eval('__LINE__')
|
78
|
+
end
|
79
|
+
|
80
|
+
def pry_file?
|
81
|
+
Pry.eval_path == target.eval('__FILE__')
|
82
|
+
end
|
83
|
+
|
84
|
+
# it's possible in some cases that the method we find by this approach is a sub-method of
|
85
|
+
# the one we're currently in, consider:
|
86
|
+
#
|
87
|
+
# class A; def b; binding.pry; end; end
|
88
|
+
# class B < A; def b; super; end; end
|
89
|
+
#
|
90
|
+
# Given that we can normally find the source_range of methods, and that we know which
|
91
|
+
# __FILE__ and __LINE__ the binding is at, we can hope to disambiguate these cases.
|
92
|
+
#
|
93
|
+
# This obviously won't work if the source is unavaiable for some reason, or if both
|
94
|
+
# methods have the same __FILE__ and __LINE__, or if we're in rbx where b.eval('__LINE__')
|
95
|
+
# is broken.
|
96
|
+
#
|
97
|
+
# @return [Pry::Method, nil] The Pry::Method representing the
|
98
|
+
# superclass method.
|
99
|
+
def find_method_in_superclass
|
100
|
+
guess = method
|
101
|
+
|
102
|
+
while guess
|
103
|
+
# needs rescue if this is a Disowned method or a C method or something...
|
104
|
+
# TODO: Fix up the exception handling so we don't need a bare rescue
|
105
|
+
if normal_method?(guess)
|
106
|
+
return guess
|
107
|
+
else
|
108
|
+
guess = guess.super
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Uhoh... none of the methods in the chain had the right __FILE__ and __LINE__
|
113
|
+
# This may be caused by rbx https://github.com/rubinius/rubinius/issues/953,
|
114
|
+
# or other unknown circumstances (TODO: we should warn the user when this happens)
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
# This is the case where the name of a method has changed
|
119
|
+
# (via alias_method) so we locate the Method object for the
|
120
|
+
# renamed method.
|
121
|
+
#
|
122
|
+
# @return [Pry::Method, nil] The Pry::Method representing the
|
123
|
+
# renamed method
|
124
|
+
def find_renamed_method
|
125
|
+
return if !valid_file?(target_file)
|
126
|
+
alias_name = all_methods_for(target_self).find do |v|
|
127
|
+
expanded_source_location(target_self.method(v).source_location) == renamed_method_source_location
|
128
|
+
end
|
129
|
+
|
130
|
+
alias_name && Pry::Method(target_self.method(alias_name))
|
131
|
+
end
|
132
|
+
|
133
|
+
def expanded_source_location(sl)
|
134
|
+
return if !sl
|
135
|
+
|
136
|
+
if pry_file?
|
137
|
+
sl
|
138
|
+
else
|
139
|
+
[File.expand_path(sl.first), sl.last]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Use static analysis to locate the start of the method definition.
|
144
|
+
# We have the `__FILE__` and `__LINE__` from the binding and the
|
145
|
+
# original name of the method so we search up until we find a
|
146
|
+
# def/define_method, etc defining a method of the appropriate name.
|
147
|
+
#
|
148
|
+
# @return [Array<String, Fixnum>] The `source_location` of the
|
149
|
+
# renamed method
|
150
|
+
def renamed_method_source_location
|
151
|
+
return @original_method_source_location if defined?(@original_method_source_location)
|
152
|
+
|
153
|
+
source_index = lines_for_file(target_file)[0..(target_line - 1)].rindex do |v|
|
154
|
+
Pry::Method.method_definition?(method.name, v)
|
155
|
+
end
|
156
|
+
|
157
|
+
@original_method_source_location = source_index &&
|
158
|
+
[target_file, index_to_line_number(source_index)]
|
159
|
+
end
|
160
|
+
|
161
|
+
def index_to_line_number(index)
|
162
|
+
# Pry.line_buffer is 0-indexed
|
163
|
+
pry_file? ? index : index + 1
|
164
|
+
end
|
165
|
+
|
166
|
+
def valid_file?(file)
|
167
|
+
(File.exists?(file) && !File.directory?(file)) || Pry.eval_path == file
|
168
|
+
end
|
169
|
+
|
170
|
+
def lines_for_file(file)
|
171
|
+
@lines_for_file ||= {}
|
172
|
+
@lines_for_file[file] ||= if Pry.eval_path == file
|
173
|
+
Pry.line_buffer
|
174
|
+
else
|
175
|
+
File.readlines(file)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def all_methods_for(obj)
|
180
|
+
obj.public_methods(false) +
|
181
|
+
obj.private_methods(false) +
|
182
|
+
obj.protected_methods(false)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'pry/helpers/documentation_helpers'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
class Pry
|
5
|
+
class WrappedModule
|
6
|
+
|
7
|
+
# This class represents a single candidate for a module/class definition.
|
8
|
+
# It provides access to the source, documentation, line and file
|
9
|
+
# for a monkeypatch (reopening) of a class/module.
|
10
|
+
class Candidate
|
11
|
+
include Pry::Helpers::DocumentationHelpers
|
12
|
+
include Pry::CodeObject::Helpers
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
# @return [String] The file where the module definition is located.
|
16
|
+
attr_reader :file
|
17
|
+
alias_method :source_file, :file
|
18
|
+
|
19
|
+
# @return [Fixnum] The line where the module definition is located.
|
20
|
+
attr_reader :line
|
21
|
+
alias_method :source_line, :line
|
22
|
+
|
23
|
+
# Methods to delegate to associated `Pry::WrappedModule
|
24
|
+
# instance`.
|
25
|
+
private_delegates = [:lines_for_file, :method_candidates,
|
26
|
+
:yard_docs?]
|
27
|
+
|
28
|
+
public_delegates = [:wrapped, :module?, :class?, :name, :nonblank_name,
|
29
|
+
:number_of_candidates]
|
30
|
+
|
31
|
+
def_delegators :@wrapper, *(private_delegates + public_delegates)
|
32
|
+
private(*private_delegates)
|
33
|
+
public(*public_delegates)
|
34
|
+
|
35
|
+
# @raise [Pry::CommandError] If `rank` is out of bounds.
|
36
|
+
# @param [Pry::WrappedModule] wrapper The associated
|
37
|
+
# `Pry::WrappedModule` instance that owns the candidates.
|
38
|
+
# @param [Fixnum] rank The rank of the candidate to
|
39
|
+
# retrieve. Passing 0 returns 'primary candidate' (the candidate with largest
|
40
|
+
# number of methods), passing 1 retrieves candidate with
|
41
|
+
# second largest number of methods, and so on, up to
|
42
|
+
# `Pry::WrappedModule#number_of_candidates() - 1`
|
43
|
+
def initialize(wrapper, rank)
|
44
|
+
@wrapper = wrapper
|
45
|
+
|
46
|
+
if number_of_candidates <= 0
|
47
|
+
raise CommandError, "Cannot find a definition for #{name} module!"
|
48
|
+
elsif rank > (number_of_candidates - 1)
|
49
|
+
raise CommandError, "No such module candidate. Allowed candidates range is from 0 to #{number_of_candidates - 1}"
|
50
|
+
end
|
51
|
+
|
52
|
+
@rank = rank
|
53
|
+
@file, @line = source_location
|
54
|
+
end
|
55
|
+
|
56
|
+
# @raise [Pry::CommandError] If source code cannot be found.
|
57
|
+
# @return [String] The source for the candidate, i.e the
|
58
|
+
# complete module/class definition.
|
59
|
+
def source
|
60
|
+
return nil if file.nil?
|
61
|
+
return @source if @source
|
62
|
+
|
63
|
+
@source = strip_leading_whitespace(Pry::Code.from_file(file).expression_at(line, number_of_lines_in_first_chunk))
|
64
|
+
end
|
65
|
+
|
66
|
+
# @raise [Pry::CommandError] If documentation cannot be found.
|
67
|
+
# @return [String] The documentation for the candidate.
|
68
|
+
def doc
|
69
|
+
return nil if file.nil?
|
70
|
+
return @doc if @doc
|
71
|
+
|
72
|
+
@doc = get_comment_content(Pry::Code.from_file(file).comment_describing(line))
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Array, nil] A `[String, Fixnum]` pair representing the
|
76
|
+
# source location (file and line) for the candidate or `nil`
|
77
|
+
# if no source location found.
|
78
|
+
def source_location
|
79
|
+
return @source_location if @source_location
|
80
|
+
|
81
|
+
file, line = first_method_source_location
|
82
|
+
return nil if !file.is_a?(String)
|
83
|
+
|
84
|
+
@source_location = [file, first_line_of_module_definition(file, line)]
|
85
|
+
rescue Pry::RescuableException
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Locate the first line of the module definition.
|
92
|
+
# @param [String] file The file that contains the module
|
93
|
+
# definition (somewhere).
|
94
|
+
# @param [Fixnum] line The module definition should appear
|
95
|
+
# before this line (if it exists).
|
96
|
+
# @return [Fixnum] The line where the module is defined. This
|
97
|
+
# line number is one-indexed.
|
98
|
+
def first_line_of_module_definition(file, line)
|
99
|
+
searchable_lines = lines_for_file(file)[0..(line - 2)]
|
100
|
+
searchable_lines.rindex { |v| class_regexes.any? { |r| r =~ v } } + 1
|
101
|
+
end
|
102
|
+
|
103
|
+
def class_regexes
|
104
|
+
mod_type_string = wrapped.class.to_s.downcase
|
105
|
+
[/^\s*#{mod_type_string}\s+(?:(?:\w*)::)*?#{wrapped.name.split(/::/).last}/,
|
106
|
+
/^\s*(::)?#{wrapped.name.split(/::/).last}\s*?=\s*?#{wrapped.class}/,
|
107
|
+
/^\s*(::)?#{wrapped.name.split(/::/).last}\.(class|instance)_eval/]
|
108
|
+
end
|
109
|
+
|
110
|
+
# This method is used by `Candidate#source_location` as a
|
111
|
+
# starting point for the search for the candidate's definition.
|
112
|
+
# @return [Array] The source location of the base method used to
|
113
|
+
# calculate the source location of the candidate.
|
114
|
+
def first_method_source_location
|
115
|
+
@first_method_source_location ||= method_candidates[@rank].first.source_location
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Array] The source location of the last method in this
|
119
|
+
# candidate's module definition.
|
120
|
+
def last_method_source_location
|
121
|
+
@end_method_source_location ||= method_candidates[@rank].last.source_location
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return the number of lines between the start of the class definition
|
125
|
+
# and the start of the last method. We use this value so we can
|
126
|
+
# quickly grab these lines from the file (without having to
|
127
|
+
# check each intervening line for validity, which is expensive) speeding up source extraction.
|
128
|
+
# @return [Fixum] Number of lines.
|
129
|
+
def number_of_lines_in_first_chunk
|
130
|
+
end_method_line = last_method_source_location.last
|
131
|
+
|
132
|
+
end_method_line - line
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|