Module: PryDebug

Defined in:
lib/pry_debug.rb,
lib/pry_debug/version.rb,
lib/pry_debug/commands.rb,
lib/pry_debug/line_breakpoint.rb,
lib/pry_debug/method_breakpoint.rb,
lib/pry_debug/conditional_breakpoint.rb

Defined Under Namespace

Modules: ConditionalBreakpoint Classes: LineBreakpoint, MethodBreakpoint

Constant Summary collapse

DefinitionFile =
File.expand_path(__FILE__)
Version =
"0.1.0"
Commands =
Pry::CommandSet.new do
  command "breakpoint", "adds a breakpoint" do |argument, *|
    if argument =~ /(.+):(\d+)/
      file, line = $1, $2.to_i

      bp = PryDebug.add_line_breakpoint(file, line)
      output.puts "added #{bp}"
    elsif argument =~ /(.+)(#|\.|::)([^#\.:]+)/
      klass, separator, meth = $1, $2, $3
      class_method = (separator != "#")

      bp = PryDebug.add_method_breakpoint(klass, meth, class_method)
      output.puts "added #{bp}"
    else
      output.puts "usage: breakpoint FILE:LINE"
      output.puts "    or breakpoint CLASS(#|.|::)METHOD"
      output.puts
      output.puts "FILE can be foo.rb or /full/path/to/foo.rb."
      output.puts "# as a separator means instance method. . and :: both mean"
      output.puts "class method."
    end
  end

  command "breakpoint-list", "prints breakpoint list" do
    output.puts PryDebug.breakpoints
  end

  command "delete", "deletes a breakpoint" do |id, *|
    PryDebug.breakpoints.reject! { |b| b.id == id.to_i }
    output.puts "breakpoint #{id} deleted"
  end

  command "cond", "adds a condition to a breakpoint" do
    id = string = nil
    if arg_string =~ /^(\d+) (.+)$/
      id, string = [$1.to_i, $2]
    else
      output.puts "usage: cond ID CODE"
      next
    end

    if bp = PryDebug.breakpoints.find { |b| b.id == id }
      bp.condition = string
      output.puts "condition set to #{bp.condition}"
    else
      output.puts "error: could not find breakpoint #{id}"
    end
  end

  command "uncond", "removes the condition of a breakpoint" do |id, *|
    if id =~ /^\d+$/ && (bp = PryDebug.breakpoints.find { |b| b.id == id.to_i })
      bp.condition = nil
      output.puts "condition unset"
    else
      output.puts "error: could not find breakpoint #{id}"
    end
  end

  command "file", "sets the file to start the debugger at" do |file, *|
    PryDebug.file = file
    output.puts "debugged file set to #{file}"
  end

  command "run", "starts the debugger" do |file, *|
    if PryDebug.debugging
      output.puts "error: debugger already started"
      next
    end

    PryDebug.file = file if file

    if !PryDebug.will_load || (PryDebug.file and File.exist? PryDebug.file)
      throw :start_debugging!, :now!
    else
      if PryDebug.file
        output.puts "error: file does not exist: #{PryDebug.file}"
      else
        output.puts "error: file is not set: #{PryDebug.file}"
      end

      output.puts "create it or set a new file using the 'file' command."
    end
  end

  command "continue", "resumes execution" do
    if !PryDebug.debugging
      output.puts "error: debugger hasn't been started yet"
    else
      throw :resume_debugging!
    end
  end

  command "next", "resumes execution until next line in the same file" do
    if !PryDebug.debugging
      output.puts "error: debugger hasn't been started yet"
    else
      throw :resume_debugging!, :next
    end
  end

  command "step", "resumes execution until next line gets executed" do
    PryDebug.stepping = true

    if PryDebug.debugging
      throw :resume_debugging!
    else # just start debugging with stepping set to true
      if !PryDebug.will_load || (PryDebug.file and File.exist? PryDebug.file)
        throw :start_debugging!, :now!
      else
        output.puts "error: file does not exist: #{PryDebug.file}"
        output.puts "create it or set a new file using the 'file' command."
      end
    end
  end

  command "break-on-raise", "toggles break on raise" do
    PryDebug.break_on_raise = !PryDebug.break_on_raise

    if PryDebug.break_on_raise
      output.puts "break on raise enabled"
    else
      output.puts "break on raise disabled"
    end
  end
end
ShortCommands =
Pry::CommandSet.new Commands do
  alias_command "f",   "file"
  alias_command "b",   "breakpoint"
  alias_command "bp",  "breakpoint"
  alias_command "bl",  "breakpoint-list"
  alias_command "del", "delete"
  alias_command "d",   "delete"
  alias_command "r",   "run"
  alias_command "c",   "continue"
  alias_command "n",   "next"
  alias_command "s",   "step"
  alias_command "bor", "break-on-raise"
end

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.break_on_raisetrue, false

Returns True if PryDebug breaks on raise.

Returns:

  • (true, false)

    True if PryDebug breaks on raise



60
61
62
# File 'lib/pry_debug.rb', line 60

def break_on_raise
  @break_on_raise
end

.breakpoint_countObject

Returns the value of attribute breakpoint_count.



17
18
19
# File 'lib/pry_debug.rb', line 17

def breakpoint_count
  @breakpoint_count
end

.breakpointsArray<LineBreakpoint,MethodBreakpoint> (readonly)

Returns All the enabled breakpoints.

Returns:



15
16
17
# File 'lib/pry_debug.rb', line 15

def breakpoints
  @breakpoints
end

.debuggingObject

Returns the value of attribute debugging.



62
63
64
# File 'lib/pry_debug.rb', line 62

def debugging
  @debugging
end

.fileString?

Returns File that PryDebug loads.

Returns:

  • (String, nil)

    File that PryDebug loads



20
21
22
# File 'lib/pry_debug.rb', line 20

def file
  @file
end

.mutexObject (readonly)

Returns the value of attribute mutex.



65
66
67
# File 'lib/pry_debug.rb', line 65

def mutex
  @mutex
end

.will_loadObject

Returns the value of attribute will_load.



63
64
65
# File 'lib/pry_debug.rb', line 63

def will_load
  @will_load
end

Class Method Details

.add_line_breakpoint(file, line) ⇒ Object



267
268
269
270
271
# File 'lib/pry_debug.rb', line 267

def add_line_breakpoint(file, line)
  bp = LineBreakpoint.new(PryDebug.breakpoint_count += 1, file, line)
  PryDebug.breakpoints << bp
  bp
end

.add_method_breakpoint(klass, method, class_method) ⇒ Object



273
274
275
276
277
278
# File 'lib/pry_debug.rb', line 273

def add_method_breakpoint(klass, method, class_method)
  bp = MethodBreakpoint.new(PryDebug.breakpoint_count += 1, klass, method,
                            class_method)
  PryDebug.breakpoints << bp
  bp
end

.clean_upObject

Resets PryDebug to its default state.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/pry_debug.rb', line 86

def clean_up
  @breakpoints      = []
  @breakpoint_count = -1
  @file             = nil

  Thread.list.each do |th|
    th[:__pry_debug_stepped_file] = nil
    th[:__pry_debug_stepping]     = false

    th[:__pry_debug_exception_binding] = nil
    th[:__pry_debug_last_exception]    = nil
  end

  @break_on_raise    = false
  @debugging         = false
  @will_load         = true

  @mutex             = Mutex.new
end

.context_of_exception(ex) ⇒ Binding?

Returns Binding where the exception was raised, if still in memory.

Returns:

  • (Binding, nil)

    Binding where the exception was raised, if still in memory.



79
80
81
82
83
# File 'lib/pry_debug.rb', line 79

def context_of_exception(ex)
  if ex.equal? last_exception
    exception_binding
  end
end

.exception_bindingBinding?

Returns Binding where last_exception was raised.

Returns:

  • (Binding, nil)

    Binding where last_exception was raised



42
43
44
# File 'lib/pry_debug.rb', line 42

def exception_binding
  Thread.current[:__pry_debug_exception_binding]
end

.exception_binding=(b) ⇒ Object



46
47
48
# File 'lib/pry_debug.rb', line 46

def exception_binding=(b)
  Thread.current[:__pry_debug_exception_binding] = b
end

.last_exceptionException?

Returns Last exception that PryDebug has heard of.

Returns:

  • (Exception, nil)

    Last exception that PryDebug has heard of



51
52
53
# File 'lib/pry_debug.rb', line 51

def last_exception
  Thread.current[:__pry_debug_last_exception]
end

.last_exception=(ex) ⇒ Object



55
56
57
# File 'lib/pry_debug.rb', line 55

def last_exception=(ex)
  Thread.current[:__pry_debug_last_exception] = ex
end

.line_breakpointsArray<LineBreakpoint>

Returns Breakpoints on a line.

Returns:



68
69
70
# File 'lib/pry_debug.rb', line 68

def line_breakpoints
  breakpoints.select { |bp| bp.is_a? LineBreakpoint }
end

.method_breakpointsArray<MethodBreakpoint>

Returns Breakpoints on a method.

Returns:



73
74
75
# File 'lib/pry_debug.rb', line 73

def method_breakpoints
  breakpoints.select { |bp| bp.is_a? MethodBreakpoint }
end

.start(load_file = true) ⇒ Object

Starts the debguger.

Parameters:

  • load_file (true, false) (defaults to: true)

    When set to false, PryDebug won’t load a file, and simply enable the tracing and let the user setup breakpoints.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/pry_debug.rb', line 115

def start(load_file = true)
  PryDebug.will_load = load_file

  # Importing user-defined commands.
  # NB: what about commands defined in both sets? Currently, user-defined
  # commands override PryDebug's. What about doing it the other way around?
  Pry.load_rc if Pry.config.should_load_rc # user might change Pry.commands
  Pry.config.should_load_rc = false # avoid loading config twice
  ShortCommands.import Pry.commands

  loop do
    should_start = catch(:start_debugging!) do
      Pry.start(TOPLEVEL_BINDING, :commands => ShortCommands)
    end

    if should_start == :now!
      set_trace_func trace_proc
      PryDebug.debugging = true

      return unless load_file

      begin
        load PryDebug.file
      rescue SystemExit
        # let this go
      rescue Exception => ex
        set_trace_func nil
        puts "unrescued exception: #{ex.class}: #{ex.message}"

        if binding = PryDebug.context_of_exception(ex)
          msg = "returning back to where the exception was raised"
          start_pry binding, nil, msg
        else
          msg =  "context of the exception is unknown, starting pry into\n"
          msg << "the exception."

          start_pry ex, nil, msg
        end
      end

      PryDebug.last_exception = PryDebug.exception_binding = nil
      PryDebug.debugging = false

      set_trace_func nil
      puts "execution terminated"
    else
      break # debugger wasn't started, leave now
    end
  end
end

.start_pry(binding, file = nil, header = nil) ⇒ Object

Starts Pry with access to ShortCommands

Parameters:

  • binding (Binding, object)

    Context to go to

  • file (String, nil) (defaults to: nil)

    Current file. Used for the next command.

  • header (String, nil) (defaults to: nil)

    Line to print before starting the debugger



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/pry_debug.rb', line 170

def start_pry(binding, file = nil, header = nil)
  PryDebug.synchronize do
    puts header if header

    ret = catch(:resume_debugging!) do
      Pry.start(binding, :commands => ShortCommands)
    end

    if ret == :next
      PryDebug.stepped_file = file
    end

    # In case trace_func was changed
    set_trace_func trace_proc
  end
end

.stepped_fileString?

Returns If not nil, the file where PryDebug needs to stop (implying that next was called).

Returns:

  • (String, nil)

    If not nil, the file where PryDebug needs to stop (implying that next was called)



24
25
26
# File 'lib/pry_debug.rb', line 24

def stepped_file
  Thread.current[:__pry_debug_stepped_file]
end

.stepped_file=(val) ⇒ Object



28
29
30
# File 'lib/pry_debug.rb', line 28

def stepped_file=(val)
  Thread.current[:__pry_debug_stepped_file] = val
end

.steppingtrue, false

Returns True if stepping.

Returns:

  • (true, false)

    True if stepping



33
34
35
# File 'lib/pry_debug.rb', line 33

def stepping
  Thread.current[:__pry_debug_steppping]
end

.stepping=(val) ⇒ Object



37
38
39
# File 'lib/pry_debug.rb', line 37

def stepping=(val)
  Thread.current[:__pry_debug_stepping] = val
end

.synchronize(&block) ⇒ Object



187
188
189
# File 'lib/pry_debug.rb', line 187

def synchronize(&block)
  PryDebug.mutex.synchronize(&block)
end

.trace_func(event, file, line, method, binding, klass) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/pry_debug.rb', line 197

def trace_func(event, file, line, method, binding, klass)
  # Ignore events in this file
  return if file && File.expand_path(file) == DefinitionFile

  case event
  when 'line'
    if PryDebug.stepped_file == file
      PryDebug.stepped_file = nil
      start_pry binding, file, "stepped at #{file}:#{line} in #{Thread.current}"
    elsif PryDebug.stepping
      PryDebug.stepping = false
      start_pry binding, file, "stepped at #{file}:#{line} in #{Thread.current}"
    elsif bp = PryDebug.line_breakpoints.find { |b| b.is_at?(file, line, binding) }
      start_pry binding, file, "reached #{bp} in #{Thread.current}"
    end
  when 'c-call', 'call'
    return unless Module === klass

    # Whether we are calling a class method or an instance method, klass
    # is the same thing, making it harder to guess if it's a class or an
    # instance method.
    #
    # In case of C method calls, self cannot be trusted. Both will be tried,
    # unless we can find out there is only an instance method of that name
    # using instance_methods.
    #
    # Otherwise, assume it's an instance method if self is_a? klass
    #
    # Notice that since self could be a BasicObject, it may not respond to
    # is_a? (even when not breaking on BasicObject#thing, this code may be
    # triggered).
    class_method, try_both = if event == "call"
                               [!(klass === binding.eval("self")), false]
                             else
                               if (klass.instance_methods & klass.methods).include? method
                                 [false, true]
                               elsif klass.instance_methods.include? method
                                 [false, false]
                               elsif klass.methods.include? method
                                 [true, false]
                               else # should never happen
                                 [false, true]
                               end
                             end

    bp = PryDebug.method_breakpoints.find do |b|
      if try_both
        b.is_at?(klass, method.to_s, true, binding) ||
          b.is_at?(klass, method.to_s, false, binding)
      else
        b.is_at?(klass, method.to_s, class_method, binding)
      end
    end

    if bp
      start_pry binding, file, "reached #{bp} in #{Thread.current}"
    end
  when 'raise'
    return unless $!

    PryDebug.last_exception    = $!
    PryDebug.exception_binding = binding

    if PryDebug.break_on_raise
      msg = "exception raised in #{Thread.current}: #{$!.class}: #{$!.message} "
      start_pry binding, file, msg
    end
  end
end

.trace_procObject



191
192
193
194
195
# File 'lib/pry_debug.rb', line 191

def trace_proc
  proc do |*args|
    PryDebug.trace_func(*args)
  end
end