pry-moves 0.1.13 → 1.0.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +9 -5
  4. data/README.md +25 -9
  5. data/lib/commands/debug.rb +21 -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 +73 -0
  16. data/lib/pry-moves/add_suffix.rb +87 -0
  17. data/lib/pry-moves/backtrace.rb +99 -50
  18. data/lib/pry-moves/bindings_stack.rb +106 -0
  19. data/lib/pry-moves/commands.rb +42 -3
  20. data/lib/pry-moves/error_with_data.rb +10 -0
  21. data/lib/pry-moves/formatter.rb +79 -0
  22. data/lib/pry-moves/painter.rb +3 -2
  23. data/lib/pry-moves/pry_ext.rb +66 -18
  24. data/lib/pry-moves/pry_wrapper.rb +30 -17
  25. data/lib/pry-moves/recursion_tracker.rb +94 -0
  26. data/lib/pry-moves/restartable.rb +39 -0
  27. data/lib/pry-moves/version.rb +1 -1
  28. data/lib/pry-moves/watch.rb +3 -0
  29. data/lib/pry-moves.rb +77 -6
  30. data/lib/pry-stack_explorer/frame_helpers.rb +114 -0
  31. data/lib/pry-stack_explorer/frame_manager.rb +6 -9
  32. data/lib/pry-stack_explorer/pry-stack_explorer.rb +2 -17
  33. data/lib/pry-stack_explorer/{commands.rb → stack_commands.rb} +6 -109
  34. data/lib/pry-stack_explorer/when_started_hook.rb +10 -65
  35. data/playground/Gemfile.lock +9 -5
  36. data/playground/README.md +1 -0
  37. data/playground/playground.rb +94 -9
  38. data/playground/sand.rb +12 -10
  39. data/playground/test.rb +5 -1
  40. data/playground/test.sh +1 -0
  41. data/pry-moves.gemspec +4 -2
  42. data/publish.sh +2 -1
  43. data/spec/backtrace_spec.rb +11 -13
  44. data/spec/blocks_spec.rb +41 -8
  45. data/spec/commands_spec.rb +26 -25
  46. data/spec/pry_debugger.rb +5 -1
  47. data/spec/redirection_spec.rb +7 -0
  48. data/spec/spec_helper.rb +9 -4
  49. data/spec/step_spec.rb +51 -0
  50. metadata +60 -13
  51. data/lib/pry-moves/helpers.rb +0 -56
  52. data/lib/pry-moves/trace_commands.rb +0 -109
  53. data/lib/pry-moves/traced_method.rb +0 -61
  54. data/lib/pry-moves/tracer.rb +0 -140
  55. data/lib/pry-moves/traversing.rb +0 -69
@@ -0,0 +1,87 @@
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::ArrayIndex,
69
+ PryMoves::ArrayCall,
70
+ PryMoves::HashKey
71
+ ]
72
+
73
+ SUFFIX_COMMANDS.each do |cmd|
74
+ Pry::Commands.add_command(cmd)
75
+ end
76
+
77
+ Pry::History.class_eval do
78
+
79
+ def <<(line)
80
+ return if ["!"].include? line
81
+ return if SUFFIX_COMMANDS.any? do |cls|
82
+ line.match(cls.match)
83
+ end
84
+ push line
85
+ end
86
+
87
+ end
@@ -2,12 +2,12 @@ require 'fileutils'
2
2
 
3
3
  class PryMoves::Backtrace
4
4
 
5
+ FILTERS = %w[/gems/ /rubygems/ /bin/ /lib/ruby/]
6
+
5
7
  class << self
6
- def lines_count; @lines_count || 5; end
7
- def lines_count=(f); @lines_count = f; end
8
8
 
9
9
  def filter
10
- @filter || /(\/gems\/|\/rubygems\/|\/bin\/|\/lib\/ruby\/)/
10
+ @filter ||= Regexp.new FILTERS.join("|")
11
11
  end
