Análisis de opciones de línea de comandos realmente barato en Ruby

114

EDIT: Por favor, por favor , por favor, lea los dos requisitos que figuran en la parte inferior de este post antes de responder. La gente sigue publicando sus nuevas gemas y bibliotecas y todo eso, que claramente no cumplen con los requisitos.

A veces quiero piratear algunas opciones de la línea de comandos de forma muy económica en un script simple. Una forma divertida de hacerlo, sin tener que lidiar con getopts o parsing ni nada de eso, es:

...
$quiet       = ARGV.delete('-d')
$interactive = ARGV.delete('-i')
...
# Deal with ARGV as usual here, maybe using ARGF or whatever.

No es del todo la sintaxis de opciones normal de Unix, porque acepta opciones que no son parámetros de línea de comandos de opciones, como en " myprog -i foo bar -q", pero puedo vivir con eso. (Algunas personas, como los desarrolladores de Subversion, prefieren esto. A veces yo también.)

Una opción que está presente o ausente no se puede implementar de manera mucho más simple que la anterior. (Una asignación, una llamada a función, un efecto secundario). ¿Existe una forma igualmente sencilla de tratar las opciones que toman un parámetro, como " -f nombre de archivo "?

EDITAR:

Un punto que no mencioné antes, porque no me había quedado claro hasta que el autor de Trollop mencionó que la biblioteca cabía "en un archivo [de 800 líneas]", es que no solo estoy buscando sintaxis, pero para una técnica que tiene las siguientes características:

  1. La totalidad del código se puede incluir en el archivo de secuencia de comandos (sin abrumar la secuencia de comandos en sí, que puede ser solo un par de docenas de líneas), de modo que uno puede colocar un solo archivo en un bindirectorio en cualquier sistema con un Ruby 1.8 estándar . [5-7] instalación y uso. Si no puede escribir una secuencia de comandos de Ruby que no tenga declaraciones de requerimiento y donde el código para analizar un par de opciones tenga menos de una docena de líneas, no cumple con este requisito.

  2. El código es lo suficientemente pequeño y simple como para que uno pueda recordar lo suficiente como para escribir directamente el código que funcionará, en lugar de cortar y pegar desde otro lugar. Piense en la situación en la que se encuentra en la consola de un servidor con cortafuegos sin acceso a Internet y desea preparar un script rápido para que lo use un cliente. No sé ustedes, pero (además de fallar el requisito anterior) memorizar incluso las 45 líneas de micro-optparse simplificado no es algo que me interese hacer.

cjs
fuente
2
¿Solo tienes curiosidad por la objeción contra getoptlong?
Mark Carey
La verbosidad de la misma. Con getoptlog, a veces las opciones que analizan el código son más largas que la parte del script que realmente hace el trabajo. No se trata solo de una cuestión estética, sino de un coste de mantenimiento.
cjs
8
No entiendo el requisito de la inclusión de la escritura - ambos getoptlongy optparseestán en la biblioteca de rubí estándar, por lo que no es necesario copiarlos al desplegar su guión - si rubí funciona en esa máquina, a continuación, require 'optparse'o require 'getoptlong'también funcionará.
rampion
Consulte stackoverflow.com/questions/21357953/… , así como la respuesta de William Morgan a continuación sobre Trollop.
fearless_fool
@CurtSampson No puedo creer cuántas personas no respondieron su pregunta. De cualquier manera, finalmente obtuve una buena respuesta sobre 3 publicaciones XD XD
OneChillDude

Respuestas:

235

Como autor de Trollop , no puedo CREER las cosas que la gente piensa que es razonable en un analizador de opciones. Seriamente. Aturde la mente.

¿Por qué debería tener que hacer un módulo que amplíe algún otro módulo para analizar las opciones? ¿Por qué debería tener que subclasificar algo? ¿Por qué debería suscribirme a algún "marco" sólo para analizar la línea de comandos?

Aquí está la versión Trollop de lo anterior:

opts = Trollop::options do
  opt :quiet, "Use minimal output", :short => 'q'
  opt :interactive, "Be interactive"
  opt :filename, "File to process", :type => String
end

