Can I use Ruby's OptionParser to accept an arbitrary argument pattern? - ruby

Let's say that I have a simple Ruby app where I want the first argument (if any) to specify the environment: TEST, DEVELOPMENT or PRODUCTION (with DEVELOPMENT being the default if no argument is given). For instance,
ruby myapp.rb test
would run it in TEST mode. Also, shorthands should be accepted, so that for instance
ruby myapp.rb t
would run the app in TEST mode and
ruby myapp.rb dev
would run it in DEVELOPMENT mode.
I'd like to use OptionParser, but it behaves very weirdly. If myapp.rb is
require 'optparse'
environment = 'DEVELOPMENT'
opts = OptionParser.new
opts.on('test') { environment = 'TEST' }
opts.on('production') { environment = 'PRODUCTION' }
opts.parse!(ARGV)
then environment becomes PRODUCTION no matter what arguments I pass; for some reason, opts.on('production') always executes its block. (It doesn't if I use a flag-style string like '-production' instead.) And there's no way I can see to have OptionParser look for strings starting with 't' rather than the exact string 'test'.
Maybe OptionParser is the wrong tool for the job. Obviously it would be trivial to split up ARGV myself. I'm just wondering what's going on with this behavior. I'm on Ruby 1.9.2.

Provided you use the parse! method, any arguments handled by opts.on calls are stripped from the ARGV array destructively. This means that the original contents of the ARGV array will no longer contain those flags after the parse! method.
I recommend parsing the remaining set of arguments manually by comparing ARGV to an array containing 'test' and 'production'.
Check out the doc:
http://ruby-doc.org/stdlib-1.9.3/libdoc/optparse/rdoc/OptionParser.html#method-i-parse-21

I'd say you need to parse out arguments like that from ARGV before running OptionParser
e.g.
env = ARGV.select{|arg| arg =~ /dev/test/prod/i}.first
P.S. I'd recommend Trollop. I find it much simpler, and it's good about picking defaults.

Related

Workaround "flip-flop deprecated" warning in Ruby

I invoke Ruby from my shell script like this:
ruby -n -e "print if %r($fromre)...%r($tore)" "$#"
Since Ruby 2.6, I get the warning
warning: flip-flop is deprecated
So it seems that i have to be prepared that the wise high priests of Ruby have decided that this (IMO useful) construct will be completely gone one day.
What would be an easy work-around? Of course I can replace
print if %r($fromre)...%r($tore)
by
inside ||= %r($fromre)
if inside
print
inside = false if %r($tore)
end
but I wonder whether there is a more concise way to write this.

Ruby require modules, libraries

I'm new to ruby. I understand that, when I see a ruby script, it usually contains lines similar to this:
#!/usr/bin/env ruby
require 'rubyfunction1'
require 'rubyfunction2'
I understand that the require lines are basically (to put it in simple basic terms), calling other scripts. That is really all there is to it. These other scripts are functions.
Now, suppose, I put the content of the rubyfunction1 and rubyfunction2 scripts into two different variables. How do I require the content of a variable?
Or, suppose I want to be able to do something like this:
require '`/home/swenson/rubyfunction1.rb`'
I understand this is a roundabout way of requiring gems/ruby functions, but I'm curious to know if it is at all possible in this manner.
Basically, if I were to run the /home/swenson/rubyfunction1.rb script by itself on the command line, it will basically output to you the content of the script. It would be equivalent to doing "cat /home/swenson/rubyfunction1.rb".
I want to be able to do something like this:
require '`/home/swenson/rubyfunction1.rb`'
require '`/home/swenson/rubyfunction2.rb`'
or
specvar1 = `/home/swenson/rubyfunction1.rb`
specvar2 = `/home/swenson/rubyfunction2.rb`
require specvar1
require specvar2
Is this possible? Any suggestions I can apply to get it to work?
UPDATE:
So here's what I ended up doing.
Main Script called example.rb:
#!/usr/bin/env ruby
add = `./add.rb` # for my purposes, this will serve as require
subtract = `./subtract.rb` # for my purposes, this will serve as require
eval add
puts "I can add: #{add(3, 2)}"
eval subtract
puts "I can now subtract #{subtract(3, 2)}"
Content of add.rb:
#!/usr/bin/env ruby
puts <<-function
#!/usr/bin/env ruby
def add(a, b)
a + b
end
function
Content of subtract.rb:
#!/usr/bin/env ruby
puts <<-function
#!/usr/bin/env ruby
def subtract(a, b)
a - b
end
function
When run from the command line, I get no errors:
# ./example.rb
I can add: 5
I can now subtract 1
Basically, what I want done is precisely this. However, I know there's probably a optimized way of doing this (without having to directly require the relative file). So please, feel free to help me update or optimize this.
I understand that the require lines are basically (to put it in simple basic terms), calling other scripts. That is really all there is to it.
Yes. load, require, and require_relative simply run a Ruby file. That's it.
These other scripts are functions.
No. They are scripts. There is no such thing as a function in Ruby.
Now, suppose, I put the content of the rubyfunction1 and rubyfunction2 scripts into two different variables. How do I require the content of a variable?
You can't. require runs a file. It takes the name of a file (more precisely, a relative path) as an argument. Ruby code is not the name of a file.
Or, suppose i want to be able to do something like this:
require '`/home/swenson/rubyfunction1.rb`'
I understand this is a roundabout way of requiring gems/ruby functions, but im curious to know if it is at all possible in this manner.
This is possible. There's nothing special about this. It will simply run a file at the path `/home/swenson/rubyfunction1.rb`. That is a slightly unusual path, but there is nothing special about it. It's just a path like any other, with some funny characters in it.
so to iterate what im trying to do, i want to be able to do something like this:
require '`/home/swenson/rubyfunction1.rb`'
require '`/home/swenson/rubyfunction2.rb`'
or
specvar1 = `/home/swenson/rubyfunction1.rb`
specvar2 = `/home/swenson/rubyfunction2.rb`
require specvar1
require specvar2
Is this possible? Any suggestions I can apply to get it to work?
It's not quite clear what you want here. Those two code snippets are in no way equivalent, they do completely different things!
The first one passes the literal strings '`/home/swenson/rubyfunction1.rb`' and '`/home/swenson/rubyfunction2.rb`' as arguments to require. The second one executes two files named /home/swenson/rubyfunction1.rb and /home/swenson/rubyfunction2.rb using the default system shell (CMD.EXE on Windows, /bin/sh on POSIX), gets the standard output as String and passes those strings to require.
Note that in the first case, the backticks ` are part of the filename, whereas in the second case, they are Ruby syntax for calling the Kernel#` method.
So, I think I understand your question correctly, let's say we have 3 files
add.rb
#!/usr/bin/env ruby
def add(a, b)
a + b
end
subtract.rb
#!/usr/bin/env ruby
puts "def subtract(a, b)"
puts " a - b"
puts "end"
example.rb
require './add.rb'
subtract = `./subtract.rb`
puts "I can add: #{add(3, 2)}"
# can't do `subtract`, yet, as we haven't `eval`ed the code even though we've run executed the file
eval subtract
puts "I can now subtract #{subtract(3, 2)}"
And the output of running ruby example.rb on the command line is:
$ ruby example.rb
I can add: 5
I can now subtract 1
So, add.rb just defines a function add. When we require that file, it gets loaded in so we can use that function in our code with no problems.
But, subtract.rb doesn't define a function...it just outputs some code, so running it on the command line looks like:
$ ./subtract.rb
def subtract(a, b)
a - b
end
So now, in our third file example.rb, we require the add.rb and then we can start using add in our code as is, but then we want to execute the subtract.rb (using back ticks here) and capture the output of it. At this point, we can't subtract 2 numbers, because we haven't done anything with the output. Then we use eval to evaluate the output of the subtract method, which will define a method for us, then we can subtract the 2 numbers without a problem.
Note that eval is generally frowned upon because it allows arbitrary code to be executed. Never eval untrusted code unless you know how to tame it. In this case, as #JörgWMittag has pointed out in the comments, this code should be trusted, otherwise you just executed an un-trusted file to get this code. Be careful with user input, though, as that's not trusted.

Ruby example with exclamation mark

I am writing a script with different options in ruby, and I can't understand how the OptionParser could help me.
In particular, there is an example in the documentation: https://docs.ruby-lang.org/en/2.1.0/OptionParser.html
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
I can understand the exclamation mark on the "end.parse" line (but I expected a parameter after that), but I find the following 2 lines confusing, p hasn't been declared, and I can't understand if it's part of the example source.
And how do I use the '-v' option? Do I simply check if options[:v] is nil or true?
Last thing, what happens to the other options? Does the OptionParser only parse switches? What if I had other parameters after the '-v'? Like myscript -v duck ketchup banana?
To answer your questions:
I can understand the exclamation mark on the "end.parse" line (but I expected a parameter after that)
The documentation states that parse! takes an optional argv parameter. If it is not supplied, it defaults to default_argv, which I imagine is the string containing the arguments passed to this script in the command line.
p hasn't been declared, and I can't understand if it's part of the example source
p is defined in Kernel, so it is (almost) always available in Ruby. p obj is equivalent to puts obj.inspect.
In this context, p is just used to illustrate that after parsing the arguments, the options hash contains all the flags/options you defined in the OptionParser block.
And how do I use the '-v' option? Do I simply check if options[:v] is nil or true?
Yes, but that would actually be options[:verbose].
Last thing, what happens to the other options? Does the OptionParser only parse switches? What if I had other parameters after the '-v'? Like myscript -v duck ketchup banana?
You will have to make multiple calls to opts.on to match all the other switches/arguments you are interested in. Look at the documentation here for explanations on how to do that.

How to get Aruba to expand wildcards

I'm writing a simple command line gem.
The library that does the actual work was developed with rspec and so far that works.
I'm trying to test the command line portion with Aruba/Cucumber, but I've come across some strange behaviour.
Just to test this, I've got a the binary file to puts ARGV, and I've got test files in tmp/aruba
When I run bundle exec gem_name tmp/aruba/*.* I am presented with the list of shell expanded file names.
Now my features file has:
Given files to work on # I set up files in tmp/aruba in this step
When I run `gem_name *.*` # standard step
Then the output should contain "Wibble"
The last step is obviously going to fail, but it shows me a diff between what it expects and the actual output. Rather than seeing a list of shell expanded filenames, all I get is "*.*"
So I'm left in the position of having an app that actually works as expected, but I can't get the tests to pass. I could take the "." and generate the list of files from there, but then I'm writing extra production code just to get the app to work under test - which I don't think is the correct way to go about it. And all because shell expansion isn't happening.
If you look at my profile, you'll see that Ruby isn't my main bag, feel free to point me at any resources that I may have missed about this, but is this just me missing something, or expected behaviour that somebody knows how to work around?
After a little digging in the Aruba source I figured out that the When I run step ends up in a code block like this:
def run!(&block)
#process = ChildProcess.build(*shellwords(#cmd))
...
begin
#process.start
...
Further digging into ChildProcess ends up here:
def launch_process
...
begin
exec(*#args)
...
And therein lies the problem. exec does not do shell expansion when the argument list is split into multiple array elements:
If exec is given a single argument, that argument is
taken as a line that is subject to shell expansion before being
executed. If multiple arguments are given, the second and
subsequent arguments are passed as parameters to command with no
shell expansion.
However playing with shellwords a bit we find:
Shellwords.shellwords('gem_name *.*')
=> ["gem_name", "*.*"] # No good
Shellwords.shellwords('"gem_name *.*"')
=> ["gem_name *.*"] # Aha!
Therefore the solution might be as simple as:
When I run `"gem_name *.*"`
If that doesn't work then you are pretty much out of luck. I would suggest you expand the file names manually since you're not really testing shell expansion here - we know that works: you are testing multiple arguments.
Therefore you should instead do:
When I run `gem_name your_file1 your_file2 your_file3`

Any way to automagically `puts` the last expression in a Ruby script?

I'm working on implementing Project Euler solutions as semantic Ruby one-liners. It would be extremely useful if I could coerce Ruby to automatically puts the value of the last expression. Is there a way to do this? For example:
#!/usr/bin/env ruby -Ilib -rrubygems -reuler
1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
I realize I can simply puts the line, but for other reasons (I plan to eval the file in tests, to compare against the expected output) I would like to avoid an explicit puts. Also, it allots me an extra four characters for the solution. :)
Is there anything I can do?
You might try running it under irb instead of directly under a Ruby interpreter.
It seems like the options -f --noprompt --noverbose might be suitable (.
#!/usr/bin/env irb -f --noprompt --noverbose -Ilib -rrubygems -reuler
'put your one-liner here'
The options have these meanings:
-f: do not use .irbrc (or IRBRC)
--noverbose: do not display the source lines
--noprompt: do not prefix the output (e.g. with =>)
result = calculate_result
puts result if File.exist?(__FILE__)
result of eval is last executed operation just like any other code block in ruby
is doing
puts eval(file_contents)
an option for you?
EDIT
you can make use of eval's second parameter which is variables binding
try the following:
do_not_puts = true
eval(file_contents, binding)
and in the file:
....
result = final_result
if defined?(do_not_puts)
result
else
puts(result)
end
Is it an option to change the way you run scripts?
script.rb:
$_= 1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
invoke with
echo nil.txt | /usr/bin/env/ruby -Ilib -rrubygems -reuler -p script.rb, where nil.txt is a file with a single newline.

Resources