¿Cómo se especifica un cambio obligatorio(no argumento) con Ruby OptionParser?
arguments (8)
Estoy escribiendo un script y quiero requerir un --host
switch con valor, pero si el parámetro --host
no está especificado, quiero que el análisis de la opción falle.
Parece que no puedo entender cómo hacerlo. Los documentos parecen solo especificar cómo hacer obligatorio el valor del argumento, no el interruptor en sí.
Convertí esto en una joya que puedes descargar e instalar desde rubygems.org:
gem install pickled_optparse
Y puede consultar el código fuente del proyecto actualizado en github:
http://github.com/PicklePumpers/pickled_optparse
- Información anterior de la publicación -
Esto realmente me estaba molestando, así que lo arreglé y mantuve el uso súper seco.
Para hacer un cambio requerido simplemente agregue un símbolo: requerido en cualquier lugar de la matriz de opciones, como sigue:
opts.on("-f", "--foo [Bar]", String, :required, "Some required option") do |option|
@options[:foo] = option
end
Luego, al final de su bloque OptionParser, agregue uno de estos para imprimir los interruptores faltantes y las instrucciones de uso:
if opts.missing_switches?
puts opts.missing_switches
puts opts
exit
end
Y finalmente, para que todo funcione, debes agregar el siguiente archivo "optparse_required_switches.rb" a tu proyecto en algún lugar y requerirlo cuando hagas tu análisis de línea de comando.
Escribí un pequeño artículo con un ejemplo en mi blog: picklepumpers.com/wordpress/?p=949
Y aquí está el archivo OptionParser modificado con un ejemplo de su uso:
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
La idea es definir un OptionParser
, ¡luego parse!
y lo puts
si faltan algunos campos. Establecer filename
de filename
para cadena vacía de forma predeterminada probablemente no sea la mejor manera de hacerlo, pero usted tuvo la idea.
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}''..."
Si falta el -i filename
, se muestra:
~/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
La respuesta de unknown (google) es buena, pero contiene un error menor.
rescue OptionParser::InvalidArgument, OptionParser::MissingArgument
debiera ser
OptionParser::InvalidOption, OptionParser::MissingArgument
De lo contrario, optparse.parse!
activará la salida de error estándar para OptionParser::InvalidOption
, no el mensaje personalizado.
Se me ocurrió una solución clara y concisa que resume sus contribuciones. OptionParser::MissingArgument
excepción OptionParser::MissingArgument
con los argumentos faltantes como un mensaje. Esta excepción queda atrapada en el bloque de rescue
junto con el resto de excepciones provenientes de OptionParser
.
#!/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
Ejecutando este ejemplo:
./program
missing argument: host
Usage: program [options]
-h, --host hostname Host name
Si haces algo como esto:
opts.on(''-h'', ''--host'',
''required host name [STRING]'') do |h|
someoptions[:host] = h || nil
end
Entonces el someoptions[:host]
será el valor de la línea de comandos o nil
(si no proporciona --host y / o no value después de --host) y puede probarlo fácilmente (y condicionalmente fallar) después el análisis:
fail "Hostname not provided" unless someoptions[:host]
Si se requiere un host , seguramente no es una opción , es un argumento .
Con eso en mente, aquí hay una manera de resolver su problema. Puede interrogar a la matriz ARGV
para ver si se ha especificado un host y, si no lo ha sido, entonces call abort("You must specify a host!")
, o similar, para que su programa se cierre con un estado de error .
Supongo que está utilizando optparse aquí, aunque la misma técnica funcionará para otras bibliotecas de análisis de opciones.
El método más simple es probablemente analizar los parámetros usando la biblioteca de análisis de opciones elegida y luego generar una excepción OptionParser :: MissingArgument si el valor de host es nulo.
El siguiente código ilustra
#!/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]}"
Ejecutando este ejemplo con una línea de comando de
./program -h somehost
pantallas simples "Host = somehost"
Mientras se ejecuta con una falta -h y sin nombre de archivo produce la siguiente salida
./program:15: missing argument: (OptionParser::MissingArgument)
Y ejecutar con una línea de comando de ./program -h produce
/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
Un enfoque que usa optparse que proporciona resultados amigables en interruptores faltantes:
#!/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}"
La ejecución sin los conmutadores -t
o -f
muestra el siguiente resultado:
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
Ejecutar el método de análisis en una cláusula de inicio / rescate permite un formato amigable ante otras fallas, como argumentos faltantes o valores de cambio no válidos, por ejemplo, intente pasar una cadena para el -n
.