Y eso es. optsahora es un hash con las teclas :quiet, :interactivey :filename. Puedes hacer lo que quieras con eso. Y obtienes una hermosa página de ayuda, formateada para adaptarse al ancho de tu pantalla, nombres cortos automáticos de argumentos, verificación de tipos ... todo lo que necesitas.

Es un archivo, por lo que puede colocarlo en su directorio lib / si no desea una dependencia formal. Tiene un DSL mínimo que es fácil de captar.

LOC por opción personas. Importa.

Freedom_Ben
fuente
39
Por cierto, +1 por haber escrito Trollop (que ya se había mencionado aquí), pero no dudes en bajar un poco el tono del primer párrafo.
cjs
33
Me temo que tiene derecho a quejarse en este caso. Cuando miras las alternativas: [1 ] [2 ] [3 ], para lo que básicamente es solo procesar una matriz de cadenas simple (no, realmente, deja que eso se hunda), no puedes evitar preguntarte ¿POR QUÉ? ¿Qué ganas con toda esa hinchazón? Esto no es C, donde las cadenas son "problemáticas". Por supuesto a cada uno lo suyo. :)
srcspider
50
Por favor, no bajes el tono de esto un poco. Es una regla de rectitud, hermano.
William Pietri
7
Siéntase libre de atenuar un poco la décima palabra.
Andrew Grimm
3
+1 para Trollop. Lo uso para mi sistema de automatización de pruebas y simplemente funciona. Además, es tan fácil de codificar que a veces reorganizo mi banner solo para experimentar el placer de hacerlo.
kinofrost
76

Comparto su disgusto por require 'getopts', principalmente debido a la genialidad que es OptionParser:

% cat temp.rb                                                            
require 'optparse'
OptionParser.new do |o|
  o.on('-d') { |b| $quiet = b }
  o.on('-i') { |b| $interactive = b }
  o.on('-f FILENAME') { |filename| $filename = filename }
  o.on('-h') { puts o; exit }
  o.parse!
end
p :quiet => $quiet, :interactive => $interactive, :filename => $filename
% ruby temp.rb                                                           
{:interactive=>nil, :filename=>nil, :quiet=>nil}
% ruby temp.rb -h                                                        
Usage: temp [options]
    -d
    -i
    -f FILENAME
    -h
% ruby temp.rb -d                                                        
{:interactive=>nil, :filename=>nil, :quiet=>true}
% ruby temp.rb -i                                                        
{:interactive=>true, :filename=>nil, :quiet=>nil}
% ruby temp.rb -di                                                       
{:interactive=>true, :filename=>nil, :quiet=>true}
% ruby temp.rb -dif apelad                                               
{:interactive=>true, :filename=>"apelad", :quiet=>true}
% ruby temp.rb -f apelad -i                                              
{:interactive=>true, :filename=>"apelad", :quiet=>nil}
rampion
fuente
6
Gracias, no puedo ver cómo esto no se ajusta a la solicitud de OP, especialmente considerando que todo está en lib estándar, en comparación con la necesidad de instalar / vender cualquier código no estándar
dolzenko
3
esto se parece a la versión trollop excepto que no necesita el archivo adicional.
Claudiu
59

Esta es la técnica estándar que suelo usar:

#!/usr/bin/env ruby

def usage(s)
    $stderr.puts(s)
    $stderr.puts("Usage: #{File.basename($0)}: [-l <logfile] [-q] file ...")
    exit(2)
end

$quiet   = false
$logfile = nil

loop { case ARGV[0]
    when '-q' then  ARGV.shift; $quiet = true
    when '-l' then  ARGV.shift; $logfile = ARGV.shift
    when /^-/ then  usage("Unknown option: #{ARGV[0].inspect}")
    else break
end; }

# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")
cjs
fuente
3
Responde la pregunta, pero hombre, Trollop parece ser mucho más fácil de tratar. ¿Por qué reinventar la rueda cuando la rueda prefabricada es mucho más suave?
Mikey TK
7
La rueda prefabricada no es más suave. Lea la pregunta nuevamente con atención, prestando especial atención a los requisitos.
cjs
2
+1 A veces necesitas reinventar la rueda, porque no quieres o simplemente no puedes usar otras dependencias como Trollop.
lzap
Trollop no necesita instalarse como una joya. Simplemente puede colocar un archivo en su libcarpeta o código y usarlo sin siquiera tocar rubygems.
Overbryd
Para mí, tuve que cambiar when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")a when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")o entraría en un ciclo de uso infinito
casey
36

