Using IRB (i.e. calling 'debugger') with Thor? - ruby

I'm trying to get Thor to trigger an IRB prompt when 'debugger' is reached in the code (like Rails, etc). Although I can trigger debugger, how do I get IRB to start automatically when debugger is triggered?
Currently, I do the following in the .thor file:
require 'ruby-debug'
desc 'irb', 'Load IRB console for this app.'
def irb
puts 'Starting IRB...'
debugger
end
This results in the debugger being triggered, but IRB must be explicitly started by typing 'irb' at the prompt:
$ thor app
Starting IRB...
(rdb:1) irb
ruby-1.9.2-p180 :001 > puts 'hello'
hello
=> nil
ruby-1.9.2-p180 :002 > exit
(rdb:1) exit
Really quit? (y/n) y
How do I get IRB to trigger instantly so I don't need to type 'irb' and an extra 'exit'?
Thanks!

For those of you who might have the same question, I did locate two solutions (see http://bashdb.sourceforge.net/ruby-debug.html#Autoirb):
OPTION 1: Set 'autoirb' to on. (my preference)
This is nice because it appears to automatically include your local .irbrc. Downside is you still have to type 'exit' twice if you want to quit the execution of the app (once to exit irb, second to exit debugger).
require 'ruby-debug'
::Debugger.settings[:autoirb] = 1
OPTION 2: Set 'autoeval' to on.
This is nice because it auto-evalutes unknown commands in the debugger and only requires entering 'exit' once to quit, since you are technically still in the debugger console (and not in a nested irb session), and your statements are simply being auto-evaluated. The downside is that your .irbrc settings are ignored. Rails uses this method (more info at: http://www.catapult-creative.com/2009/08/12/make-ruby-debug-work-better/).
require 'ruby-debug'
::Debugger.settings[:autoeval] = 1

Related

IRB: exit a changed binding (`cb`) without exiting entire IRB session?

In an IRB session, I can use cb (change binding) to enter an object, but how do I get out? If I use exit it exits the entire IRB session.
$ irb
❯ o = Object.new
=> #<Object:0x007fc8a32258a8>
❯ cb(o)
=> #<Object:0x007fc8a32258a8>
❯ self
=> #<Object:0x007fc8a32258a8>
❯ exit
$
For that matter, how do I continue navigating objects once I've started? I've found I can access the cb method from whichever object I'm in by first extending IRB::ExtendCommandBundle, but is there a less tedious way?
Your best bet might be to use pry instead. It's a very powerful debugger that can also be used for most of the things irb is used for. I use it for inspecting my code and objects in rails apps.
https://github.com/pry/pry
Get it with gem install pry
Then you can do this:
$ pry
[1] pry(main)> cd ""
[2] pry(""):1> self
=> ""
[3] pry(""):1> exit
=> ""
[4] pry(main)> exit
$
Along with much, much more.

Why is the --simple-prompt command not working?

I'm just starting with Ruby, so please bear with me. The problem is that when I enter --simple-prompt into irb on OS 10.10.3 terminal, I don't get the simple prompt, I get an error message:
irb(main):001:0> --simple-prompt
NameError: undefined local variable or method `simple' for main:Object
from (irb):1
from /usr/local/bin/irb:11:in `<main>'
I'm assuming that the following problem is related to the above in that -v, -cw, etc. don't seem to work either. Thanks for any help!
You seem to be confused about passing flags to commands vs issuing statements in a REPL.
To start irb with the --simple-prompt option enabled, pass it in like so:
$ irb --simple-prompt
>>
Then you should be able to execute Ruby code.
>> puts "hello world!"
hello world!
=> nil
>>
The issue is that the --simple-prompt is a command line option to toggle the prompt.
You can see this by typing exit to get out of IRB and then typing
irb --help
Which gives you something like this:
Usage: irb.rb [options] [programfile] [arguments]
-f Suppress read of ~/.irbrc
-m Bc mode (load mathn, fraction or matrix are available)
-d Set $DEBUG to true (same as `ruby -d')
-r load-module Same as `ruby -r'
-I path Specify $LOAD_PATH directory
-U Same as `ruby -U`
-E enc Same as `ruby -E`
-w Same as `ruby -w`
-W[level=2] Same as `ruby -W`
--context-mode n Set n[0-3] to method to create Binding Object,
when new workspace was created
--echo Show result(default)
--noecho Don't show result
--inspect Use `inspect' for output (default except for bc mode)
--noinspect Don't use inspect for output
--readline Use Readline extension module
--noreadline Don't use Readline extension module
--prompt prompt-mode/--prompt-mode prompt-mode
Switch prompt mode. Pre-defined prompt modes are
`default', `simple', `xmp' and `inf-ruby'
--inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
Suppresses --readline.
--sample-book-mode/--simple-prompt
Simple prompt mode
--noprompt No prompt mode
--single-irb Share self with sub-irb.
--tracer Display trace for each execution of commands.
--back-trace-limit n
Display backtrace top n and tail n. The default
value is 16.
--irb_debug n Set internal debug level to n (not for popular use)
--verbose Show details
--noverbose Don't show details
-v, --version Print the version of irb
-h, --help Print help
-- Separate options of irb from the list of command-line args
The --sample-book-mode or --simple-prompt is used in a command like this:
irb --simple-prompt
You can also do:
irb --prompt simple
But you can not do these things inside of IRB.
There is a way to change the prompt inside IRB, though, by changing the #prompt variable.

Command line script that runs a method only when it is executed?

I am building a gem for command line use, and I am aware of the if __FILE__ == $0 method for determining whether the file being run is the current file (from Run code only if script called from the command line), however this doesn't work in my case. I have a module with an initialize function that I would like to run when the gem is called from the command line.
module MyApp
def self.initialize
# do command line stuff
end
def self.test
# run a rake test
end
end
MyApp::initialize
However, when running rake test it calls the initialize function which returns an error:
/Library/WebServer/Documents/myapp ❤ rake test
Options:
-v, --[no-]verbose Run verbosely
-h, --help Show this message
rake aborted!
Command failed with status (255): [ruby -I"lib" -I"/Users/bbriggs/.rvm/gems/ruby-2.0.0-p195/gems/rake-10.1.0/lib" "/Users/bbriggs/.rvm/gems/ruby-2.0.0-p195/gems/rake-10.1.0/lib/rake/rake_test_loader.rb" "test/test_myapp.rb" ]
/Users/bbriggs/.rvm/gems/ruby-2.0.0-p195/bin/ruby_noexec_wrapper:14:in `eval'
/Users/bbriggs/.rvm/gems/ruby-2.0.0-p195/bin/ruby_noexec_wrapper:14:in `<main>'
Tasks: TOP => test
(See full trace by running task with --trace)
I think this is because I am doing MyApp:initialize, because if I take that out of my code the rake test runs as expected, but the command line tool no longer works.
At the moment I am testing my app via bundle exec bin/myapp, and printing out the __FILE__ and $0 variables give me bin/myapp and path/to/lib/myapp.rb respectively, so I was wondering what the best way is to detect whether the module is being required or called directly. Am I even doing this right? I'm a bit of a Ruby newbie. :-)
Finally figured this out. Instead of running MyApp:initialize in lib/myapp.rb I put it in the bin/myapp file. This ensures that it is only run when the app is run from the command line and not when being tested via Rake or required by another script.

How do I configure ruby to enter the debugger on Ctrl-C (SIGINT)?

I'd like to enter the debugger upon typing ctrl-C (or sending a SIGINT). I have installed the debugger (I'm running Ruby 1.9.3) and verified that it works. I've added this to my setup files (this is for Padrino, but I assume it would be similar for Rails):
# file: config/boot.rb
Padrino.before_load do
trap("SIGINT") { debugger } if Padrino.env == :development
end
... but typing Ctrl-C does not invoke the debugger. In fact, if I replace debugger with puts "saw an interrupt!", typing Ctrl-C doesn't cause a print to happen either.
update
Following this suggestion from Mike Dunlavey, I tried explicitly calling catch Interrupt from within the debugger:
$ rdebug `which padrino` console
^Z^Z$HOME/usr/bin/padrino:9
require 'rubygems'
(rdb:1) catch Interrupt
Catch exception Interrupt.
(rdb:1) c
=> Loading development console (Padrino v.0.10.7)
=> Loading Application BlueDotAe
=> Loading Application Admin
irb(main):001:0> C-c C-c^C
irb(main):001:0>
No joy -- interrupt did not enter the debugger.
What am I missing?
If you want to trap SIGINT while running in the console, the short answer is: you cannot unless you monkey-patch IRB. Every Ruby app (whether padrino, or rails or whatnot) that uses the console will end up calling usr/lib/ruby/1.9.1/irb.rb, and in IRB.start, it does:
trap("SIGINT") do
irb.signal_handle
end
... just before entering the main loop. This will override any trap("SIGINT") you might have put in your startup code.
But if you want to trap SIGINT in a script file (for example, if you want to profile your code as described by Mike Dunlavey here), you can create a script file such as:
# File: profile_complex_operation.rb
trap("SIGINT") { debugger }
MyApp.complex_operation
and then invoke it as in:
$ ruby profile_complex_operation.rb
Now, when you hit ^C (or send SIGINT from another process), it will enter the debugger.
You may try to use GDB wrapper for Ruby (GitHub).
Install on Linux via:
sudo apt-get install gdb python-dev ncurses-dev ruby-rvm
gem install gdb.rb
Basic usage:
require 'gdb'
# create a new GDB::Ruby instance and attach it to
# pid 12345
gdb = GDB::Ruby.new(12345)
# print the (ruby) backtrace of the remote process
gdb.backtrace.each { |line| puts line }
# show the current local variables, and their values
p gdb.local_variables
# evaluate arbitrary ruby code in the remote process
p gdb.eval('%(pid #{$$})')
# show how many instances of each class exist in the
# remote process
p gdb.object_space
# raise an exception in the remote process
gdb.raise Exception, "go boom!"
# close the connection to the remote process
gdb.quit
Or to debug the hung process, attach it via:
rvmsudo gdb.rb PID
then:
# in gdb get a ruby stacktrace with file names and line numbers
# here I'm filtering by files that are actually in my app dir
(gdb) ruby eval caller.select{|l| l =~ /app\//}
Source: Using gdb to inspect a hung ruby process
Some alternatives:
rbtrace - like strace, but for ruby code (usage: rbtrace -p <PID> --firehose).
debug.rb script by tmm1 (author of gdb.rb) which can help to debug a process using strace/gdb.
See also:
Debugging Ruby Tools
Check why ruby script hangs

How would I modify this rake task to start a thin server with support for local debugging?

Question
I want to be able to debug my Sinatra website hosted by Thin on my local machine, and I want to be able to initiate this by using rake.
I'm not able to accept answers suggesting the use of different technologies (e.g. Windows, Rails, Java) or other servers (e.g. unicorn, passenger, puma); however, if what I'm asking for is impossible, then I will accept that answer.
What I've tried
My current Rakefile contains a task :start which starts the Thin server, but when it hits the breakpoint no output is displayed on my terminal. If I start Thin directly from the terminal, then I see the (rdb:1) prompt when it hits the breakpoint as expected. In either case, the Thin server is correctly running the site (confirmed by commenting out the breakpoint).
Gemfile
source :rubygems
gem 'sinatra'
gem 'thin'
gem 'debugger-pry'
Rakefile
task :start do
conf = File.expand_path('config.ru', File.dirname(__FILE__))
`thin -e development -R #{conf} --debug start`
end
config.ru
require File.expand_path('app', File.dirname(__FILE__))
run ModularExample::App.new
app.rb
require 'sinatra'
require 'debugger/pry'
module ModularExample
class App < Sinatra::Base
get '/' do
debugger
"Hello, world"
end
end
end
You're not outputting to STDOUT. The backticks execute the command and return the output as a string so you could do something like
puts `thin -e development -R #{conf} --debug start`
but you want to stream the processes output to STDOUT so you actually want to do:
task :start do
conf = File.expand_path('config.ru', File.dirname(__FILE__))
exec("thin -e development -R #{conf} --debug start")
end
Learn more about calling command line calls from this question.

Resources