pryx 0.11.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 +20 -18
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,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pryx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Billy.Zheng(zw963)
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: awesome_print
|
|
@@ -44,28 +43,28 @@ dependencies:
|
|
|
44
43
|
requirements:
|
|
45
44
|
- - "~>"
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0.
|
|
46
|
+
version: '0.16'
|
|
48
47
|
type: :runtime
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - "~>"
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0.
|
|
53
|
+
version: '0.16'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
55
|
name: looksee
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
58
|
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '5.
|
|
60
|
+
version: '5.1'
|
|
62
61
|
type: :runtime
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '5.
|
|
67
|
+
version: '5.1'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: pry-aa_ancestors
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,14 +85,14 @@ dependencies:
|
|
|
86
85
|
requirements:
|
|
87
86
|
- - "~>"
|
|
88
87
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '1.
|
|
88
|
+
version: '1.7'
|
|
90
89
|
type: :runtime
|
|
91
90
|
prerelease: false
|
|
92
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
92
|
requirements:
|
|
94
93
|
- - "~>"
|
|
95
94
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '1.
|
|
95
|
+
version: '1.7'
|
|
97
96
|
- !ruby/object:Gem::Dependency
|
|
98
97
|
name: pry-hier
|
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -137,19 +136,19 @@ dependencies:
|
|
|
137
136
|
- !ruby/object:Gem::Version
|
|
138
137
|
version: '1.6'
|
|
139
138
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
139
|
+
name: binding_of_caller
|
|
141
140
|
requirement: !ruby/object:Gem::Requirement
|
|
142
141
|
requirements:
|
|
143
142
|
- - "~>"
|
|
144
143
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0
|
|
144
|
+
version: '2.0'
|
|
146
145
|
type: :runtime
|
|
147
146
|
prerelease: false
|
|
148
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
148
|
requirements:
|
|
150
149
|
- - "~>"
|
|
151
150
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0
|
|
151
|
+
version: '2.0'
|
|
153
152
|
- !ruby/object:Gem::Dependency
|
|
154
153
|
name: m
|
|
155
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -170,14 +169,14 @@ dependencies:
|
|
|
170
169
|
requirements:
|
|
171
170
|
- - '='
|
|
172
171
|
- !ruby/object:Gem::Version
|
|
173
|
-
version:
|
|
172
|
+
version: '6.0'
|
|
174
173
|
type: :development
|
|
175
174
|
prerelease: false
|
|
176
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
177
176
|
requirements:
|
|
178
177
|
- - '='
|
|
179
178
|
- !ruby/object:Gem::Version
|
|
180
|
-
version:
|
|
179
|
+
version: '6.0'
|
|
181
180
|
- !ruby/object:Gem::Dependency
|
|
182
181
|
name: ritual
|
|
183
182
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -223,6 +222,11 @@ files:
|
|
|
223
222
|
- lib/pry-nav/tracer.rb
|
|
224
223
|
- lib/pry-nav/version.rb
|
|
225
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
|
|
226
230
|
- lib/pry-state.rb
|
|
227
231
|
- lib/pry-state/hook_action.rb
|
|
228
232
|
- lib/pry-state/printer.rb
|
|
@@ -245,7 +249,6 @@ licenses:
|
|
|
245
249
|
- MIT
|
|
246
250
|
metadata:
|
|
247
251
|
rubygems_mfa_required: 'true'
|
|
248
|
-
post_install_message:
|
|
249
252
|
rdoc_options: []
|
|
250
253
|
require_paths:
|
|
251
254
|
- lib
|
|
@@ -253,15 +256,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
253
256
|
requirements:
|
|
254
257
|
- - ">="
|
|
255
258
|
- !ruby/object:Gem::Version
|
|
256
|
-
version: '2.
|
|
259
|
+
version: '2.7'
|
|
257
260
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
258
261
|
requirements:
|
|
259
262
|
- - ">="
|
|
260
263
|
- !ruby/object:Gem::Version
|
|
261
264
|
version: '0'
|
|
262
265
|
requirements: []
|
|
263
|
-
rubygems_version:
|
|
264
|
-
signing_key:
|
|
266
|
+
rubygems_version: 4.0.3
|
|
265
267
|
specification_version: 4
|
|
266
268
|
summary: pry extension tools!
|
|
267
269
|
test_files: []
|