Dado que nadie pareció mencionarlo, y el título se refiere a un análisis de línea de comandos barato , ¿por qué no dejar que el intérprete Ruby haga el trabajo por usted? Si pasa el -sinterruptor (en su shebang, por ejemplo), obtiene interruptores muy simples de forma gratuita, asignados a variables globales de una sola letra. Aquí está su ejemplo usando ese interruptor:

#!/usr/bin/env ruby -s
puts "#$0: Quiet=#$q Interactive=#$i, ARGV=#{ARGV.inspect}"

Y aquí está la salida cuando guardo eso como ./testy lo modifico +x:

$ ./test
./test: Quiet= Interactive=, ARGV=[]
$ ./test -q foo
./test: Quiet=true Interactive=, ARGV=["foo"]
$ ./test -q -i foo bar baz
./test: Quiet=true Interactive=true, ARGV=["foo", "bar", "baz"]
$ ./test -q=very foo
./test: Quiet=very Interactive=, ARGV=["foo"]

Consulte ruby -hpara obtener más detalles.

Eso debe ser tan barato como se pone. Generará un NameError si prueba un cambio como -:, por lo que hay algo de validación allí. Por supuesto, no puede tener ningún cambio después de un argumento sin cambio, pero si necesita algo sofisticado, realmente debería usar como mínimo OptionParser. De hecho, lo único que me molesta de esta técnica es que recibirás una advertencia (si las has habilitado) cuando accedas a una variable global no configurada, pero sigue siendo falsa, por lo que funciona bien para herramientas desechables y rápidas. guiones.

Una advertencia señalada por FelipeC en los comentarios en " Cómo hacer un análisis de opciones de línea de comandos realmente barato en Ruby ", es que su shell podría no ser compatible con el shebang de 3 tokens; es posible que deba reemplazar /usr/bin/env ruby -wcon la ruta real a su ruby ​​(como /usr/local/bin/ruby -w), o ejecutarlo desde un script contenedor, o algo así.

bjjb
fuente
2
Gracias :) Espero que no haya estado esperando esta respuesta durante los últimos dos años.
DarkHeart
3
De hecho, he estado esperando esta respuesta durante los últimos dos años. :-) Más en serio, este es el tipo de pensamiento inteligente que estaba buscando. La advertencia es un poco molesta, pero puedo pensar en formas de mitigar eso.
cjs
Me alegro de poder ayudar (eventualmente), @CurtSampson, Las banderas de la resonancia magnética se arrancan directamente de Perl, donde tienden a usarse gratuitamente en frases breves. Siéntase libre de aceptar, si la respuesta aún le resulta útil. :)
bjjb
1
No puede usar múltiples argumentos en un shebang en Linux. / usr / bin / env: 'ruby -s': No
existe
13

Construí micro-optparse para esta obvia necesidad de un analizador de opciones corto pero fácil de usar. Tiene una sintaxis similar a Trollop y tiene 70 líneas cortas. Si no necesita validaciones y puede prescindir de las líneas vacías, puede reducirlo a 45 líneas. Creo que eso es exactamente lo que estabas buscando.

Ejemplo corto:

options = Parser.new do |p|
  p.version = "fancy script version 1.0"
  p.option :verbose, "turn on verbose mode"
  p.option :number_of_chairs, "defines how many chairs are in the classroom", :default => 1
  p.option :room_number, "select room number", :default => 2, :value_in_set => [1,2,3,4]
end.process!

Llamar al script con -ho --helpimprimirá

Usage: micro-optparse-example [options]
    -v, --[no-]verbose               turn on verbose mode
    -n, --number-of-chairs 1         defines how many chairs are in the classroom
    -r, --room-number 2              select room number
    -h, --help                       Show this message
    -V, --version                    Print version

Comprueba si la entrada es del mismo tipo que el valor predeterminado, genera accesos cortos y largos, imprime mensajes de error descriptivos si se dan argumentos no válidos y más.

