ing*_*ger 12 ruby options getopt optparse
有没有办法在一个Ruby程序中多次启动OptionParser,每个程序都有不同的选项?
例如:
$ myscript.rb --subsys1opt a --subsys2opt b
Run Code Online (Sandbox Code Playgroud)
在这里,myscript.rb将使用subsys1和subsys2,将它们的选项处理逻辑委托给它们,可能是首先处理'a'的序列,然后是单独的OptionParser对象中的'b'; 每次选择仅与该上下文相关的选项.最后阶段可以检查在每个部件处理完他们之后没有任何未知数.
用例是:
在松散耦合的前端程序中,各种组件具有不同的参数,我不希望'main'知道所有内容,只是为每个部分委派参数/选项集.
将一些更大的系统(如RSpec)嵌入到我的应用程序中,我只需通过命令行通过他们的选项,而不知道那些包装器.
我会用一些分隔符选项OK,以及像--或--vmargs在某些Java应用程序.
在Unix世界中有许多类似的东西的实际例子(startx/X,git plumbing和瓷器),其中一层处理一些选项但将其余部分传播到下层.
开箱即用,这似乎不起作用.每次OptionParse.parse!通话都会进行详尽的处理,对任何不知道的事情都会失败.我想我很乐意跳过未知选项.
任何提示,也许是替代方法都是受欢迎的.
假设解析器运行的顺序已明确定义,您可以将额外的选项存储在临时全局变量中,并OptionParser#parse!在每组选项上运行。
最简单的方法是使用您提到的分隔符。假设第二组参数与第一组参数由分隔符分隔--。然后这将做你想做的事:
opts = OptionParser.new do |opts|
# set up one OptionParser here
end
both_args = $*.join(" ").split(" -- ")
$extra_args = both_args[1].split(/\s+/)
opts.parse!(both_args[0].split(/\s+/))
Run Code Online (Sandbox Code Playgroud)
然后,在第二个代码/上下文中,您可以执行以下操作:
other_opts = OptionParser.new do |opts|
# set up the other OptionParser here
end
other_opts.parse!($extra_args)
Run Code Online (Sandbox Code Playgroud)
或者,这可能是“更合适”的方法,您可以简单地使用OptionParser#parse, 而不带感叹号,这不会从数组中删除命令行开关$*,并确保没有定义选项两组相同。我建议不要$*手动修改数组,因为如果您只查看第二部分,它会使您的代码更难以理解,但您可以这样做。在这种情况下,您必须忽略无效选项:
begin
opts.parse
rescue OptionParser::InvalidOption
puts "Warning: Invalid option"
end
Run Code Online (Sandbox Code Playgroud)
正如评论中指出的那样,第二种方法实际上不起作用。但是,如果您无论如何都必须修改$*数组,则可以这样做:
tmp = Array.new
while($*.size > 0)
begin
opts.parse!
rescue OptionParser::InvalidOption => e
tmp.push(e.to_s.sub(/invalid option:\s+/,''))
end
end
tmp.each { |a| $*.push(a) }
Run Code Online (Sandbox Code Playgroud)
它不仅仅是一点点 hacky,但它应该做你想做的事。
我需要一个不会抛出的解决方案OptionParser::InvalidOption,并且无法在当前答案中找到优雅的解决方案.这个猴子补丁基于其他一个答案,但清理它并使其更像当前的order!语义.但请参阅下文,了解多次传递选项解析所固有的未解决问题.
class OptionParser
# Like order!, but leave any unrecognized --switches alone
def order_recognized!(args)
extra_opts = []
begin
order!(args) { |a| extra_opts << a }
rescue OptionParser::InvalidOption => e
extra_opts << e.args[0]
retry
end
args[0, 0] = extra_opts
end
end
Run Code Online (Sandbox Code Playgroud)
工作就像order!除了投掷之外InvalidOption,它留下了无法识别的开关ARGV.
RSpec测试:
describe OptionParser do
before(:each) do
@parser = OptionParser.new do |opts|
opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| @found << f }
end
@found = []
end
describe 'order_recognized!' do
it 'finds good switches using equals (--foo=3)' do
argv = %w(one two --foo=3 three)
@parser.order_recognized!(argv)
expect(@found).to eq([3])
expect(argv).to eq(%w(one two three))
end
it 'leaves unknown switches alone' do
argv = %w(one --bar=2 two three)
@parser.order_recognized!(argv)
expect(@found).to eq([])
expect(argv).to eq(%w(one --bar=2 two three))
end
it 'leaves unknown single-dash switches alone' do
argv = %w(one -bar=2 two three)
@parser.order_recognized!(argv)
expect(@found).to eq([])
expect(argv).to eq(%w(one -bar=2 two three))
end
it 'finds good switches using space (--foo 3)' do
argv = %w(one --bar=2 two --foo 3 three)
@parser.order_recognized!(argv)
expect(@found).to eq([3])
expect(argv).to eq(%w(one --bar=2 two three))
end
it 'finds repeated args' do
argv = %w(one --foo=1 two --foo=3 three)
@parser.order_recognized!(argv)
expect(@found).to eq([1, 3])
expect(argv).to eq(%w(one two three))
end
it 'maintains repeated non-switches' do
argv = %w(one --foo=1 one --foo=3 three)
@parser.order_recognized!(argv)
expect(@found).to eq([1, 3])
expect(argv).to eq(%w(one one three))
end
it 'maintains repeated unrecognized switches' do
argv = %w(one --bar=1 one --bar=3 three)
@parser.order_recognized!(argv)
expect(@found).to eq([])
expect(argv).to eq(%w(one --bar=1 one --bar=3 three))
end
it 'still raises InvalidArgument' do
argv = %w(one --foo=bar)
expect { @parser.order_recognized!(argv) }.to raise_error(OptionParser::InvalidArgument)
end
it 'still raises MissingArgument' do
argv = %w(one --foo)
expect { @parser.order_recognized!(argv) }.to raise_error(OptionParser::MissingArgument)
end
end
end
Run Code Online (Sandbox Code Playgroud)
问题:通常OptionParser允许缩写选项,前提是有足够的字符来唯一标识预期的选项.多阶段解析选项会破坏这一点,因为OptionParser在第一遍中没有看到所有可能的参数.例如:
describe OptionParser do
context 'one parser with similar prefixed options' do
before(:each) do
@parser1 = OptionParser.new do |opts|
opts.on('--foobar=BAR', OptionParser::DecimalInteger) { |f| @found_foobar << f }
opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| @found_foo << f }
end
@found_foobar = []
@found_foo = []
end
it 'distinguishes similar prefixed switches' do
argv = %w(--foo=3 --foobar=4)
@parser1.order_recognized!(argv)
expect(@found_foobar).to eq([4])
expect(@found_foo).to eq([3])
end
end
context 'two parsers in separate passes' do
before(:each) do
@parser1 = OptionParser.new do |opts|
opts.on('--foobar=BAR', OptionParser::DecimalInteger) { |f| @found_foobar << f }
end
@parser2 = OptionParser.new do |opts|
opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| @found_foo << f }
end
@found_foobar = []
@found_foo = []
end
it 'confuses similar prefixed switches' do
# This is not generally desirable behavior
argv = %w(--foo=3 --foobar=4)
@parser1.order_recognized!(argv)
@parser2.order_recognized!(argv)
expect(@found_foobar).to eq([3, 4])
expect(@found_foo).to eq([])
end
end
end
Run Code Online (Sandbox Code Playgroud)