pry 0.10.0.pre4-x64-mingw32
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.
- 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 +341 -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
|