pry-moves 0.1.13 → 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 +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