Class: Pry

Inherits:
Object
  • Object
show all
Defined in:
lib/pry/commands/stepping.rb,
lib/pry-byebug/pry_ext.rb,
lib/pry/byebug/breakpoints.rb,
lib/pry/commands/breakpoint.rb

Overview

Main Pry class.

We’re going to add to it custom breakpoint commands for Pry-Byebug

Defined Under Namespace

Modules: Byebug

Constant Summary collapse

SteppingCommands =
CommandSet.new do
  create_command 'step' do
    description 'Step execution into the next line or method.'

    banner <<-BANNER
      Usage: step [TIMES]

      Step execution forward. By default, moves a single step.

      Examples:
        step   #=> Move a single step forward.
        step 5 #=> Execute the next 5 steps.
    BANNER

    def process
      PryByebug.check_file_context(target)

      breakout_navigation :step, args.first
    end
  end

  create_command 'next' do
    description 'Execute the next line within the current stack frame.'

    banner <<-BANNER
      Usage: next [LINES]

      Step over within the same frame. By default, moves forward a single
      line.

      Examples:
        next   #=> Move a single line forward.
        next 4 #=> Execute the next 4 lines.
    BANNER

    def process
      PryByebug.check_file_context(target)

      breakout_navigation :next, args.first
    end
  end

  create_command 'finish' do
    description 'Execute until current stack frame returns.'

    banner <<-BANNER
      Usage: finish
    BANNER

    def process
      PryByebug.check_file_context(target)

      breakout_navigation :finish
    end
  end

  create_command 'continue' do
    description 'Continue program execution and end the Pry session.'

    banner <<-BANNER
      Usage: continue
    BANNER

    def process
      PryByebug.check_file_context(target)

      breakout_navigation :continue
    end
  end

  helpers do
    def breakout_navigation(action, times = nil)
      _pry_.binding_stack.clear # Clear the binding stack.

      # Break out of the REPL loop and signal tracer
      throw :breakout_nav, action: action, times: times, pry: _pry_
    end
  end
