pryx 0.12.0 → 0.12.1
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 +4 -4
- data/lib/pry-stack_explorer/commands.rb +365 -0
- data/lib/pry-stack_explorer/frame_manager.rb +85 -0
- data/lib/pry-stack_explorer/version.rb +3 -0
- data/lib/pry-stack_explorer/when_started_hook.rb +96 -0
- data/lib/pry-stack_explorer.rb +138 -0
- data/lib/pryx/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0a4fd9f677ce53582ab29d0aa651a8f882430ad9f38b3e09a4f0241b45258b3
|
|
4
|
+
data.tar.gz: 3ea2ef13871304a19da84298d3c69f8a8a7ff3988611927ae5827e2117dc3f8a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b85934aaac5f9df4959d02f68d744e076f2a5d5bbd23bc0ec139727623899779500b214620608c57362348f70c274e241b45bcdb6acd9a0ff622dccb8d6e279e
|
|
7
|
+
data.tar.gz: a350c9b58d4b744611534709cf3aa0c63132cf3e68547801223a8c5baa70056440f89394ea8fa51c837fda44c18d3c7fefa3a507269d18474accc4c859eae022
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
module PryStackExplorer
|
|
2
|
+
module FrameHelpers
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
# @return [PryStackExplorer::FrameManager] The active frame manager for
|
|
6
|
+
# the current `Pry` instance.
|
|
7
|
+
def frame_manager
|
|
8
|
+
PryStackExplorer.frame_manager(pry_instance)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @return [Array<PryStackExplorer::FrameManager>] All the frame
|
|
12
|
+
# managers for the current `Pry` instance.
|
|
13
|
+
def frame_managers
|
|
14
|
+
PryStackExplorer.frame_managers(pry_instance)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Boolean] Whether there is a context to return to once
|
|
18
|
+
# the current `frame_manager` is popped.
|
|
19
|
+
def prior_context_exists?
|
|
20
|
+
frame_managers.count > 1 || frame_manager.prior_binding
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Return a description of the frame (binding).
|
|
24
|
+
# This is only useful for regular old bindings that have not been
|
|
25
|
+
# enhanced by `#of_caller`.
|
|
26
|
+
# @param [Binding] b The binding.
|
|
27
|
+
# @return [String] A description of the frame (binding).
|
|
28
|
+
def frame_description(b)
|
|
29
|
+
b_self = b.eval('self')
|
|
30
|
+
b_method = b.eval('__method__')
|
|
31
|
+
|
|
32
|
+
if b_method && b_method != :__binding__ && b_method != :__binding_impl__
|
|
33
|
+
b_method.to_s
|
|
34
|
+
elsif b_self.instance_of?(Module)
|
|
35
|
+
"<module:#{b_self}>"
|
|
36
|
+
elsif b_self.instance_of?(Class)
|
|
37
|
+
"<class:#{b_self}>"
|
|
38
|
+
else
|
|
39
|
+
"<main>"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Return a description of the passed binding object. Accepts an
|
|
44
|
+
# optional `verbose` parameter.
|
|
45
|
+
# @param [Binding] b The binding.
|
|
46
|
+
# @param [Boolean] verbose Whether to generate a verbose description.
|
|
47
|
+
# @return [String] The description of the binding.
|
|
48
|
+
def frame_info(b, verbose = false)
|
|
49
|
+
meth = b.eval('__method__')
|
|
50
|
+
b_self = b.eval('self')
|
|
51
|
+
meth_obj = Pry::Method.from_binding(b) if meth
|
|
52
|
+
|
|
53
|
+
type = b.frame_type ? "[#{b.frame_type}]".ljust(9) : ""
|
|
54
|
+
desc = b.frame_description ? "#{b.frame_description}" : "#{frame_description(b)}"
|
|
55
|
+
sig = meth_obj ? "<#{signature_with_owner(meth_obj)}>" : ""
|
|
56
|
+
|
|
57
|
+
self_clipped = "#{Pry.view_clip(b_self)}"
|
|
58
|
+
path = '@ ' + b.source_location.join(':')
|
|
59
|
+
|
|
60
|
+
if !verbose
|
|
61
|
+
"#{type} #{desc} #{sig}"
|
|
62
|
+
else
|
|
63
|
+
"#{type} #{desc} #{sig}\n in #{self_clipped} #{path}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param [Pry::Method] meth_obj The method object.
|
|
68
|
+
# @return [String] Signature for the method object in Class#method format.
|
|
69
|
+
def signature_with_owner(meth_obj)
|
|
70
|
+
if !meth_obj.undefined?
|
|
71
|
+
args = meth_obj.parameters.inject([]) do |arr, (type, name)|
|
|
72
|
+
name ||= (type == :block ? 'block' : "arg#{arr.size + 1}")
|
|
73
|
+
arr << case type
|
|
74
|
+
when :req then name.to_s
|
|
75
|
+
when :opt then "#{name}=?"
|
|
76
|
+
when :rest then "*#{name}"
|
|
77
|
+
when :block then "&#{name}"
|
|
78
|
+
else '?'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
"#{meth_obj.name_with_owner}(#{args.join(', ')})"
|
|
82
|
+
else
|
|
83
|
+
"#{meth_obj.name_with_owner}(UNKNOWN) (undefined method)"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Regexp.new(args[0])
|
|
88
|
+
def find_frame_by_regex(regex, up_or_down)
|
|
89
|
+
frame_index = find_frame_by_block(up_or_down) do |b|
|
|
90
|
+
b.eval("__method__").to_s =~ regex
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if frame_index
|
|
94
|
+
frame_index
|
|
95
|
+
else
|
|
96
|
+
raise Pry::CommandError, "No frame that matches #{regex.source} found!"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def find_frame_by_object_regex(class_regex, method_regex, up_or_down)
|
|
101
|
+
frame_index = find_frame_by_block(up_or_down) do |b|
|
|
102
|
+
class_match = b.eval("self.class").to_s =~ class_regex
|
|
103
|
+
meth_match = b.eval("__method__").to_s =~ method_regex
|
|
104
|
+
|
|
105
|
+
class_match && meth_match
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if frame_index
|
|
109
|
+
frame_index
|
|
110
|
+
else
|
|
111
|
+
raise Pry::CommandError, "No frame that matches #{class_regex.source}" + '#' + "#{method_regex.source} found!"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def find_frame_by_block(up_or_down)
|
|
116
|
+
start_index = frame_manager.binding_index
|
|
117
|
+
|
|
118
|
+
if up_or_down == :down
|
|
119
|
+
enum = frame_manager.bindings[0..start_index - 1].reverse_each
|
|
120
|
+
else
|
|
121
|
+
enum = frame_manager.bindings[start_index + 1..-1]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
new_frame = enum.find do |b|
|
|
125
|
+
yield(b)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
frame_manager.bindings.index(new_frame)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
Commands = Pry::CommandSet.new do
|
|
134
|
+
create_command "up", "Go up to the caller's context." do
|
|
135
|
+
include FrameHelpers
|
|
136
|
+
|
|
137
|
+
banner <<-BANNER
|
|
138
|
+
Usage: up [OPTIONS]
|
|
139
|
+
Go up to the caller's context. Accepts optional numeric parameter for how many frames to move up.
|
|
140
|
+
Also accepts a string (regex) instead of numeric; for jumping to nearest parent method frame which matches the regex.
|
|
141
|
+
e.g: up #=> Move up 1 stack frame.
|
|
142
|
+
e.g: up 3 #=> Move up 2 stack frames.
|
|
143
|
+
e.g: up meth #=> Jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`.
|
|
144
|
+
BANNER
|
|
145
|
+
|
|
146
|
+
def process
|
|
147
|
+
inc = args.first.nil? ? "1" : args.first
|
|
148
|
+
|
|
149
|
+
if !frame_manager
|
|
150
|
+
raise Pry::CommandError, "Nowhere to go!"
|
|
151
|
+
else
|
|
152
|
+
if inc =~ /\d+/
|
|
153
|
+
frame_manager.change_frame_to frame_manager.binding_index + inc.to_i
|
|
154
|
+
elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(inc)
|
|
155
|
+
new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)
|
|
156
|
+
frame_manager.change_frame_to new_frame_index
|
|
157
|
+
elsif inc =~ /^[^-].*$/
|
|
158
|
+
new_frame_index = find_frame_by_regex(Regexp.new(inc), :up)
|
|
159
|
+
frame_manager.change_frame_to new_frame_index
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
create_command "down", "Go down to the callee's context." do
|
|
166
|
+
include FrameHelpers
|
|
167
|
+
|
|
168
|
+
banner <<-BANNER
|
|
169
|
+
Usage: down [OPTIONS]
|
|
170
|
+
Go down to the callee's context. Accepts optional numeric parameter for how many frames to move down.
|
|
171
|
+
Also accepts a string (regex) instead of numeric; for jumping to nearest child method frame which matches the regex.
|
|
172
|
+
e.g: down #=> Move down 1 stack frame.
|
|
173
|
+
e.g: down 3 #=> Move down 2 stack frames.
|
|
174
|
+
e.g: down meth #=> Jump to nearest child stack frame whose method matches /meth/ regex, i.e `my_method`.
|
|
175
|
+
BANNER
|
|
176
|
+
|
|
177
|
+
def process
|
|
178
|
+
inc = args.first.nil? ? "1" : args.first
|
|
179
|
+
|
|
180
|
+
if !frame_manager
|
|
181
|
+
raise Pry::CommandError, "Nowhere to go!"
|
|
182
|
+
else
|
|
183
|
+
if inc =~ /\d+/
|
|
184
|
+
if frame_manager.binding_index - inc.to_i < 0
|
|
185
|
+
raise Pry::CommandError, "At bottom of stack, cannot go further!"
|
|
186
|
+
else
|
|
187
|
+
frame_manager.change_frame_to frame_manager.binding_index - inc.to_i
|
|
188
|
+
end
|
|
189
|
+
elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(inc)
|
|
190
|
+
new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :down)
|
|
191
|
+
frame_manager.change_frame_to new_frame_index
|
|
192
|
+
elsif inc =~ /^[^-].*$/
|
|
193
|
+
new_frame_index = find_frame_by_regex(Regexp.new(inc), :down)
|
|
194
|
+
frame_manager.change_frame_to new_frame_index
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
create_command "frame", "Switch to a particular frame." do
|
|
201
|
+
include FrameHelpers
|
|
202
|
+
|
|
203
|
+
banner <<-BANNER
|
|
204
|
+
Usage: frame [OPTIONS]
|
|
205
|
+
Switch to a particular frame. Accepts numeric parameter (or regex for method name) for the target frame to switch to (use with show-stack).
|
|
206
|
+
Negative frame numbers allowed. When given no parameter show information about the current frame.
|
|
207
|
+
|
|
208
|
+
e.g: frame 4 #=> jump to the 4th frame
|
|
209
|
+
e.g: frame meth #=> jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`
|
|
210
|
+
e.g: frame -2 #=> jump to the second-to-last frame
|
|
211
|
+
e.g: frame #=> show information info about current frame
|
|
212
|
+
BANNER
|
|
213
|
+
|
|
214
|
+
def process
|
|
215
|
+
if !frame_manager
|
|
216
|
+
raise Pry::CommandError, "nowhere to go!"
|
|
217
|
+
else
|
|
218
|
+
|
|
219
|
+
if args[0] =~ /\d+/
|
|
220
|
+
frame_manager.change_frame_to args[0].to_i
|
|
221
|
+
elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(args[0])
|
|
222
|
+
new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)
|
|
223
|
+
frame_manager.change_frame_to new_frame_index
|
|
224
|
+
elsif args[0] =~ /^[^-].*$/
|
|
225
|
+
new_frame_index = find_frame_by_regex(Regexp.new(args[0]), :up)
|
|
226
|
+
frame_manager.change_frame_to new_frame_index
|
|
227
|
+
else
|
|
228
|
+
output.puts "##{frame_manager.binding_index} #{frame_info(target, true)}"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
create_command "stack", "Show all frames" do
|
|
235
|
+
include FrameHelpers
|
|
236
|
+
|
|
237
|
+
banner <<-BANNER
|
|
238
|
+
Usage: stack [OPTIONS]
|
|
239
|
+
Show all accessible stack frames.
|
|
240
|
+
e.g: stack -v
|
|
241
|
+
|
|
242
|
+
alias: show-stack
|
|
243
|
+
BANNER
|
|
244
|
+
|
|
245
|
+
def options(opt)
|
|
246
|
+
opt.on :v, :verbose, "Include extra information."
|
|
247
|
+
opt.on :H, :head, "Display the first N stack frames (defaults to 10).", :optional_argument => true, :as => Integer, :default => 10
|
|
248
|
+
opt.on :T, :tail, "Display the last N stack frames (defaults to 10).", :optional_argument => true, :as => Integer, :default => 10
|
|
249
|
+
opt.on :c, :current, "Display N frames either side of current frame (default to 5).", :optional_argument => true, :as => Integer, :default => 5
|
|
250
|
+
opt.on :a, :app, "Display application frames only", optional_argument: true
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def memoized_info(index, b, verbose)
|
|
254
|
+
frame_manager.user[:frame_info] ||= Hash.new { |h, k| h[k] = [] }
|
|
255
|
+
|
|
256
|
+
if verbose
|
|
257
|
+
frame_manager.user[:frame_info][:v][index] ||= frame_info(b, verbose)
|
|
258
|
+
else
|
|
259
|
+
frame_manager.user[:frame_info][:normal][index] ||= frame_info(b, verbose)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
private :memoized_info
|
|
264
|
+
|
|
265
|
+
# @return [Array<Fixnum, Array<Binding>>] Return tuple of
|
|
266
|
+
# base_frame_index and the array of frames.
|
|
267
|
+
def selected_stack_frames
|
|
268
|
+
if opts.present?(:head)
|
|
269
|
+
[0, frame_manager.bindings[0..(opts[:head] - 1)]]
|
|
270
|
+
|
|
271
|
+
elsif opts.present?(:tail)
|
|
272
|
+
tail = opts[:tail]
|
|
273
|
+
if tail > frame_manager.bindings.size
|
|
274
|
+
tail = frame_manager.bindings.size
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
base_frame_index = frame_manager.bindings.size - tail
|
|
278
|
+
[base_frame_index, frame_manager.bindings[base_frame_index..-1]]
|
|
279
|
+
|
|
280
|
+
elsif opts.present?(:current)
|
|
281
|
+
first_frame_index = frame_manager.binding_index - (opts[:current])
|
|
282
|
+
first_frame_index = 0 if first_frame_index < 0
|
|
283
|
+
last_frame_index = frame_manager.binding_index + (opts[:current])
|
|
284
|
+
[first_frame_index, frame_manager.bindings[first_frame_index..last_frame_index]]
|
|
285
|
+
|
|
286
|
+
else
|
|
287
|
+
[0, frame_manager.bindings]
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private :selected_stack_frames
|
|
292
|
+
|
|
293
|
+
def process
|
|
294
|
+
return no_stack_available! unless frame_manager
|
|
295
|
+
|
|
296
|
+
title = "Showing all accessible frames in stack (#{frame_manager.bindings.size} in total):"
|
|
297
|
+
|
|
298
|
+
content = [
|
|
299
|
+
bold(title),
|
|
300
|
+
"---",
|
|
301
|
+
make_stack_lines
|
|
302
|
+
].join("\n")
|
|
303
|
+
|
|
304
|
+
stagger_output content
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
def make_stack_lines
|
|
310
|
+
frames_with_indices.map do |b, i|
|
|
311
|
+
make_stack_line(b, i, (i == frame_manager.binding_index))
|
|
312
|
+
end.join("\n")
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def frames_with_indices
|
|
316
|
+
if opts.present?(:app) && defined?(ActiveSupport::BacktraceCleaner)
|
|
317
|
+
app_frames
|
|
318
|
+
else
|
|
319
|
+
offset_frames
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# "=> #0 method_name <Class#method(...)>"
|
|
324
|
+
def make_stack_line(b, i, active)
|
|
325
|
+
arw = active ? "=>" : " "
|
|
326
|
+
|
|
327
|
+
"#{arw} ##{i} #{memoized_info(i, b, opts[:v])}"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def offset_frames
|
|
331
|
+
base_frame_index, frames = selected_stack_frames
|
|
332
|
+
|
|
333
|
+
frames.each_with_index.map do |frame, index|
|
|
334
|
+
[frame, index + base_frame_index]
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def no_stack_available!
|
|
339
|
+
output.puts "No caller stack available!"
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
LOCATION_LAMBDA = ->(_binding){ _binding.source_location[0] }
|
|
343
|
+
|
|
344
|
+
def app_frames
|
|
345
|
+
locations = frame_manager.bindings.map(&LOCATION_LAMBDA)
|
|
346
|
+
filtered = backtrace_cleaner.clean(locations)
|
|
347
|
+
|
|
348
|
+
frame_manager.bindings
|
|
349
|
+
.each_with_index
|
|
350
|
+
.map
|
|
351
|
+
.select do |_binding, _index|
|
|
352
|
+
LOCATION_LAMBDA.call(_binding).in?(filtered)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# also see Rails::BacktraceCleaner
|
|
357
|
+
def backtrace_cleaner
|
|
358
|
+
@backtrace_cleaner ||= ActiveSupport::BacktraceCleaner.new
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
alias_command "show-stack", "stack"
|
|
363
|
+
|
|
364
|
+
end
|
|
365
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module PryStackExplorer
|
|
2
|
+
|
|
3
|
+
# This class represents a call-stack. It stores the
|
|
4
|
+
# frames that make up the stack and is responsible for updating the
|
|
5
|
+
# associated Pry instance to reflect the active frame. It is fully Enumerable.
|
|
6
|
+
class FrameManager
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
# @return [Array<Binding>] The array of bindings that constitute
|
|
10
|
+
# the call-stack.
|
|
11
|
+
attr_accessor :bindings
|
|
12
|
+
|
|
13
|
+
# @return [Fixnum] The index of the active frame (binding) in the call-stack.
|
|
14
|
+
attr_accessor :binding_index
|
|
15
|
+
|
|
16
|
+
# @return [Hash] A hash for user defined data
|
|
17
|
+
attr_reader :user
|
|
18
|
+
|
|
19
|
+
# @return [Binding] The binding of the Pry instance before the
|
|
20
|
+
# FrameManager took over.
|
|
21
|
+
attr_reader :prior_binding
|
|
22
|
+
|
|
23
|
+
# @return [Array] The backtrace of the Pry instance before the
|
|
24
|
+
# FrameManager took over.
|
|
25
|
+
attr_reader :prior_backtrace
|
|
26
|
+
|
|
27
|
+
def initialize(bindings, _pry_)
|
|
28
|
+
self.bindings = bindings
|
|
29
|
+
self.binding_index = 0
|
|
30
|
+
@pry = _pry_
|
|
31
|
+
@user = {}
|
|
32
|
+
@prior_binding = _pry_.binding_stack.last
|
|
33
|
+
@prior_backtrace = _pry_.backtrace
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Iterate over all frames
|
|
37
|
+
def each(&block)
|
|
38
|
+
bindings.each(&block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Ensure the Pry instance's active binding is the frame manager's
|
|
42
|
+
# active binding.
|
|
43
|
+
def refresh_frame(run_whereami=true)
|
|
44
|
+
change_frame_to binding_index, run_whereami
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Binding] The currently active frame
|
|
48
|
+
def current_frame
|
|
49
|
+
bindings[binding_index]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Set the binding index (aka frame index), but raising an Exception when invalid
|
|
53
|
+
# index received. Also converts negative indices to their positive counterparts.
|
|
54
|
+
# @param [Fixnum] index The index.
|
|
55
|
+
def set_binding_index_safely(index)
|
|
56
|
+
if index > bindings.size - 1
|
|
57
|
+
raise Pry::CommandError, "At top of stack, cannot go further!"
|
|
58
|
+
elsif index < -bindings.size
|
|
59
|
+
raise Pry::CommandError, "At bottom of stack, cannot go further!"
|
|
60
|
+
else
|
|
61
|
+
# wrap around negative indices
|
|
62
|
+
index = (bindings.size - 1) + index + 1 if index < 0
|
|
63
|
+
|
|
64
|
+
self.binding_index = index
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Change active frame to the one indexed by `index`.
|
|
69
|
+
# Note that indexing base is `0`
|
|
70
|
+
# @param [Fixnum] index The index of the frame.
|
|
71
|
+
def change_frame_to(index, run_whereami=true)
|
|
72
|
+
|
|
73
|
+
set_binding_index_safely(index)
|
|
74
|
+
|
|
75
|
+
if @pry.binding_stack.empty?
|
|
76
|
+
@pry.binding_stack.replace [bindings[binding_index]]
|
|
77
|
+
else
|
|
78
|
+
@pry.binding_stack[-1] = bindings[binding_index]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@pry.run_command "whereami" if run_whereami
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module PryStackExplorer
|
|
2
|
+
class WhenStartedHook
|
|
3
|
+
include Pry::Helpers::BaseHelpers
|
|
4
|
+
|
|
5
|
+
def caller_bindings(target)
|
|
6
|
+
bindings = binding.callers
|
|
7
|
+
|
|
8
|
+
bindings = remove_internal_frames(bindings)
|
|
9
|
+
bindings = remove_debugger_frames(bindings)
|
|
10
|
+
bindings = bindings.drop(1) if pry_method_frame?(bindings.first)
|
|
11
|
+
|
|
12
|
+
# Use the binding returned by #of_caller if possible (as we get
|
|
13
|
+
# access to frame_type).
|
|
14
|
+
# Otherwise stick to the given binding (target).
|
|
15
|
+
if !PryStackExplorer.bindings_equal?(target, bindings.first)
|
|
16
|
+
bindings.shift
|
|
17
|
+
bindings.unshift(target)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
bindings
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(target, options, _pry_)
|
|
24
|
+
target ||= _pry_.binding_stack.first if _pry_
|
|
25
|
+
options = {
|
|
26
|
+
:call_stack => true,
|
|
27
|
+
:initial_frame => 0
|
|
28
|
+
}.merge!(options)
|
|
29
|
+
|
|
30
|
+
return if !options[:call_stack]
|
|
31
|
+
|
|
32
|
+
if options[:call_stack].is_a?(Array)
|
|
33
|
+
bindings = options[:call_stack]
|
|
34
|
+
|
|
35
|
+
if !valid_call_stack?(bindings)
|
|
36
|
+
raise ArgumentError, ":call_stack must be an array of bindings"
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
bindings = caller_bindings(target)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
PryStackExplorer.create_and_push_frame_manager bindings, _pry_, :initial_frame => options[:initial_frame]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# remove internal frames related to setting up the session
|
|
48
|
+
def remove_internal_frames(bindings)
|
|
49
|
+
start_frames = internal_frames_with_indices(bindings)
|
|
50
|
+
return bindings if start_frames.empty?
|
|
51
|
+
|
|
52
|
+
start_frame_index = start_frames.first.last
|
|
53
|
+
|
|
54
|
+
if start_frames.size >= 2
|
|
55
|
+
# god knows what's going on in here
|
|
56
|
+
idx1, idx2 = start_frames.take(2).map(&:last)
|
|
57
|
+
start_frame_index = idx2 if !nested_session?(bindings[idx1..idx2])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
bindings.drop(start_frame_index + 1)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# remove pry-nav / pry-debugger / pry-byebug frames
|
|
64
|
+
def remove_debugger_frames(bindings)
|
|
65
|
+
bindings.drop_while { |b| b.source_location[0] =~ /pry-(?:nav|debugger|byebug)/ }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# binding.pry frame
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def pry_method_frame?(binding)
|
|
71
|
+
safe_send(binding.eval("__method__"), :==, :pry)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# When a pry session is started within a pry session
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def nested_session?(bindings)
|
|
77
|
+
bindings.detect do |b|
|
|
78
|
+
safe_send(b.eval("__method__"), :==, :re) &&
|
|
79
|
+
safe_send(b.eval("self.class"), :equal?, Pry)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [Array<Array<Binding, Fixnum>>]
|
|
84
|
+
def internal_frames_with_indices(bindings)
|
|
85
|
+
bindings.each_with_index.select do |b, i|
|
|
86
|
+
b.frame_type == :method &&
|
|
87
|
+
safe_send(b.eval("self"), :equal?, Pry) &&
|
|
88
|
+
safe_send(b.eval("__method__"), :==, :start)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def valid_call_stack?(bindings)
|
|
93
|
+
bindings.any? && bindings.all? { |v| v.is_a?(Binding) }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# pry-stack_explorer.rb
|
|
2
|
+
# (C) John Mair (banisterfiend); MIT license
|
|
3
|
+
|
|
4
|
+
require "pry" unless defined?(::Pry)
|
|
5
|
+
require "pry-stack_explorer/version"
|
|
6
|
+
require "pry-stack_explorer/commands"
|
|
7
|
+
require "pry-stack_explorer/frame_manager"
|
|
8
|
+
require "pry-stack_explorer/when_started_hook"
|
|
9
|
+
require "binding_of_caller"
|
|
10
|
+
|
|
11
|
+
module PryStackExplorer
|
|
12
|
+
|
|
13
|
+
# short-hand for `PryStackExplorer`
|
|
14
|
+
::SE = self
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# @return [Hash] The hash storing all frames for all Pry instances for
|
|
18
|
+
# the current thread.
|
|
19
|
+
def frame_hash
|
|
20
|
+
Thread.current[:__pry_frame_managers__] ||= Hash.new { |h, k| h[k] = [] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Return the complete frame manager stack for the Pry instance
|
|
24
|
+
# @param [Pry] _pry_ The Pry instance associated with the frame
|
|
25
|
+
# managers
|
|
26
|
+
# @return [Array] The stack of Pry::FrameManager objections
|
|
27
|
+
def frame_managers(_pry_)
|
|
28
|
+
frame_hash[_pry_]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Create a `Pry::FrameManager` object and push it onto the frame
|
|
32
|
+
# manager stack for the relevant `_pry_` instance.
|
|
33
|
+
# @param [Array] bindings The array of bindings (frames)
|
|
34
|
+
# @param [Pry] _pry_ The Pry instance associated with the frame manager
|
|
35
|
+
def create_and_push_frame_manager(bindings, _pry_, options={})
|
|
36
|
+
fm = FrameManager.new(bindings, _pry_)
|
|
37
|
+
frame_hash[_pry_].push fm
|
|
38
|
+
push_helper(fm, options)
|
|
39
|
+
fm
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Update the Pry instance to operate on the specified frame for the
|
|
43
|
+
# current frame manager.
|
|
44
|
+
# @param [PryStackExplorer::FrameManager] fm The active frame manager.
|
|
45
|
+
# @param [Hash] options The options hash.
|
|
46
|
+
def push_helper(fm, options={})
|
|
47
|
+
options = {
|
|
48
|
+
:initial_frame => 0
|
|
49
|
+
}.merge!(options)
|
|
50
|
+
|
|
51
|
+
fm.change_frame_to(options[:initial_frame], false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private :push_helper
|
|
55
|
+
|
|
56
|
+
# Delete the currently active frame manager
|
|
57
|
+
# @param [Pry] _pry_ The Pry instance associated with the frame
|
|
58
|
+
# managers.
|
|
59
|
+
# @return [Pry::FrameManager] The popped frame manager.
|
|
60
|
+
def pop_frame_manager(_pry_)
|
|
61
|
+
return if frame_managers(_pry_).empty?
|
|
62
|
+
|
|
63
|
+
popped_fm = frame_managers(_pry_).pop
|
|
64
|
+
pop_helper(popped_fm, _pry_)
|
|
65
|
+
popped_fm
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Restore the Pry instance to operate on the previous
|
|
69
|
+
# binding. Also responsible for restoring Pry instance's backtrace.
|
|
70
|
+
# @param [Pry::FrameManager] popped_fm The recently popped frame manager.
|
|
71
|
+
# @param [Pry] _pry_ The Pry instance associated with the frame managers.
|
|
72
|
+
def pop_helper(popped_fm, _pry_)
|
|
73
|
+
if frame_managers(_pry_).empty?
|
|
74
|
+
if _pry_.binding_stack.empty?
|
|
75
|
+
_pry_.binding_stack.push popped_fm.prior_binding
|
|
76
|
+
else
|
|
77
|
+
_pry_.binding_stack[-1] = popped_fm.prior_binding
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
frame_hash.delete(_pry_)
|
|
81
|
+
else
|
|
82
|
+
frame_manager(_pry_).refresh_frame(false)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# restore backtrace
|
|
86
|
+
_pry_.backtrace = popped_fm.prior_backtrace
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private :pop_helper
|
|
90
|
+
|
|
91
|
+
# Clear the stack of frame managers for the Pry instance
|
|
92
|
+
# @param [Pry] _pry_ The Pry instance associated with the frame managers
|
|
93
|
+
def clear_frame_managers(_pry_)
|
|
94
|
+
pop_frame_manager(_pry_) until frame_managers(_pry_).empty?
|
|
95
|
+
frame_hash.delete(_pry_) # this line should be unnecessary!
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
alias_method :delete_frame_managers, :clear_frame_managers
|
|
99
|
+
|
|
100
|
+
# @return [PryStackExplorer::FrameManager] The currently active frame manager
|
|
101
|
+
def frame_manager(_pry_)
|
|
102
|
+
frame_hash[_pry_].last
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Simple test to check whether two `Binding` objects are equal.
|
|
106
|
+
# @param [Binding] b1 First binding.
|
|
107
|
+
# @param [Binding] b2 Second binding.
|
|
108
|
+
# @return [Boolean] Whether the `Binding`s are equal.
|
|
109
|
+
def bindings_equal?(b1, b2)
|
|
110
|
+
(b1.eval('self').equal?(b2.eval('self'))) &&
|
|
111
|
+
(b1.eval('__method__') == b2.eval('__method__')) &&
|
|
112
|
+
(b1.eval('local_variables').map { |v| b1.eval("#{v}") }.equal?(
|
|
113
|
+
b2.eval('local_variables').map { |v| b2.eval("#{v}") }))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
Pry.config.hooks.add_hook(:after_session, :delete_frame_manager) do |_, _, _pry_|
|
|
119
|
+
PryStackExplorer.clear_frame_managers(_pry_)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
Pry.config.hooks.add_hook(:when_started, :save_caller_bindings, PryStackExplorer::WhenStartedHook.new)
|
|
123
|
+
|
|
124
|
+
# Import the StackExplorer commands
|
|
125
|
+
Pry.config.commands.import PryStackExplorer::Commands
|
|
126
|
+
|
|
127
|
+
# monkey-patch the whereami command to show some frame information,
|
|
128
|
+
# useful for navigating stack.
|
|
129
|
+
Pry.config.hooks.add_hook(:before_whereami, :stack_explorer) do
|
|
130
|
+
if PryStackExplorer.frame_manager(pry_instance) && !internal_binding?(target)
|
|
131
|
+
bindings = PryStackExplorer.frame_manager(pry_instance).bindings
|
|
132
|
+
binding_index = PryStackExplorer.frame_manager(pry_instance).binding_index
|
|
133
|
+
|
|
134
|
+
output.puts "\n"
|
|
135
|
+
output.puts "#{Pry::Helpers::Text.bold('Frame number:')} #{binding_index}/#{bindings.size - 1}"
|
|
136
|
+
output.puts "#{Pry::Helpers::Text.bold('Frame type:')} #{bindings[binding_index].frame_type}" if bindings[binding_index].frame_type
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/pryx/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pryx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Billy.Zheng(zw963)
|
|
@@ -136,19 +136,19 @@ dependencies:
|
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '1.6'
|
|
138
138
|
- !ruby/object:Gem::Dependency
|
|
139
|
-
name:
|
|
139
|
+
name: binding_of_caller
|
|
140
140
|
requirement: !ruby/object:Gem::Requirement
|
|
141
141
|
requirements:
|
|
142
142
|
- - "~>"
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '0
|
|
144
|
+
version: '2.0'
|
|
145
145
|
type: :runtime
|
|
146
146
|
prerelease: false
|
|
147
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
149
|
- - "~>"
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '0
|
|
151
|
+
version: '2.0'
|
|
152
152
|
- !ruby/object:Gem::Dependency
|
|
153
153
|
name: m
|
|
154
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -222,6 +222,11 @@ files:
|
|
|
222
222
|
- lib/pry-nav/tracer.rb
|
|
223
223
|
- lib/pry-nav/version.rb
|
|
224
224
|
- lib/pry-remote.rb
|
|
225
|
+
- lib/pry-stack_explorer.rb
|
|
226
|
+
- lib/pry-stack_explorer/commands.rb
|
|
227
|
+
- lib/pry-stack_explorer/frame_manager.rb
|
|
228
|
+
- lib/pry-stack_explorer/version.rb
|
|
229
|
+
- lib/pry-stack_explorer/when_started_hook.rb
|
|
225
230
|
- lib/pry-state.rb
|
|
226
231
|
- lib/pry-state/hook_action.rb
|
|
227
232
|
- lib/pry-state/printer.rb
|