12
12
  def filter=(f); @filter = f; end
13
13
 
@@ -20,77 +20,98 @@ class PryMoves::Backtrace
20
20
  # not used
21
21
  end
22
22
  end
23
+
23
24
  end
24
25
 
25
- def initialize(binding, pry)
26
- @binding, @pry = binding, pry
26
+ def initialize(pry)
27
+ @pry = pry
28
+ @formatter = PryMoves::Formatter.new
27
29
  end
28
30
 
29
31
  def run_command(param, param2)
30
- if param.is_a?(String) and (match = param.match /^>(.*)/)
32
+ if param == 'save'
33
+ @filter = 'hidden'
34
+ @@backtrace = build_backtrace
35
+ @pry.output.puts "💾 Backtrace saved (#{@@backtrace.count} lines)"
36
+ elsif param == 'diff'
37
+ @filter = 'hidden'
38
+ diff
39
+ elsif param.is_a?(String) and (match = param.match /^>(.*)/)
31
40
  suffix = match[1].size > 0 ? match[1] : param2
32
- write_to_file build, suffix
41
+ @formatter.colorize = false
42
+ write_to_file build_backtrace, suffix
43
+ elsif param and param.match /\d+/
44
+ index = param.to_i
45
+ frame_manager.goto_index index
33
46
  else
34
- @colorize = true
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
47
+ print_backtrace param
40
48
  end
41
49
  end
42
50
 
43
51
  private
44
52
 
45
- def build
53
+ def print_backtrace filter
54
+ @colorize = true
55
+ @lines_numbers = true
56
+ @filter = filter if filter.is_a? String
57
+ @pry.output.puts build_backtrace
58
+ end
59
+
60
+ def build_backtrace
61
+ show_all = %w(a all).include?(@filter)
62
+ show_vapid = %w(+ hidden vapid).include?(@filter) || show_all
46
63
  result = []
47
- show_vapid = %w(+ all hidden vapid).include? @lines_count
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
64
+ current_object, vapid_count = nil, 0
57
65
 
58
- build_result stack, result
59
- end
66
+ recursion = PryMoves::Recursion::Holder.new
67
+
68
+ frame_manager.bindings.reverse.each do |binding|
69
+ next if !show_all and binding.eval('__FILE__').match self.class::filter
70
+
71
+ if !show_vapid and binding.hidden
72
+ vapid_count += 1
73
+ next
74
+ end
75
+
76
+ if vapid_count > 0
77
+ result << "👽 frames hidden: #{vapid_count}"
78
+ vapid_count = 0
79
+ end
60
80
 
61
- def build_result(stack, result)
62
- current_object = nil
63
- stack.each do |binding|
64
81
  obj, debug_snapshot = binding.eval '[self, (debug_snapshot rescue nil)]'
65
82
  # Comparison of objects directly may raise exception
66
83
  if current_object.object_id != obj.object_id
67
- result << "#{debug_snapshot || format_obj(obj)}:"
84
+ result << "#{debug_snapshot || @formatter.format_obj(obj)}"
68
85
  current_object = obj
69
86
  end
70
87
 
71
- result << build_line(binding)
88
+ file, line = binding.eval('[__FILE__, __LINE__]')
89
+ recursion.track file, line, result.count, binding.index unless show_vapid
90
+ result << build_line(binding, file, line)
72
91
  end
73
- result
74
- end
75
92
 
76
- def format_obj(obj)
77
- if @colorize
78
- PryMoves::Painter.colorize obj
79
- else
80
- obj.inspect
81
- end
93
+ # recursion.each { |t| t.apply result }
94
+
95
+ result << "👽 frames hidden: #{vapid_count}" if vapid_count > 0
96
+
97
+ result
82
98
  end
83
99
 
84
- def build_line(binding)
85
- file = PryMoves::Helpers.shorten_path "#{binding.eval('__FILE__')}"
100
+ def build_line(binding, file, line)
101
+ file = @formatter.shorten_path "#{file}"
86
102
 
