Naprawdę tanie parsowanie opcji wiersza poleceń w Rubim

114

EDIT: Proszę, proszę , proszę zapoznać się z wymaganiami dwa wymienione na dole tego postu, zanim odpowiedział. Ludzie wciąż publikują swoje nowe perełki, biblioteki i tak dalej, co najwyraźniej nie spełnia wymagań.

Czasami chcę bardzo tanio zhakować niektóre opcje wiersza poleceń do prostego skryptu. Zabawnym sposobem na zrobienie tego bez zajmowania się getopts, parsowaniem lub czymkolwiek w tym stylu jest:

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

Nie jest to do końca normalna składnia opcji Unixa, ponieważ akceptuje opcje nie będące opcjami parametry wiersza poleceń, jak w " myprog -i foo bar -q", ale mogę z tym żyć. (Niektórzy ludzie, na przykład programiści Subversion, wolą to. Czasami ja też.)

Opcji, która jest po prostu obecna lub nieobecna, nie można zaimplementować w prostszy sposób niż powyższe. (Jedno przypisanie, jedno wywołanie funkcji, jeden efekt uboczny). Czy istnieje równie prosty sposób radzenia sobie z opcjami, które pobierają parametr, na przykład „ -f nazwa pliku ”?

EDYTOWAĆ:

Jedna uwaga, której nie powiedziałem wcześniej, ponieważ nie stało się dla mnie jasne, dopóki autor Trollop nie wspomniał, że biblioteka mieści się „w jednym [800-wierszowym] pliku”, jest to, że szukam nie tylko czystego składnia, ale dla techniki, która ma następujące cechy:

  1. Cały kod można zawrzeć w pliku skryptu (bez przytłaczania samego skryptu, który może mieć tylko kilkadziesiąt linii), dzięki czemu można upuścić pojedynczy plik w katalogu binna dowolnym systemie ze standardowym Ruby 1.8 , [5-7] instalacji i używaj go. Jeśli nie możesz napisać skryptu Rubiego, który nie zawiera instrukcji wymagających, a kod do parsowania kilku opcji ma mniej niż kilkanaście wierszy, nie spełnisz tego wymagania.

  2. Kod jest mały i na tyle prosty, że można go zapamiętać na tyle, aby bezpośrednio wpisać kod, który załatwi sprawę, zamiast wycinać i wklejać z innego miejsca. Pomyśl o sytuacji, w której jesteś na konsoli serwera z zaporą ogniową bez dostępu do Internetu i chcesz wrzucić razem szybki skrypt do użycia przez klienta. Nie wiem jak Ty, ale (poza niespełnieniem powyższego wymagania) zapamiętywanie nawet 45 linijek uproszczonej mikroopcji nie jest czymś, na czym mi zależy.

cjs
źródło
2
Ciekawi Cię tylko sprzeciw wobec getoptlong?
Mark Carey,
Szczegółowość tego. W getoptlog czasami kod parsujący opcje jest dłuższy niż część skryptu, która faktycznie działa. Nie jest to tylko kwestia estetyczna, ale kwestia kosztów utrzymania.
cjs
8
Nie rozumiem wymóg integracji skryptu - zarówno getoptlongi optparsesą w standardowej bibliotece Ruby, więc nie ma potrzeby kopiowania ich podczas wdrażania skryptu - jeżeli rubin działa na tej maszynie, to require 'optparse'czy require 'getoptlong'będzie działać zbyt.
rampion
Zobacz stackoverflow.com/questions/21357953/… , a także poniżej odpowiedź Williama Morgana na temat Trollopa.
Fearless_fool
@CurtSampson Nie mogę uwierzyć, ile osób nie odpowiedziało na Twoje pytanie. Tak czy inaczej, w końcu dostałem dobrą odpowiedź na temat 3 postów w dół XD XD
OneChillDude

Odpowiedzi:

235

Jako autor Trollop , nie mogę WIERZYĆ rzeczy, które ludzie uważają za rozsądne w parserze opcji. Poważnie. To zdumiewa umysł.

Dlaczego powinienem tworzyć moduł, który rozszerza inny moduł, aby analizować opcje? Dlaczego powinienem cokolwiek tworzyć podklasy? Dlaczego miałbym subskrybować jakiś „framework” tylko po to, aby przeanalizować wiersz poleceń?

