How to create subcommands for Ruby programs? - ruby

I am writing a Ruby CLI (Command Line Interface) program and I would like to be able to call subcommands similar to what rails does when you call rails generate ... or rails server etc. Can anyone point me in the right direction on how to do this?

You just need to get the command line arguments and work with those. They are stored in the global array ARGV:
ARGV.each do|a|
puts "Argv: #{a}"
end
That prints out the arguments sent to the ruby script

The standard library's OptionParser class exists specifically for handling command-line arguments like this. Here's a tutorial. It should simplify your work considerably.

Related

how to build wrapper script

Sort of an odd question, but: how would one go about creating a wrapper shell script that can be used in the #! line in other scripts.
wrap.sh
#!/bin/bash
set -e
echo "wrapper!"
exec ruby "$#"
test.rb
#!/usr/bin/env wrap.sh
puts RUBY_VERSION
puts "the ducks come from the trucks"
wrap.sh is in the path, and test.rb is marked as executable.
Now I do:
./test.rb
wrapper!
ruby: no Ruby script found in input (LoadError)
The goal is to execute the ruby script via the wrapper (The ruby version can be either local or comes from a traveling ruby install that is shipped along with the app).
As far as I can tell ruby is invoked, it's just unhappy with the #! in the test.rb and refuses to run the script. I cannot remove the #! because that's how the script is executed in the first place.
Any workarounds for this?
So, I cannot use rbenv/rvm/etc. There is more logic in the wrapper than this, but this is the gist of it.
Looks to me like the arguments are not being passed to Ruby in "$#". I don't think the bang-hash line is the problem.
I don't see anything in your script which actually passes the contents of test.rb to wrapper.sh, which is the bigger issue.
Perhaps the real problem can be solved by some other means? For example, is the problem you're trying to solve to run arbitrary commands prior to the invocation of any Ruby script from the command line? Perhaps it can be approached that way...
It looks like Ruby just checks that the hash-bang line contains "ruby": https://github.com/ruby/ruby/blob/v2_2_2/ruby.c#L1580 So basically having ruby somewhere in the #! line is all it takes.

Ruby .gets doesn't work

I'm trying to get simple user input in Ruby, but I can't get it working. I'm using the gets method, but the program never stops to ask me for input. I'm using Sublime Text 2 as my text editor, and I run the program in it, too (if this makes a difference).
Here's my code:
puts "What is your name?"
name = gets
puts "Hello " + name + ". How are you?"
And here's the error (and output) given to me:
C:/Users/Sten Sootla/Desktop/Ruby workspace/exercise.rb:3:in `+': can't convert nil into String (TypeError)
from C:/Users/Sten Sootla/Desktop/Ruby workspace/exercise.rb:3:in `'
What is your name?
[Finished in 0.1s with exit code 1]
Why doesn't the program stop to ask me for input?
Try using $stdin.gets instead of just a plain gets, this will force the input to come from stdin
Here's how I understand it. gets and puts are instance methods of IO, and the default IOs are $stdout and $stdin.
Calls to gets/puts will only be effective if the translator is capable of handling stdout/in e.g. IRB
If you run a ruby file from bash it works too.
io_test.rb
puts gets
in bash
ruby io_test.rb
Then it will "put" into stdout whatever it "gets" from stdin.
If you want to run code within ST2, check out the SublimeREPL plugin, available through Package Control. While you can use IRB, its main Ruby interface is through pry, which is a lot more powerful. You can use it as a classic REPL (think Clojure or LISP), and you can also transfer your code from one tab into the running REPL in another tab by selection, line range, or block.
Interestingly, your code above works in IRB for me, but not pry for some reason - it's reading my $EDITOR environment variable, which is set to subl -w but failing with Errno::ENOENT: No such file or directory - subl -w. Strange...
At any rate, I highly highly recommend SublimeREPL, as it's a really powerful tool, and is self-contained within ST2, so you don't have to keep flipping back and forth to your terminal, saving and reloading your programs.

What's the point of ARGV in Ruby?

