Robert Klemme
10/25/2007 1:15:00 PM
2007/10/25, Alexy Khrabrov <deliverable@gmail.com>:
> Greetings -- I'm writing a reusable options module for my scripts.
> All scripts in the same project share most of the common command-line
> options. I'm relying on the optparse module to do it. The desired
> functionality is a report on all options being used, structured so
> that it's also defined incrementally next to each options, and
> possibility to disable some of the reusable options and add some new
> ones.
>
> This is problem with optparse: how to report all
> options in verbose mode, including the defaults for those not set --
> but the opt.on(...) { block } is triggered only by the actual option.
> Moreover, it's evaluated in the parse() call context, which makes
> using variables in those blocks problematic. Consider the following
> toy program I wrote to develop a better, reusable option processing.
>
> #!/usr/bin/env ruby
> #
> # Created by Alexy Khrabrov on 2007-10-24.
> # Copyright (c) 2007. All rights reserved.
>
> require 'optparse'
>
> DIR_CODES = %w[central subdir shotgun same cwd]
> DIR_CODE_ALIASES = { :gun => :shotgun, :working => :cwd }
>
> class Options
>
> def initialize(no=nil,xdefs=nil,&block)
> @o = { # default values
> :file_ext => ".txt",
> :dir_kind => :shotgun,
> :dirout => "kuku"
> }
> # inject new defaults, if any -- hash add, h1+h2?
> xdefs.each { |k,v| @o[k] = v } if xdefs
>
> opt = nil
> report = []
> begin
> files = OptionParser.new do |opt|
> opt.banner = "Preved!"
>
> unless no[o=:verbose]
> help = "run verbosely"
> short = "-v"
> # trying to replace late-binding below with
> ref_o = lambda { o } # but ref_o would be different in
> another opt block!
> opt.on(short, "--[no-]#{o}", help) { |val| @o[:verbose] =
> val }
> report << [o,short,help]
> end
>
> unless no[o=:dirout]
> help="directory location for the output files"
> short = "-d"
> opt.on(short, "--#{o} DIR",
> DIR_CODES.map { |x| x.to_sym }, help) { |val| @o[:dirout]
> = val }
> report << [o,short,help]
> end
>
> code_list = (DIR_CODE_ALIASES.keys + DIR_CODES).map{|x|
> x.to_sym}.join(',')
> unless no[o=:moredir]
> help="more dirs with aliases, #{code_list}"
> short = "-m"
> opt.on(short, "--#{o} DIR",
> DIR_CODES.map{|x|x.to_sym}, DIR_CODE_ALIASES, help) { |
> val| @o[o] = val }
> report << [o,short,help]
> end
>
> yield opt, @o, report if block
>
> end.parse(*ARGV) # parse it! remainder -> files
>
> report.each do |o,short,help|
> val = @o[o] || "unset"
> STDERR.printf "--%s\t%s,\t%s: %s\n", o, short, help, val
> end
>
> rescue OptionParser::InvalidOption => o
> puts "#{o}"
> puts opt.help
> exit(1)
> rescue OptionParser::AmbiguousArgument => o
> puts "#{o}"
> exit(1)
> end
> end # initialize
>
> def method_missing (m, *a, &block)
> @o[m.to_sym]
> end
> end # Options
>
> def main
> no = {:moredir => 1}
> xdefaults = { :extra => "hurry to see!" }
> puts
> o = Options.new(no, xdefaults) do |opt,hash,report|
> name = :extra
> help = "extra option, added in the new script"
> short = "-x"
> opt.on(short,"--#{name} X",help) { |val| hash[:extra] = val }
> report << [name,short,help]
> end
> puts
> puts "verbose => #{o.verbose}"
> puts "file_ext => #{o.file_ext}"
> puts "dir_kind => #{o.dir_kind}"
> puts "dir_more => #{o.dir_more}"
> puts "extra => #{o.extra}"
> end
>
> if $0 == __FILE__
> main
> end
>
> The idea was that each option has a name, by which it is referred to
> in an internal hash @o. The hash parameter called `no' contains those
> options from the preexistng Options instance, which we want to disable.
> Additional options are defined in the block given to Options.new;
> their defaults are passed as a hash to that constructor. Also one can
> override predefined defaults through the same extra defaults hash.
> So far so good.
>
> But notice the assignment in disablement check,
>
> unless no[o=:verbose]
>
> -- I planned on using o uniformly later, perhaps abstracting more out
> of my newly uniform opt.on sections. Yet I was getting an obscure
> error there when the block looked like
>
> { |val| @o[o] = val }
>
> -- o turned out to be the name of the option from the *last* opt.on
> block! All these blocks are evaluated later, in parse(). So I had to
> write
>
> { |val| @o[:verbose] = val }
>
> explicitly. Played with lambda and Proc, yet they, too, are only
> evaluated later. We want to get at *that very o*, but we can't even
> stick a binding into the block, as it'll be only evaluated later!
> Notice we can stick o into the parameter strings given to each
> opt.on() as they are evaluated right away. I was wondering about
> getting the name back from an internal value for long, yet it requires
> getting into optparse internals and is not a clean solution to
> overcoming the dynamic binding in this case.
>
> Is there a way to "constantize" o in @o[o], when o==:verbose, to be
> equivalent to @o[:verbose] -- replacing variable reference by it
> literal value in a location in the program text?
>
> BTW, Python's optparse allows default definitions...
> Cheers,
> Alexy
Unfortunately I don't have time to go through all of this, but did you
consider to define a block with the default options like this?
DEFAULT_OPTS = lambda do |opts|
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options.verbose = v
end
...
end
...
opts = OptionParser.new do |opts|
DEFAULT_OPTS[opts]
opts.on("--type [TYPE]", [:text, :binary, :auto],
"Select transfer type (text, binary, auto)") do |t|
options.transfer_type = t
end
end
Kind regards
robert