Oto wersja powyższego Trollopa:

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

I to wszystko. optsobecnie jest hash z kluczami :quiet, :interactivei :filename. Możesz z nim zrobić, co chcesz. Otrzymujesz piękną stronę pomocy, sformatowaną tak, aby pasowała do szerokości ekranu, automatyczne krótkie nazwy argumentów, sprawdzanie typów ... wszystko, czego potrzebujesz.

To jeden plik, więc możesz go upuścić w swoim katalogu lib /, jeśli nie chcesz formalnej zależności. Ma minimalne DSL, które można łatwo odebrać.

LOC na opcję osób. To ma znaczenie.

Wolność_Ben
źródło
39
Przy okazji, +1 za napisanie Trollopa (o którym już tu wspomniano), ale nie krępuj się nieco stonować pierwszy akapit.
cjs
33
Obawiam się, że w tej sprawie ma prawo narzekać. Kiedy spojrzysz na alternatywy: [1 ] [2 ] [3 ], ponieważ po prostu przetwarza prostą tablicę ciągów (nie, naprawdę, pozwól temu zagłębić się), nie możesz powstrzymać się od zastanowienia DLACZEGO? Co zyskujesz z tego całego wzdęcia? To nie jest C, gdzie ciągi są „problematyczne”. Oczywiście dla każdego własnego. :)
srcspider
50
Proszę, nie stonuj tego trochę. To prawy jacht, bracie.
William Pietri
7
Możesz nieco złagodzić dziesiąte słowo.
Andrew Grimm,
3
+1 dla Trollopa. Używam go do mojego systemu automatyzacji testów i po prostu działa. Poza tym jest to tak łatwe do kodowania, że ​​czasami zmieniam mój baner, aby doświadczyć radości z tego.
kinofrost
76

Podzielam wasz odrazę do require 'getopts', głównie z powodu niesamowitości, jaką jest 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
źródło
6
Dzięki, nie widzę, jak to nie pasuje do żądania OP, zwłaszcza biorąc pod uwagę, że wszystko jest w standardowej bibliotece, w porównaniu z potrzebą instalowania / sprzedawania dowolnego niestandardowego kodu
dolzenko
3
wygląda to tak samo, jak wersja trollop, z wyjątkiem tego, że nie wymaga dodatkowego pliku.
Claudiu
59

Oto standardowa technika, której zwykle używam:

#!/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
źródło
3
Odpowiada na pytanie, ale człowieku, Trollop wydaje się być o wiele łatwiejszy w obsłudze. Po co wymyślać koło na nowo, skoro gotowe koło jest o wiele gładsze?
Mikey TK
7
Gotowe koło nie jest gładsze. Przeczytaj uważnie pytanie ponownie, zwracając szczególną uwagę na wymagania.
cjs
2
+1 Czasami musisz odkryć koło na nowo, ponieważ nie chcesz lub po prostu nie możesz użyć innych zależności, takich jak Trollop.
lzap
Trollop nie musi być instalowany jako klejnot. Możesz po prostu upuścić jeden plik w libfolderze lub kodzie i używać go bez dotykania rubygemów.
Overbryd
Dla mnie musiałem zmienić when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")na when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")lub wpadłby w nieskończoną pętlę użytkowania
casey
36

Ponieważ nikt nie pojawił się, żeby go wymienić, a tytuł ma odnosić się do taniego wiersza polecenia parsowania, dlaczego po prostu nie pozwól Ruby tłumacza pracować dla Ciebie? Jeśli przejdziesz -sprzełącznik (na przykład w swoim shebang), otrzymasz za darmo proste przełączniki, przypisane do jednoliterowych zmiennych globalnych. Oto przykład użycia tego przełącznika:

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

A oto wynik, kiedy zapiszę to jako ./testi 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"]

Zobacz ruby -hszczegóły.

To musi być tak tanie, jak to tylko możliwe. Spowoduje to zgłoszenie błędu NameError, jeśli spróbujesz przełączyć się -:, więc jest tam pewna walidacja. Oczywiście nie możesz mieć żadnych przełączników po argumencie bez przełączania, ale jeśli potrzebujesz czegoś wymyślnego, naprawdę powinieneś używać co najmniej OptionParser. Właściwie jedyną rzeczą, która mnie denerwuje w tej technice, jest to, że otrzymasz ostrzeżenie (jeśli je włączyłeś) podczas uzyskiwania dostępu do nieustawionej zmiennej globalnej, ale nadal jest to błąd, więc działa dobrze w przypadku narzędzi jednorazowych i szybkich skrypty.