Yo comparé varias opciones-analizador mediante el uso de cada opción-analizador para el problema que tenía. Puede utilizar estos ejemplos y mi resumen para tomar una decisión informativa. No dude en agregar más implementaciones a la lista. :)

Florian Pilz
fuente
La biblioteca en sí parece que puede ser genial. Sin embargo, ¿no es falso comparar los recuentos de líneas con Trollop, ya que depende y requiere optparsecuál es (más o menos) 1937 líneas?
Telemachus
6
Comparar los recuentos de líneas está absolutamente bien, ya que optparsees una biblioteca predeterminada, es decir, se envía con cada instalación de Ruby. Trollopes una biblioteca de terceros, por lo que debe importar el código completo cada vez que desee incluirlo en un proyecto. µ-optparse siempre solo requiere las ~ 70 líneas, ya optparseque ya están ahí.
Florian Pilz
8

Entiendo totalmente por qué desea evitar optparse: puede ser demasiado. Pero hay algunas soluciones mucho más "ligeras" (en comparación con OptParse) que vienen como bibliotecas pero son lo suficientemente simples como para hacer que valga la pena instalar una sola gema.

Por ejemplo, consulte este ejemplo de OptiFlag . Solo unas pocas líneas para el procesamiento. Un ejemplo ligeramente truncado adaptado a su caso:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

ARGV.flags.f # => .. whatever ..

También hay toneladas de ejemplos personalizados . Recuerdo haber usado otro que era aún más fácil, pero se me ha escapado por ahora, pero volveré y agregaré un comentario aquí si lo encuentro.

Peter Cooper
fuente
Siéntase libre de editar su respuesta para que se adapte mejor a la pregunta aclarada.
cjs
4

Esto es lo que uso para argumentos realmente baratos:

def main
  ARGV.each { |a| eval a }
end

main

así que si lo ejecutas programname foo barllama a foo y luego a bar. Es útil para guiones desechables.

Chrismealy
fuente
3

Puedes probar algo como:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end
Stefan
fuente
3

¿Has considerado a Thor? por wycats? Creo que es mucho más limpio que optparse. Si ya tiene un script escrito, podría ser un poco más trabajo formatearlo o refactorizarlo para Thor, pero hace que las opciones de manejo sean muy simples.

Aquí está el fragmento de ejemplo del archivo README:

class MyApp < Thor                                                # [1]
  map "-L" => :list                                               # [2]

  desc "install APP_NAME", "install one of the available apps"    # [3]
  method_options :force => :boolean, :alias => :optional          # [4]
  def install(name)
    user_alias = options[:alias]
    if options.force?
      # do something
    end
    # ... other code ...
  end

  desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
  def list(search = "")
    # list everything
  end
end

Thor mapea automáticamente los comandos como tales:

app install myname --force

Eso se convierte en:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Hereda de Thor para convertir una clase en un mapeador de opciones
  2. Asigne identificadores no válidos adicionales a métodos específicos. En este caso, convierta -L en: list
  3. Describe el método inmediatamente a continuación. El primer parámetro es la información de uso y el segundo parámetro es la descripción.
  4. Proporcione opciones adicionales. Estos se calcularán desde y params. En este caso, se agrega una opción --force y una -f.
Jack Chu
fuente
Me gusta el mapeo de comandos, ya que un solo binario con un montón de subcomandos es algo que hago a menudo. Aún así, aunque te has alejado mucho de la 'luz'. ¿Podría encontrar una forma aún más sencilla de expresar esa misma funcionalidad? ¿Qué pasa si no necesita imprimir la --helpsalida? ¿Qué pasa si "head myprogram.rb" fuera la salida de ayuda?
cjs
3

Aquí está mi analizador de opciones rápido y sucio favorito:

case ARGV.join
when /-h/
  puts "help message"
  exit
when /-opt1/
  puts "running opt1"
end

Las opciones son expresiones regulares, por lo que "-h" también coincidiría con "--help".

Legible, fácil de recordar, sin biblioteca externa y código mínimo.

EdwardTeach
fuente
Sí lo haría. Si eso es un problema, puede agregar más /-h(\b|elp)
expresiones
2

Trollop es bastante barato.

