Análise de opções de linha de comando realmente barata em Ruby


114

EDIT: Por favor , por favor , leia os dois requisitos listados no final desta postagem antes de responder. As pessoas continuam postando suas novas joias, bibliotecas e outros enfeites, que claramente não atendem aos requisitos.

Às vezes eu quero hackear de forma muito barata algumas opções de linha de comando em um script simples. Uma maneira divertida de fazer isso, sem lidar com getopts ou parsing ou qualquer coisa assim, é:

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

Não é bem a sintaxe de opções Unix normal, porque aceitará parâmetros de linha de comando de opções não, como em " myprog -i foo bar -q", mas posso viver com isso. (Algumas pessoas, como os desenvolvedores do Subversion, preferem isso. Às vezes eu também.)

Uma opção que está apenas presente ou ausente não pode ser implementada de forma muito mais simples do que a anterior. (Uma atribuição, uma chamada de função, um efeito colateral.) Existe uma maneira igualmente simples de lidar com opções que usam um parâmetro, como " -f nome do arquivo "?

EDITAR:

Um ponto que eu não disse antes, porque não ficou claro para mim até que o autor de Trollop mencionou que a biblioteca cabia "em um arquivo [de 800 linhas]", é que não estou procurando apenas por sintaxe, mas para uma técnica que possui as seguintes características:

  1. Todo o código pode ser incluído no arquivo de script (sem sobrecarregar o próprio script, que pode ter apenas algumas dezenas de linhas), de forma que se possa colocar um único arquivo em um bindiretório em qualquer sistema com um Ruby 1.8 padrão . [5-7] instalação e uso. Se você não pode escrever um script Ruby que não tenha instruções de requerimento e onde o código para analisar algumas opções tem menos de uma dúzia de linhas ou mais, você falhou neste requisito.

  2. O código é pequeno e simples o suficiente para que se possa lembrar o suficiente para digitar diretamente o código que fará o truque, em vez de cortar e colar de outro lugar. Pense na situação em que você está no console de um servidor com firewall sem acesso à Internet e deseja criar um script rápido para um cliente usar. Eu não sei sobre você, mas (além de falhar no requisito acima) memorizar até mesmo as 45 linhas do microoptparse simplificado não é algo que eu gostaria de fazer.


2
Apenas curioso para saber a objeção contra getoptlong?
Mark Carey

A verbosidade disso. Com getoptlog, às vezes as opções de análise de código são mais longas do que a parte do script que realmente faz o trabalho. Este não é apenas um problema estético, mas um problema de custo de manutenção.
cjs

8
Eu não entendo o requisito de inclusão de script - ambos getoptlonge optparseestão na biblioteca ruby ​​padrão, então você NÃO PRECISA copiá-los ao implantar seu script - se o ruby ​​funcionar nessa máquina, então require 'optparse'ou require 'getoptlong'funcionará também.
rampion

Consulte stackoverflow.com/questions/21357953/… , bem como a resposta de William Morgan abaixo sobre Trollop.
fearless_fool

@CurtSampson Não posso acreditar quantas pessoas não responderam sua pergunta. De qualquer forma, finalmente obtive uma boa resposta sobre 3 postagens no XD XD
OneChillDude

Respostas:


235

Como autor de Trollop , não posso ACREDITAR as coisas que as pessoas acham que são razoáveis ​​em um analisador de opções. Seriamente. Isso confunde a mente.

Por que devo fazer um módulo que estende algum outro módulo para analisar opções? Por que eu deveria ter que criar uma subclasse? Por que eu deveria assinar alguma "estrutura" apenas para analisar a linha de comando?

Aqui está a versão Trollop do acima:

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

E é isso. optsé agora um hash com chaves :quiet, :interactivee :filename. Você pode fazer o que quiser com isto. E você obtém uma bela página de ajuda, formatada para caber na largura da sua tela, nomes de argumentos curtos automáticos, verificação de tipo ... tudo que você precisa.

É um arquivo, então você pode soltá-lo em seu diretório lib / se não quiser uma dependência formal. Ele tem um DSL mínimo que é fácil de entender.

LOC por opção de pessoas. Importa.


39
Aliás, +1 por ter escrito Trollop (que já havia sido mencionado aqui), mas fique à vontade para suavizar um pouco o primeiro parágrafo.
cjs