87
- signature = PryMoves::Helpers.method_signature binding
103
+ signature = @formatter.method_signature binding
88
104
  signature = ":#{binding.frame_type}" if !signature or signature.length < 1
89
105
 
90
- indent = frame_manager.current_frame == binding ?
91
- ' => ': ' '
106
+ indent = if frame_manager.current_frame == binding
107
+ '==> '
108
+ elsif true #@lines_numbers
109
+ s = "#{binding.index}:".ljust(4, ' ')
110
+ @colorize ? "\e[2;49;90m#{s}\e[0m" : s
111
+ else
112
+ ' '
113
+ end
92
114
 
93
- line = binding.eval('__LINE__')
94
115
  "#{indent}#{file}:#{line} #{signature}"
95
116
  end
96
117
 
@@ -98,10 +119,6 @@ class PryMoves::Backtrace
98
119
  PryStackExplorer.frame_manager(@pry)
99
120
  end
100
121
 
101
- def stack_bindings(vapid_frames)
102
- frame_manager.filter_bindings vapid_frames: vapid_frames
103
- end
104
-
105
122
  def write_to_file(lines, file_suffix)
106
123
  log_path = log_path file_suffix
107
124
  File.write log_path, lines.join("\n")
@@ -115,4 +132,36 @@ class PryMoves::Backtrace
115
132
  "#{root}/backtrace_#{file_suffix}.log"
116
133
  end
117
134
 
