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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +6 -4
  4. data/README.md +28 -8
  5. data/lib/commands/debug.rb +17 -0
  6. data/lib/commands/finish.rb +26 -0
  7. data/lib/commands/goto.rb +19 -0
  8. data/lib/commands/iterate.rb +22 -0
  9. data/lib/commands/next.rb +37 -0
  10. data/lib/commands/next_breakpoint.rb +22 -0
  11. data/lib/commands/step.rb +83 -0
  12. data/lib/commands/trace_command.rb +87 -0
  13. data/lib/commands/trace_helpers.rb +49 -0
  14. data/lib/commands/traced_method.rb +71 -0
  15. data/lib/debug_sugar.rb +72 -0
  16. data/lib/pry-moves/add_suffix.rb +88 -0
  17. data/lib/pry-moves/backtrace.rb +70 -46
  18. data/lib/pry-moves/bindings_stack.rb +97 -0
  19. data/lib/pry-moves/commands.rb +50 -7
  20. data/lib/pry-moves/formatter.rb +74 -0
  21. data/lib/pry-moves/painter.rb +5 -0
  22. data/lib/pry-moves/pry_ext.rb +64 -16
  23. data/lib/pry-moves/pry_wrapper.rb +30 -17
  24. data/lib/pry-moves/restartable.rb +38 -0
  25. data/lib/pry-moves/version.rb +1 -1
  26. data/lib/pry-moves/watch.rb +3 -0
  27. data/lib/pry-moves.rb +65 -4
  28. data/lib/pry-stack_explorer/VERSION +2 -0
  29. data/lib/pry-stack_explorer/frame_manager.rb +6 -9
  30. data/lib/pry-stack_explorer/pry-stack_explorer.rb +3 -17
  31. data/lib/pry-stack_explorer/{commands.rb → stack_commands.rb} +10 -6
  32. data/lib/pry-stack_explorer/when_started_hook.rb +17 -63
  33. data/playground/Gemfile.lock +9 -9
  34. data/playground/README.md +1 -0
  35. data/playground/playground.rb +94 -9
  36. data/playground/sand.rb +46 -12
  37. data/playground/test.rb +5 -1
  38. data/playground/test.sh +1 -0
  39. data/pry-moves.gemspec +3 -2
  40. data/publish.sh +3 -0
  41. data/spec/backtrace_spec.rb +11 -13
  42. data/spec/blocks_spec.rb +41 -8
  43. data/spec/commands_spec.rb +26 -25
  44. data/spec/pry_debugger.rb +5 -1
  45. data/spec/redirection_spec.rb +7 -0
  46. data/spec/spec_helper.rb +9 -4
  47. data/spec/step_spec.rb +51 -0
  48. metadata +43 -10
  49. data/lib/pry-moves/helpers.rb +0 -50
  50. data/lib/pry-moves/trace_commands.rb +0 -105
  51. data/lib/pry-moves/tracer.rb +0 -169
@@ -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
@@ -5,39 +5,48 @@ module PryMoves
5
5
  block_command 'step', 'Step execution into the next line or method.' do |param|
6
6
  breakout_navigation :step, param
7
7
  end
8
+ alias_command 's', 'step'
8
9
 
9
10
  block_command 'finish', 'Finish - xule tut neponyatnogo' do |param|
10
11
  breakout_navigation :finish, param
11
12
  end
13
+ alias_command 'f', 'finish'
12
14
 
13
15
  block_command 'next', 'Execute the next line stepping into blocks' do |param|
14
16
  breakout_navigation :next, param
15
17
  end
18
+ alias_command 'n', 'next'
16
19
 
17
20
  block_command 'nn', 'Execute the next line skipping blocks' do |param|
18
21
  breakout_navigation :next, 'blockless'
19
22
  end
20
23
 
24
+ block_command 'next_breakpoint', 'Go to next breakpoint' do |param|
25
+ breakout_navigation :next_breakpoint, param
26
+ end
27
+ alias_command 'b', 'next_breakpoint'
28
+
21
29
  block_command 'iterate', 'Go to next iteration of current block' do |param|
22
30
  breakout_navigation :iterate, param
23
31
  end
24
32
 
33
+ block_command 'goto', 'goto line' do |param|
34
+ breakout_navigation :goto, param
35
+ end
36
+ alias_command 'g', 'goto'
37
+
25
38
  block_command 'continue', 'Continue program execution and end the Pry session' do
26
39
  check_file_context
27
40
  run 'exit-all'
28
41
  end
29
-
30
42
  alias_command 'c', 'continue'
