Действительно дешевый синтаксический анализ параметров командной строки в Ruby


114

РЕДАКТИРОВАТЬ: Пожалуйста, пожалуйста , пожалуйста, прочтите два требования, перечисленные внизу этого сообщения, прежде чем отвечать. Люди продолжают публиковать свои новые жемчужины, библиотеки и тому подобное, что явно не соответствует требованиям.

Иногда я хочу очень дешево превратить некоторые параметры командной строки в простой скрипт. Интересный способ сделать это без использования getopts, синтаксического анализа или чего-то подобного:

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

Это не совсем обычный синтаксис параметров Unix, потому что он принимает параметры, не являющиеся параметрами командной строки, как в " myprog -i foo bar -q", но я могу с этим жить. (Некоторые люди, например разработчики Subversion, предпочитают это. Иногда я тоже.)

Вариант, который просто присутствует или отсутствует, не может быть реализован намного проще, чем описанный выше. (Одно присвоение, один вызов функции, один побочный эффект.) Есть ли столь же простой способ работать с параметрами, которые принимают параметр, например " -f имя файла "?

РЕДАКТИРОВАТЬ:

Один момент, о котором я не говорил ранее, потому что мне не становилось ясно, пока автор Trollop не упомянул, что библиотека помещается «в один [800-строчный] файл», заключается в том, что я ищу не только чистый синтаксис, но для техники, имеющей следующие характеристики:

  1. Весь код может быть включен в файл сценария (не перегружая сам сценарий, который может состоять всего из пары десятков строк), так что можно поместить один файл в каталог в binлюбой системе со стандартным Ruby 1.8. . [5-7] установка и использование. Если вы не можете написать сценарий Ruby, в котором нет операторов require, а код для синтаксического анализа нескольких параметров занимает менее десятка строк или около того, вы не выполняете это требование.

  2. Код небольшой и достаточно простой, чтобы его можно было запомнить достаточно, чтобы напрямую ввести код, который сделает свое дело, а не вырезать и вставить где-то еще. Подумайте о ситуации, когда вы находитесь на консоли сервера, защищенного брандмауэром, без доступа к Интернету, и вы хотите быстро собрать сценарий для использования клиентом. Не знаю, как вы, но (помимо невыполнения указанного выше требования) запоминание даже 45 строк упрощенного micro-optparse - это не то, чем я хочу заниматься.


2
Просто любопытно возражение против долгой работы?
Марк Кэри

Многословие этого. С getoptlog иногда код синтаксического анализа параметров длиннее, чем часть сценария, которая фактически выполняет работу. Это не просто эстетическая проблема, это вопрос стоимости обслуживания.
cjs

8
Я не понимаю , требования сценария включения - как getoptlongи optparseв стандартной библиотеке рубина, так что вам не нужно копировать их при развертывании сценария - если рубин работает на этой машине, то require 'optparse'или require 'getoptlong'тоже будет работать.
rampion

См. Stackoverflow.com/questions/21357953/… , а также ответ Уильяма Моргана о Trollop ниже.
fearless_fool

@CurtSampson Я не могу поверить, сколько людей не ответили на ваш вопрос. В любом случае, наконец-то получил хороший ответ о 3 сообщениях на XD XD
OneChillDude

Ответы:


236

Как автор Trollop , я не могу поверить в то, что люди считают разумным в парсере опций. Шутки в сторону. Это поражает воображение.

Зачем мне нужно делать модуль, расширяющий какой-либо другой модуль для анализа параметров? Зачем мне что-то подклассифицировать? Зачем мне нужно подписываться на какую-то «структуру» только для анализа командной строки?

Вот версия вышесказанного от Trollop:

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

И это все. optsТеперь хэш с ключами :quiet, :interactiveи :filename. Вы можете делать с ним все, что хотите. И вы получите красивую страницу справки, отформатированную под ширину экрана, автоматические короткие имена аргументов, проверку типов ... все, что вам нужно.

Это один файл, поэтому вы можете поместить его в свой каталог lib /, если не хотите формальной зависимости. Он имеет минимальный DSL, который легко подобрать.

LOC на выбор людей. Это имеет значение.


39
Кстати, +1 за то, что написал Trollop (о котором уже упоминалось здесь), но не стесняйтесь немного смягчить первый абзац.
cjs