g33kz0r
fuente
Eso sería, < trollop.rubyforge.org >. Me gusta bastante, creo, aunque en realidad no estaba buscando una biblioteca.
cjs
Es cierto que es una biblioteca. Sin embargo, a <800 LOC, es bastante insignificante. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r
1
Estaba pensando que tal vez 30-50 líneas serían buenas, si fuera tan lejos como para usar una "biblioteca". Pero, de nuevo, supongo que una vez que tienes un archivo separado lleno de código, el diseño de la API es más importante que el conteo de líneas. Aún así, no estoy seguro de querer incluirlo en un script único que solo quiero colocar en el directorio bin en un sistema aleatorio.
cjs
1
Lo entendiste al revés: el punto es evitar tener que tener una estrategia de implementación más compleja.
cjs
1
Estás bastante equivocado, porque estás malinterpretando (o ignorando) las necesidades e intenciones de la persona que hizo la pregunta. Le sugiero que vuelva a leer la pregunta con atención, en particular los dos últimos puntos.
cjs
2

Si desea un analizador de línea de comando simple para comandos clave / valor sin el uso de gemas:

Pero esto solo funciona si siempre tiene pares clave / valor.

# example
# script.rb -u username -p mypass

# check if there are even set of params given
if ARGV.count.odd? 
    puts 'invalid number of arguments'
    exit 1
end

# holds key/value pair of cl params {key1 => value1, key2 => valye2, ...}
opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end

# set defaults if no params are given
opts['-u'] ||= 'root'

# example use of opts
puts "username:#{opts['-u']} password:#{opts['-p']}"

Si no necesita ninguna verificación , puede usar:

opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end
Roger
fuente
2

Aquí está el fragmento de código que utilizo en la parte superior de la mayoría de mis scripts:

arghash = Hash.new.tap { |h| # Parse ARGV into a hash
    i = -1                      
    ARGV.map{  |s| /(-[a-zA-Z_-])?([^=]+)?(=)?(.+)?/m.match(s).to_a }
     .each{ |(_,a,b,c,d)| h[ a ? "#{a}#{b}#{c}" : (i+=1) ] =
                             (a ? (c ? "#{d}" : true) : "#{b}#{c}#{d}") 
          }
    [[:argc,Proc.new  {|| h.count{|(k,_)| !k.is_a?(String)}}],
     [:switches, Proc.new {|| h.keys.select{|k| k[0] == '-' }}]
    ].each{|(n,p)| h.define_singleton_method(n,&p) }
}

También odio requerir archivos adicionales en mis scripts rápidos y sucios. Mi solución es casi lo que estás pidiendo. Pego un fragmento de código de 10 líneas en la parte superior de cualquiera de mis scripts que analiza la línea de comando y pega argumentos posicionales y cambia a un objeto Hash (generalmente asignado a un objeto que he llamado arghash en los ejemplos a continuación).

Aquí hay una línea de comando de ejemplo que quizás desee analizar ...

./myexampleprog.rb -s -x=15 --longswitch arg1 --longswitch2=val1 arg2

Que se convertiría en un hash como este.

 { 
   '-s' => true, 
   '-x=' => '15', 
   '--longswitch' => true, 
   '--longswitch2=' => 'val1', 
   0 => 'arg1', 
   1 => 'arg2'
 }

Además de eso, se agregan dos métodos de conveniencia al Hash:

  • argc() devolverá el recuento de argumentos que no son de cambio.
  • switches() devolverá una matriz que contiene las claves para los interruptores que están presentes

