require 'pry' require 'pry-byebug/breakpoints' module PryByebug Commands = Pry::CommandSet.new do create_command 'step' do description 'Step execution into the next line or method.' banner <<-BANNER Usage: step [TIMES] Aliases: s 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 check_file_context breakout_navigation :step, args.first end end alias_command 's', 'step' create_command 'next' do description 'Execute the next line within the current stack frame.' banner <<-BANNER Usage: next [LINES] Aliases: n 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 check_file_context breakout_navigation :next, args.first end end alias_command 'n', 'next' create_command 'finish' do description 'Execute until current stack frame returns.' banner <<-BANNER Usage: finish Aliases: f BANNER def process check_file_context breakout_navigation :finish end end alias_command 'f', 'finish' create_command 'continue' do description 'Continue program execution and end the Pry session.' banner <<-BANNER Usage: continue Aliases: c BANNER def process check_file_context run 'exit-all' end end alias_command 'c', 'continue' create_command 'break' do description 'Set or edit a breakpoint.' banner <<-BANNER Usage: break [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. (Same as `breakpoints`) break --show 2 Show details about breakpoint #2. BANNER def options(opt) opt.on :c, :condition, 'Change the condition of a breakpoint.', :argument => true, :as => Integer opt.on :s, :show, 'Show breakpoint details and source.', :argument => true, :as => Integer opt.on :D, :delete, 'Delete a breakpoint.', :argument => true, :as => Integer opt.on :d, :disable, 'Disable a breakpoint.', :argument => true, :as => Integer opt.on :e, :enable, 'Enable a disabled breakpoint.', :argument => true, :as => Integer opt.on :'disable-all', 'Disable all breakpoints.' opt.on :'delete-all', 'Delete all breakpoints.' method_options(opt) end def process Pry.processor.pry = _pry_ { :delete => :delete, :disable => :disable, :enable => :enable, :'disable-all' => :disable_all, :'delete-all' => :clear }.each do |action, method| if opts.present?(action) Breakpoints.__send__ method, *(method == action ? [opts[action]] : []) return run 'breakpoints' end end if opts.present?(:condition) Breakpoints.change(opts[:condition], args.empty? ? nil : args.join(' ')) run 'breakpoints' elsif opts.present?(:show) print_full_breakpoint Breakpoints.find_by_id(opts[:show]) elsif args.empty? run 'breakpoints' else new_breakpoint end end def new_breakpoint place = args.shift condition = args.join(' ') if 'if' == args.shift bp = case place when /^(\d+)$/ # Line number only line = $1 unless PryByebug.check_file_context(target) raise ArgumentError, 'Line number declaration valid only in a file context.' end Breakpoints.add_file(target.eval('__FILE__'), line.to_i, condition) when /^(.+):(\d+)$/ # File and line number Breakpoints.add_file($1, $2.to_i, condition) when /^(.*)[.#].+$/ # Method or class name if $1.strip.empty? unless PryByebug.check_file_context(target) raise ArgumentError, 'Method name declaration valid only in a file context.' end place = target.eval('self.class.to_s') + place end Breakpoints.add_method(place,condition) else raise 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 if Breakpoints.count > 0 if opts.verbose? # Long-form with source output Breakpoints.each { |b| print_full_breakpoint(b) } else # Simple table output max_width = [Math.log10(Breakpoints.count).ceil, 1].max header = "#{' ' * (max_width - 1)}# Enabled At " output.puts output.puts text.bold(header) output.puts text.bold('-' * header.size) Breakpoints.each do |breakpoint| output.printf "%#{max_width}d ", breakpoint.id output.print breakpoint.enabled? ? 'Yes ' : 'No ' output.print breakpoint.to_s output.print " (if #{breakpoint.expr})" if breakpoint.expr output.puts end output.puts end else output.puts text.bold('No breakpoints defined.') end end end alias_command 'breaks', 'breakpoints' helpers do def breakout_navigation(action, times = nil) _pry_.binding_stack.clear # Clear the binding stack. throw :breakout_nav, { # Break out of the REPL loop and signal tracer :action => action, :times => times, :pry => _pry_ } end # Ensures that a command is executed in a local file context. def check_file_context unless PryByebug.check_file_context(target) raise Pry::CommandError, 'Cannot find local context. Did you use `binding.pry`?' end end # Print out full information about a breakpoint including surrounding code # at that point. def print_full_breakpoint(breakpoint) line = breakpoint.pos output.print text.bold("Breakpoint #{breakpoint.id}: ") output.print "#{breakpoint.to_s} " output.print breakpoint.enabled? ? '(Enabled)' : '(Disabled)' output.puts ' :' if (expr = breakpoint.expr) output.puts "#{text.bold('Condition:')} #{expr}" end output.puts output.puts breakpoint.source_code.with_line_numbers.to_s output.puts end end end end Pry.commands.import PryByebug::Commands