33
Боюсь, в этом случае он имеет право жаловаться. Когда вы смотрите на альтернативы: [1 ] [2 ] [3 ], для того, что в основном представляет собой просто обработку простого массива строк (нет, на самом деле, позвольте этому погрузиться), вы не можете не задаться вопросом, ПОЧЕМУ? Что вы получаете от всего этого раздувания? Это не C, где строки «проблематичны». Конечно каждому свое. :)
srcspider

50
Пожалуйста, не смягчайте это немного. Это праведная стяжка, брат.
Уильям Пьетри,

7
Не стесняйтесь немного смягчить десятое слово.
Эндрю Гримм

3
+1 для Троллопа. Я использую его для своей системы автоматизации тестирования, и он просто работает. Кроме того, с его помощью так легко кодировать, что иногда я переставляю свой баннер, просто чтобы испытать радость.
kinofrost

76

Я разделяю ваше отвращение require 'getopts', в основном из-за того, что 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
Спасибо, я не вижу, как это не соответствует запросу OP, особенно учитывая, что все это в стандартной библиотеке, по сравнению с необходимостью установки / продажи любого нестандартного кода
dolzenko

3
это выглядит так же, как версия для троллопа, за исключением того, что дополнительный файл не требуется.
Claudiu

59

Вот стандартная техника, которую я обычно использую:

#!/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
Отвечает на вопрос, но, черт возьми, с Троллопом, похоже, справиться намного проще. Зачем изобретать велосипед, если готовое колесо намного более гладкое?
Mikey TK

7
Готовое колесо не более гладкое. Прочтите вопрос еще раз внимательно, обращая особое внимание на требования.
cjs 03

2
+1 Иногда вам нужно изобретать велосипед, потому что вы не хотите или просто не можете использовать другие зависимости, такие как Trollop.
lzap

Trollop не нужно устанавливать как драгоценный камень. Вы можете просто поместить один файл в свою libпапку или код и использовать его, даже не касаясь rubygems.
Overbryd 01

Для меня мне пришлось перейти when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")на, when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")иначе
получился

36

Поскольку никто не упомянул об этом, а заголовок действительно относится к дешевому синтаксическому анализу командной строки, почему бы просто не позволить интерпретатору Ruby сделать эту работу за вас? Если вы передадите -sпереключатель (например, в вашем shebang), вы бесплатно получите простые переключатели, назначенные однобуквенным глобальным переменным. Вот ваш пример с использованием этого переключателя:

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

И вот результат, когда я сохраняю это как ./testи 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"]

Подробнее ruby -hсм.

Это должно быть максимально дешево. Он вызовет NameError, если вы попробуете переключить, например -:, так что там есть некоторая проверка. Конечно, у вас не может быть никаких переключателей после аргумента без переключателя, но если вам нужно что-то необычное, вам действительно стоит использовать как минимум OptionParser. Фактически, единственное, что меня раздражает в этой технике, это то, что вы получите предупреждение (если вы включили их) при доступе к неустановленной глобальной переменной, но оно все равно ложно, поэтому отлично работает для одноразовых инструментов и быстрого скрипты.

Одно предостережение, указанное FelipeC в комментариях к статье « Как сделать действительно дешевый синтаксический анализ параметров командной строки в Ruby », заключается в том, что ваша оболочка может не поддерживать 3-токеновый шебанг; вам может потребоваться заменить /usr/bin/env ruby -wфактический путь к вашему рубину (например /usr/local/bin/ruby -w) или запустить его из сценария-оболочки или что-то в этом роде.


2
Спасибо :) Я очень надеюсь, что он не ждал этого ответа последние два года.
DarkHeart

3
Я действительно ждал этого ответа последние два года. :-) Если серьезно, я искал именно такого рода умное мышление. Предупреждение немного раздражает, но я могу придумать способы смягчить его.
cjs 04

Рад, что смог (в конце концов) помочь, @CurtSampson. Флаги MRI взяты прямо из Perl, где они, как правило, используются бесплатно в однострочниках оболочки. Не стесняйтесь принять, если ответ все еще вам полезен. :)
bjjb 06