Esto es para permitir algunas cosas rápidas y sucias como ...

  • Validar que tengo el número correcto de argumentos posicionales independientemente de los interruptores pasados ​​( arghash.argc == 2 )
  • Acceda a los argumentos posicionales por su posición relativa, independientemente de los interruptores que aparezcan antes o intercalados con argumentos posicionales (por ejemplo, arghash[1]siempre obtiene el segundo argumento que no es el interruptor).
  • Admite conmutadores de valor asignado en la línea de comando, como "--max = 15", a los que se puede acceder mediante arghash['--max='] cual se obtiene un valor de '15' dada la línea de comando de ejemplo.
  • Pruebe la presencia o ausencia de un conmutador en la línea de comando utilizando una notación muy simple, como arghash['-s']que se evalúa como verdadero si está presente y nulo si está ausente.
  • Pruebe la presencia de un interruptor o alternativas de interruptores usando operaciones establecidas como

    puts USAGETEXT if !(%w(-h --help) & arghash.switches()).empty?

  • Identificar el uso de interruptores no válidos mediante operaciones de configuración como

    puts "Invalid switch found!" if !(arghash.switches - %w(-valid1 -valid2)).empty?

  • Especifique los valores predeterminados para los argumentos que faltan usando un Hash.merge()ejemplo simple como el siguiente, que completa un valor para -max = si no se estableció uno y agrega un cuarto argumento posicional si no se pasó uno.

    with_defaults = {'-max=' => 20, 3 => 'default.txt'}.merge(arghash)

David Foster
fuente
(He editado esto para mejorar el formato del código, principalmente usando la alineación para hacer que el bloque y la estructura de control sean más claros, lo cual creo que es especialmente importante en algo tan denso con la puntuación. Pero si odias el nuevo formato, no dudes en para deshacer la edición.)
cjs
Esto es bastante bueno, si no lo más fácil de leer del mundo. Me gusta que también demuestre que cambiar la "sintaxis" del argumento (aquí, permitir opciones después de argumentos posicionales y no permitir argumentos de opción excepto mediante el uso =) puede marcar la diferencia en el código que necesita.
cjs
Gracias por reformatear. Definitivamente es oscuro de leer y uno podría intercambiar fácilmente la longitud del código por claridad. Ahora que confío en este código, más o menos, lo trato como una joya y nunca trato de averiguar qué está haciendo bajo las sábanas (así que la claridad ya no es importante ahora que tengo confianza).
David Foster
1

Esto es muy similar a la respuesta aceptada, pero usando ARGV.delete_ifcuál es lo que uso en mi analizador simple . La única diferencia real es que las opciones con argumentos deben estar juntas (p -l=file. Ej .).

def usage
  "usage: #{File.basename($0)}: [-l=<logfile>] [-q] file ..."
end

$quiet = false
$logfile = nil

ARGV.delete_if do |cur|
  next false if cur[0] != '-'
  case cur
  when '-q'
    $quiet = true
  when /^-l=(.+)$/
    $logfile = $1
  else
    $stderr.puts "Unknown option: #{cur}"
    $stderr.puts usage
    exit 1
  end
end

puts "quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}"
FelipeC
fuente
0

Aparentemente @WilliamMorgan y yo pensamos igual. Acabo de publicar anoche en Github lo que ahora veo es una biblioteca similar a Trollop (¿nombrada cómo?) Después de haber hecho una búsqueda de OptionParser en Github, consulte Switches

Hay algunas diferencias, pero la filosofía es la misma. Una diferencia obvia es que Switches depende de OptionParser.

thoran
fuente
0

Estoy desarrollando mi propia gema de analizador de opciones llamada Acclaim .

Lo escribí porque quería crear interfaces de línea de comandos de estilo git y poder separar limpiamente la funcionalidad de cada comando en clases separadas, pero también se puede usar sin el marco de comandos completo:

(options = []) << Acclaim::Option.new(:verbose, '-v', '--verbose')
values = Acclaim::Option::Parser.new(ARGV, options).parse!
puts 'Verbose.' if values.verbose?

Aún no hay una versión estable, pero ya he implementado algunas características como:

  • analizador de opciones personalizadas
  • análisis flexible de los argumentos de la opción que permite tanto el mínimo como el opcional
  • soporte para muchos estilos de opciones
  • reemplazar, agregar o aumentar en múltiples instancias de la misma opción
  • controladores de opciones personalizadas
  • controladores de tipo personalizado
  • controladores predefinidos para las clases de biblioteca estándar comunes

Hay mucho énfasis en los comandos, por lo que puede ser un poco pesado para el análisis simple de la línea de comandos, pero funciona bien y lo he estado usando en todos mis proyectos. Si está interesado en el aspecto de la interfaz de comandos, consulte la página de GitHub del proyecto para obtener más información y ejemplos.

