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,49 @@
1
+ module PryMoves::TraceHelpers
2
+
3
+ def redirect_step?(binding_)
4
+ return false unless binding_.local_variable_defined? :debug_redirect
5
+
6
+ debug_redirect = binding_.local_variable_get(:debug_redirect)
7
+ @step_into_funcs = [debug_redirect] if debug_redirect
8
+ true
9
+ end
10
+
11
+ def debug_info(file, line, id)
12
+ puts "📽 call_depth:#{@call_depth} #{@method[:file]}:#{file}"
13
+ puts "#{id} #{@method[:start]} > #{line} > #{@method[:end]}"
14
+ end
15
+
16
+ def current_frame_digest(upward: 0)
17
+ # binding_ from tracing_func doesn't have @iseq,
18
+ # therefore binding should be re-retrieved using 'binding_of_caller' lib
19
+ frame_digest(binding.of_caller(3 + upward))
20
+ end
21
+
22
+ def frame_digest(binding_)
23
+ #puts "frame_digest for: #{binding_.eval '__callee__'}"
24
+ iseq = binding_.instance_variable_get('@iseq')
25
+ Digest::MD5.hexdigest iseq.disasm
26
+ end
27
+
28
+ def current_frame_type(upward: 0)
29
+ # binding_ from tracing_func doesn't have @iseq,
30
+ # therefore binding should be re-retrieved using 'binding_of_caller' lib
31
+ frame_type(binding.of_caller(3 + upward))
32
+ end
33
+
34
+ def frame_type(binding_)
35
+ line = binding_.instance_variable_get('@iseq').disasm.split("\n").first
36
+ m = line.match /\== disasm: #<ISeq:([\w ]+)@/
37
+ if m
38
+ str = m[1]
39
+ if str.start_with? 'block in '
40
+ :block
41
+ else
42
+ :method
43
+ end
44
+ else
45
+ :unknown
46
+ end
47
+ end
48
+
49
+ end
@@ -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
60
59
 
61
- def build_result(stack, result)
62
- current_object = nil
63
- stack.each do |binding|
64
- obj = binding.eval 'self'
65
- if current_object != obj
66
- result << "#{format_obj(obj)}:"
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
69
+
70
+ obj, debug_snapshot = binding.eval '[self, (debug_snapshot rescue nil)]'
71
+ # Comparison of objects directly may raise exception
72
+ if current_object.object_id != obj.object_id
73
+ result << "#{debug_snapshot || @formatter.format_obj(obj)}"
67
74
  current_object = obj
68
75
  end
69
76
 
70
77
  result << build_line(binding)
71
78
  end
72
- result
73
- end
74
79
 
75
- def format_obj(obj)
76
- if @colorize
77
- PryMoves::Painter.colorize obj
78
- else
79
- obj.inspect
80
- end
80
+ result << "👽 frames hidden: #{vapid_count}" if vapid_count > 0
81
+
82
+ result
81
83
  end
82
84
 
83
85
  def build_line(binding)
84
- file = "#{binding.eval('__FILE__')}"
85
- file.gsub!( /^#{Rails.root.to_s}/, '') if defined? Rails
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