31
- alias_command 's', 'step'
32
- alias_command 'n', 'next'
33
- alias_command 'f', 'finish'
34
43
 
35
44
  block_command 'watch', 'Display value of expression on every move' do |param|
36
45
  PryMoves::Watch.instance.process_cmd param, target
37
46
  end
38
47
 
39
48
  block_command 'bt', 'Backtrace' do |param, param2|
40
- PryMoves::Backtrace.new(target, _pry_).run_command param, param2
49
+ PryMoves::Backtrace.new(_pry_).run_command param, param2
41
50
  end
42
51
 
43
52
  block_command 'debug', '' do
@@ -45,6 +54,23 @@ module PryMoves
45
54
  breakout_navigation :debug, cmd
46
55
  end
47
56
 
57
+ block_command :restart, '' do
58
+ PryMoves.restart_requested = true
59
+ run 'continue'
60
+ end
61
+ alias_command '@', 'restart'
62
+
63
+ block_command :reload, '' do
64
+ PryMoves.reload_requested = true
65
+ run 'continue'
66
+ end
67
+ alias_command '#', 'reload'
68
+
69
+ block_command /^\\(\w+)$/, 'Execute command explicitly' do |param|
70
+ Pry.config.ignore_once_var_precedence = true
71
+ run param
72
+ end
73
+
48
74
  block_command '!', 'exit' do
49
75
  PryMoves.unlock
50
76
  Pry.config.exit_requested = true
@@ -58,16 +84,32 @@ module PryMoves
58
84
 
59
85
  helpers do
60
86
  def breakout_navigation(action, param)
87
+ return if var_precedence action
88
+
61
89
  check_file_context
62
90
  _pry_.binding_stack.clear # Clear the binding stack.
63
91
  throw :breakout_nav, { # Break out of the REPL loop and
64
92
  action: action, # signal the tracer.
65
93
  param: param,
66
- binding: target,
67
- pry: _pry_
94
+ binding: target
68
95
  }
69
96
  end
70
97
 
98
+ def var_precedence action
99
+ if Pry.config.ignore_once_var_precedence
100
+ Pry.config.ignore_once_var_precedence = false
101
+ return
102
+ end
103
+
104
+ input = Pry.config.original_user_input || action
105
+ binding_value = target.eval(input) rescue nil
106
+ unless binding_value.nil?
107
+ puts "ℹ️️ Variable \"#{input}\" found. To execute command type its alias or \\#{input}"
108
+ puts PryMoves::Painter.colorize binding_value
109
+ true
110
+ end
111
+ end
112
+
71
113
  # Ensures that a command is executed in a local file context.
72
114
  def check_file_context
73
115
  unless PryMoves.check_file_context(target)
@@ -75,6 +117,7 @@ module PryMoves
75
117
  end
76
118
  end
77
119
  end
120
+
78
121
  end
79
122
  end
80
123
 