Jedynym zastrzeżeniem, na które zwrócił uwagę FelipeC w komentarzach w " Jak zrobić naprawdę tanią analizę opcji wiersza poleceń w Rubim ", jest to, że twoja powłoka może nie obsługiwać 3-znakowego shebang; może zajść potrzeba zastąpienia /usr/bin/env ruby -wrzeczywistą ścieżką do twojego ruby ​​(np. /usr/local/bin/ruby -w), uruchomienia go ze skryptu opakowującego lub czegoś podobnego.

bjjb
źródło
2
Dzięki :) Mam nadzieję, że nie czekał na tę odpowiedź od dwóch lat.
DarkHeart
3
Rzeczywiście czekałem na tę odpowiedź od dwóch lat. :-) A mówiąc poważnie, właśnie takiego sprytnego myślenia szukałem. Ostrzeżenie jest nieco denerwujące, ale mogę wymyślić sposoby na złagodzenie tego.
cjs
Cieszę się, że mogłem (w końcu) pomóc, @CurtSampson, flagi MRI są wyrwane prosto z Perla, gdzie są zwykle używane nieodpłatnie w jednowarstwowych powłokach. Zapraszam do zaakceptowania, jeśli odpowiedź jest nadal przydatna. :)
bjjb
1
W Linuksie nie można używać wielu argumentów w shebang. / usr / bin / env: 'ruby -s': Nie ma takiego pliku lub katalogu
FelipeC
13

Zbudowałem mikro-optparse, aby wypełnić tę oczywistą potrzebę krótkiego, ale łatwego w użyciu parsera opcji. Ma składnię podobną do Trollopa i jest krótka o 70 linii. Jeśli nie potrzebujesz walidacji i możesz obejść się bez pustych wierszy, możesz zmniejszyć liczbę do 45 wierszy. Myślę, że właśnie tego szukałeś.

Krótki przykład:

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!

Wywołanie skryptu za pomocą -hlub --helpspowoduje wydrukowanie

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

Sprawdza, czy dane wejściowe są tego samego typu, co wartość domyślna, generuje krótkie i długie metody dostępu, wyświetla opisowe komunikaty o błędach w przypadku podania nieprawidłowych argumentów i nie tylko.

I w porównaniu kilka Option parser za pomocą każdego Option parser do problemu miałem. Możesz skorzystać z tych przykładów i mojego podsumowania, aby podjąć pouczającą decyzję. Zapraszam do dodawania kolejnych realizacji do listy. :)

Florian Pilz
źródło
Sama biblioteka wygląda na to, że może być świetna. Jednak czy porównywanie liczby linii z Trollopem nie jest nieszczere, ponieważ polegasz na optparse1937 liniach i wymagasz od nich (dajesz lub bierzesz).
Telemachus
6
Porównywanie liczby linii jest absolutnie OK, ponieważ optparsejest to biblioteka domyślna, tj. Jest dostarczana z każdą instalacją Rubiego. Trollopjest biblioteką innej firmy, dlatego za każdym razem, gdy chcesz dołączyć go do projektu, musisz zaimportować cały kod. µ-optparse zawsze wymaga tylko ~ 70 linii, ponieważ optparsejuż tam są.
Florian Pilz
8

Całkowicie rozumiem, dlaczego chcesz uniknąć optparse - może to być za dużo. Ale jest kilka znacznie „lżejszych” rozwiązań (w porównaniu do OptParse), które są dostępne jako biblioteki, ale są na tyle proste, że opłacalna jest pojedyncza instalacja klejnotów.

Na przykład sprawdź ten przykład OptiFlag . Tylko kilka wierszy do przetworzenia. Nieco skrócony przykład dostosowany do Twojego przypadku:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

Istnieje również mnóstwo niestandardowych przykładów . Pamiętam, że korzystałem z innego, który był jeszcze łatwiejszy, ale na razie mi to umknęło, ale wrócę i dodam tutaj komentarz, jeśli go znajdę.

