1

I want to make a script invoked with this syntax:

script.rb [common-options] arg1 [arg1-options] [arg2 [arg2-options] …]

For example:

script.rb --verbose --dry-run a1 --outfile c1 a2 --reprocess --outfile c2 a3 a4 --outfile b42

Returning something like this to my code:

options = {verbose: true, :'dry-run' => true}
args = {
  'a1' => {outfile: 'c1'},
  'a2' => {outfile: 'c2', reprocess: true}, 
  'a3' => {},
  'a4' => {outfile: 'b42'},
}

I can't figure out from the OptionParser documentation whether it's possible at all.

Does anyone know a solution for this?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Envek
  • 4,426
  • 3
  • 34
  • 42
  • The point of code like OptionParser is that you don't NEED positional arguments. If you want positional, drop OptionParser and use `ARGV` which returns the command-line as passed to the program. Your desired example isn't positional though. It looks like you haven't played with the `Array` capability which lets you pass in multiple parameters to each option. – the Tin Man Mar 20 '17 at 16:37
  • Asking for recommendations for software is off-topic for Stack Overflow. You can ask for recommendations on [softwarerecs.se]. – the Tin Man Mar 20 '17 at 16:42

1 Answers1

1

Finally found solution in "Using ruby's OptionParser to parse sub-commands".

I need to use the poorly documented order method which will parse options only up to first positional argument. parse will parse the whole ARGV:

require 'optparse'

options = {}
filenames = []
file_options = Hash.new do |hash, key| hash[key] = {} end
filename = nil

file_parser = OptionParser.new do |o|
  o.banner = 'Options for every file (goes after every filename):'
  o.on '-o', '--outfile=FILE', 'A file to write result to' do |arg|
    file_options[filename][:outfile] = arg
  end
end

global_parser = OptionParser.new do |o|
  o.banner = [
    "Super duper file processor.\n",
    "Usage: #{$PROGRAM_NAME} [options] file1 [file1-options] [file2 [file2-options] …]",
  ].join("\n")
  o.separator ''
  o.separator 'Common options:'
  o.on '-v', '--verbose', 'Display result filenames' do |arg|
    options[:verbose] = arg
  end
  o.on '-h', '--help', 'Print this help and exit' do
    $stderr.puts opts
    exit
  end
  o.separator ''
  o.separator file_parser.help
end

begin
  argv = global_parser.order(ARGV)
  while (filename = argv.shift)
    filenames.push(filename)
    file_parser.order!(argv)
  end
rescue OptionParser::MissingArgument => e
  $stderr.puts e.message
  $stderr.puts global_parser
  exit 1
end

if filenames.empty?
  $stderr.puts global_parser
  exit 1
end

If I execute my script like this:

script.rb -v a -o a.out b c d -o d.out

I will get:

options      = {:verbose=>true}
file_options = {"a"=>{:outfile=>"a.out"}, "d"=>{:outfile=>"d.out"}}

And this help text will be generated:

Super duper file processor.

Usage: script.rb [options] file1 [file1-options] [file [file2-options]…]

Common options:
    -v, --verbose                    Display converted filenames
    -h, --help                       Print this help and exit

Options for every file (goes after every filename):
    -o, --outfile=FILE               A file to write result to
Community
  • 1
  • 1
Envek
  • 4,426
  • 3
  • 34
  • 42
  • 1
    I'd recommend using parenthesis to wrap your parameters to methods. Without them you stand a good chance of running into binding problems when you pass a block to a method and Ruby doesn't understand what you're trying to do. This code might work, but continuing that habit will run into the problem. – the Tin Man Mar 20 '17 at 16:39