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.
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 +15 -7
  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 +67 -43
  18. data/lib/pry-moves/bindings_stack.rb +97 -0
  19. data/lib/pry-moves/commands.rb +42 -3
  20. data/lib/pry-moves/formatter.rb +74 -0
  21. data/lib/pry-moves/painter.rb +3 -2
  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 -6
  28. data/lib/pry-stack_explorer/frame_manager.rb +6 -9
  29. data/lib/pry-stack_explorer/pry-stack_explorer.rb +1 -16
  30. data/lib/pry-stack_explorer/{commands.rb → stack_commands.rb} +10 -6
  31. data/lib/pry-stack_explorer/when_started_hook.rb +10 -65
  32. data/playground/Gemfile.lock +6 -4
  33. data/playground/README.md +1 -0
  34. data/playground/playground.rb +94 -9
  35. data/playground/test.rb +5 -1
  36. data/playground/test.sh +1 -0
  37. data/pry-moves.gemspec +3 -2
  38. data/publish.sh +2 -1
  39. data/spec/backtrace_spec.rb +11 -13
  40. data/spec/blocks_spec.rb +41 -8
  41. data/spec/commands_spec.rb +26 -25
  42. data/spec/pry_debugger.rb +5 -1
  43. data/spec/redirection_spec.rb +7 -0
  44. data/spec/spec_helper.rb +9 -4
  45. data/spec/step_spec.rb +51 -0
  46. metadata +43 -13
  47. data/lib/pry-moves/helpers.rb +0 -56
  48. data/lib/pry-moves/trace_commands.rb +0 -109
  49. data/lib/pry-moves/traced_method.rb +0 -61
  50. data/lib/pry-moves/tracer.rb +0 -140
  51. 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
@@ -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
@@ -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(binding, pry)
26
- @binding, @pry = binding, pry
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
- @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
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
- 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
55
+ current_object, vapid_count = nil, 0
57
56
 
58
- build_result stack, result
59
- end
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
- def format_obj(obj)
77
- if @colorize
78
- PryMoves::Painter.colorize obj
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 = PryMoves::Helpers.shorten_path "#{binding.eval('__FILE__')}"
86
+ file = @formatter.shorten_path "#{binding.eval('__FILE__')}"
86
87
 
87
- signature = PryMoves::Helpers.method_signature binding
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