1
Вы не можете использовать несколько аргументов в shebang в Linux. / usr / bin / env: 'ruby -s': нет такого файла или каталога
FelipeC

13

Я создал micro-optparse, чтобы удовлетворить эту очевидную потребность в коротком, но простом в использовании парсере опций. Он имеет синтаксис, аналогичный Trollop, и состоит из 70 строк. Если вам не нужны проверки и можно обойтись без пустых строк, вы можете сократить его до 45 строк. Я думаю, это именно то, что вы искали.

Краткий пример:

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!

Вызов скрипта с помощью -hили --helpраспечатает

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

Он проверяет, имеет ли ввод тот же тип, что и значение по умолчанию, генерирует короткие и длинные методы доступа, печатает описательные сообщения об ошибках, если указаны недопустимые аргументы, и многое другое.

Я сравнил несколько парсеров опций , используя каждый парсер опций для моей проблемы. Вы можете использовать эти примеры и мое резюме, чтобы принять информативное решение. Не стесняйтесь добавлять в список другие реализации. :)


Сама библиотека может показаться отличной. Однако не будет ли неискренне сравнивать количество строк с Trollop, поскольку вы зависите и требуете, optparseчто составляет (плюс-минус) 1937 строк.
Телемах,

6
Сравнивать количество строк абсолютно нормально, так как optparse как это библиотека по умолчанию, то есть она поставляется с каждой установкой Ruby. Trollopявляется сторонней библиотекой, поэтому вы должны импортировать полный код каждый раз, когда хотите включить его в проект. µ-optparse всегда требует только ~ 70 строк, поскольку они optparseуже есть.
Флориан Пильц

8

Я полностью понимаю, почему вы хотите избежать optparse - его может оказаться слишком много. Но есть несколько гораздо более «легких» решений (по сравнению с OptParse), которые поставляются в виде библиотек, но достаточно просты, чтобы сделать установку одного гема оправданной.

Например, посмотрите этот пример OptiFlag . Всего несколько строк на обработку. Слегка усеченный пример для вашего случая:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

Также существует масса индивидуальных примеров . Я помню, как использовал другой, который был еще проще, но пока он ускользнул от меня, но я вернусь и добавлю здесь комментарий, если найду его.


Не стесняйтесь редактировать свой ответ, чтобы он лучше соответствовал уточненному вопросу.
cjs 02

4

Вот что я использую для действительно очень дешевых аргументов:

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

main

поэтому, если вы запустите, programname foo barон вызовет foo, а затем bar. Это удобно для одноразовых скриптов.


3

Вы можете попробовать что-то вроде:

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

3

Вы считали Тора викатами? Я думаю, что это намного чище, чем optparse. Если у вас уже есть сценарий, может потребоваться дополнительная работа по его форматированию или рефакторингу для tor, но это действительно упрощает работу с параметрами.

Вот пример фрагмента из 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

Тор автоматически отображает команды как таковые:

app install myname --force

Это преобразуется в:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Наследовать от Тора, чтобы превратить класс в сопоставитель параметров
  2. Сопоставьте дополнительные недействительные идентификаторы с определенными методами. В этом случае преобразуйте -L в: список
  3. Опишите метод сразу ниже. Первый параметр - это информация об использовании, а второй - описание.
  4. Предоставьте любые дополнительные возможности. Они будут собраны из - и - params. В этом случае добавляются опции --force и -f.

Мне нравится отображение команд, поскольку я часто делаю один двоичный файл с кучей подкоманд. Тем не менее, хоть ты и ушел далеко от «света». Не могли бы вы найти еще более простой способ выразить ту же функциональность? Что делать, если вам не нужно печатать --helpвывод? Что, если бы вывод справки был "head myprogram.rb"?
cjs

3

Вот мой любимый парсер быстрых и грязных опций:

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

Параметры являются регулярными выражениями, поэтому "-h" также будет соответствовать "--help".

Читаемый, легко запоминающийся, без внешней библиотеки и минимальный код.


Да, было бы. Если это проблема, вы можете добавить больше регулярных выражений, например/-h(\b|elp)
EdwardTeach

2

Троллоп стоит довольно дешево.


Это было бы, < trollop.rubyforge.org >. Думаю, мне это скорее нравится, хотя я действительно не искал библиотеку.
cjs