Matheus Moreira
fuente
1
Recomiendo encarecidamente Acclaim. Es fácil de usar y tiene todas las opciones que necesita.
bowsersenior
0

Suponga que un comando tiene como máximo una acción y un número arbitrario de opciones como esta:

cmd.rb
cmd.rb action
cmd.rb action -a -b ...
cmd.rb action -ab ...

El análisis sin validación puede ser así:

ACTION = ARGV.shift
OPTIONS = ARGV.join.tr('-', '')

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end
Bohr
fuente
0

https://github.com/soveran/clap

other_args = Clap.run ARGV,
  "-s" => lambda { |s| switch = s },
  "-o" => lambda { other = true }

46LOC (en 1.0.0), sin dependencia del analizador de opciones externo. Hace el trabajo. Probablemente no sea tan completo como otros, pero es 46LOC.

Si verifica el código, puede duplicar con bastante facilidad la técnica subyacente: asigne lambdas y use la aridad para asegurarse de que el número adecuado de argumentos siga la marca si realmente no desea una biblioteca externa.

Sencillo. Barato.


EDITAR : el concepto subyacente se redujo, ya que supongo que podría copiarlo / pegarlo en un script para hacer un analizador de línea de comando razonable. Definitivamente no es algo que me gustaría memorizar, pero usar el lambda arity como un analizador barato es una idea novedosa:

flag = false
option = nil
opts = {
  "--flag" => ->() { flag = true },
  "--option" => ->(v) { option = v }
}

argv = ARGV
args = []

while argv.any?
  item = argv.shift
  flag = opts[item]

  if flag
    raise ArgumentError if argv.size < arity
    flag.call(*argv.shift(arity))
  else
    args << item
  end
end

# ...do stuff...
Ben Alavi
fuente
Lea el punto 1 al final de la pregunta. Si no puede escribir todo el código necesario en su respuesta aquí, no es una respuesta a la pregunta.
cjs
¡Buen punto! Creo que en ese momento asumí que la biblioteca era lo suficientemente pequeña como para que pudieras copiar / pegar todo en cualquier script en el que estuvieras trabajando sin necesidad de una dependencia externa, pero definitivamente no es una línea clara que me gustaría guardar en la memoria. para cumplir con su punto # 2. Creo que el concepto subyacente es lo suficientemente novedoso y genial que seguí adelante e hice una versión resumida que responde a tu pregunta de manera un poco más apropiada.
Ben Alavi
-1

Voy a compartir mi propio analizador de opciones simple en el que he estado trabajando durante algún tiempo. Son solo 74 líneas de código, y hace lo básico de lo que hace el analizador de opciones interno de Git. Tomé OptionParser como inspiración, y también la de Git.

https://gist.github.com/felipec/6772110

Se parece a esto:

opts = ParseOpt.new
opts.usage = "git foo"

opts.on("b", "bool", help: "Boolean") do |v|
 $bool = v
end

opts.on("s", "string", help: "String") do |v|
 $str = v
end

opts.on("n", "number", help: "Number") do |v|
 $num = v.to_i
end

opts.parse
FelipeC
fuente
Ni siquiera revisaste el código. He puesto otra respuesta eliminando el código de análisis.
FelipeC
No era necesario, dado que dijiste que tenía 74 líneas. Sin embargo, lo acabo de ver ahora y todavía viola la primera oración del requisito 2. (Esta respuesta también viola la convención de Stack Overflow de que debe incluir el código en su respuesta en lugar de dar un enlace fuera del sitio)
cjs
-1

EasyOptions no requiere ningún código de análisis de opciones. Simplemente escriba el texto de ayuda, requiera, listo.

## Options:
##   -i, --interactive  Interactive mode
##   -q, --quiet        Silent mode

require 'easyoptions'
unless EasyOptions.options[:quiet]
    puts 'Interactive mode enabled' if EasyOptions.options[:interactive]
    EasyOptions.arguments.each { |item| puts "Argument: #{item}" }
end
Renato Silva
fuente
EasyOptions es un solo archivo Ruby sin declaraciones de requerimiento, y no hay código de análisis para recordar en absoluto. Parece que, en cambio, desea algo integrable que sea lo suficientemente potente pero fácil de recordar.
Renato Silva