pry-moves 0.1.13 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +6 -4
- data/README.md +15 -7
- 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 +67 -43
- data/lib/pry-moves/bindings_stack.rb +97 -0
- data/lib/pry-moves/commands.rb +42 -3
- data/lib/pry-moves/formatter.rb +74 -0
- data/lib/pry-moves/painter.rb +3 -2
- 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 -6
- data/lib/pry-stack_explorer/frame_manager.rb +6 -9
- data/lib/pry-stack_explorer/pry-stack_explorer.rb +1 -16
- data/lib/pry-stack_explorer/{commands.rb → stack_commands.rb} +10 -6
- data/lib/pry-stack_explorer/when_started_hook.rb +10 -65
- data/playground/Gemfile.lock +6 -4
- data/playground/README.md +1 -0
- data/playground/playground.rb +94 -9
- data/playground/test.rb +5 -1
- data/playground/test.sh +1 -0
- data/pry-moves.gemspec +3 -2
- data/publish.sh +2 -1
- 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 -13
- data/lib/pry-moves/helpers.rb +0 -56
- data/lib/pry-moves/trace_commands.rb +0 -109
- data/lib/pry-moves/traced_method.rb +0 -61
- data/lib/pry-moves/tracer.rb +0 -140
- data/lib/pry-moves/traversing.rb +0 -69
@@ -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
|
59
|
+
|
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
|
60
69
|
|
61
|
-
def build_result(stack, result)
|
62
|
-
current_object = nil
|
63
|
-
stack.each do |binding|
|
64
70
|
obj, debug_snapshot = binding.eval '[self, (debug_snapshot rescue nil)]'
|
65
71
|
# Comparison of objects directly may raise exception
|
66
72
|
if current_object.object_id != obj.object_id
|
67
|
-
result << "#{debug_snapshot || format_obj(obj)}
|
73
|
+
result << "#{debug_snapshot || @formatter.format_obj(obj)}"
|
68
74
|
current_object = obj
|
69
75
|
end
|
70
76
|
|
71
77
|
result << build_line(binding)
|
72
78
|
end
|
73
|
-
result
|
74
|
-
end
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
else
|
80
|
-
obj.inspect
|
81
|
-
end
|
80
|
+
result << "👽 frames hidden: #{vapid_count}" if vapid_count > 0
|
81
|
+
|
82
|
+
result
|
82
83
|
end
|
83
84
|
|
84
85
|
def build_line(binding)
|
85
|
-
file =
|
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
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class PryMoves::BindingsStack < Array
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@vapid_bindings = []
|
5
|
+
bindings = binding.callers # binding_of_caller has bug and always returns callers of current binding,
|
6
|
+
# no matter at which binding method is called. So no need to pass here binding
|
7
|
+
pre_callers = Thread.current[:pre_callers]
|
8
|
+
bindings = bindings + pre_callers if pre_callers
|
9
|
+
concat remove_internal_frames(bindings)
|
10
|
+
set_indices
|
11
|
+
mark_vapid_frames
|
12
|
+
end
|
13
|
+
|
14
|
+
def suggest_initial_frame_index
|
15
|
+
m = PryMoves::TracedMethod.last
|
16
|
+
return 0 if m and m.binding_inside?(first)
|
17
|
+
index{|b| not vapid? b} || 0
|
18
|
+
end
|
19
|
+
def initial_frame
|
20
|
+
find{|b| not vapid? b}
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_with_details
|
24
|
+
self.reverse.each do |binding|
|
25
|
+
yield binding, vapid?(binding)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def vapid?(binding)
|
30
|
+
@vapid_bindings.include? binding
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def set_indices
|
36
|
+
reverse.each_with_index do |binding, index|
|
37
|
+
binding.index = index
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def mark_vapid_frames
|
42
|
+
stepped_out = false
|
43
|
+
actual_file, actual_method = nil, nil
|
44
|
+
|
45
|
+
# here calls checked in reverse order - from latest to parent:
|
46
|
+
each do |binding|
|
47
|
+
file, method, obj = binding.eval("[__FILE__, __method__, self]")
|
48
|
+
|
49
|
+
if file.match PryMoves::Backtrace::filter
|
50
|
+
@vapid_bindings << binding
|
51
|
+
elsif stepped_out
|
52
|
+
if actual_file == file and actual_method == method or
|
53
|
+
binding.local_variable_defined? :pry_moves_deferred_call
|
54
|
+
stepped_out = false
|
55
|
+
else
|
56
|
+
@vapid_bindings << binding
|
57
|
+
end
|
58
|
+
elsif binding.frame_type == :block
|
59
|
+
stepped_out = true
|
60
|
+
actual_file = file
|
61
|
+
actual_method = method
|
62
|
+
elsif obj and method and obj.method(method).source.strip.match /^delegate\s/
|
63
|
+
@vapid_bindings << binding
|
64
|
+
end
|
65
|
+
|
66
|
+
if binding.local_variable_defined? :hide_from_stack
|
67
|
+
@vapid_bindings << binding
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# remove internal frames related to setting up the session
|
73
|
+
def remove_internal_frames(bindings)
|
74
|
+
i = top_internal_frame_index(bindings)
|
75
|
+
# DEBUG:
|
76
|
+
#bindings.each_with_index do |b, index|
|
77
|
+
# puts "#{index}: #{b.eval("self.class")} #{b.eval("__method__")}"
|
78
|
+
#end
|
79
|
+
# puts "FOUND top internal frame in #{bindings.size} frames: [#{i}] #{bindings[i].ai}"
|
80
|
+
|
81
|
+
bindings.drop i+1
|
82
|
+
end
|
83
|
+
|
84
|
+
def top_internal_frame_index(bindings)
|
85
|
+
bindings.rindex do |b|
|
86
|
+
if b.frame_type == :method
|
87
|
+
method, self_ = b.eval("[__method__, self]")
|
88
|
+
|
89
|
+
self_.equal?(Pry) && method == :start ||
|
90
|
+
self_.class == Binding && method == :pry ||
|
91
|
+
self_.is_a?(PryMoves::TraceCommand) && method == :tracing_func ||
|
92
|
+
b.local_variable_defined?(:pry_moves_stack_root)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|