Правда, это библиотека. Однако при LOC <800 это довольно незначительно. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r

1
Я вроде как подумал, что, может быть, 30-50 строк было бы неплохо, если бы я зашел так далеко, чтобы использовать «библиотеку». Но опять же, я думаю, что когда вы дойдете до отдельного файла, полного кода, дизайн API станет более важным, чем количество строк. Тем не менее, я не уверен, что захочу включить его в одноразовый скрипт, который я просто хочу поместить в каталог bin в случайной системе.
cjs

1
У вас все наоборот: суть в том, чтобы избежать необходимости иметь более сложную стратегию развертывания.
cjs 09

1
Вы ошибаетесь, потому что полностью неверно истолковываете (или игнорируете) потребности и намерения человека, задавшего вопрос. Я предлагаю вам внимательно перечитать вопрос, особенно последние два пункта.
cjs 09

2

Если вам нужен простой парсер командной строки для команд ключ / значение без использования драгоценных камней:

Но это работает, только если у вас всегда есть пары ключ / значение.

# 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']}"

Если вам не нужна какая-либо проверка, вы можете просто использовать:

opts = {} 

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

2

Вот фрагмент кода, который я использую в верхней части большинства своих скриптов:

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

Я также ненавижу требовать дополнительных файлов в моих быстрых и грязных сценариях. Мое решение очень близко к тому, о чем вы просите. Я вставляю 10-строчный фрагмент кода в верхнюю часть любого из моих сценариев, который анализирует командную строку, вставляет позиционные аргументы и переключается на объект Hash (обычно назначаемый объекту, который я назвал arghash в примерах ниже).

Вот пример командной строки, которую вы можете захотеть проанализировать ...

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

Что станет таким хешем.

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

В дополнение к этому к хешу добавляются два удобных метода:

  • argc() вернет количество непереключаемых аргументов.
  • switches() вернет массив, содержащий ключи для имеющихся переключателей

Это означает, что можно позволить некоторые быстрые и грязные вещи вроде ...

  • Убедитесь, что у меня правильное количество позиционных аргументов независимо от переключателей, переданных в ( arghash.argc == 2 )
  • Доступ к позиционным аргументам по их относительному положению, независимо от того, какие переключатели появляются перед позиционными аргументами или перемежаются с ними (например, arghash[1] всегда получает второй аргумент без переключателя).
  • Поддержка переключателей с присвоенными значениями в командной строке, таких как "--max = 15", к которым может получить доступ arghash['--max='] что дает значение «15» с учетом примера командной строки.
  • Проверьте наличие или отсутствие переключателя в командной строке, используя очень простые обозначения, такие как arghash['-s'] которая принимает значение true, если он присутствует, и nil, если его нет.
  • Проверьте наличие переключателя или альтернативных переключателей, используя операции установки, такие как

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

  • Определите использование недопустимых переключателей, используя операции с наборами, такие как

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

  • Укажите значения по умолчанию для отсутствующих аргументов, используя простой Hash.merge()пример, такой как приведенный ниже пример, который заполняет значение для -max =, если он не был установлен, и добавляет 4-й позиционный аргумент, если он не был передан.

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


(Я отредактировал это, чтобы улучшить форматирование кода, в основном используя выравнивание, чтобы сделать структуру блоков и элементов управления более четкими, что, на мой взгляд, особенно важно в чем-то столь плотном с пунктуацией. Но если вы ненавидите новое форматирование, пожалуйста, не стесняйтесь чтобы отменить редактирование.)
cjs

Это довольно приятный, если не самый простой в мире текст для чтения. Мне нравится, что это также демонстрирует, что изменение «синтаксиса» аргумента (здесь, разрешение опций после позиционных аргументов и запрещение аргументов опций, кроме как с использованием =) может повлиять на нужный вам код.
cjs

Спасибо за переформатирование. Это определенно непонятно для чтения, и можно легко поменять длину кода на ясность. Теперь, когда я более или менее доверяю этому коду, я отношусь к нему как к драгоценному камню и никогда не пытаюсь понять, что он делает под покровом (так что ясность больше не важна, когда у меня есть доверие).
Дэвид Фостер

1