Peter Cooper
źródło
Zachęcamy do edycji odpowiedzi, aby lepiej odpowiadała wyjaśnionemu pytaniu.
cjs
4

Oto, czego używam do naprawdę, naprawdę tanich argumentów:

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

main

więc jeśli uruchomisz programname foo bar, wywoła foo, a następnie bar. Jest to przydatne w przypadku jednorazowych skryptów.

chryzmatyczny
źródło
3

Możesz spróbować czegoś takiego:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end
Stefan
źródło
3

Czy rozważałeś Thor by wycats? Myślę, że jest o wiele czystszy niż optparse. Jeśli masz już napisany skrypt, sformatowanie go lub refaktoryzacja dla thora może wymagać trochę więcej pracy, ale sprawia, że ​​obsługa opcji jest bardzo prosta.

Oto przykładowy fragment z pliku 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 automatycznie mapuje polecenia takie jak:

app install myname --force

To zostanie przekonwertowane na:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Dziedzicz po Thorze, aby zmienić klasę w program mapujący opcje
  2. Odwzoruj dodatkowe nieprawidłowe identyfikatory na określone metody. W tym przypadku przekonwertuj -L na: list
  3. Opisz metodę bezpośrednio poniżej. Pierwszy parametr to informacje o użytkowaniu, a drugi to opis.
  4. Podaj dodatkowe opcje. Będą one zbierane z - i - parametrów. W tym przypadku dodawane są opcje --force i -f.
Jack Chu
źródło
Podoba mi się mapowanie poleceń, ponieważ często wykonuję pojedynczy plik binarny z kilkoma podpoleceniami. Mimo to odeszliście od „światła”. Czy mógłbyś znaleźć jeszcze prostszy sposób na wyrażenie tej samej funkcjonalności? A co jeśli nie musisz drukować --helpwyników? A co jeśli "head myprogram.rb" był wyjściem pomocy?
cjs
3

Oto mój ulubiony parser opcji szybkiego i łatwego:

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

Opcje są wyrażeniami regularnymi, więc „-h” również pasowałoby do „--help”.

Czytelny, łatwy do zapamiętania, brak zewnętrznej biblioteki i minimalny kod.

EdwardTeach
źródło
Tak, będzie. Jeśli jest to problem, możesz dodać więcej /-h(\b|elp)
wyrażeń
2

Trollop jest dość tani.

g33kz0r
źródło
To byłoby < trollop.rubyforge.org >. Wydaje mi się, że raczej mi się to podoba, chociaż tak naprawdę nie szukałem biblioteki.
cjs
To prawda, to biblioteka. Jednak przy <800 LOC jest to dość pomijalne. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r
1
Myślałem, że może 30-50 wierszy byłoby dobre, gdybym posunął się do tego, żeby użyć „biblioteki”. Ale z drugiej strony, myślę, że kiedy już uzyskasz oddzielny plik pełen kodu, projekt API jest ważniejszy niż liczba wierszy. Mimo to nie jestem pewien, czy chciałbym włączyć go do jednorazowego skryptu, który chcę po prostu umieścić w katalogu bin w losowym systemie.
cjs
1
Masz to wstecz: chodzi o to, aby uniknąć konieczności posiadania bardziej złożonej strategii wdrażania.
cjs
1
Całkowicie się mylisz, ponieważ całkowicie błędnie interpretujesz (lub ignorujesz) potrzeby i intencje osoby, która zadała pytanie. Proponuję ponownie uważnie przeczytać pytanie, a zwłaszcza dwa ostatnie punkty.
cjs
2

Jeśli potrzebujesz prostego parsera wiersza poleceń dla poleceń klucz / wartość bez użycia klejnotów:

Ale to działa tylko wtedy, gdy zawsze masz pary klucz / wartość.

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

Jeśli nie potrzebujesz sprawdzania, możesz po prostu użyć:

opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end
zrozumiałem
źródło
2

Oto fragment kodu, którego używam u góry większości moich skryptów:

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

Nienawidzę również wymagać dodatkowych plików w moich szybkich skryptach. Moje rozwiązanie jest prawie tym, o co prosisz. Wklejam 10-wierszowy fragment kodu na górze dowolnego z moich skryptów, który analizuje wiersz poleceń i umieszcza argumenty pozycyjne i przełącza się w obiekt Hash (zwykle przypisany do obiektu, który nazwałem arghash w poniższych przykładach).