33
Ele tem o direito de reclamar neste caso, infelizmente. Quando você olha para as alternativas: [1 ] [2 ] [3 ], para o que basicamente é apenas processar uma matriz de string simples (não, realmente, deixe-o penetrar), você não pode deixar de se perguntar POR QUE? O que você ganha com todo esse inchaço? Isso não é C, onde as strings são "problemáticas". Claro, para cada um. :)
srcspider

50
Por favor, não diminua isso um pouco. É uma frase justa, irmão.
William Pietri

7
Sinta-se à vontade para suavizar um pouco a décima palavra.
Andrew Grimm,

3
1 para Trollop. Eu o uso para meu sistema de automação de teste e ele simplesmente funciona. Além disso, é tão fácil codificar com que às vezes eu reorganizo meu banner apenas para sentir a alegria dele.
Kinofrost

76

Compartilho sua repulsa por require 'getopts', principalmente devido à grandiosidade que é 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}

6
Obrigado, não consigo ver como isso não se encaixa na solicitação de OPs, especialmente considerando que está tudo na biblioteca padrão, em comparação com a necessidade de instalar / vender qualquer código não padrão
dolzenko

3
isso se parece com a versão trollop, exceto que não precisa do arquivo extra.
Claudiu

59

Esta é a técnica padrão que costumo 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}")

3
Responde a pergunta, mas cara, Trollop parece ser muito mais fácil de lidar. Por que reinventar a roda quando a roda pré-fabricada é muito mais lisa?
Mikey TK

7
A roda pré-fabricada não é mais lisa. Leia a pergunta novamente com atenção, prestando muita atenção aos requisitos.
cjs

2
+1 Às vezes você precisa reinventar a roda, porque você não quer ou simplesmente não pode usar outras dependências como Trollop.
lzap

O Trollop não precisa ser instalado como uma gema. Você pode simplesmente soltar um arquivo em sua libpasta ou código e usá-lo sem nem mesmo tocar em rubygems.
Overbryd 01 de

Para mim, eu tive que mudar when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")para when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")ou entraria em um loop de uso infinito
casey

36

Já que ninguém apareceu para mencioná-lo, e o título se refere à análise de linha de comando barata , por que não deixar o interpretador Ruby fazer o trabalho por você? Se você passar no -sswitch (em seu shebang, por exemplo), você obterá switches muito simples de graça, atribuídos a variáveis ​​globais de uma única letra. Aqui está seu exemplo usando essa opção:

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

E aqui está a saída quando eu salvo isso como ./teste faço o chmod +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"]

Veja ruby -hpara detalhes.

Isso deve ser tão barato quanto ele ganha. Ele levantará um NameError se você tentar uma opção como -:, portanto, há alguma validação aí. Claro, você não pode ter nenhuma opção após um argumento não-switch, mas se você precisa de algo sofisticado, você realmente deve usar no mínimo OptionParser. Na verdade, a única coisa que me irrita sobre essa técnica é que você receberá um aviso (se você os habilitou) ao acessar uma variável global não definida, mas ainda é falsey, por isso funciona muito bem para ferramentas descartáveis ​​e rápidas scripts.

Uma advertência apontada por FelipeC nos comentários em " Como fazer uma análise de opção de linha de comando realmente barata em Ruby ", é que seu shell pode não suportar o shebang de 3 tokens; você pode precisar substituir /usr/bin/env ruby -wpelo caminho real para o seu ruby ​​(como /usr/local/bin/ruby -w), ou executá-lo a partir de um script de invólucro, ou algo assim.


2
Obrigado :) Espero que ele não tenha esperado por essa resposta nos últimos dois anos.
DarkHeart

3
De fato, estou esperando por essa resposta há dois anos. :-) Mais a sério, este é o tipo de pensamento inteligente que eu procurava. O aviso é um pouco chato, mas posso pensar em maneiras de atenuar isso.
cjs

Ainda bem que pude (eventualmente) ajudar, @CurtSampson, as bandeiras do MRI são arrancadas direto do Perl, onde tendem a ser usadas gratuitamente em um shell one-liners. Sinta-se à vontade para aceitar, se a resposta ainda for útil para você. :)
bjjb

