pry-moves 0.1.1 → 0.1.2
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/README.md +43 -14
- data/dynamic_debug_experiments.rb +42 -0
- data/lib/pry-moves/backtrace.rb +110 -0
- data/lib/pry-moves/commands.rb +14 -4
- data/lib/pry-moves/helpers.rb +28 -0
- data/lib/pry-moves/pry_ext.rb +45 -7
- data/lib/pry-moves/pry_wrapper.rb +69 -0
- data/lib/pry-moves/tracer.rb +99 -88
- data/lib/pry-moves/version.rb +1 -1
- data/lib/pry-moves/watch.rb +57 -0
- data/lib/pry-moves.rb +27 -0
- data/lib/pry-stack_explorer/commands.rb +276 -0
- data/lib/pry-stack_explorer/frame_manager.rb +88 -0
- data/lib/pry-stack_explorer/pry-stack_explorer.rb +139 -0
- data/lib/pry-stack_explorer/when_started_hook.rb +111 -0
- data/playground/demo.rb +37 -6
- data/playground/sand.rb +5 -5
- data/playground/threads.rb +26 -0
- data/pry-moves.gemspec +1 -1
- metadata +18 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9397716c77781c90d1a1130b30a780226ac44aac
|
4
|
+
data.tar.gz: 9f26eae9c3731f48c93c875f3af90c5e6025c749
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2b54ca87a8935f49de7a9d016446db8dd1a065af3e98062db580afd0ef95de7597c15def6bfc9048cfca5e1ffaeb386c315ddaf2dbf79ab14f031122c81f4e0
|
7
|
+
data.tar.gz: 6b2039aadade2fb859e382edcb38080d33c3bf4d3b8d025d0a783f7029607445d05744372156462bc9de0c0045772266ae12a0039d597abafc200662362cc912
|
data/README.md
CHANGED
@@ -11,13 +11,17 @@ _An execution control add-on for [Pry][pry]._
|
|
11
11
|
|
12
12
|
* `n` - **next** line in current frame, including block lines (moving to next line goes as naturally expected)
|
13
13
|
* `s` - **step** into function execution
|
14
|
-
* `s func_name` -
|
15
|
-
* `f` - **finish** execution of current frame and stop at next line on higher level
|
14
|
+
* `s func_name` - step into first method called by name `func_name`
|
15
|
+
* `f` - **finish** execution of current frame (block or method) and stop at next line on higher level
|
16
16
|
* `c` - **continue**
|
17
|
-
* `bt` -
|
17
|
+
* `bt` - show latest 5 lines from backtrace
|
18
18
|
* `bt 10` - latest 10 lines
|
19
|
-
* `bt all
|
20
|
-
* `
|
19
|
+
* `bt all` - full backtrace
|
20
|
+
* `bt >foo` - write backtrace to file `log/backtrace_foo.log`
|
21
|
+
* `up`/`down`/`top`/`bottom` - move over call stack
|
22
|
+
* `up +` - move up, including vapid frames (block callers, hidden frames)
|
23
|
+
* `up pattern` - move up till first frame which method name or file position in format `folder/script.rb:12` matches regexp pattern
|
24
|
+
* `debug some_method(param, param2)` - call `some_method(param, param2)` and interactively step into it
|
21
25
|
* `!` - exit
|
22
26
|
|
23
27
|
|
@@ -38,22 +42,46 @@ end
|
|
38
42
|
|
39
43
|
_Demo class source [here](https://github.com/garmoshka-mo/pry-moves/issues/1)_
|
40
44
|
|
45
|
+
## Backtrace and call stack
|
46
|
+
|
47
|
+
You can explicitly hide frames from backtrace and call stack by defining `hide_from_stack` variable:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
def insignificant_method
|
51
|
+
hide_from_stack = true
|
52
|
+
something_insignificant
|
53
|
+
yield
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
41
57
|
## Configuration
|
42
58
|
|
43
59
|
Here is default configuration, you can override it:
|
44
60
|
|
45
61
|
```ruby
|
46
|
-
PryMoves::Backtrace::
|
62
|
+
PryMoves::Backtrace::lines_count = 5
|
63
|
+
PryMoves::Backtrace::filter =
|
47
64
|
/(\/gems\/|\/rubygems\/|\/bin\/|\/lib\/ruby\/|\/pry-moves\/)/
|
48
|
-
|
49
|
-
PryMoves::Backtrace::format do |line|
|
50
|
-
defined?(Rails) : line.gsub( /^#{Rails.root.to_s}/, '') : line
|
51
|
-
end
|
52
65
|
```
|
53
66
|
|
54
|
-
##
|
67
|
+
## Threads, helpers
|
68
|
+
|
69
|
+
`pry-moves` can't stop other threads on `binding.pry`, so they will continue to run.
|
70
|
+
This makes `pry-moves` not always suitable for debugging of multi-thread projects.
|
71
|
+
|
72
|
+
Though you can pause other threads with helper which will suspend execution on current line,
|
73
|
+
until ongoing debug session will be finished with `continue`:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
PryMoves.synchronize_threads
|
77
|
+
```
|
78
|
+
|
79
|
+
_For example, you can put it into function which periodically reports status of thread (if you have such)_
|
80
|
+
|
81
|
+
Other helpers:
|
82
|
+
* `PryMoves.open?` - if pry input dialog active. Can be used to suppress output from ongoing parallel threads
|
55
83
|
|
56
|
-
|
84
|
+
## pry-remote
|
57
85
|
|
58
86
|
Rudimentary support for [`pry-remote`][pry-remote] (>= 0.1.1) is also included.
|
59
87
|
Ensure `pry-remote` is loaded or required before `pry-moves`. For example, in a
|
@@ -65,9 +93,10 @@ gem 'pry-remote'
|
|
65
93
|
gem 'pry-moves'
|
66
94
|
```
|
67
95
|
|
96
|
+
## Performance
|
97
|
+
|
68
98
|
Please note that debugging functionality is implemented through
|
69
|
-
[`set_trace_func`][set_trace_func], which imposes
|
70
|
-
penalty.
|
99
|
+
[`set_trace_func`][set_trace_func], which imposes certain performance penalty.
|
71
100
|
|
72
101
|
## Contributors
|
73
102
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
if @command[:action] == :ababa
|
2
|
+
puts 'catch debug'
|
3
|
+
|
4
|
+
|
5
|
+
set_trace_func (
|
6
|
+
Proc.new { |event, file, line, id, binding_, classname|
|
7
|
+
#if file == '(pry)'
|
8
|
+
#unless file.match /\/gems\/|\/ruby\//
|
9
|
+
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
|
10
|
+
if event=='line' and file == 'sand.rb' and line != 47
|
11
|
+
set_trace_func nil
|
12
|
+
Pry.start(binding_, @pry_start_options)
|
13
|
+
end
|
14
|
+
})
|
15
|
+
|
16
|
+
puts "CALLER:\n#{caller.join "\n"}\n"
|
17
|
+
|
18
|
+
#Pry.start(command[:binding], @pry_start_options)
|
19
|
+
return return_value
|
20
|
+
end
|
21
|
+
|
22
|
+
TracePoint.new(:line) {|tp|p [tp.lineno, tp.event]}.enable
|
23
|
+
|
24
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
25
|
+
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
30
|
+
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname unless file.match /\/gems\/|\/ruby\//
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
35
|
+
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname if file == '(pry)'
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
class PryMoves::Backtrace
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def lines_count; @lines_count || 5; end
|
7
|
+
def lines_count=(f); @lines_count = f; end
|
8
|
+
|
9
|
+
def filter
|
10
|
+
@filter || /(\/gems\/|\/rubygems\/|\/bin\/|\/lib\/ruby\/|\/pry-moves\/)/
|
11
|
+
end
|
12
|
+
def filter=(f); @filter = f; end
|
13
|
+
|
14
|
+
def format(&block)
|
15
|
+
@formatter = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def formatter
|
19
|
+
@formatter || lambda do |line|
|
20
|
+
# not used
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(binding, pry)
|
26
|
+
@binding, @pry = binding, pry
|
27
|
+
end
|
28
|
+
|
29
|
+
def build(lines_count = nil)
|
30
|
+
result = []
|
31
|
+
show_vapid = lines_count == 'all'
|
32
|
+
stack = stack_bindings(show_vapid)
|
33
|
+
.reverse.reject do |binding|
|
34
|
+
binding.eval('__FILE__').match self.class::filter
|
35
|
+
end
|
36
|
+
|
37
|
+
if lines_count.is_a? String and lines_count.match /\d+/
|
38
|
+
lines_count = lines_count.to_i
|
39
|
+
end
|
40
|
+
if lines_count.is_a?(Numeric) and stack.count > lines_count
|
41
|
+
result << "Latest #{lines_count} lines: (`bt all` for full tracing)"
|
42
|
+
stack = stack.last(lines_count)
|
43
|
+
end
|
44
|
+
|
45
|
+
build_result stack, result
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_command(param)
|
49
|
+
if param.is_a?(String) and (match = param.match /^>(.*)/)
|
50
|
+
write_to_file build, match[1]
|
51
|
+
else
|
52
|
+
puts build(param || PryMoves::Backtrace::lines_count)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def build_result(stack, result)
|
59
|
+
current_object = nil
|
60
|
+
stack.each do |binding|
|
61
|
+
obj = binding.eval 'self'
|
62
|
+
if current_object != obj
|
63
|
+
colored_obj = ""
|
64
|
+
Pry::ColorPrinter.pp obj, colored_obj
|
65
|
+
result << "#{colored_obj.chomp}:"
|
66
|
+
current_object = obj
|
67
|
+
end
|
68
|
+
|
69
|
+
result << build_line(binding)
|
70
|
+
end
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_line(binding)
|
75
|
+
file = "#{binding.eval('__FILE__')}"
|
76
|
+
file.gsub!( /^#{Rails.root.to_s}/, '') if defined? Rails
|
77
|
+
|
78
|
+
signature = PryMoves::Helpers.method_signature_with_owner binding
|
79
|
+
|
80
|
+
indent = frame_manager.current_frame == binding ?
|
81
|
+
' => ': ' '
|
82
|
+
|
83
|
+
"#{indent}#{file}:#{binding.eval('__LINE__')} "+
|
84
|
+
" #{signature} :#{binding.frame_type}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def frame_manager
|
88
|
+
PryStackExplorer.frame_manager(@pry)
|
89
|
+
end
|
90
|
+
|
91
|
+
def stack_bindings(vapid_frames)
|
92
|
+
frame_manager.filter_bindings vapid_frames: vapid_frames
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_to_file(lines, file_suffix)
|
96
|
+
log_path = log_path file_suffix
|
97
|
+
File.open(log_path, "w") do |f|
|
98
|
+
f.puts lines
|
99
|
+
end
|
100
|
+
puts "Backtrace logged to #{log_path}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def log_path(file_suffix)
|
104
|
+
root = defined?(Rails) ? Rails.root.to_s : '.'
|
105
|
+
root += '/log'
|
106
|
+
FileUtils.mkdir_p root
|
107
|
+
"#{root}/backtrace_#{file_suffix}.log"
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/pry-moves/commands.rb
CHANGED
@@ -22,15 +22,25 @@ module PryMoves
|
|
22
22
|
run 'exit-all'
|
23
23
|
end
|
24
24
|
|
25
|
-
block_command 'bt', 'Backtrace' do |param|
|
26
|
-
PryMoves::Backtrace.new(target).print param
|
27
|
-
end
|
28
|
-
|
29
25
|
alias_command 'c', 'continue'
|
30
26
|
alias_command 's', 'step'
|
31
27
|
alias_command 'n', 'next'
|
32
28
|
alias_command 'f', 'finish'
|
33
29
|
|
30
|
+
block_command 'watch', 'Display value of expression on every move' do |param|
|
31
|
+
PryMoves::Watch.instance.process_cmd param, target
|
32
|
+
end
|
33
|
+
|
34
|
+
block_command 'bt', 'Backtrace' do |param|
|
35
|
+
PryMoves::Backtrace.new(target, _pry_).run_command param
|
36
|
+
end
|
37
|
+
|
38
|
+
block_command 'debug', '' do |*command_parts|
|
39
|
+
check_file_context
|
40
|
+
cmd = command_parts.join ' '
|
41
|
+
breakout_navigation :debug, cmd
|
42
|
+
end
|
43
|
+
|
34
44
|
# Hit Enter to repeat last command
|
35
45
|
command /^$/, "repeat last command" do
|
36
46
|
_pry_.run_command Pry.history.to_a.last
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PryMoves::Helpers
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# @return [String] Signature for the method object in Class#method format.
|
6
|
+
def method_signature_with_owner(binding)
|
7
|
+
meth = binding.eval('__method__')
|
8
|
+
meth_obj = meth ? Pry::Method.from_binding(binding) : nil
|
9
|
+
if !meth_obj
|
10
|
+
""
|
11
|
+
elsif meth_obj.undefined?
|
12
|
+
"#{meth_obj.name_with_owner}(UNKNOWN) (undefined method)"
|
13
|
+
else
|
14
|
+
args = meth_obj.parameters.inject([]) do |arr, (type, name)|
|
15
|
+
name ||= (type == :block ? 'block' : "arg#{arr.size + 1}")
|
16
|
+
arr << case type
|
17
|
+
when :req then name.to_s
|
18
|
+
when :opt then "#{name}=?"
|
19
|
+
when :rest then "*#{name}"
|
20
|
+
when :block then "&#{name}"
|
21
|
+
else '?'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
"#{meth_obj.name_with_owner}(#{args.join(', ')})"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/pry-moves/pry_ext.rb
CHANGED
@@ -9,7 +9,7 @@ class << Pry
|
|
9
9
|
|
10
10
|
if target.is_a?(Binding) && PryMoves.check_file_context(target)
|
11
11
|
# Wrap the tracer around the usual Pry.start
|
12
|
-
PryMoves::
|
12
|
+
PryMoves::PryWrapper.new(target, options).run do
|
13
13
|
start_without_pry_nav(target, old_options)
|
14
14
|
end
|
15
15
|
else
|
@@ -23,20 +23,58 @@ end
|
|
23
23
|
|
24
24
|
Binding.class_eval do
|
25
25
|
|
26
|
-
alias
|
26
|
+
alias pry_forced pry
|
27
27
|
|
28
28
|
def pry
|
29
|
-
|
29
|
+
unless Pry.config.disable_breakpoints
|
30
|
+
PryMoves.synchronize_threads
|
31
|
+
pry_forced
|
32
|
+
end
|
30
33
|
end
|
31
34
|
|
32
35
|
end
|
33
36
|
|
34
|
-
|
37
|
+
Pry.config.pager = false
|
38
|
+
|
39
|
+
Pry::Command::Whereami.class_eval do
|
40
|
+
# Negligent function from Pry - evidently poor output format
|
41
|
+
# would be wanted to be changed often by developers,
|
42
|
+
# but definition so long... :(
|
43
|
+
def process
|
44
|
+
if bad_option_combination?
|
45
|
+
raise CommandError, "Only one of -m, -c, -f, and LINES may be specified."
|
46
|
+
end
|
47
|
+
|
48
|
+
if nothing_to_do?
|
49
|
+
return
|
50
|
+
elsif internal_binding?(target)
|
51
|
+
handle_internal_binding
|
52
|
+
return
|
53
|
+
end
|
35
54
|
|
36
|
-
|
55
|
+
set_file_and_dir_locals(@file)
|
37
56
|
|
38
|
-
|
39
|
-
bindings.drop_while { |b| b.eval("__FILE__") =~ /\/pry-/ }
|
57
|
+
_pry_.pager.page build_output
|
40
58
|
end
|
41
59
|
|
60
|
+
def build_output
|
61
|
+
lines = []
|
62
|
+
lines << "#{text.bold('From:')} #{location}"
|
63
|
+
lines << PryMoves::Watch.instance.output(target) unless PryMoves::Watch.instance.empty?
|
64
|
+
lines << ''
|
65
|
+
lines << "#{code.with_line_numbers(use_line_numbers?).with_marker(marker).highlighted}"
|
66
|
+
lines << ''
|
67
|
+
lines.join "\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
def location
|
71
|
+
me = target.eval 'self' rescue nil
|
72
|
+
if me
|
73
|
+
colored_str = ''
|
74
|
+
Pry::ColorPrinter.pp me, colored_str
|
75
|
+
me = colored_str.chomp
|
76
|
+
end
|
77
|
+
file = defined?(Rails) ? @file.gsub(Rails.root.to_s, '') : @file
|
78
|
+
"#{file}:#{@line} #{me}"
|
79
|
+
end
|
42
80
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pry' unless defined? Pry
|
2
|
+
|
3
|
+
module PryMoves
|
4
|
+
class PryWrapper
|
5
|
+
def initialize(binding_, pry_start_options = {})
|
6
|
+
@init_binding = binding_
|
7
|
+
@pry_start_options = pry_start_options # Options to use for Pry.start
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(&block)
|
11
|
+
PryMoves.lock
|
12
|
+
|
13
|
+
return_value = nil
|
14
|
+
PryMoves.is_open = true
|
15
|
+
@command = catch(:breakout_nav) do # Coordinates with PryMoves::Commands
|
16
|
+
return_value = yield
|
17
|
+
nil # Nothing thrown == no navigational command
|
18
|
+
end
|
19
|
+
PryMoves.is_open = false
|
20
|
+
|
21
|
+
if @command
|
22
|
+
trace_command
|
23
|
+
else
|
24
|
+
PryMoves.unlock
|
25
|
+
if @pry_start_options[:pry_remote] && PryMoves.current_remote_server
|
26
|
+
PryMoves.current_remote_server.teardown
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
return_value
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def trace_command
|
36
|
+
if @command[:action] == :debug
|
37
|
+
wrap_debug
|
38
|
+
else
|
39
|
+
start_tracing
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def wrap_debug
|
44
|
+
#puts "##wrap debug"
|
45
|
+
#puts "CALLER:\n#{caller.join "\n"}\n"
|
46
|
+
# Thread.abort_on_exception=true
|
47
|
+
Thread.new do
|
48
|
+
Thread.current[:pry_moves_debug] = true
|
49
|
+
#@command[:binding].eval 'puts "###########"'
|
50
|
+
start_tracing
|
51
|
+
begin
|
52
|
+
@command[:binding].eval @command[:param]
|
53
|
+
rescue => e
|
54
|
+
Thread.current.set_trace_func nil
|
55
|
+
puts e
|
56
|
+
end
|
57
|
+
end.join
|
58
|
+
binding_ = @last_runtime_binding || @init_binding
|
59
|
+
Pry.start(binding_, @pry_start_options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_tracing
|
63
|
+
@last_runtime_binding = @command[:binding]
|
64
|
+
@tracer = PryMoves::Tracer.new @command, @pry_start_options
|
65
|
+
@tracer.trace
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|