Oto przykładowy wiersz poleceń, który możesz chcieć przeanalizować ...

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

Który stałby się takim hasłem.

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

Oprócz tego do skrótu dodano dwie wygodne metody:

  • argc() zwróci liczbę argumentów bez przełączania.
  • switches() zwróci tablicę zawierającą klucze dla obecnych przełączników

Oznacza to pozwolenie na szybkie i brudne rzeczy, takie jak ...

  • Sprawdź poprawność Mam odpowiednią liczbę argumentów pozycyjnych niezależnie od przełączników przekazanych w ( arghash.argc == 2 )
  • Uzyskuj dostęp do argumentów pozycyjnych poprzez ich względne położenie, niezależnie od przełączników występujących przed lub przeplatanych argumentami pozycyjnymi (np. arghash[1]Zawsze pobiera drugi argument nie przełączający).
  • Obsługa przełączników z przypisaną wartością w wierszu poleceń, takich jak „--max = 15”, do których można uzyskać dostęp, arghash['--max=']co daje wartość „15”, biorąc pod uwagę przykładowy wiersz poleceń.
  • Sprawdź obecność lub brak przełącznika w wierszu poleceń, używając bardzo prostej notacji, takiej jak arghash['-s']wartość true, jeśli jest obecny i zero, jeśli jej nie ma.
  • Sprawdź obecność przełącznika lub alternatywnych przełączników za pomocą operacji na zestawach, takich jak

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

  • Zidentyfikuj użycie nieprawidłowych przełączników za pomocą operacji na zestawach, takich jak

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

  • Określ wartości domyślne dla brakujących argumentów, używając prostego Hash.merge()przykładu, takiego jak poniższy przykład, który wypełnia wartość -max =, jeśli nie została ustawiona, i dodaje czwarty argument pozycyjny, jeśli nie został przekazany.

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

David Foster
źródło
(Edytowałem to, aby wyraźnie poprawić formatowanie kodu, głównie używając wyrównania, aby uczynić blok i strukturę kontrolną bardziej przejrzystą, co moim zdaniem jest szczególnie ważne w czymś tak gęstym z interpunkcją. Ale jeśli nienawidzisz nowego formatowania, nie krępuj się aby cofnąć edycję.)
cjs
To całkiem fajne, jeśli nie najłatwiejsza do przeczytania rzecz na świecie. Podoba mi się, że to również pokazuje, że zmiana "składni" argumentu (tutaj zezwolenie na opcje po argumentach pozycyjnych i niedozwolenie argumentów opcji z wyjątkiem użycia =) może mieć wpływ na potrzebny kod.
cjs
Dzięki za przeformatowanie. Jest zdecydowanie niejasny do odczytania i można łatwo wymienić długość kodu dla przejrzystości. Teraz, gdy ufam temu kodowi, traktuję go jak klejnot i nigdy nie próbuję dowiedzieć się, co robi pod okładkami (więc jasność nie jest już ważna, teraz, gdy mam zaufanie).
David Foster,
1

Jest to bardzo podobne do zaakceptowanej odpowiedzi, ale używając ARGV.delete_iftego, czego używam w moim prostym parserze . Jedyną prawdziwą różnicą jest to, że opcje z argumentami muszą być razem (np -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}"
FelipeC
źródło
0

Najwyraźniej @WilliamMorgan i myślę podobnie. Właśnie opublikowałem wczoraj wieczorem na Github, co teraz widzę, jest podobną biblioteką do Trollop (Nazwana jak?) Po wyszukaniu OptionParser na Github, zobacz Przełączniki

Jest kilka różnic, ale filozofia jest taka sama. Jedną oczywistą różnicą jest to, że przełączniki są zależne od OptionParser.

Thoran
źródło
0

Rozwijam się własny klejnot parsera opcji o nazwie Acclaim .

Napisałem go, ponieważ chciałem stworzyć interfejsy wiersza poleceń w stylu git i móc wyraźnie oddzielić funkcjonalność każdego polecenia na osobne klasy, ale można go również używać bez całej struktury poleceń:

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