1
Você não pode usar vários argumentos em um shebang no Linux. / usr / bin / env: 'ruby -s': Não
existe

13

Eu construí o microoptparse para preencher essa necessidade óbvia de um analisador de opções curto, mas fácil de usar. Ele tem uma sintaxe semelhante a Trollop e tem 70 linhas curtas. Se você não precisa de validações e pode ficar sem linhas vazias, você pode reduzir para 45 linhas. Acho que é exatamente isso que você estava procurando.

Pequeno exemplo:

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!

Chamar o script com -hou --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

Ele verifica se a entrada é do mesmo tipo que o valor padrão, gera acessadores curtos e longos, imprime mensagens de erro descritivas se argumentos inválidos são fornecidos e muito mais.

Eu comparei vários analisadores de opções usando cada analisador de opções para o problema que tive. Você pode usar esses exemplos e meu resumo para tomar uma decisão informativa. Sinta-se à vontade para adicionar mais implementações à lista. :)


A própria biblioteca parece que pode ser ótima. No entanto, não é falso comparar a contagem de linhas com Trollop, uma vez que você depende e exige optparse(mais ou menos) 1937 linhas.
Telêmaco

6
Comparar as contagens de linha é absolutamente OK, já que optparseé uma biblioteca padrão, ou seja, ela vem com todas as instalações do Ruby. Trollopé uma biblioteca de terceiros, portanto, você deve importar o código completo toda vez que quiser incluí-lo em um projeto. µ-optparse sempre requer apenas ~ 70 linhas, uma vez que optparsejá está lá.
Florian Pilz de

8

Eu entendo perfeitamente por que você deseja evitar optparse - pode ser demais. Mas existem algumas soluções muito mais "leves" (em comparação com o OptParse) que vêm como bibliotecas, mas são simples o suficiente para fazer uma única instalação gem valer a pena.

Por exemplo, verifique este exemplo OptiFlag . Apenas algumas linhas para o processamento. Um exemplo ligeiramente truncado sob medida para seu caso:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

Também existem muitos exemplos personalizados . Lembro-me de usar outro que foi ainda mais fácil, mas me escapou por enquanto, mas voltarei e adicionarei um comentário aqui se eu encontrar.


Sinta-se à vontade para editar sua resposta para melhor atender à pergunta esclarecida.
cjs

4

Isso é o que eu uso para argumentos muito, muito baratos:

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

main

então, se você executá- programname foo barlo, chama foo e bar. É útil para scripts descartáveis.


3

Você pode tentar algo como:

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

3

Você já considerou Thor por wycats? Acho que é muito mais limpo do que optparse. Se você já tem um script escrito, pode dar mais trabalho formatá-lo ou refatorá-lo para o thor, mas torna as opções de manuseio muito simples.

Aqui está o snippet de exemplo do 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 mapeia comandos automaticamente como:

app install myname --force

Isso é convertido em:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Herdar de Thor para transformar uma classe em um mapeador de opções
  2. Mapeie identificadores não válidos adicionais para métodos específicos. Neste caso, converta -L para: lista
  3. Descreva o método imediatamente abaixo. O primeiro parâmetro é a informação de uso e o segundo parâmetro é a descrição.
  4. Forneça quaisquer opções adicionais. Eles serão organizados a partir de - e - parâmetros. Neste caso, as opções --force e -f são adicionadas.

Gosto da coisa de mapeamento de comandos, já que um único binário com vários subcomandos é algo que faço com frequência. Ainda assim, embora você tenha se afastado da 'luz'. Você poderia encontrar uma maneira ainda mais simples de expressar essa mesma funcionalidade? E se você não precisasse imprimir a --helpsaída? E se "head myprogram.rb" fosse a saída de ajuda?
cjs

3

Aqui está meu analisador de opções rápido e sujo favorito:

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

As opções são expressões regulares, portanto "-h" também corresponderia a "--help".

Legível, fácil de lembrar, sem biblioteca externa e código mínimo.


Sim, seria. Se isso for um problema, você pode adicionar mais regex, por exemplo/-h(\b|elp)
EdwardTeach

2

Trollop é muito barato.