118
- end
135
+ def diff
136
+ return STDERR.puts "No backtrace saved. Use `bt save` first".yellow unless defined? @@backtrace
137
+
138
+ diff = Diffy::Diff.new(@@backtrace.join("\n"), build_backtrace.join("\n")).to_s "color"
139
+ diff = 'Backtraces are equal' if diff.strip.empty?
140
+ @pry.output.puts diff
141
+ end
142
+
143
+ end
144
+
145
+ Pry.config.exception_handler = proc do |output, exception, _|
146
+
147
+ def print_error message, exception, output
148
+ output.puts message.red
149
+ exception.backtrace.reject! {|l| l.match /sugar\.rb/}
150
+ exception.backtrace.first(3).each { output.puts _1.white }
151
+ end
152
+
153
+ if Pry::UserError === exception && SyntaxError === exception
154
+ output.puts "SyntaxError: #{exception.message.sub(/.*syntax error, */m, '')}"
155
+ else
156
+
157
+ print_error "#{exception.class}: #{exception.message}", exception, output
158
+
159
+ if exception.respond_to? :cause
160
+ cause = exception.cause
161
+ while cause
162
+ print_error "Caused by #{cause.class}: #{cause}\n", exception, output
163
+ cause = cause.cause
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,106 @@
1
+ class PryMoves::BindingsStack < Array
2
+
3
+ def initialize options
4
+ @options = options
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| for_for_initial_frame b} || 0
18
+ end
19
+ def initial_frame
20
+ find {|b| for_for_initial_frame b}
21
+ end
22
+
23
+ def for_for_initial_frame b
24
+ not b.hidden and (
25
+ @options[:is_error] or b.eval("__method__") != :initialize
26
+ )
27
+ end
28
+
29
+ private
30
+
31
+ def set_indices
32
+ reverse.each_with_index do |binding, index|
33
+ binding.index = index
34
+ end
35
+ end
36
+
37
+ def mark_vapid_frames
38
+ stepped_out = false
39
+ actual_file, actual_method = nil, nil
40
+
41
+ # here calls checked in reverse order - from latest to parent:
42
+ each do |binding|
43
+ file, method, obj = binding.eval("[__FILE__, __method__, self]")
44
+
45
+ if file.match PryMoves::Backtrace::filter
46
+ binding.hidden = true
47
+ elsif stepped_out
48
+ if actual_file == file and actual_method == method or
49
+ binding.local_variable_defined? :pry_moves_deferred_call
50
+ stepped_out = false
51
+ else
52
+ binding.hidden = true
53
+ end
54
+ elsif binding.frame_type == :block
55
+ stepped_out = true
56
+ actual_file = file
57
+ actual_method = method
58
+ elsif obj and method and obj.method(method).source.strip.match /^delegate\s/
59
+ binding.hidden = true
60
+ end
61
+
62
+ if binding.local_variable_defined? :hide_from_stack
63
+ binding.hidden = true
64
+ end
65
+ end
66
+
67
+ stack_tip_met = false
68
+ stack_tips = PryMoves.stack_tips || []
69
+ reverse.each do |binding|
70
+ if binding.local_variable_defined?(:pry_moves_stack_tip) ||
71
+ stack_tips.include?(binding.eval("__method__"))
72
+ stack_tip_met = true
73
+ end
74
+ binding.hidden = true if stack_tip_met
75
+ end
76
+ end
77
+
78
+ # remove internal frames related to setting up the session
79
+ def remove_internal_frames(bindings)
80
+ i = top_internal_frame_index(bindings)
81
+ # DEBUG:
82
+ #bindings.each_with_index do |b, index|
83
+ # puts "#{index}: #{b.eval("self.class")} #{b.eval("__method__")}"
84
+ #end
85
+ # puts "FOUND top internal frame in #{bindings.size} frames: [#{i}] #{bindings[i].ai}"
86
+
87
+ bindings.drop i+1
88
+ end
89
+
90
+ def top_internal_frame_index(bindings)
91
+ pry_moves_debug = Thread.current[:pry_moves_debug]
92
+ bindings.rindex do |b|
93
+ if not pry_moves_debug and b.frame_type == :eval
94
+ true
95
+ elsif b.frame_type == :method
96
+ method, self_ = b.eval("[__method__, self, __FILE__]")
97
+
98
+ self_.equal?(Pry) && method == :start ||
99
+ self_.class == Binding && method == :pry ||
100
+ self_.is_a?(PryMoves::TraceCommand) && method == :tracing_func ||
101
+ b.local_variable_defined?(:pry_moves_stack_end)
102
+ end
103
+ end
104
+ end
105
+
106
+ end
@@ -21,6 +21,11 @@ module PryMoves
21
21
  breakout_navigation :next, 'blockless'
22
22
  end
23
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
+
24
29
  block_command 'iterate', 'Go to next iteration of current block' do |param|
25
30
  breakout_navigation :iterate, param
26
31
  end
@@ -41,7 +46,7 @@ module PryMoves
41
46
  end
42
47
 
43
48
  block_command 'bt', 'Backtrace' do |param, param2|
44
- PryMoves::Backtrace.new(target, _pry_).run_command param, param2
49
+ PryMoves::Backtrace.new(_pry_).run_command param, param2
45
50
  end
46
51
 
47
52
  block_command 'debug', '' do
@@ -49,6 +54,23 @@ module PryMoves
49
54
  breakout_navigation :debug, cmd
50
55
  end
51
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
+
52
74
  block_command '!', 'exit' do
53
75
  PryMoves.unlock
54
76
  Pry.config.exit_requested = true
@@ -62,16 +84,32 @@ module PryMoves
62
84
 
63
85
  helpers do
64
86
  def breakout_navigation(action, param)
87
+ return if var_precedence action
88
+
65
89
  check_file_context
66
90
  _pry_.binding_stack.clear # Clear the binding stack.
67
91
  throw :breakout_nav, { # Break out of the REPL loop and
68
92
  action: action, # signal the tracer.
69
93
  param: param,
70
- binding: target,
71
- pry: _pry_
94
+ binding: target
72
95
  }
73
96
  end
74
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
+
75
113
  # Ensures that a command is executed in a local file context.
76
114
  def check_file_context