Это очень похоже на принятый ответ, но ARGV.delete_ifя использую его в своем простом парсере . Единственная реальная разница в том, что параметры с аргументами должны быть вместе (например -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

Очевидно, мы с @WilliamMorgan одинаково думаем. Вчера вечером я выпустил на Github библиотеку, похожую на Trollop (названную как?), После поиска OptionParser на Github, см. Переключатели.

Есть несколько отличий, но философия та же. Одно очевидное отличие состоит в том, что Switches зависит от OptionParser.


0

Я разрабатываю свой собственный гем синтаксического анализатора под названием Acclaim .

Я написал его, потому что хотел создать интерфейсы командной строки в стиле git и иметь возможность четко разделить функциональность каждой команды на отдельные классы, но ее также можно использовать без всей командной структуры:

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

Стабильной версии пока нет, но я уже реализовал некоторые функции, такие как:

  • парсер настраиваемых параметров
  • гибкий анализ аргументов опции, позволяющий использовать как минимальные, так и необязательные
  • поддержка многих стилей опций
  • заменить, добавить или поднять в нескольких экземплярах одного и того же параметра
  • обработчики пользовательских опций
  • обработчики настраиваемых типов
  • предопределенные обработчики для общих классов стандартной библиотеки

Большое внимание уделяется командам, поэтому для простого синтаксического анализа командной строки это может быть немного сложно, но оно работает хорошо, и я использовал его во всех своих проектах. Если вас интересует аспект командного интерфейса , посетите страницу проекта GitHub для получения дополнительной информации и примеров.


1
Я очень рекомендую Acclaim. Он прост в использовании и имеет все необходимые параметры.
bowsersenior

0

Предположим, что у команды есть не более одного действия и произвольное количество опций, например:

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

Разбор без проверки может быть таким:

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 (в 1.0.0), нет зависимости от внешнего синтаксического анализатора параметров. Выполняет свою работу. Возможно, не так полнофункциональный, как другие, но это 46LOC.

Если вы проверите код, вы можете довольно легко продублировать базовый метод - назначить лямбды и использовать арность, чтобы обеспечить правильное количество аргументов, следующих за флагом, если вам действительно не нужна внешняя библиотека.

Просто. Дешевые.


РЕДАКТИРОВАТЬ : основная концепция сводится к тому, что, я полагаю, вы можете скопировать / вставить ее в скрипт, чтобы создать разумный синтаксический анализатор командной строки. Это определенно не то, что я бы запомнил, но использование лямбда-арности в качестве дешевого парсера - новая идея:

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

Пожалуйста, прочтите пункт 1 в конце вопроса. Если вы не можете ввести здесь весь необходимый код прямо в своем ответе, это не ответ на вопрос.
cjs

Хорошая точка зрения! Я думаю, что в то время я предполагал, что библиотека была достаточно маленькой, чтобы вы могли скопировать / вставить все это в любой сценарий, над которым вы работали, без необходимости во внешней зависимости, но это определенно не чистый однострочный файл, который я бы зафиксировал в памяти чтобы выполнить вашу точку # 2. Я действительно думаю, что основная концепция нова и достаточно крута, поэтому я пошел дальше и сделал упрощенную версию, которая немного более адекватно отвечает на ваш вопрос.
Бен Алави

-1

Я собираюсь поделиться своим собственным простым анализатором параметров, над которым я работал некоторое время. Это всего лишь 74 строки кода, и он выполняет основы того, что делает внутренний синтаксический анализатор параметров Git. Я взял за основу OptionParser, а также Git.

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

Выглядит это так:

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

Мне не нужно было знать, что вы сказали, что это 74 строки. Однако я только что посмотрел на него, и он по-прежнему нарушает первое предложение требования 2. (Этот ответ также нарушает соглашение о переполнении стека, согласно которому вы должны включить код в свой ответ, а не давать ссылку за пределами сайта.)
cjs

-1

EasyOptions вообще не требует кода синтаксического анализа параметров. Просто напишите текст справки, требуется, готово.

## 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 - это отдельный файл Ruby без операторов require, и здесь вообще нет кода синтаксического анализа, который нужно запомнить. Кажется, вместо этого вы хотите что-то встраиваемое, достаточно мощное, но простое для запоминания.
Ренато Силва
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.