Isso seria, < trollop.rubyforge.org >. Eu gosto disso, eu acho, embora eu realmente não estivesse procurando por uma biblioteca.
cjs

É verdade que é uma biblioteca. No entanto, a <800 LOC, é bastante insignificante. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r

1
Eu estava pensando que talvez 30-50 linhas seriam boas, se eu fosse tão longe a ponto de usar uma "biblioteca". Mas, novamente, eu acho que uma vez que você tenha um arquivo separado cheio de código, o design da API é mais importante do que a contagem de linhas. Ainda assim, não tenho certeza se gostaria de incluí-lo em um script único que desejo apenas inserir no diretório bin em um sistema aleatório.
cjs

1
Você entendeu ao contrário: o objetivo é evitar a necessidade de uma estratégia de implantação mais complexa.
cjs

1
Você está totalmente errado, porque está interpretando mal (ou ignorando) as necessidades e intenções da pessoa que fez a pergunta. Eu sugiro que você releia a pergunta com atenção, especialmente os dois pontos finais.
CJS

2

Se você deseja um analisador de linha de comando simples para comandos de chave / valor sem o uso de gems:

Mas isso funciona se você sempre tiver pares de chave / 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']}"

Se você não precisa de nenhuma verificação, pode apenas usar:

opts = {} 

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

2

Este é o snippet de código que uso na parte superior da maioria dos meus 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) }
}

Eu também odeio exigir arquivos adicionais em meus scripts rápidos e sujos. Minha solução é quase o que você está pedindo. Eu colo um trecho de código de 10 linhas no topo de qualquer um dos meus scripts que analisa a linha de comando e coloca args posicionais e muda para um objeto Hash (geralmente atribuído a um objeto que chamei de arghash nos exemplos abaixo).

Aqui está um exemplo de linha de comando que você pode querer analisar ...

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

Que se tornaria um Hash assim.

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

Além disso, dois métodos de conveniência são adicionados ao Hash:

  • argc() retornará a contagem de argumentos não alternativos.
  • switches() devolverá um array contendo as chaves para os interruptores que estão presentes

Isso significa permitir algumas coisas rápidas e sujas como ...

  • Validar se eu tenho o número certo de argumentos posicionais, independentemente das opções passadas em ( arghash.argc == 2)
  • Acesse os argumentos posicionais por sua posição relativa, independentemente das opções que aparecem antes ou intercaladas com os argumentos posicionais (por exemplo, arghash[1]sempre obtém o segundo argumento não-switch).
  • Suporta opções de valor atribuído na linha de comando, como "--max = 15", que pode ser acessado por meio do arghash['--max=']qual produz um valor de '15' dada a linha de comando de exemplo.
  • Teste a presença ou ausência de um switch na linha de comando usando uma notação muito simples, como arghash['-s']qual avalia como verdadeiro se estiver presente e nulo se estiver ausente.
  • Teste a presença de um interruptor ou alternativas de interruptores usando operações definidas como

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

  • Identifique o uso de interruptores inválidos usando operações definidas, como

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

  • Especifique os valores padrão para argumentos ausentes usando um simples Hash.merge(), como o exemplo abaixo que preenche um valor para -max = se um não foi definido e adiciona um 4º argumento posicional se um não foi passado.

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


(Eu editei isso para limpar e melhorar a formatação do código, principalmente usando o alinhamento para deixar a estrutura de bloco e controle mais clara, o que considero especialmente importante em algo tão denso com pontuação. Mas se você odeia a nova formatação, fique à vontade para desfazer a edição.)
cjs

Isso é muito bom, senão a coisa mais fácil do mundo de ler. Eu gosto disso, também demonstra que alterar a "sintaxe" do argumento (aqui, permitindo opções após argumentos posicionais e desaprovando argumentos de opção, exceto pelo uso =) pode fazer a diferença no código de que você precisa.
cjs

Obrigado pela reformatação. É definitivamente obscuro de ler e pode-se facilmente trocar o comprimento do código por clareza. Agora que confio neste código, mais ou menos, eu o trato como uma joia e nunca tento descobrir o que ele está fazendo nos bastidores (então a clareza não é mais importante agora que tenho confiança).
David Foster

1

Isso é muito semelhante à resposta aceita, mas usando ARGV.delete_ifqual é o que eu uso no meu analisador simples . A única diferença real é que as opções com argumentos devem estar juntas (por exemplo -l=file).

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}"

