如何使用 Ruby OptionParser 指定所需的开关(不是参数)?
Posted
技术标签:
【中文标题】如何使用 Ruby OptionParser 指定所需的开关(不是参数)?【英文标题】:How do you specify a required switch (not argument) with Ruby OptionParser? 【发布时间】:2010-12-05 04:56:47 【问题描述】:我正在编写一个脚本,我想要一个带有值的--host
开关,但是如果没有指定--host
开关,我希望选项解析失败。
我似乎不知道该怎么做。文档似乎只指定了如何使参数值强制,而不是开关本身。
【问题讨论】:
你需要详细说明,举个例子...... 【参考方案1】:这个想法是定义一个OptionParser
,然后定义parse!
,如果缺少某些字段,则定义puts
。默认情况下将filename
设置为空字符串可能不是最好的方法,但你明白了。
require 'optparse'
filename = ''
options = OptionParser.new do |opts|
opts.banner = "Usage: swift-code-style.rb [options]"
opts.on("-iNAME", "--input-filename=NAME", "Input filename") do |name|
filename = name
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end
options.parse!
if filename == ''
puts "Missing filename.\n---\n"
puts options
exit
end
puts "Processing '#filename'..."
如果-i filename
缺失,则显示:
~/prj/gem/swift-code-kit ./swift-code-style.rb
Missing filename.
---
Usage: swift-code-style.rb [options]
-i, --input-filename=NAME Input filename
-h, --help Prints this help
【讨论】:
【参考方案2】:我想出了一个清晰简洁的解决方案来总结您的贡献。它会引发OptionParser::MissingArgument
异常,其中缺少参数作为消息。此异常与来自 OptionParser
的其余异常一起被捕获在 rescue
块中。
#!/usr/bin/env ruby
require 'optparse'
options =
optparse = OptionParser.new do |opts|
opts.on('-h', '--host hostname', "Host name") do |host|
options[:host] = host
end
end
begin
optparse.parse!
mandatory = [:host]
missing = mandatory.select |param| options[param].nil?
raise OptionParser::MissingArgument, missing.join(', ') unless missing.empty?
rescue OptionParser::ParseError => e
puts e
puts optparse
exit
end
运行这个例子:
./program
missing argument: host
Usage: program [options]
-h, --host hostname Host name
【讨论】:
【参考方案3】:一种使用 optparse 的方法,在丢失的开关上提供友好的输出:
#!/usr/bin/env ruby
require 'optparse'
options =
optparse = OptionParser.new do |opts|
opts.on('-f', '--from SENDER', 'username of sender') do |sender|
options[:from] = sender
end
opts.on('-t', '--to RECIPIENTS', 'comma separated list of recipients') do |recipients|
options[:to] = recipients
end
options[:number_of_files] = 1
opts.on('-n', '--num_files NUMBER', Integer, "number of files to send (default #options[:number_of_files])") do |number_of_files|
options[:number_of_files] = number_of_files
end
opts.on('-h', '--help', 'Display this screen') do
puts opts
exit
end
end
begin
optparse.parse!
mandatory = [:from, :to] # Enforce the presence of
missing = mandatory.select |param| options[param].nil? # the -t and -f switches
unless missing.empty? #
raise OptionParser::MissingArgument.new(missing.join(', ')) #
end #
rescue OptionParser::InvalidOption, OptionParser::MissingArgument #
puts $!.to_s # Friendly output when parsing fails
puts optparse #
exit #
end #
puts "Performing task with options: #options.inspect"
在没有-t
或-f
开关的情况下运行会显示以下输出:
Missing options: from, to
Usage: test_script [options]
-f, --from SENDER username of sender
-t, --to RECIPIENTS comma separated list of recipients
-n, --num_files NUMBER number of files to send (default 1)
-h, --help
在 begin/rescue 子句中运行 parse 方法允许对其他故障进行友好格式化,例如缺少参数或无效的开关值,例如,尝试为 -n
开关传递字符串。
【讨论】:
根据 neilfws 的 cmets 修复 这还不错,但它仍然不是很干燥。最后你必须做很多工作,并且必须在两个地方指定你的开关。在下面查看我的修复,它更简单,更干燥。也在我的博客上:picklepumpers.com/wordpress/?p=949 在 STDERR 上输出可能会更好。【参考方案4】:如果你这样做:
opts.on('-h', '--host',
'required host name [STRING]') do |h|
someoptions[:host] = h || nil
end
然后someoptions[:host]
将是来自命令行的值或nil
(如果您不提供 --host 和/或在 --host 之后没有值)并且您可以轻松地对其进行测试(并且有条件失败)解析后:
fail "Hostname not provided" unless someoptions[:host]
【讨论】:
【参考方案5】:来自未知(谷歌)的答案很好,但包含一个小错误。
rescue OptionParser::InvalidArgument, OptionParser::MissingArgument
应该是
OptionParser::InvalidOption, OptionParser::MissingArgument
否则,optparse.parse!
将触发OptionParser::InvalidOption
的标准错误输出,而不是自定义消息。
【讨论】:
【参考方案6】:如果需要host,那么肯定不是选项,而是参数。
考虑到这一点,这里有一种方法可以解决您的问题。您可以查询ARGV
数组以查看是否已指定主机,如果尚未指定,则调用abort("You must specify a host!")
或类似方法,以使您的程序以错误状态退出。
【讨论】:
【参考方案7】:我把它变成了一个 gem,你可以从 rubygems.org 下载和安装:
gem install pickled_optparse
您可以在 github 上查看更新的项目源代码:http://github.com/PicklePumpers/pickled_optparse
-- 较早的帖子信息--
这真的非常困扰我,所以我修复了它并保持使用超级干燥。
要使开关成为必需,只需在选项数组的任意位置添加一个 :required 符号,如下所示:
opts.on("-f", "--foo [Bar]", String, :required, "Some required option") do |option|
@options[:foo] = option
end
然后在您的 OptionParser 块的末尾添加其中一个以打印出缺少的开关和使用说明:
if opts.missing_switches?
puts opts.missing_switches
puts opts
exit
end
最后,为了使这一切正常工作,您需要将以下“optparse_required_switches.rb”文件添加到您的项目中的某个地方,并在您进行命令行解析时需要它。
我在博客上写了一篇带有示例的小文章: http://picklepumpers.com/wordpress/?p=949
这是修改后的 OptionParser 文件及其用法示例:
required_switches_example.rb
#!/usr/bin/env ruby
require 'optparse'
require_relative 'optparse_required_switches'
# Configure options based on command line options
@options =
OptionParser.new do |opts|
opts.banner = "Usage: test [options] in_file[.srt] out_file[.srt]"
# Note that :required can be anywhere in the parameters
# Also note that OptionParser is bugged and will only check
# for required parameters on the last option, not my bug.
# required switch, required parameter
opts.on("-s Short", String, :required, "a required switch with just a short") do |operation|
@options[:operation] = operation
end
# required switch, optional parameter
opts.on(:required, "--long [Long]", String, "a required switch with just a long") do |operation|
@options[:operation] = operation
end
# required switch, required parameter
opts.on("-b", "--both ShortAndLong", String, "a required switch with short and long", :required) do |operation|
@options[:operation] = operation
end
# optional switch, optional parameter
opts.on("-o", "--optional [Whatever]", String, "an optional switch with short and long") do |operation|
@options[:operation] = operation
end
# Now we can see if there are any missing required
# switches so we can alert the user to what they
# missed and how to use the program properly.
if opts.missing_switches?
puts opts.missing_switches
puts opts
exit
end
end.parse!
optparse_required_switches.rb
# Add required switches to OptionParser
class OptionParser
# An array of messages describing the missing required switches
attr_reader :missing_switches
# Convenience method to test if we're missing any required switches
def missing_switches?
!@missing_switches.nil?
end
def make_switch(opts, block = nil)
short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
ldesc, sdesc, desc, arg = [], [], []
default_style = Switch::NoArgument
default_pattern = nil
klass = nil
n, q, a = nil
# Check for required switches
required = opts.delete(:required)
opts.each do |o|
# argument class
next if search(:atype, o) do |pat, c|
klass = notwice(o, klass, 'type')
if not_style and not_style != Switch::NoArgument
not_pattern, not_conv = pat, c
else
default_pattern, conv = pat, c
end
end
# directly specified pattern(any object possible to match)
if (!(String === o || Symbol === o)) and o.respond_to?(:match)
pattern = notwice(o, pattern, 'pattern')
if pattern.respond_to?(:convert)
conv = pattern.method(:convert).to_proc
else
conv = SPLAT_PROC
end
next
end
# anything others
case o
when Proc, Method
block = notwice(o, block, 'block')
when Array, Hash
case pattern
when CompletingHash
when nil
pattern = CompletingHash.new
conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert)
else
raise ArgumentError, "argument pattern given twice"
end
o.each |pat, *v| pattern[pat] = v.fetch(0) pat
when Module
raise ArgumentError, "unsupported argument type: #o", ParseError.filter_backtrace(caller(4))
when *ArgumentStyle.keys
style = notwice(ArgumentStyle[o], style, 'style')
when /^--no-([^\[\]=\s]*)(.+)?/
q, a = $1, $2
o = notwice(a ? Object : TrueClass, klass, 'type')
not_pattern, not_conv = search(:atype, o) unless not_style
not_style = (not_style || default_style).guess(arg = a) if a
default_style = Switch::NoArgument
default_pattern, conv = search(:atype, FalseClass) unless default_pattern
ldesc << "--no-#q"
long << 'no-' + (q = q.downcase)
nolong << q
when /^--\[no-\]([^\[\]=\s]*)(.+)?/
q, a = $1, $2
o = notwice(a ? Object : TrueClass, klass, 'type')
if a
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
ldesc << "--[no-]#q"
long << (o = q.downcase)
not_pattern, not_conv = search(:atype, FalseClass) unless not_style
not_style = Switch::NoArgument
nolong << 'no-' + o
when /^--([^\[\]=\s]*)(.+)?/
q, a = $1, $2
if a
o = notwice(NilClass, klass, 'type')
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
ldesc << "--#q"
long << (o = q.downcase)
when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
q, a = $1, $2
o = notwice(Object, klass, 'type')
if a
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
sdesc << "-#q"
short << Regexp.new(q)
when /^-(.)(.+)?/
q, a = $1, $2
if a
o = notwice(NilClass, klass, 'type')
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
sdesc << "-#q"
short << q
when /^=/
style = notwice(default_style.guess(arg = o), style, 'style')
default_pattern, conv = search(:atype, Object) unless default_pattern
else
desc.push(o)
end
end
default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
if !(short.empty? and long.empty?)
s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block)
elsif !block
if style or pattern
raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
end
s = desc
else
short << pattern
s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block)
end
# Make sure required switches are given
if required && !(default_argv.include?("-#short[0]") || default_argv.include?("--#long[0]"))
@missing_switches ||= [] # Should be placed in initialize if incorporated into Ruby proper
# This is more clear but ugly and long.
#missing = "-#short[0]" if !short.empty?
#missing = "#missing or " if !short.empty? && !long.empty?
#missing = "#missing--#long[0]" if !long.empty?
# This is less clear and uglier but shorter.
missing = "#"-#short[0]" if !short.empty?#" or " if !short.empty? && !long.empty?#"--#long[0]" if !long.empty?"
@missing_switches << "Missing switch: #missing"
end
return s, short, long,
(not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
nolong
end
end
【讨论】:
pickled_optparse requires Ruby version ~> 1.9.2.
:(
@DamianNowak - 克隆 github 存储库并修改 pickled_optparse.gemspec 文件的第 36 行。然后执行gem build .\pickled_optparse.gemspec && gem install .\pickled_optparse-0.1.1.gem
,一切顺利。【参考方案8】:
我假设您在这里使用 optparse,尽管相同的技术也适用于其他选项解析库。
最简单的方法可能是使用您选择的选项解析库解析参数,然后如果 host 的值为 nil,则引发 OptionParser::MissingArgument Exception。
以下代码说明
#!/usr/bin/env ruby
require 'optparse'
options =
optparse = OptionParser.new do |opts|
opts.on('-h', '--host HOSTNAME', "Mandatory Host Name") do |f|
options[:host] = f
end
end
optparse.parse!
#Now raise an exception if we have not found a host option
raise OptionParser::MissingArgument if options[:host].nil?
puts "Host = #options[:host]"
用命令行运行这个例子
./program -h somehost
简单显示“Host = somehost”
在缺少 -h 且没有文件名的情况下运行时会产生以下输出
./program:15: missing argument: (OptionParser::MissingArgument)
使用 ./program -h 命令行运行会产生
/usr/lib/ruby/1.8/optparse.rb:451:in `parse': missing argument: -h (OptionParser::MissingArgument)
from /usr/lib/ruby/1.8/optparse.rb:1288:in `parse_in_order'
from /usr/lib/ruby/1.8/optparse.rb:1247:in `catch'
from /usr/lib/ruby/1.8/optparse.rb:1247:in `parse_in_order'
from /usr/lib/ruby/1.8/optparse.rb:1241:in `order!'
from /usr/lib/ruby/1.8/optparse.rb:1332:in `permute!'
from /usr/lib/ruby/1.8/optparse.rb:1353:in `parse!'
from ./program:13
【讨论】:
这不是库的原生特性,如果你有更多需要的开关,它就不是很干燥。谢谢。 与其引发神秘的异常,我更喜欢通过调用Kernel.abort
以非零状态退出。它需要一个可选参数,您可以使用它来指定中止的原因。
同意泰德。它根本不是 DRY,应该为它不是 DRY 而感到羞耻。我的第一个命令行应用程序需要一个强制开关,所以不包括在内是不合情理的。
@Ted - 我同意。当我查看库 python 有 ruby 时,看起来很可悲。带有示例的清晰文档、足够的灵活性甚至是教程。使用 optparse,我可以获得一些半成品文档和一组有限的选项。 docs.python.org/2.7/library/argparse.html
DRY 问题可以轻松解决。例如 ` options = required = [] optparse = OptionParser.new do |opts|必需
以上是关于如何使用 Ruby OptionParser 指定所需的开关(不是参数)?的主要内容,如果未能解决你的问题,请参考以下文章