OptionParser, requiring subparameters - ruby

I'm trying to figure out how to define subparameters for one of my parameters. This is what I have that is NOT working:
require 'optparse'
options = {}
OptionParser.new do |parser|
parser.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script") do |lib|
parser.make_switch(["-p"], '--pop THING') do |o|
puts "You required #{o}!"
end
end
parser.on("-f", '--file FILE', 'File to be processed') do |file|
puts "This is the file: #{file}"
end
end.parse!
I'd like to do:
ruby myapp -r Library -p thing #<--required params
or
ruby myapp -f

Related

How to default to information if none is given using optparse

I have a program that creates emails, what I want to do is when the -t flag is given and no argument is given with the flag, default to something, instead it outputs the usual: <main>': missing argument: -t (OptionParser::MissingArgument)
So my question being, if I have this flag:
require 'optparse'
OPTIONS = {}
OptionParser.new do |opts|
opts.on('-t INPUT', '--type INPUT', 'Specify who to say hello to'){ |o| OPTIONS[:type] = o }
end.parse!
def say_hello
puts "Hello #{OPTIONS[:type]}"
end
case
when OPTIONS[:type]
say_hello
else
puts "Hello World"
end
and I run this flag without the required argument INPUThow do I get the program to out put the Hello World instead of the: <main>': missing argument: -t (OptionParser::MissingArgument)?
Examples:
C:\Users\bin\ruby\test_folder>ruby opt.rb -t hello
Hello hello
C:\Users\bin\ruby\test_folder>ruby opt.rb -t
opt.rb:7:in `<main>': missing argument: -t (OptionParser::MissingArgument)
C:\Users\bin\ruby\test_folder>
I figured out that by adding brackets around the INPUT I can provide the option to provide input examples:
require 'optparse'
OPTIONS = {}
OptionParser.new do |opts|
opts.on('-t [INPUT]', '--type [INPUT]', 'Specify the type of email to be generated'){ |o| OPTIONS[:type] = o }
end.parse!
def say_hello
puts "Hello #{OPTIONS[:type]}"
end
case
when OPTIONS[:type]
say_hello
else
puts "Hello World"
end
Output:
C:\Users\bin\ruby\test_folder>ruby opt.rb -t
Hello World
C:\Users\bin\ruby\test_folder>ruby opt.rb -t hello
Hello hello
So if I do this:
require 'optparse'
OPTIONS = {}
OptionParser.new do |opts|
opts.on('-t [INPUT]', '--type [INPUT]', 'Specify the type of email to be generated'){ |o| OPTIONS[:type] = o }
end.parse!
def say_hello
puts "Hello #{OPTIONS[:type]}"
puts
puts OPTIONS[:type]
end
case
when OPTIONS[:type]
say_hello
else
puts "Hello World"
puts OPTIONS[:type] unless nil; puts "No value given"
end
I can output the information provided, or when there's no information provided I can output No value given:
C:\Users\bin\ruby\test_folder>ruby opt.rb -t hello
Hello hello
hello
C:\Users\bin\ruby\test_folder>ruby opt.rb -t
Hello World
No value given

OptionParser with subcommands

I'm trying to create a command line program with sub-commands using OptionParser. I'm following "ruby's OptionParser to get subcommands".
The problem is that it does not allow for a use case like this:
ruby main.rb --version
#=> main.rb in `<main>': undefined method `order!' for nil:NilClass (NoMethodError)
But it does allow for this:
ruby main.rb foo --options
ruby main.rb --options foo
ruby main.rb --options foo --options
How would I be properly handle command line arguments, in the case that no subcommand is given.
My example code is:
global = OptionParser.new do |opts|
opts.banner = "Usage: opt.rb [options] [subcommand [options]]"
opts.on("-v", "--version", "Print the version") do |v|
options[:version] = v
end
opts.separator ""
opts.separator subtext
end
The lines with the error:
global.order!
command = ARGV.shift
subcommands[command].order!
If global.order! uses all of ARGV, then command is nil. So... check for that.
global.order!
command = ARGV.shift
unless command
STDERR.puts "ERROR: no subcommand"
STDERR.puts global # prints usage
exit(-1)
end
subcommands[command].order!
Maybe this'll help:
require 'optparse'
VERSION = '1.0.0'
options = {}
OptionParser.new do |opt|
opt.on('-f', '--foo', 'Foo it') { |o| options[:foo] = o }
opt.on_tail('-v', '--version') do
puts VERSION
exit
end
end.parse!
puts options
Saving it as "test.rb" and running it with ruby test.rb returns:
{}
Running it with ruby test.rb -f or --foo returns:
{:foo=>true}
Running it with ruby test.rb -v or --version returns:
1.0.0
For more fun, running ruby test.rb -h or --help returns:
Usage: test [options]
-f, --foo Foo it
even though I didn't define -h or --help.
If I wanted the -v and --version flags to appear in the list then I'd change them from a on_tail method to a normal on method:
require 'optparse'
VERSION = '1.0.0'
options = {}
OptionParser.new do |opt|
opt.on('-f', '--foo', 'Foo it') { |o| options[:foo] = o }
opt.on('-v', '--version', 'Returns the version') do
puts VERSION
exit
end
end.parse!
puts options
which would return:
Usage: test [options]
-f, --foo Foo it
-v, --version Returns the version
I can add:
puts ARGV
to the end of the script and see that OptionParser is correctly handling flags and parameters:
>ruby test.rb bar --foo
{:foo=>true}
bar
>ruby test.rb --foo bar
{:foo=>true}
bar
See "Pass variables to Ruby script via command line" for more information.
There is no way your example code will handle your sample inputs using --options. No handler for --options is defined. Nor is subtext. Your code returns:
undefined local variable or method `subtext' for main:Object (NameError)
Stripping the block to:
global = OptionParser.new do |opts|
opts.on("-v", "--version", "Print the version") do |v|
options[:version] = v
end
end
and running again returns:
invalid option: --options (OptionParser::InvalidOption)
So, again, your example doesn't match the results you say you're getting.

Command Aliasing in Thor

Is it possible to create aliases for commands in Thor?
Much like command aliasing in Commander. https://github.com/tj/commander#command-aliasing
I am able to find aliases for options, but not for the command itself.
Using the example from Thor,
#!/usr/bin/env ruby
require 'thor'
# cli.rb
class MyCLI < Thor
desc "hello NAME", "say hello to NAME"
def hello(name)
puts "Hello #{name}"
end
end
MyCLI.start(ARGV)
I should be able to run
$ ./cli.rb hello John
Hello John
I would like to alias the command "hello" to "hi" as well.
You can use map for this:
http://www.rubydoc.info/github/wycats/thor/master/Thor#map-class_method
#!/usr/bin/env ruby
require 'thor'
# cli.rb
class MyCLI < Thor
desc "hello NAME", "say hello to NAME"
def hello(name)
puts "Hello #{name}"
end
map hi: :hello
end
MyCLI.start(ARGV)
Use method_option for aliases.
#!/usr/bin/env ruby
require 'thor'
# cli.rb
class MyCLI < Thor
desc "hello NAME", "say hello to NAME"
method_option :hello , :aliases => "-hello" , :desc => "Hello Command"
def hello(name)
puts "Hello #{name}"
end
end
MyCLI.start(ARGV)

How to fail erb + json rendering when some keys are missing from the json file

We use this ruby script to render our config files from erb template + json config file. It is basically from an example from http://ruby-doc.org/stdlib-2.1.1/libdoc/erb/rdoc/ERB.html
Recently we accidentally removed something from the json, while we still referenced it in the erb file. The script works, and simply replaces the placeholder with empty string. Is there a way to make it fail?
In the below example
$ render.rb conf.json template2.erb out2 ; echo $?
will fail with 1, because a full block is missing, however if only some key-value pairs are missing, it doesn't warn or fail:
$ render.rb conf.json template1.erb out1 ; echo $?
will exit with 0
conf.json:
{
"block1" : {
"param1": "p1"
}
}
template1.erb:
foo=<%= #config['block1']['param1'] %>:<%= #config['block1']['missing_param'] %>
template2.erb:
foo=<%= #config['block1']['param1'] %>:<%= #config['block1']['missing_param'] %>/<%= #config['missing_block']['anything'] %>
render.rb:
#!/usr/bin/env ruby
# usage: conf_gen.rb config_file.json erb_template output_file
require 'erb'
require 'json'
require 'pathname'
class MyRenderer
def initialize(config_path)
#config = JSON.parse(File.read(config_path))
end
end
if ARGV.size != 3
puts "Hey, missing arguments.\nUsage: conf_gen.rb <json config file> <erb template> <output file>"
exit
end
config_path = ARGV.shift
template_filename = ARGV.shift
output_file = ARGV.shift
erb = ERB.new(File.read(template_filename))
erb.filename = template_filename
ConfigRenderer = erb.def_class(MyRenderer, 'render()')
output = File.new(output_file, 'w')
output.puts(ConfigRenderer.new(config_path).render())
output.close
puts "Finished Successfully"

How to use getoptlong class in ruby?

I need help using getoptlong class in Ruby. I need to execute command prog_name.ruby -u -i -s filename. So far I can only execute it with prog_name.ruby -u filename -i filename -s filename.
This is my getoptlong code:
require 'getoptlong'
class CommonLog
parser = GetoptLong.new
parser.set_options(["-h", "--help", GetoptLong::NO_ARGUMENT],
["-u", "--url", GetoptLong::NO_ARGUMENT],
["-i", "--ip", GetoptLong::NO_ARGUMENT],
["-s", "--stat", GetoptLong::NO_ARGUMENT])
begin
begin
opt,arg = parser.get_option
break if not opt
case opt
when "-h" || "--help"
puts "Usage: -u filename"
puts "Usage: -i filename"
puts "Usage: -s filename"
exit
when "-u" || "--url"
log = CommonLog.new(ARGV[0])
log.urlReport
when "-i" || "--ip"
log = CommonLog.new(ARGV[0])
log.ipReport
when "-s" || "--stat"
log = CommonLog.new(ARGV[0])
log.statReport
end
rescue => err
puts "#{err.class()}: #{err.message}"
puts "Usage: -h -u -i -s filename"
exit
end
end while 1
if ARGV[0] == nil || ARGV.size != 1
puts "invalid! option and filename required"
puts "usage: -h -u -i -s filename"
end
I'm going to answer by recommending looking at the new-ish "slop" gem. It's a wrapper around getoptlong.
You can use gem install slop if you're using RVM, or sudo gem install slop otherwise.
GetOptLong is very powerful but, though I've used it several times, I still have to go review the docs each time.
If you want a bit more power, with an "easier to use interface than GetOptLong", look into Ruby's OptionParser. You'll need to work out the logic better, but this is a quick pass converting your code. I had to stub out a class for the CommonLog gem because I don't use it. The important stuff follows the line pulling log from ARGV:
require 'optparse'
class CommonLog
def initialize(*args); end
def urlReport(); puts "running urlReport()"; end
def ipReport(); puts "running ipReport()"; end
def statReport(arg); puts "running statReport(#{arg})"; end
end
log = CommonLog.new(ARGV[0])
OptionParser.new { |opts|
opts.banner = "Usage: #{File.basename($0)} -u -i -s filename"
opts.on( '-u', '--[no-]url', 'some short text describing URL') do
log.urlReport()
end
opts.on('-i', '--[no-]ip', 'some short text describing IP') do
log.ipReport()
end
opts.on('-s', '--stat FILENAME', 'some short text describing STAT') do |arg|
log.statReport(arg)
end
}.parse!
Also, as a quick critique, you are not writing idiomatic Ruby code:
when statements can be written: when "-h", "--help"
if ARGV[0] == nil || ARGV.size != 1 is convoluted. Study up on how ARGV and arrays work. Normally, for ARGV[0] to be nil there will be no more arguments, so ARGV.empty? would probably suffice.
you have several errors in the sample program
#each and #get only return the first string in the option and convert the others to it.
You should put that check for arguments before the options processing
You probably don't want this in with your logging class
require 'getoptlong'
# don't pollute CommonLog with this
include CommonLog
# if this is the startup module
if __FILE__ == $0 then
# Check to ensure there are arguments
if ARGV.size < 1
puts "invalid! option and filename required"
puts "usage: -h -u -i -s filename"
end
# set up parser and get the options
parser_opts=GetoptLong.new(
["--help", "-h", GetoptLong::NO_ARGUMENT],
["--url", "-u", GetoptLong::NO_ARGUMENT],
["--ip", "-i", "--ip", GetoptLong::NO_ARGUMENT],
["--stat", "-s", GetoptLong::NO_ARGUMENT]
)
parser_opts.each do |opt,arg|
begin # this is for the exception processing
case opt
when "--help" #only the first option is returned read ruby doc on #each
puts "Usage: -u filename"
puts "Usage: -i filename"
puts "Usage: -s filename"
exit
when "--url" #only the first option is returned
log = CommonLog.new(ARGV[0])
log.urlReport
when "--ip" #only the first option is returned
log = CommonLog.new(ARGV[0])
log.ipReport
when "--stat" #only the first option is returned
log = CommonLog.new(ARGV[0])
log.statReport
else # this should not be used
puts "unexpected option %s"%opt
puts "Usage: -h -u -i -s filename"
end
rescue Exception => err #rescuing an unexpected Exception
puts "#{err.class()}: #{err.message}"
puts "Usage: -h -u -i -s filename"
Kernel.exit
end
end
end

Resources