0

Aparentemente, @WilliamMorgan e eu pensamos da mesma forma. Acabei de lançar ontem à noite para o Github o que agora vejo é uma biblioteca semelhante ao Trollop (chamado como?) Depois de ter feito uma pesquisa por OptionParser no Github, consulte Switches

Existem algumas diferenças, mas a filosofia é a mesma. Uma diferença óbvia é que Switches depende de OptionParser.


0

Estou desenvolvendo minha própria gem de analisador de opções chamada Acclaim .

Eu o escrevi porque queria criar interfaces de linha de comando no estilo git e ser capaz de separar claramente a funcionalidade de cada comando em classes separadas, mas também pode ser usado sem a estrutura de comando inteira:

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

Ainda não há versão estável, mas já implementei alguns recursos como:

  • analisador de opção personalizada
  • análise flexível dos argumentos da opção que permite tanto o mínimo quanto o opcional
  • suporte para muitos estilos de opção
  • substituir, acrescentar ou aumentar em várias instâncias da mesma opção
  • manipuladores de opções personalizadas
  • manipuladores de tipo personalizado
  • manipuladores predefinidos para as classes de biblioteca padrão comuns

Há muita ênfase nos comandos, então pode ser um pouco pesado para a análise simples da linha de comando, mas funciona bem e tenho usado em todos os meus projetos. Se você estiver interessado no aspecto da interface de comando, verifique a página GitHub do projeto para obter mais informações e exemplos.


1
Eu recomendo altamente Acclaim. É fácil de usar e possui todas as opções de que você precisa.
bowsersenior

0

Suponha que um comando tenha no máximo uma ação e um número arbitrário de opções como este:

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

A análise sem validação pode ser assim:

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

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end

0

https://github.com/soveran/clap

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

46LOC (em 1.0.0), sem dependência do analisador de opção externa. Faz o trabalho. Provavelmente não tão completo quanto os outros, mas é 46LOC.

Se você verificar o código, poderá facilmente duplicar a técnica subjacente - atribua lambdas e use o arity para garantir que o número adequado de argumentos siga a sinalização se você realmente não quiser uma biblioteca externa.

Simples. Barato.


EDIT : o conceito subjacente se resumiu, suponho que você pode copiar / colar em um script para fazer um analisador de linha de comando razoável. Definitivamente, não é algo que eu guardaria na memória, mas usar a lambda aridade como um analisador barato é uma ideia nova:

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...

Leia o ponto 1 no final da pergunta. Se você não conseguir digitar todo o código necessário em sua resposta aqui, isso não é uma resposta à pergunta.
cjs

Bom ponto! Acho que na época eu presumi que a lib era pequena o suficiente para que você pudesse copiar / colar tudo em qualquer script em que estivesse trabalhando sem precisar de uma dependência externa, mas definitivamente não é uma linha limpa que eu colocaria na memória para cumprir seu ponto # 2. Eu acho que o conceito subjacente é novo e legal o suficiente para que eu fui em frente e fiz uma versão resumida que responde sua pergunta um pouco mais apropriadamente.
Ben Alavi

-1

Vou compartilhar meu próprio analisador de opções simples no qual venho trabalhando há algum tempo. São apenas 74 linhas de código e fazem o básico do que o analisador de opções interno do Git faz. Tomei OptionParser como inspiração, e também do Git.

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

Se parece com isso:

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

Você nem mesmo verificou o código. Eu coloquei outra resposta retirando o código de análise.
FelipeC

Não precisei dizer que você disse que tinha 74 linhas. No entanto, acabei de examinar agora e ainda viola a primeira frase do requisito 2. (Esta resposta também viola a convenção do Stack Overflow de que você deve incluir o código em sua resposta em vez de fornecer um link externo.)
cjs

-1

EasyOptions não requer nenhum código de análise de opção. Basta escrever o texto de ajuda, exigir, pronto.

## 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

EasyOptions é um único arquivo Ruby sem instruções require e não há nenhum código de análise para lembrar. Em vez disso, parece que você deseja algo incorporável que seja poderoso o suficiente, mas simples de lembrar.
Renato Silva
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.