OptionParser可以跳过未知选项,稍后在Ruby程序中处理吗?

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'; 每次选择仅与该上下文相关的选项.最后阶段可以检查在每个部件处理完他们之后没有任何未知数.

用例是:

  1. 在松散耦合的前端程序中,各种组件具有不同的参数,我不希望'main'知道所有内容,只是为每个部分委派参数/选项集.

  2. 将一些更大的系统(如RSpec)嵌入到我的应用程序中,我只需通过命令行通过他们的选项,而不知道那些包装器.

我会用一些分隔符选项OK,以及像----vmargs在某些Java应用程序.

在Unix世界中有许多类似的东西的实际例子(startx/X,git plumbing和瓷器),其中一层处理一些选项但将其余部分传播到下层.

开箱即用,这似乎不起作用.每次OptionParse.parse!通话都会进行详尽的处理,对任何不知道的事情都会失败.我想我很乐意跳过未知选项.

任何提示,也许是替代方法都是受欢迎的.

Dav*_*man 5

假设解析器运行的顺序已明确定义,您可以将额外的选项存储在临时全局变量中,并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,但它应该做你想做的事。


Sco*_*ttJ 5

我需要一个不会抛出的解决方案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)