pry-moves 0.1.9 → 1.0.0
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/Gemfile +1 -1
- data/Gemfile.lock +6 -4
- data/README.md +28 -8
- data/lib/commands/debug.rb +17 -0
- data/lib/commands/finish.rb +26 -0
- data/lib/commands/goto.rb +19 -0
- data/lib/commands/iterate.rb +22 -0
- data/lib/commands/next.rb +37 -0
- data/lib/commands/next_breakpoint.rb +22 -0
- data/lib/commands/step.rb +83 -0
- data/lib/commands/trace_command.rb +87 -0
- data/lib/commands/trace_helpers.rb +49 -0
- data/lib/commands/traced_method.rb +71 -0
- data/lib/debug_sugar.rb +72 -0
- data/lib/pry-moves/add_suffix.rb +88 -0
- data/lib/pry-moves/backtrace.rb +70 -46
- data/lib/pry-moves/bindings_stack.rb +97 -0
- data/lib/pry-moves/commands.rb +50 -7
- data/lib/pry-moves/formatter.rb +74 -0
- data/lib/pry-moves/painter.rb +5 -0
- data/lib/pry-moves/pry_ext.rb +64 -16
- data/lib/pry-moves/pry_wrapper.rb +30 -17
- data/lib/pry-moves/restartable.rb +38 -0
- data/lib/pry-moves/version.rb +1 -1
- data/lib/pry-moves/watch.rb +3 -0
- data/lib/pry-moves.rb +65 -4
- data/lib/pry-stack_explorer/VERSION +2 -0
- data/lib/pry-stack_explorer/frame_manager.rb +6 -9
- data/lib/pry-stack_explorer/pry-stack_explorer.rb +3 -17
- data/lib/pry-stack_explorer/{commands.rb → stack_commands.rb} +10 -6
- data/lib/pry-stack_explorer/when_started_hook.rb +17 -63
- data/playground/Gemfile.lock +9 -9
- data/playground/README.md +1 -0
- data/playground/playground.rb +94 -9
- data/playground/sand.rb +46 -12
- data/playground/test.rb +5 -1
- data/playground/test.sh +1 -0
- data/pry-moves.gemspec +3 -2
- data/publish.sh +3 -0
- data/spec/backtrace_spec.rb +11 -13
- data/spec/blocks_spec.rb +41 -8
- data/spec/commands_spec.rb +26 -25
- data/spec/pry_debugger.rb +5 -1
- data/spec/redirection_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -4
- data/spec/step_spec.rb +51 -0
- metadata +43 -10
- data/lib/pry-moves/helpers.rb +0 -50
- data/lib/pry-moves/trace_commands.rb +0 -105
- data/lib/pry-moves/tracer.rb +0 -169
@@ -0,0 +1,49 @@
|
|
1
|
+
module PryMoves::TraceHelpers
|
2
|
+
|
3
|
+
def redirect_step?(binding_)
|
4
|
+
return false unless binding_.local_variable_defined? :debug_redirect
|
5
|
+
|
6
|
+
debug_redirect = binding_.local_variable_get(:debug_redirect)
|
7
|
+
@step_into_funcs = [debug_redirect] if debug_redirect
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def debug_info(file, line, id)
|
12
|
+
puts "📽 call_depth:#{@call_depth} #{@method[:file]}:#{file}"
|
13
|
+
puts "#{id} #{@method[:start]} > #{line} > #{@method[:end]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_frame_digest(upward: 0)
|
17
|
+
# binding_ from tracing_func doesn't have @iseq,
|
18
|
+
# therefore binding should be re-retrieved using 'binding_of_caller' lib
|
19
|
+
frame_digest(binding.of_caller(3 + upward))
|
20
|
+
end
|
21
|
+
|
22
|
+
def frame_digest(binding_)
|
23
|
+
#puts "frame_digest for: #{binding_.eval '__callee__'}"
|
24
|
+
iseq = binding_.instance_variable_get('@iseq')
|
25
|
+
Digest::MD5.hexdigest iseq.disasm
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_frame_type(upward: 0)
|
29
|
+
# binding_ from tracing_func doesn't have @iseq,
|
30
|
+
# therefore binding should be re-retrieved using 'binding_of_caller' lib
|
31
|
+
frame_type(binding.of_caller(3 + upward))
|
32
|
+
end
|
33
|
+
|
34
|
+
def frame_type(binding_)
|
35
|
+
line = binding_.instance_variable_get('@iseq').disasm.split("\n").first
|
36
|
+
m = line.match /\== disasm: #<ISeq:([\w ]+)@/
|
37
|
+
if m
|
38
|
+
str = m[1]
|
39
|
+
if str.start_with? 'block in '
|
40
|
+
:block
|
41
|
+
else
|
42
|
+
:method
|
43
|
+
end
|
44
|
+
else
|
45
|
+
:unknown
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class PryMoves::TracedMethod < Hash
|
2
|
+
|
3
|
+
@@last = nil
|
4
|
+
def self.last
|
5
|
+
@@last
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(binding_)
|
9
|
+
super()
|
10
|
+
|
11
|
+
method = find_method_definition binding_
|
12
|
+
if method
|
13
|
+
source = method.source_location
|
14
|
+
set_method({
|
15
|
+
file: source[0],
|
16
|
+
start: source[1],
|
17
|
+
name: method.name,
|
18
|
+
end: (source[1] + method.source.count("\n") - 1)
|
19
|
+
})
|
20
|
+
else
|
21
|
+
set_method({file: binding_.eval('__FILE__')})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def within?(file, line, id = nil)
|
26
|
+
return unless self[:file] == file
|
27
|
+
return unless self[:start].nil? or
|
28
|
+
line.between?(self[:start], self[:end])
|
29
|
+
return unless id.nil? or self[:name] == id # fix for bug in traced_method: return for dynamic methods has line number inside of caller
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def binding_inside?(binding)
|
35
|
+
within? *binding.eval('[__FILE__, __LINE__, __method__]')
|
36
|
+
end
|
37
|
+
|
38
|
+
def before_end?(line)
|
39
|
+
self[:end] and line < self[:end]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def find_method_definition(binding)
|
45
|
+
method_name, obj, file =
|
46
|
+
binding.eval '[__method__, self, __FILE__]'
|
47
|
+
return unless method_name
|
48
|
+
|
49
|
+
method = obj.method(method_name)
|
50
|
+
return method if method.source_location[0] == file
|
51
|
+
|
52
|
+
# If found file was different - search definition at superclasses:
|
53
|
+
obj.class.ancestors.each do |cls|
|
54
|
+
if cls.instance_methods(false).include? method_name
|
55
|
+
method = cls.instance_method method_name
|
56
|
+
return method if method.source_location[0] == file
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
PryMoves.messages << "⚠️ Unable to find definition for method #{method_name} in #{obj}"
|
61
|
+
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_method(method)
|
66
|
+
#puts "set_traced_method #{method}"
|
67
|
+
merge! method
|
68
|
+
@@last = self
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/debug_sugar.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
def debug *args
|
2
|
+
pry_moves_stack_root = true
|
3
|
+
PryMoves.debug *args
|
4
|
+
end
|
5
|
+
|
6
|
+
def error(msg, debug_object = nil)
|
7
|
+
pry_moves_stack_root = true
|
8
|
+
err = "😱 #{msg}"
|
9
|
+
unless PryMoves.open?
|
10
|
+
if PryMoves.stop_on_breakpoints
|
11
|
+
lines = [err.red]
|
12
|
+
lines.prepend debug_object.ai if debug_object
|
13
|
+
PryMoves.debug lines.join("\n")
|
14
|
+
else
|
15
|
+
STDERR.puts debug_object.ai if debug_object
|
16
|
+
STDERR.puts err.ljust(80, ' ').red
|
17
|
+
end
|
18
|
+
end
|
19
|
+
raise msg
|
20
|
+
end
|
21
|
+
|
22
|
+
def shit!(err = 'Oh, shit!', debug_object = nil)
|
23
|
+
pry_moves_stack_root = true
|
24
|
+
message = "💩 #{err.is_a?(String) ? err : err.message}"
|
25
|
+
raise err unless PryMoves.stop_on_breakpoints
|
26
|
+
lines = [message.red]
|
27
|
+
lines.prepend debug_object.ai if debug_object
|
28
|
+
PryMoves.debug lines.join("\n")
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def required(var)
|
33
|
+
pry_moves_stack_root = true
|
34
|
+
error("required parameter is missing") if var.nil?
|
35
|
+
var
|
36
|
+
end
|
37
|
+
|
38
|
+
RSpec.configure do |config|
|
39
|
+
|
40
|
+
config.before(:each) do
|
41
|
+
PryMoves.launched_specs_examples += 1
|
42
|
+
PryMoves.stop_on_breakpoints =
|
43
|
+
PryMoves.launched_specs_examples < 2
|
44
|
+
end
|
45
|
+
|
46
|
+
config.around(:each) do |example|
|
47
|
+
PryMoves.restartable do
|
48
|
+
example.run
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end if defined? RSpec
|
53
|
+
|
54
|
+
Rake::Task.class_eval do
|
55
|
+
|
56
|
+
alias execute_origin_for_pry_moves execute
|
57
|
+
|
58
|
+
def execute(args=nil)
|
59
|
+
args ||= EMPTY_TASK_ARGS
|
60
|
+
PryMoves.restartable do
|
61
|
+
reload_actions if PryMoves.reload_rake_tasks
|
62
|
+
execute_origin_for_pry_moves args
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def reload_actions
|
67
|
+
rake_task_path = actions[0].source_location[0]
|
68
|
+
actions.clear
|
69
|
+
load rake_task_path
|
70
|
+
end
|
71
|
+
|
72
|
+
end if defined? Rake and defined? Rake::Task
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module PryMoves
|
2
|
+
|
3
|
+
class AddSuffix < Pry::ClassCommand
|
4
|
+
|
5
|
+
group 'Input and Output'
|
6
|
+
description "Continue traversing of last object in history."
|
7
|
+
|
8
|
+
banner <<-'BANNER'
|
9
|
+
Usage: .method | 123 | :hash_key
|
10
|
+
|
11
|
+
Continue traversing of last object in history
|
12
|
+
|
13
|
+
E.g. `orders` will list array, then `3` will enter `orders[3]`, then `.price` will enter `orders[3].price`
|
14
|
+
BANNER
|
15
|
+
|
16
|
+
def process(cmd)
|
17
|
+
last_cmd = Pry.history.to_a[-1]
|
18
|
+
cmd = "#{last_cmd}#{wrap_suffix(cmd)}"
|
19
|
+
_pry_.pager.page " > #{cmd}\n"
|
20
|
+
_pry_.eval cmd
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def wrap_suffix(cmd)
|
26
|
+
cmd
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class Method < AddSuffix
|
32
|
+
match(/^\.(.+)$/) # when \. moved into group - match doesn't work because it overlaps with pry internal command
|
33
|
+
|
34
|
+
def wrap_suffix(cmd)
|
35
|
+
".#{cmd}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ArgumentCall < AddSuffix
|
40
|
+
match(/^(\(.*\).*)/)
|
41
|
+
end
|
42
|
+
|
43
|
+
class ArrayIndex < AddSuffix
|
44
|
+
match(/^(\d+)$/)
|
45
|
+
|
46
|
+
def wrap_suffix(cmd)
|
47
|
+
"[#{cmd}]"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ArrayCall < AddSuffix
|
52
|
+
match(/^(\[\d+\].*)/)
|
53
|
+
end
|
54
|
+
|
55
|
+
class HashKey < AddSuffix
|
56
|
+
match(/^(:\w+)$/)
|
57
|
+
|
58
|
+
def wrap_suffix(cmd)
|
59
|
+
"[#{cmd}]"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
SUFFIX_COMMANDS = [
|
67
|
+
PryMoves::Method,
|
68
|
+
PryMoves::ArgumentCall,
|
69
|
+
PryMoves::ArrayIndex,
|
70
|
+
PryMoves::ArrayCall,
|
71
|
+
PryMoves::HashKey
|
72
|
+
]
|
73
|
+
|
74
|
+
SUFFIX_COMMANDS.each do |cmd|
|
75
|
+
Pry::Commands.add_command(cmd)
|
76
|
+
end
|
77
|
+
|
78
|
+
Pry::History.class_eval do
|
79
|
+
|
80
|
+
def <<(line)
|
81
|
+
return if ["!"].include? line
|
82
|
+
return if SUFFIX_COMMANDS.any? do |cls|
|
83
|
+
line.match(cls.match)
|
84
|
+
end
|
85
|
+
push line
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
data/lib/pry-moves/backtrace.rb
CHANGED
@@ -3,8 +3,6 @@ require 'fileutils'
|
|
3
3
|
class PryMoves::Backtrace
|
4
4
|
|
5
5
|
class << self
|
6
|
-
def lines_count; @lines_count || 5; end
|
7
|
-
def lines_count=(f); @lines_count = f; end
|
8
6
|
|
9
7
|
def filter
|
10
8
|
@filter || /(\/gems\/|\/rubygems\/|\/bin\/|\/lib\/ruby\/)/
|
@@ -20,75 +18,84 @@ class PryMoves::Backtrace
|
|
20
18
|
# not used
|
21
19
|
end
|
22
20
|
end
|
21
|
+
|
23
22
|
end
|
24
23
|
|
25
|
-
def initialize(
|
26
|
-
|
24
|
+
def initialize(pry)
|
25
|
+
@pry = pry
|
26
|
+
@formatter = PryMoves::Formatter.new
|
27
27
|
end
|
28
28
|
|
29
29
|
def run_command(param, param2)
|
30
30
|
if param.is_a?(String) and (match = param.match /^>(.*)/)
|
31
31
|
suffix = match[1].size > 0 ? match[1] : param2
|
32
|
+
@formatter.colorize = false
|
32
33
|
write_to_file build, suffix
|
34
|
+
elsif param and param.match /\d+/
|
35
|
+
index = param.to_i
|
36
|
+
frame_manager.goto_index index
|
33
37
|
else
|
34
|
-
|
35
|
-
if param.is_a? String and param.match /\d+/
|
36
|
-
param = param.to_i
|
37
|
-
end
|
38
|
-
@lines_count = param || PryMoves::Backtrace::lines_count
|
39
|
-
@pry.output.puts build
|
38
|
+
print_backtrace param
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
42
|
private
|
44
43
|
|
44
|
+
def print_backtrace filter
|
45
|
+
@colorize = true
|
46
|
+
@lines_numbers = true
|
47
|
+
@filter = filter if filter.is_a? String
|
48
|
+
@pry.output.puts build
|
49
|
+
end
|
50
|
+
|
45
51
|
def build
|
52
|
+
show_vapid = %w(+ a all hidden vapid).include?(@filter)
|
53
|
+
show_all = %w(a all).include?(@filter)
|
46
54
|
result = []
|
47
|
-
|
48
|
-
stack = stack_bindings(show_vapid)
|
49
|
-
.reverse.reject do |binding|
|
50
|
-
binding.eval('__FILE__').match self.class::filter
|
51
|
-
end
|
52
|
-
|
53
|
-
if @lines_count.is_a?(Numeric) and stack.count > @lines_count
|
54
|
-
result << "Latest #{@lines_count} lines: (`bt all` for full tracing)"
|
55
|
-
stack = stack.last(@lines_count)
|
56
|
-
end
|
55
|
+
current_object, vapid_count = nil, 0
|
57
56
|
|
58
|
-
|
59
|
-
|
57
|
+
frame_manager.bindings.each_with_details do |binding, vapid|
|
58
|
+
next if !show_all and binding.eval('__FILE__').match self.class::filter
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
if !show_vapid and vapid
|
61
|
+
vapid_count += 1
|
62
|
+
next
|
63
|
+
end
|
64
|
+
|
65
|
+
if vapid_count > 0
|
66
|
+
result << "👽 frames hidden: #{vapid_count}"
|
67
|
+
vapid_count = 0
|
68
|
+
end
|
69
|
+
|
70
|
+
obj, debug_snapshot = binding.eval '[self, (debug_snapshot rescue nil)]'
|
71
|
+
# Comparison of objects directly may raise exception
|
72
|
+
if current_object.object_id != obj.object_id
|
73
|
+
result << "#{debug_snapshot || @formatter.format_obj(obj)}"
|
67
74
|
current_object = obj
|
68
75
|
end
|
69
76
|
|
70
77
|
result << build_line(binding)
|
71
78
|
end
|
72
|
-
result
|
73
|
-
end
|
74
79
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
else
|
79
|
-
obj.inspect
|
80
|
-
end
|
80
|
+
result << "👽 frames hidden: #{vapid_count}" if vapid_count > 0
|
81
|
+
|
82
|
+
result
|
81
83
|
end
|
82
84
|
|
83
85
|
def build_line(binding)
|
84
|
-
file = "#{binding.eval('__FILE__')}"
|
85
|
-
file.gsub!( /^#{Rails.root.to_s}/, '') if defined? Rails
|
86
|
+
file = @formatter.shorten_path "#{binding.eval('__FILE__')}"
|
86
87
|
|
87
|
-
signature =
|
88
|
+
signature = @formatter.method_signature binding
|
88
89
|
signature = ":#{binding.frame_type}" if !signature or signature.length < 1
|
89
90
|
|
90
|
-
indent = frame_manager.current_frame == binding
|
91
|
-
|
91
|
+
indent = if frame_manager.current_frame == binding
|
92
|
+
'==> '
|
93
|
+
elsif true #@lines_numbers
|
94
|
+
s = "#{binding.index}:".ljust(4, ' ')
|
95
|
+
@colorize ? "\e[2;49;90m#{s}\e[0m" : s
|
96
|
+
else
|
97
|
+
' '
|
98
|
+
end
|
92
99
|
|
93
100
|
line = binding.eval('__LINE__')
|
94
101
|
"#{indent}#{file}:#{line} #{signature}"
|
@@ -98,10 +105,6 @@ class PryMoves::Backtrace
|
|
98
105
|
PryStackExplorer.frame_manager(@pry)
|
99
106
|
end
|
100
107
|
|
101
|
-
def stack_bindings(vapid_frames)
|
102
|
-
frame_manager.filter_bindings vapid_frames: vapid_frames
|
103
|
-
end
|
104
|
-
|
105
108
|
def write_to_file(lines, file_suffix)
|
106
109
|
log_path = log_path file_suffix
|
107
110
|
File.write log_path, lines.join("\n")
|
@@ -115,4 +118,25 @@ class PryMoves::Backtrace
|
|
115
118
|
"#{root}/backtrace_#{file_suffix}.log"
|
116
119
|
end
|
117
120
|
|
118
|
-
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Pry.config.exception_handler = proc do |output, exception, _|
|
124
|
+
if Pry::UserError === exception && SyntaxError === exception
|
125
|
+
output.puts "SyntaxError: #{exception.message.sub(/.*syntax error, */m, '')}"
|
126
|
+
else
|
127
|
+
|
128
|
+
output.puts "#{exception.class}: #{exception.message}"
|
129
|
+
exception.backtrace.reject! {|l| l.match /sugar\.rb/}
|
130
|
+
output.puts "from #{exception.backtrace.first}"
|
131
|
+
|
132
|
+
if exception.respond_to? :cause
|
133
|
+
cause = exception.cause
|
134
|
+
while cause
|
135
|
+
output.puts "Caused by #{cause.class}: #{cause}\n"
|
136
|
+
cause.backtrace.reject! {|l| l.match /sugar\.rb/}
|
137
|
+
output.puts "from #{cause.backtrace.first}"
|
138
|
+
cause = cause.cause
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|