Na razie brak stabilnej wersji, ale zaimplementowałem już kilka funkcji, takich jak:

  • parser opcji niestandardowych
  • elastyczne parsowanie argumentów opcji, które dopuszcza zarówno wartość minimalną, jak i opcjonalną
  • obsługa wielu stylów opcji
  • zamień, dołącz lub podnieś w wielu wystąpieniach tej samej opcji
  • niestandardowe programy obsługi opcji
  • programy obsługi typów niestandardowych
  • predefiniowane programy obsługi dla typowych klas bibliotek standardowych

Duży nacisk kładzie się na polecenia, więc może to być trochę trudne do prostego analizowania wiersza poleceń, ale działa dobrze i używam go we wszystkich moich projektach. Jeśli interesuje Cię aspekt interfejsu poleceń, sprawdź stronę projektu GitHub, aby uzyskać więcej informacji i przykładów.

Matheus Moreira
źródło
1
Bardzo polecam Acclaim. Jest łatwy w użyciu i ma wszystkie potrzebne opcje.
bowsersenior
0

Załóżmy, że polecenie ma co najwyżej jedną akcję i dowolną liczbę opcji, takich jak ta:

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

Parsowanie bez walidacji może wyglądać następująco:

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

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end
Bohr
źródło
0

https://github.com/soveran/clap

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

46LOC (w wersji 1.0.0), brak zależności od zewnętrznego parsera opcji. Wykonuje zadanie. Prawdopodobnie nie jest tak w pełni funkcjonalny jak inne, ale jest to 46LOC.

Jeśli sprawdzisz kod, możesz dość łatwo zduplikować podstawową technikę - przypisz lambdy i użyj arity, aby upewnić się, że odpowiednia liczba argumentów podąża za flagą, jeśli naprawdę nie chcesz mieć zewnętrznej biblioteki.

Prosty. Tani.


EDYCJA : podstawowa koncepcja sprowadziła się do końca, ponieważ przypuszczam, że można skopiować / wkleić go do skryptu, aby stworzyć rozsądny parser wiersza poleceń. Na pewno nie jest to coś, co chciałbym zapamiętać, ale użycie arności lambda jako taniego parsera to nowatorski pomysł:

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
źródło
Przeczytaj punkt 1 na końcu pytania. Jeśli nie możesz wpisać całego niezbędnego kodu bezpośrednio w odpowiedzi tutaj, nie jest to odpowiedź na pytanie.
cjs
Słuszna uwaga! Myślę, że w tamtym czasie założyłem, że biblioteka jest na tyle mała, że ​​można skopiować / wkleić całość do dowolnego skryptu, nad którym pracujesz, bez potrzeby zewnętrznej zależności, ale zdecydowanie nie jest to czysty, jednoliniowy wiersz, który zapisałbym w pamięci aby spełnić twój punkt # 2. Myślę, że podstawowa koncepcja jest nowatorska i na tyle fajna, że ​​poszedłem do przodu i stworzyłem skróconą wersję, która trochę lepiej odpowiada na twoje pytanie.
Ben Alavi
-1

Zamierzam udostępnić własny prosty parser opcji, nad którym pracuję od jakiegoś czasu. To zaledwie 74 wiersze kodu i zawiera podstawy tego, co robi wewnętrzny parser opcji Gita. Jako inspirację wziąłem OptionParser, a także Git.

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

To wygląda tak:

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
źródło
Nawet nie sprawdziłeś kodu. Podałem inną odpowiedź, usuwając kod parsujący.
FelipeC
Nie musiałem przyznać, że powiedziałeś, że ma 74 wiersze. Jednakże, właśnie teraz spojrzał na niego i wciąż jest niezgodny pierwsze zdanie wymogu 2. (Ta odpowiedź narusza również konwencję z przepełnieniem stosu, które należy uwzględnić kod w swojej odpowiedzi, a nie dając odnośnik off-site).
cjs
-1

EasyOptions w ogóle nie wymaga żadnego kodu analizującego opcje. Po prostu napisz tekst pomocy, wymagaj, gotowe.

## 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
źródło
EasyOptions to pojedynczy plik Ruby bez instrukcji wymagania i nie ma żadnego kodu parsującego do zapamiętania. Wygląda na to, że zamiast tego chcesz czegoś osadzalnego, które jest wystarczająco potężne, ale łatwe do zapamiętania.
Renato Silva