@@ -0,0 +1,74 @@
1
+ class PryMoves::Formatter
2
+
3
+ attr_accessor :colorize
4
+
5
+ def initialize colorize = true
6
+ @colorize = colorize
7
+ end
8
+
9
+ def method_signature(binding)
10
+ meth = binding.eval('__method__')
11
+ meth_obj = meth ? Pry::Method.from_binding(binding) : nil
12
+ if !meth_obj
13
+ ""
14
+ elsif meth_obj.undefined?
15
+ "#{meth_obj.name}(UNKNOWN) (undefined method)"
16
+ else
17
+ args = meth_obj.parameters.map.with_index do |(type, name), i|
18
+ if name
19
+ value = format_arg binding, name.to_s
20
+ show_value = true
21
+ else
22
+ name = (type == :block ? 'block' : "arg#{i + 1}")
23
+ end
24
+ name = case type
25
+ when :req then "#{name} ="
26
+ when :key then "#{name}:"
27
+ when :opt then "#{name}=?"
28
+ when :rest then "*#{name}"
29
+ when :block then "&#{name}"
30
+ else '?'
31
+ end
32
+ show_value ? "#{name} #{value}" : name
33
+ end
34
+ "#{meth_obj.name}(#{args.join(', ')})"
35
+ end
36
+ end
37
+
38
+ def format_arg binding, arg_name
39
+ arg = binding.eval(arg_name.to_s)
40
+ format_obj arg
41
+ end
42
+
43
+ def first_line str
44
+ str.split("\n").first
45
+ end
46
+
47
+ def cut_string str
48
+ str.length > 50 ? "#{str.first 50}..." : str
49
+ end
50
+
51
+ PATH_TRASH = defined?(Rails) ? Rails.root.to_s : Dir.pwd
52
+
53
+ def shorten_path(path)
54
+ path.gsub( /^#{PATH_TRASH}\//, '')
55
+ end
56
+
57
+ def format_obj(obj)
58
+ if obj.is_a? String
59
+ format_obj2 cut_string first_line obj
60
+ else
61
+ first_line format_obj2 obj
62
+ end
63
+ end
64
+
65
+ def format_obj2(obj)
66
+ if @colorize
67
+ PryMoves::Painter.colorize obj
68
+ else
69
+ i = obj.inspect
70
+ i.start_with?('#<') ? obj.class.to_s : i
71
+ end
72
+ end
73
+
74
+ end
@@ -14,10 +14,15 @@ module PryMoves::Painter
14
14
 
15
15
  def self.colorize(obj)
16
16
  colored_str = Canvas.new
17
+ i = obj.inspect
18
+ obj = obj.class if i.is_a?(String) && i.start_with?("#<")
17
19
  catch (:cut) do
18
20
  Pry::ColorPrinter.pp obj, colored_str
19
21
  end
20
22
  colored_str.chomp
23
+ rescue => e
24
+ "⛔️ Inspect error: #{e}\n" +
25
+ "#{e.backtrace.first(3).join("\n")}"
21
26
  end
22
27
 
23
28
  end
@@ -1,31 +1,31 @@
1
1
  class << Pry
2
- alias_method :start_without_pry_nav, :start
3
-
4
- def start_with_pry_nav(target = TOPLEVEL_BINDING, options = {})
5
- old_options = options.reject { |k, _| k == :pry_remote }
2
+ alias pry_moves_origin_start start
6
3
 
4
+ def start(target = TOPLEVEL_BINDING, options = {})
7
5
  if target.is_a?(Binding) && PryMoves.check_file_context(target)
8
6
  # Wrap the tracer around the usual Pry.start
9
- PryMoves::PryWrapper.new(target, options).run do
10
- start_without_pry_nav(target, old_options)
11
- end
7
+ original_verbosity = $VERBOSE
8
+ $VERBOSE = nil # Disable warnings for pry-moves
9
+ PryMoves::PryWrapper.new(target, options, self).run
10
+ $VERBOSE = original_verbosity
12
11
  else
13
12
  # No need for the tracer unless we have a file context to step through
14
- start_without_pry_nav(target, old_options)
13
+ pry_moves_origin_start(target, options)
15
14
  end
16
15
  end
17
16
 
18
- alias_method :start, :start_with_pry_nav
19
17
  end
20
18
 
21
19
  Binding.class_eval do
22
20
 
21
+ attr_accessor :index
22
+
23
23
  alias pry_forced pry
24
24
 
25
25
  def pry
26
- unless Pry.config.disable_breakpoints
27
- PryMoves.synchronize_threads ||
28
- return # Don't start binding.pry when semaphore locked by current thread
26
+ if !Pry.config.disable_breakpoints and
27
+ # Don't start binding.pry when semaphore locked by current thread
28
+ PryMoves.synchronize_threads
29
29
  pry_forced
30
30
  end
31
31
  end
@@ -34,6 +34,32 @@ end
34
34
 
35
35
  Pry.config.pager = false
36
36
 
37
+ Pry::Command.class_eval do
38
+ class << self
39
+ attr_accessor :original_user_input
40
+ end
41
+
42
+ alias run_origin_for_pry_moves run
43
+ def run(command_string, *args)
44
+ Pry.config.original_user_input = self.class.original_user_input
45
+ result = run_origin_for_pry_moves command_string, *args
46
+ Pry.config.original_user_input = nil
47
+ result
48
+ end
49
+ end
50
+
51
+ Pry::CommandSet.class_eval do
52
+
53
+ alias alias_command_origin_for_pry_moves alias_command
54
+
55
+ def alias_command(match, action, options = {})
56
+ cmd = alias_command_origin_for_pry_moves match, action, options
57
+ cmd.original_user_input = match
58
+ cmd
59
+ end
60
+
61
+ end
62
+
37
63
  Pry::Command::Whereami.class_eval do
38
64
  # Negligent function from Pry - evidently poor output format
39
65
  # would be wanted to be changed often by developers,
@@ -56,11 +82,19 @@ Pry::Command::Whereami.class_eval do
56
82
  end
57
83
 
58
84
  def build_output
59
- lines = []
60
- lines << "#{text.bold('From:')} #{location}"
61
- lines << PryMoves::Watch.instance.output(target) unless PryMoves::Watch.instance.empty?
85
+ lines = ['']
86
+
87
+ formatter = PryMoves::Formatter.new
88
+ prefix = Thread.current[:pry_moves_debug] ? "👾 " : ""
89
+ lines << "#{prefix}#{formatter.shorten_path location}"
90
+ lines << " ." + formatter.method_signature(target)
62
91
  lines << ''
63
92
  lines << "#{code.with_line_numbers(use_line_numbers?).with_marker(marker).highlighted}"
93
+
94
+ lines << PryMoves::Watch.instance.output(target) unless PryMoves::Watch.instance.empty?
95
+ lines.concat PryMoves.messages
96
+ PryMoves.messages.clear
97
+
64
98
  lines << ''
65
99
  lines.join "\n"
66
100
  end
@@ -82,4 +116,18 @@ Pry::Code::LOC.class_eval do
82
116
  tuple[0] = " #{marker} #{ line }"
83
117
  end
84
118
 
85
- end
119
+ end
120
+
121
+ Pry::Output.class_eval do
122
+
123
+ alias pry_moves_origin_for_puts puts
124
+
125
+ def puts *args
126
+ first = args[0]
127
+ if first.is_a? String and first.start_with? "(pry) output error"
128
+ first.slice! 400..-1
129
+ end
130
+ pry_moves_origin_for_puts *args
131
+ end
132
+
133
+ end if defined? Pry::Output
@@ -2,24 +2,24 @@ require 'pry' unless defined? Pry
2
2
 
3
3
  module PryMoves
4
4
  class PryWrapper
5
- def initialize(binding_, pry_start_options = {})
5
+ def initialize(binding_, pry_start_options, pry)
6
6
  @init_binding = binding_
7
7
  @pry_start_options = pry_start_options # Options to use for Pry.start
8
+ @pry = pry
8
9
  end
9
10
 
10
- def run(&block)
11
+ def run
11
12
  PryMoves.lock
12
13
 
13
- Pry.config.marker = "⛔️" if @pry_start_options[:exit_from_method]
14
-
15
- return_value = nil
16
- PryMoves.is_open = true
17
- @command = catch(:breakout_nav) do # Coordinates with PryMoves::Commands
18
- return_value = yield
19
- nil # Nothing thrown == no navigational command
14
+ initial_frame = PryMoves::BindingsStack.new.initial_frame
15
+ if not @pry_start_options[:pry_moves_loop] and
16
+ initial_frame.local_variable_defined? :debug_redirect
17
+ debug_redirect = initial_frame.local_variable_get(:debug_redirect)
18
+ PryMoves.messages << "⏩ redirected to #{debug_redirect}"
19
+ @command = {action: :step, binding: initial_frame}
20
+ else
21
+ start_pry
20
22
  end
21
- PryMoves.is_open = false
22
- Pry.config.marker = "=>"
23
23
 
24
24
  if @command
25
25
  trace_command
@@ -30,11 +30,24 @@ class PryWrapper
30
30
  end
31
31
  end
32
32
 
33
- return_value
33
+ @return_value
34
34
  end
35
35
 
36
36
  private
37
37
 
38
+ def start_pry
39
+ Pry.config.marker = "⛔️" if @pry_start_options[:exit_from_method]
40
+ PryMoves.is_open = true
41
+
42
+ @command = catch(:breakout_nav) do # Coordinates with PryMoves::Commands
43
+ @return_value = @pry.pry_moves_origin_start(@init_binding, @pry_start_options)
44
+ nil # Nothing thrown == no navigational command
45
+ end
46
+
47
+ PryMoves.is_open = false
48
+ Pry.config.marker = "=>"
49
+ end
50
+
38
51
  def trace_command
39
52
  if @command[:action] == :debug
40
53
  wrap_debug
@@ -61,9 +74,9 @@ class PryWrapper
61
74
  tracer = start_tracing
62
75
  begin
63
76
  @command[:binding].eval @command[:param]
64
- rescue => e
77
+ rescue StandardError, SyntaxError => e
65
78
  Thread.current.set_trace_func nil
66
- puts e
79
+ puts "❌️ #{e}"
67
80
  end
68
81
  tracer.stop_tracing
69
82
  end.join
@@ -73,9 +86,9 @@ class PryWrapper
73
86
 
74
87
  def start_tracing
75
88
  @last_runtime_binding = @command[:binding]
76
- tracer = PryMoves::Tracer.new @command, @pry_start_options
77
- tracer.trace
78
- tracer
89
+ PryMoves::TraceCommand.trace @command, @pry_start_options do |binding|
90
+ Pry.start(binding, @pry_start_options)
91
+ end
79
92
  end
80
93
 
81
94
  end