end
BreakpointCommands =
CommandSet.new do
  create_command 'break' do
    description 'Set or edit a breakpoint.'

    banner <<-BANNER
      Usage:   break <METHOD | FILE:LINE | LINE> [if CONDITION]
               break --condition N [CONDITION]
               break [--show | --delete | --enable | --disable] N
               break [--delete-all | --disable-all]
      Aliases: breakpoint

      Set a breakpoint. Accepts a line number in the current file, a file and
      line number, or a method, and an optional condition.

      Pass appropriate flags to manipulate existing breakpoints.

      Examples:

        break SomeClass#run         Break at the start of `SomeClass#run`.
        break Foo#bar if baz?       Break at `Foo#bar` only if `baz?`.
        break app/models/user.rb:15 Break at line 15 in user.rb.
        break 14                    Break at line 14 in the current file.

        break --condition 4 x > 2   Add/change condition on breakpoint #4.
        break --condition 3         Remove the condition on breakpoint #3.

        break --delete 5            Delete breakpoint #5.
        break --disable-all         Disable all breakpoints.

        break                       List all breakpoints.
        break --show 2              Show details about breakpoint #2.
    BANNER

    def options(opt)
      defaults = { argument: true, as: Integer }

      opt.on :c, :condition, 'Change condition of a breakpoint.', defaults
      opt.on :s, :show, 'Show breakpoint details and source.', defaults
      opt.on :D, :delete, 'Delete a breakpoint.', defaults
      opt.on :d, :disable, 'Disable a breakpoint.', defaults
      opt.on :e, :enable, 'Enable a disabled breakpoint.', defaults
      opt.on :'disable-all', 'Disable all breakpoints.'
      opt.on :'delete-all', 'Delete all breakpoints.'
    end

    def process
      all = %w(condition show delete disable enable disable-all delete-all)
      all.each do |option|
        next unless opts.present?(option)

        method_name = "process_#{option.gsub('-', '_')}"
        return send(method_name)
      end

      new_breakpoint unless args.empty?
    end

    %w(delete disable enable).each do |command|
      define_method(:"process_#{command}") do
        breakpoints.send(command, opts[command])
        run 'breakpoints'
      end
    end

    %w(disable-all delete-all).each do |command|
      method_name = command.gsub('-', '_')
      define_method(:"process_#{method_name}") do
        breakpoints.send(method_name)
        run 'breakpoints'
      end
    end

    def process_show
      print_full_breakpoint(breakpoints.find_by_id(opts[:show]))
    end

    def process_condition
      expr = args.empty? ? nil : args.join(' ')
      breakpoints.change(opts[:condition], expr)
    end

    def new_breakpoint
      place = args.shift
      condition = args.join(' ') if 'if' == args.shift

      bp =
        case place
        when /^(\d+)$/
          errmsg = 'Line number declaration valid only in a file context.'
          PryByebug.check_file_context(target, errmsg)

          file, lineno = target.eval('__FILE__'), Regexp.last_match[1].to_i
          breakpoints.add_file(file, lineno, condition)
        when /^(.+):(\d+)$/
          file, lineno = Regexp.last_match[1], Regexp.last_match[2].to_i
          breakpoints.add_file(file, lineno, condition)
        when /^(.*)[.#].+$/  # Method or class name
          if Regexp.last_match[1].strip.empty?
            errmsg = 'Method name declaration valid only in a file context.'
            PryByebug.check_file_context(target, errmsg)
            place = target.eval('self.class.to_s') + place
          end
          breakpoints.add_method(place, condition)
        else
          fail(ArgumentError, 'Cannot identify arguments as breakpoint')
        end

      print_full_breakpoint(bp)
    end
  end
  alias_command 'breakpoint', 'break'

  create_command 'breakpoints' do
    description 'List defined breakpoints.'

    banner <<-BANNER
      Usage:   breakpoints [OPTIONS]
      Aliases: breaks

      List registered breakpoints and their current status.
    BANNER

    def options(opt)
      opt.on :v, :verbose, 'Print source around each breakpoint.'
    end

    def process
      return bold_puts('No breakpoints defined.') if breakpoints.count == 0

      if opts.verbose?
        breakpoints.each { |b| print_full_breakpoint(b) }
      else
        print_breakpoints_header
        breakpoints.each { |b| print_short_breakpoint(b) }
        output.puts
      end
    end
  end
  alias_command 'breaks', 'breakpoints'

  helpers do
    #
    # Byebug's array of breakpoints.
    #
    def breakpoints
      Byebug::Breakpoints
    end

    #
    # Prints a message with bold font.
    #
    def bold_puts(msg)
      output.puts(text.bold(msg))
    end

    #
    # Print out full information about a breakpoint.
    #
    # Includes surrounding code at that point.
    #
    def print_full_breakpoint(br)
      header = "Breakpoint #{br.id}:"
      status = br.enabled? ? 'Enabled' : 'Disabled'
      code = br.source_code.with_line_numbers.to_s
      condition = br.expr ? "#{text.bold('Condition:')} #{br.expr}\n" : ''

      output.puts <<-EOP.gsub(/ {8}/, '')

        #{text.bold(header)} #{br} (#{status}) #{condition}

      #{code}

      EOP
    end

    #
    # Print out concise information about a breakpoint.
    #
    def print_short_breakpoint(breakpoint)
      id = sprintf('%*d', max_width, breakpoint.id)
      status = breakpoint.enabled? ? 'Yes' : 'No'
      expr = breakpoint.expr ? breakpoint.expr : ''

      output.puts("  #{id} #{status}     #{breakpoint} #{expr}")
    end

    #
    # Prints a header for the breakpoint list.
    #
    def print_breakpoints_header
      header = "#{' ' * (max_width - 1)}# Enabled At "

      output.puts <<-EOP.gsub(/ {8}/, '')

        #{text.bold(header)}
        #{text.bold('-' * header.size)}
      EOP
    end

    #
    # Max width of breakpoints id column
    #
    def max_width
      ::Byebug.breakpoints.last.id.to_s.length
    end
  end
end

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.processorObject (readonly)

Returns the value of attribute processor.



6
7
8
# File 'lib/pry-byebug/pry_ext.rb', line 6

def processor
  @processor
end

Class Method Details

.start_with_pry_byebug(target = TOPLEVEL_BINDING, options = {}) ⇒ Object Also known as: start



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/pry-byebug/pry_ext.rb', line 8

def start_with_pry_byebug(target = TOPLEVEL_BINDING, options = {})
  @processor ||= Byebug::PryProcessor.new

  if target.is_a?(Binding) && PryByebug.file_context?(target)
    # Wrap processor around the usual Pry.start to catch navigation commands
    @processor.start
  else
    # No need for the tracer unless we have a file context to step through
    start_without_pry_byebug(target, options)
  end
end