77
115
  unless PryMoves.check_file_context(target)
@@ -79,6 +117,7 @@ module PryMoves
79
117
  end
80
118
  end
81
119
  end
120
+
82
121
  end
83
122
  end
84
123
 
@@ -0,0 +1,10 @@
1
+ class PryMoves::ErrorWithData < StandardError
2
+
3
+ attr_reader :metadata
4
+
5
+ def initialize(msg, metadata)
6
+ super msg
7
+ @metadata = metadata
8
+ end
9
+
10
+ end
@@ -0,0 +1,79 @@
1
+ class PryMoves::Formatter
2
+
3
+ attr_accessor :colorize
4
+
5
+ def initialize colorize = true
6
+ @colorize = colorize
7
+ end
8
+
9
+ MAX_PARAMS = 5
10
+ def method_signature(binding)
11
+ meth = binding.eval('__method__')
12
+ meth_obj = meth ? Pry::Method.from_binding(binding) : nil
13
+ if !meth_obj
14
+ ""
15
+ elsif meth_obj.undefined?
16
+ "#{meth_obj.name}(UNKNOWN) (undefined method)"
17
+ else
18
+ args = meth_obj.parameters.map.with_index do |(type, name), i|
19
+ if name
20
+ value = format_arg binding, name.to_s
21
+ show_value = true
22
+ else
23
+ name = (type == :block ? 'block' : "arg#{i + 1}")
24
+ end
25
+ name = case type
26
+ when :req then "#{name} ="
27
+ when :key then "#{name}:"
28
+ when :opt then "#{name}=?"
29
+ when :rest then "*#{name}"
30
+ when :block then "&#{name}"
31
+ else '?'
32
+ end
33
+ show_value ? "#{name} #{value}" : name
34
+ end
35
+ if args.count > MAX_PARAMS
36
+ args = args.first(MAX_PARAMS) + ["(#{args.count - MAX_PARAMS} more params)…"]
37
+ end
38
+ "#{meth_obj.name}(#{args.join(', ')})"
39
+ end
40
+ end
41
+
42
+ def format_arg binding, arg_name
43
+ arg = binding.eval(arg_name.to_s)
44
+ format_obj arg
45
+ end
46
+
47
+ def first_line str
48
+ str.split("\n").first
49
+ end
50
+
51
+ def cut_string str
52
+ return str unless str
53
+ str.length > 50 ? "#{str.first 50}..." : str
54
+ end
55
+
56
+ PATH_TRASH = defined?(Rails) ? Rails.root.to_s : Dir.pwd
57
+
58
+ def shorten_path(path)
59
+ path.gsub( /^#{PATH_TRASH}\//, '')
60
+ end
61
+
62
+ def format_obj(obj)
63
+ if obj.is_a? String
64
+ format_obj2 cut_string first_line obj
65
+ else
66
+ first_line format_obj2 obj
67
+ end
68
+ end
69
+
70
+ def format_obj2(obj)
71
+ if @colorize
72
+ PryMoves::Painter.colorize obj
73
+ else
74
+ i = obj.inspect
75
+ i.start_with?('#<') ? obj.class.to_s : i
76
+ end
77
+ end
78
+
79
+ end
@@ -14,13 +14,14 @@ module PryMoves::Painter
14
14
 
15
15
  def self.colorize(obj)
16
16
  colored_str = Canvas.new
17
- obj = obj.class if obj.inspect.start_with? "#<"
17
+ i = obj.inspect
18
+ obj = obj.class if i.is_a?(String) && i.start_with?("#<")
18
19
  catch (:cut) do
19
20
  Pry::ColorPrinter.pp obj, colored_str
20
21
  end
21
22
  colored_str.chomp
22
23
  rescue => e
23
- "Inspect error: #{e}\n" +
24
+ "⛔️ Inspect error: #{e}\n" +
24
25
  "#{e.backtrace.first(3).join("\n")}"
25
26
  end
26
27