From: Alexy Khrabrov Date: 2007-10-25T22:00:09+09:00 Subject: overcoming dynamic block evaluation 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