What's the point of ARGV in Ruby?
first, second, third = ARGV
puts "The script is called: #{$0}"
puts "Your first variable is: #{first}"
puts "Your second variable is: #{second}"
puts "Your third variable is: #{third}"
What's the point of this when to run the file I need to do:
ruby ex1.rb
and to put in the first, second and third variables I need to type in
ruby ex1.rb blah blah blah
How does this benefit at all the person running the program? They can't do it anyway since I'd assume it be an executable:
user = ARGV.first
prompt = '> '
puts "Hi #{user}, I'm the #{$0} script."
puts "I'd like to ask you a few questions."
puts "Do you like me #{user}?"
print prompt
likes = STDIN.gets.chomp()
puts "Where do you live #{user}?"
print prompt
lives = STDIN.gets.chomp()
puts "What kind of computer do you have?"
print prompt
computer = STDIN.gets.chomp()
puts <<MESSAGE
Alright, so you said #{likes} about liking me.
You live in #{lives}. Not sure where that is.
And you have a #{computer} computer. Nice.
MESSAGE
Can someone please explain this to me?
What's the point of ARGV in Ruby?
ARGV "contains the arguments passed to your script, one per element."
What's the point of this when to run the file you need to do: ruby ex1.rb and to put in the first, second and third variables you need to type in ruby ex1.rb blah blah blah.
That is the exact point, to be able to specify arguments when running the file like:
ruby ex1.rb -a -b 3
How does this benefit at all the person running the program?
Same benefit any other command-line program gets from being able to do so: the user can specify upfront what they want, and it's easier to script than an interactive CLI.
They can't do it anyway since I'd assume it be an executable.
Yes, they can. You just gave an example of exactly how the user would run your program that way. Whether it's executable or not doesn't change anything.
ARGV has a long tradition and comes from the UNIX/POSIX world where most C programs must contain the following:
int main(int argc, char **argv)
{
return(0);
}
There argc represents the number of arguments supplied, and argv is a low-level pointer to the first of potentially a number of string pointers. The name argv has stuck around in various forms.
Command-line arguments are very popular with developers, though they're usually not used quite as you seem to think. Un-named arguments are much harder to deal with. That's why things like optparse exist to help deal with them.
Here's an example from the OptionParser documentation:
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!
p options
p ARGV
This allows you to define much more usable command-line arguments and then use them. For your example, I'd expect it to work like this:
test_script --live "New York" --computer "Lenovo" --like
That makes it quite obvious what the arguments are because they're named.
Consider some Ruby command line utilities like rails, rake,gem, or bundle. While none of these are end-user applications (like web apps or GUI apps), they are still programs written in Ruby that users interact with. All of them take arguments:
$ rails new my_app
$ rake tests
$ gem install rails --no-rdoc --no-ri
$ bundle update
It is possible to use ARGV to implement these kinds of command line programs that accept arguments. While we'll often use the OptionParser standard library or some other tool for this purpose, ARGV is the low level collection those tools are built on top of.
So if you've ever run gem, rails, rake, or bundle, or any command line developer tool that is written in Ruby, you will have benefited from ARGV!
The point of ARGV is that it enables scripts to be able to run without human input, and also allows for the case where the input may vary from one iteration to the next.
For example you may be running three different scripts to get three values. These values could then be passed into this script and ARGV can be used to get the values of these options.
I used to run a script that asked me for an email and password. It was used to log into one of 100's of accounts. Like most Ruby scripts, I'd run it from the command line. This had to semblance to being an "executable", especially not one with an icon that you click.
I'd run the program and it would ask me for email and password then log me in using Watir.
puts "Email of Account"
account_email = gets.chomp
puts "Enter Password"
password = gets.chomp
# code that logs me in
However, I found that taking in those values via ARGV was much faster for my purposes.
account_email, password = ARGV
# code that logs me in
Then I could just type
$ ruby my_script.rb my.email#emailplace.com mypassword
and hit enter. Also I could just press the up arrow in the console to run it again and again or recent versions with different names and passwords to test my script quickly.
Taking it a step further, I made a shell script that could run this scripts, args and all in many combinations
ruby my_script.rb oneofmy#emails.com mypassword
ruby my_script.rb another#emails.com otherpassword
Finally, sometimes I needed to set dozens of settings per each time I ran a script, and then run that script many times. So I could also easily insert a dozens variables into a script from the command line
ruby my_script.rb option1 option2 option3 option4
I'm very new to using ARGV and hope to learn better ways to use it, but this is one feature I found very useful.

How can I make RSpec output to console when run as a command %x[rspec] from Ruby script?

I have a class with an instance method that runs RSpec using the %x[] notation:
class TestRunner
def run_rspec
# do stuff
%x[rspec spec -c -f documentation]
# do more stuff
end
end
When I do this:
> tr = TestRunner.new
> tr.run_rspec
The documentation (group and example names) does not appear in the console.
To contrast, when I run rspec straight from the command line I get this:
$ rspec spec -c -f documentation
a group name
an example
another example
...
I don't want to do this:
puts %x[rspec spec -c -f documentation
Because then the output all spits out in one huge clump at the very end. I want it to run in "real time," with each example showing up as each test is run.
Is there a way, with the setup I have, to get RSpec to announce what it's doing, as it's doing it (as it does when run normally from the command line)?
I've been advised that system() and the other shell methods can be dangerous to use, so I've opted to switch to the even-better approach of using RSpec itself:
RSpec::Core::Runner.run(['spec', '-c', '-f', 'documentation'])
rather than calling it via shell from my Ruby script.
Ruby offers several options for running programs from the command line. I was using %x[], the wrong choice for my use case.
Solution: Use system(), not %x[] -- rspec will write to STDOUT in real-time when I call it with system('rspec spec').
Some background in case it's helpful to anyone who stumbles upon this question:
Consider the differences between Ruby's command-line options:
%x[command] accumulates the result of command and returns it, in one chunk.
exec('command') will output command as command runs, but will replace whatever process called it -- i.e., if you use exec in your Ruby script, your Ruby script won't finish.
system('command') executes command in a subshell, and returns to the calling script.
This is why system was the choice for my script.

Run a Ruby library from the command-line

I've just learned the basics of Ruby after being very happy with Python for several years (I'm still using Python for some things), but I'd like to know if there's an idiom or hack to solve this particular problem.
I have a Ruby script which I'd like to be able to do require script_name with, but I'd also like to be able to run ruby script_name.rb from the terminal and have it run as a command line script. In Python this would be done by having the following structure at the bottom of the script:
if __name__ == '__main__':
# do something here
However, I can't seem to find an equivalent in Ruby. Is there a way of detecting whether or not the current script is being run from the command-line? Maybe some Kernel:: method or something? Ideally what I'd like is something like this at the bottom of the script:
if from_command_line?
# do something here
end
You want to use:
if __FILE__ == $0
# do stuff
end
__FILE__ is the source file name and $0 is the name of the script currently being executed.
You can find a similar functionality in ruby.
__FILE__ the current source file name.
$0 Contains the name of the script being executed. May be assignable.
source: Ruby Quick Ref
While
if __FILE__ == $0
Foo.run
end
is the common approach, I'm currently using
if File.identical?(__FILE__, $0)
Foo.run
end
because programs like ruby-prof can make $0 not equal __FILE__ even when you use --replace-progname.

Resources