Parsing Opsi Baris Perintah yang Sangat Murah di Ruby

114

EDIT: Tolong, tolong , harap baca dua persyaratan yang tercantum di bagian bawah posting ini sebelum membalas. Orang-orang terus memposting permata dan perpustakaan baru mereka dan yang lainnya, yang jelas tidak memenuhi persyaratan.

Kadang-kadang saya ingin meretas beberapa opsi baris perintah dengan sangat murah menjadi skrip sederhana. Cara yang menyenangkan untuk melakukannya, tanpa berurusan dengan getopts atau parsing atau semacamnya, adalah:

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

Ini bukan sintaks opsi Unix yang normal, karena ini akan menerima opsi parameter baris perintah non-opsi, seperti dalam " myprog -i foo bar -q", tetapi saya bisa menerimanya. (Beberapa orang, seperti para pengembang Subversion, lebih suka ini. Terkadang saya juga melakukannya.)

Opsi yang baru saja ada atau tidak ada tidak dapat diterapkan lebih sederhana dari yang di atas. (Satu tugas, satu panggilan fungsi, satu efek samping.) Apakah ada cara yang sama sederhananya untuk menangani opsi yang menggunakan parameter, seperti " -f nama file "?

EDIT:

Satu hal yang tidak saya kemukakan sebelumnya, karena tidak menjadi jelas bagi saya sampai penulis Trollop menyebutkan bahwa perpustakaan tersebut sesuai "dalam satu file [800-baris]," adalah bahwa saya tidak hanya mencari yang bersih sintaks, tetapi untuk teknik yang memiliki karakteristik sebagai berikut:

  1. Keseluruhan kode dapat dimasukkan ke dalam file skrip (tanpa membebani skrip itu sendiri, yang mungkin hanya beberapa lusin baris), sehingga seseorang dapat meletakkan satu file di bindirektori pada sistem apa pun dengan standar Ruby 1.8 . [5-7] instalasi dan menggunakannya. Jika Anda tidak dapat menulis skrip Ruby yang tidak memiliki pernyataan require dan kode untuk mengurai beberapa opsi berada di bawah selusin baris atau lebih, Anda gagal dalam persyaratan ini.

  2. Kode ini kecil dan cukup sederhana sehingga orang dapat cukup mengingatnya untuk langsung mengetik kode yang akan melakukan trik, daripada memotong dan menempel dari tempat lain. Pikirkan situasi di mana Anda berada di konsol server yang di-firewall tanpa akses Internet, dan Anda ingin menggabungkan skrip cepat untuk digunakan klien. Saya tidak tahu tentang Anda, tetapi (selain gagal memenuhi persyaratan di atas) menghafal bahkan 45 baris mikro-optparse yang disederhanakan bukanlah sesuatu yang ingin saya lakukan.

cjs
sumber
2
Hanya penasaran dengan keberatan terhadap getoptlong?
Mark Carey
Verbositasnya. Dengan getoptlog, terkadang kode penguraian opsi lebih panjang daripada bagian skrip yang benar-benar berfungsi. Ini bukan hanya masalah estetika, tetapi masalah biaya perawatan.
cjs
8
Saya tidak memahami persyaratan penyertaan skrip - keduanya getoptlongdan optparseberada di pustaka ruby ​​standar, jadi Anda TIDAK PERLU menyalinnya saat menerapkan skrip Anda - jika ruby ​​berfungsi pada mesin itu, maka require 'optparse'atau require 'getoptlong'akan berfungsi juga.
rampion
Lihat stackoverflow.com/questions/21357953/… , serta jawaban William Morgan di bawah ini tentang Trollop.
fearless_fool
@CurtSampson Saya tidak percaya berapa banyak orang yang tidak menjawab pertanyaan Anda. Either way, akhirnya mendapat jawaban yang bagus tentang 3 posting di XD XD
OneChillDude

Jawaban:

235

Sebagai penulis Trollop , saya tidak PERCAYA hal-hal yang menurut orang masuk akal dalam pengurai opsi. Sungguh. Itu mengejutkan pikiran.

Mengapa saya harus membuat modul yang memperluas beberapa modul lain ke opsi parse? Mengapa saya harus membuat subkelas apapun? Mengapa saya harus berlangganan beberapa "framework" hanya untuk mengurai baris perintah?

Berikut versi Trollop di atas:

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

Dan itu dia. optssekarang menjadi hash dengan kunci :quiet, :interactivedan :filename. Anda dapat melakukan apapun yang Anda inginkan dengannya. Dan Anda mendapatkan halaman bantuan yang indah, diformat agar sesuai dengan lebar layar Anda, nama argumen pendek otomatis, pemeriksaan jenis ... semua yang Anda butuhkan.

Itu satu file, jadi Anda bisa meletakkannya di direktori lib / jika Anda tidak menginginkan ketergantungan formal. Ini memiliki DSL minimal yang mudah diambil.

LOC per orang pilihan. Itu penting.

Freedom_Ben
sumber
39
BTW, +1 karena telah menulis Trollop (yang telah disebutkan di sini), tetapi jangan ragu untuk sedikit meredam paragraf pertama.
cjs
33
Dia punya hak untuk mengeluh dalam kasus ini, saya khawatir. Ketika Anda melihat alternatif: [1 ] [2 ] [3 ], untuk apa pada dasarnya hanya memproses array string sederhana (tidak juga, biarkan itu meresap), Anda tidak bisa tidak bertanya-tanya MENGAPA? Apa yang Anda dapatkan dari semua pembengkakan itu? Ini bukan C, di mana string, "bermasalah". Tentu saja untuk masing-masing miliknya. :)
srcspider
50
Tolong jangan sedikit merendahkan ini. Itu screed yang benar, saudara.
William Pietri
7
Jangan ragu untuk sedikit mengecilkan kata kesepuluh.
Andrew Grimm
3
1 untuk Trollop. Saya menggunakannya untuk sistem otomasi pengujian saya dan berfungsi. Ditambah lagi, sangat mudah untuk membuat kode dengan itu kadang-kadang saya mengatur ulang spanduk saya hanya untuk merasakan kesenangannya.
kinofrost
76

Saya berbagi ketidaksukaan Anda require 'getopts', terutama karena kedahsyatannya yaitu 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
sumber
6
Terima kasih, saya tidak dapat melihat bagaimana ini tidak sesuai dengan permintaan OP, terutama mengingat semuanya dalam lib standar, dibandingkan dengan kebutuhan untuk menginstal / menjajakan kode non-standar
dolzenko
3
ini terlihat seperti versi trollop kecuali tidak membutuhkan file tambahan.
Claudiu
59

Berikut teknik standar yang biasa saya gunakan:

#!/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
sumber
3
Menjawab pertanyaannya, tetapi kawan, Trollop tampaknya jauh lebih mudah untuk ditangani. Mengapa menemukan kembali roda ketika roda yang telah dibuat sebelumnya jauh lebih mulus?
Mikey TK
7
Roda yang sudah jadi tidak lebih mulus. Baca lagi pertanyaannya dengan cermat, perhatikan persyaratannya dengan cermat.
cjs
2
+1 Terkadang Anda perlu menemukan kembali roda, karena Anda tidak ingin atau tidak dapat menggunakan dependensi lain seperti Trollop.
lzap
Trollop tidak perlu dipasang sebagai permata. Anda cukup meletakkan satu file di libfolder atau kode Anda dan menggunakannya bahkan tanpa menyentuh rubygems.
Overbryd
Bagi saya, saya harus mengubah when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")ke when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")atau itu akan menjadi loop penggunaan tak terbatas
casey
36

Karena tidak ada yang menyebutkannya, dan judulnya memang merujuk pada penguraian baris perintah yang murah , mengapa tidak membiarkan penerjemah Ruby yang mengerjakannya untuk Anda? Jika Anda melewatkan -ssakelar (di shebang Anda, misalnya), Anda mendapatkan sakelar sederhana secara gratis, yang ditetapkan ke variabel global huruf tunggal. Inilah contoh Anda menggunakan sakelar itu:

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

Dan inilah hasilnya ketika saya menyimpannya sebagai ./testdan chmod-nya +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"]

Lihat ruby -huntuk detailnya.

Itu pasti semurah yang didapat. Ini akan memunculkan NameError jika Anda mencoba sakelar seperti -:, jadi ada beberapa validasi di sana. Tentu saja, Anda tidak dapat memiliki sakelar setelah argumen non-sakelar, tetapi jika Anda membutuhkan sesuatu yang mewah, Anda benar-benar harus menggunakan OptionParser minimum. Faktanya, satu-satunya hal yang mengganggu saya tentang teknik ini adalah Anda akan mendapatkan peringatan (jika Anda mengaktifkannya) saat mengakses variabel global yang tidak disetel, tetapi masih salah, jadi ini berfungsi dengan baik untuk alat sekali pakai dan cepat. skrip.

Satu peringatan yang ditunjukkan oleh FelipeC dalam komentar di " Bagaimana melakukan parsing opsi baris perintah yang sangat murah di Ruby ", adalah bahwa shell Anda mungkin tidak mendukung shebang 3-token; Anda mungkin perlu mengganti /usr/bin/env ruby -wdengan jalur sebenarnya ke ruby ​​Anda (seperti /usr/local/bin/ruby -w), atau menjalankannya dari skrip pembungkus, atau sesuatu.

bjjb
sumber
2
Terima kasih :) Saya harap dia tidak menunggu jawaban ini selama dua tahun terakhir.
DarkHeart
3
Saya memang sudah menunggu jawaban ini selama dua tahun terakhir. :-) Lebih serius lagi, ini adalah jenis pemikiran cerdas yang saya cari. Peringatannya agak mengganggu, tapi saya bisa memikirkan cara untuk menguranginya.
cjs
Senang saya bisa (akhirnya) membantu, @CurtSampson, Bendera MRI dirobek langsung dari Perl, di mana mereka cenderung digunakan secara serampangan di shell one-liners. Jangan ragu untuk menerimanya, jika jawabannya masih berguna untuk Anda. :)
bjjb
1
Anda tidak dapat menggunakan banyak argumen dalam shebang di Linux. / usr / bin / env: 'ruby -s': Tidak ada file atau direktori seperti itu
FelipeC
13

Saya membangun mikro-optparse untuk memenuhi kebutuhan yang jelas ini akan parser opsi yang singkat namun mudah digunakan. Ini memiliki sintaks yang mirip dengan Trollop dan pendek 70 baris. Jika Anda tidak memerlukan validasi dan dapat melakukannya tanpa baris kosong, Anda dapat memotongnya menjadi 45 baris. Saya pikir itulah yang Anda cari.

Contoh singkat:

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!

Memanggil skrip dengan -hatau --helpakan dicetak

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

Ia memeriksa apakah input berjenis sama dengan nilai default, menghasilkan aksesor pendek dan panjang, mencetak pesan kesalahan deskriptif jika argumen tidak valid diberikan dan banyak lagi.

Saya membandingkan beberapa pengurai opsi dengan menggunakan setiap pengurai opsi untuk masalah yang saya miliki. Anda dapat menggunakan contoh ini dan ringkasan saya untuk membuat keputusan yang informatif. Jangan ragu untuk menambahkan lebih banyak implementasi ke daftar. :)

Florian Pilz
sumber
Perpustakaannya sendiri sepertinya bagus. Namun, bukankah tidak jujur ​​membandingkan jumlah baris dengan Trollop karena Anda bergantung pada dan memerlukan optparseyang (memberi atau menerima) 1937 baris.
Telemakus
6
Membandingkan jumlah baris tidak apa-apa, karena optparsemerupakan library default, yaitu ia dikirimkan dengan setiap instalasi ruby. Trollopadalah pustaka pihak ketiga, oleh karena itu Anda harus mengimpor kode lengkap setiap kali Anda ingin memasukkannya ke dalam proyek. µ-optparse selalu hanya membutuhkan ~ 70 baris, karena optparsesudah ada.
Florian Pilz
8

Saya sangat mengerti mengapa Anda ingin menghindari optparse - ini bisa menjadi terlalu banyak. Tetapi ada beberapa solusi yang jauh "lebih ringan" (dibandingkan dengan OptParse) yang datang sebagai pustaka tetapi cukup sederhana untuk membuat satu instalasi gem berharga.

Misalnya, lihat contoh OptiFlag ini . Hanya beberapa baris untuk diproses. Contoh yang sedikit terpotong yang disesuaikan dengan kasus Anda:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

Ada banyak contoh yang disesuaikan juga . Saya ingat menggunakan yang lain yang lebih mudah, tetapi untuk saat ini telah luput dari saya tetapi saya akan kembali dan menambahkan komentar di sini jika saya menemukannya.

Peter Cooper
sumber
Jangan ragu untuk mengedit jawaban Anda agar lebih sesuai dengan pertanyaan yang diklarifikasi.
cjs
4

Inilah yang saya gunakan untuk argumen yang sangat, sangat murah:

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

main

jadi jika Anda menjalankannya programname foo barpanggilan foo dan kemudian bar. Ini berguna untuk skrip sekali pakai.

chrismealy
sumber
3

Anda dapat mencoba sesuatu seperti:

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

Sudahkah Anda mempertimbangkan Thor dengan wycats? Saya pikir ini jauh lebih bersih daripada optparse. Jika Anda sudah memiliki skrip yang ditulis, mungkin perlu lebih banyak pekerjaan untuk memformatnya atau memfaktor ulangnya untuk thor, tetapi itu membuat opsi penanganan menjadi sangat sederhana.

Berikut cuplikan contoh dari 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 secara otomatis memetakan perintah seperti:

app install myname --force

Itu akan diubah menjadi:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Mewarisi dari Thor untuk mengubah kelas menjadi pemeta opsi
  2. Petakan pengenal non-valid tambahan ke metode tertentu. Dalam kasus ini, ubah -L ke: list
  3. Jelaskan metode di bawah ini. Parameter pertama adalah informasi penggunaan, dan parameter kedua adalah deskripsi.
  4. Berikan opsi tambahan apa pun. Ini akan diatur dari - dan - params. Dalam kasus ini, opsi --force dan -f ditambahkan.
Jack Chu
sumber
Saya suka hal pemetaan perintah, karena satu biner dengan sekumpulan sub-perintah adalah sesuatu yang sering saya lakukan. Tetap saja, meskipun Anda telah menjauh dari 'cahaya'. Dapatkah Anda menemukan cara yang lebih sederhana untuk mengekspresikan fungsi yang sama? Bagaimana jika Anda tidak perlu mencetak --helpkeluaran? Bagaimana jika "head myprogram.rb" adalah keluaran bantuan?
cjs
3

Inilah pengurai opsi cepat-dan-kotor favorit saya:

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

Opsinya adalah ekspresi reguler, jadi "-h" juga akan cocok dengan "--help".

Dapat dibaca, mudah diingat, tidak ada perpustakaan eksternal, dan kode minimal.

EdwardTeach
sumber
Ya itu akan. Jika itu masalahnya, Anda dapat menambahkan lebih banyak regex, misalnya/-h(\b|elp)
EdwardTeach
2

Trollop cukup murah.

g33kz0r
sumber
Itu akan menjadi, < trollop.rubyforge.org >. Saya lebih suka itu, saya pikir, meskipun saya sebenarnya tidak mencari perpustakaan.
cjs
Benar, ini adalah perpustakaan. Namun, di <800 LOC, itu cukup diabaikan. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r
1
Saya agak berpikir bahwa mungkin 30-50 baris akan bagus, jika saya menggunakan "perpustakaan". Tapi sekali lagi, saya kira setelah Anda mendapatkan file terpisah yang penuh dengan kode, desain API lebih penting daripada jumlah baris. Namun, saya tidak yakin ingin memasukkannya ke dalam skrip satu kali yang hanya ingin saya masukkan ke direktori bin pada sistem acak.
cjs
1
Anda mendapatkannya terbalik: intinya adalah menghindari keharusan memiliki strategi penerapan yang lebih kompleks.
cjs
1
Anda salah, karena Anda salah mengartikan (atau mengabaikan) kebutuhan dan niat orang yang mengajukan pertanyaan. Saya sarankan Anda membaca ulang pertanyaan itu dengan cermat, terutama dua poin terakhir.
cjs
2

Jika Anda menginginkan parser baris perintah sederhana untuk perintah kunci / nilai tanpa menggunakan permata:

Tetapi ini hanya berfungsi jika Anda selalu memiliki pasangan kunci / nilai.

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

Jika Anda tidak 'memerlukan pemeriksaan, Anda bisa menggunakan:

opts = {} 

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

Berikut cuplikan kode yang saya gunakan di bagian atas sebagian besar skrip saya:

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

Saya juga tidak suka meminta file tambahan dalam skrip cepat-dan-kotor saya. Solusi saya hampir seperti yang Anda minta. Saya menempelkan potongan 10 baris kode di bagian atas salah satu skrip saya yang mengurai baris perintah dan menempelkan argumen posisi dan beralih ke objek Hash (biasanya ditugaskan ke objek yang saya beri nama arghash dalam contoh di bawah).

Berikut adalah contoh baris perintah yang mungkin ingin Anda parse ...

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

Yang akan menjadi Hash seperti ini.

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

Selain itu, dua metode kemudahan ditambahkan ke Hash:

  • argc() akan mengembalikan jumlah argumen non-sakelar.
  • switches() akan mengembalikan array yang berisi kunci untuk sakelar yang ada

Ini berarti mengizinkan beberapa barang cepat dan kotor seperti ...

  • Validasi Saya mendapatkan jumlah argumen posisi yang tepat terlepas dari sakelar yang diteruskan ( arghash.argc == 2 )
  • Akses argumen posisi berdasarkan posisi relatifnya, terlepas dari sakelar yang muncul sebelum atau diselingi dengan argumen posisi (mis arghash[1] selalu mendapatkan argumen non-sakelar kedua).
  • Mendukung sakelar yang diberi nilai pada baris perintah seperti "--max = 15" yang dapat diakses oleh arghash['--max='] yang menghasilkan nilai '15' dengan contoh baris perintah.
  • Uji ada atau tidaknya sakelar di baris perintah menggunakan notasi yang sangat sederhana seperti arghash['-s'] yang mengevaluasi benar jika ada dan nol jika tidak ada.
  • Uji keberadaan sakelar atau sakelar alternatif menggunakan operasi set seperti

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

  • Identifikasi penggunaan sakelar yang tidak valid menggunakan operasi yang ditetapkan seperti

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

  • Tentukan nilai default untuk argumen yang hilang menggunakan yang sederhana Hash.merge()seperti contoh di bawah ini yang mengisi nilai untuk -max = jika tidak ada dan menambahkan argumen posisi ke-4 jika tidak diteruskan.

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

David Foster
sumber
(Saya telah mengedit ini untuk membersihkan memperbaiki pemformatan kode, terutama menggunakan penyelarasan untuk membuat blok dan struktur kontrol lebih jelas, yang menurut saya sangat penting dalam sesuatu yang padat dengan tanda baca ini. Tetapi jika Anda membenci pemformatan baru, silakan untuk membatalkan pengeditan.)
cjs
Ini cukup bagus, jika bukan hal termudah di dunia untuk dibaca. Saya suka itu juga menunjukkan bahwa mengubah argumen "sintaks" (di sini, mengizinkan opsi setelah argumen posisi dan tidak mengizinkan argumen opsi kecuali dengan menggunakan =) dapat membuat perbedaan dalam kode yang Anda butuhkan.
cjs
Terima kasih telah memformat ulang. Ini jelas tidak jelas untuk dibaca dan orang dapat dengan mudah menukar panjang kode untuk kejelasan. Sekarang saya mempercayai kode ini, kurang lebih, saya memperlakukannya seperti permata dan saya tidak pernah mencoba untuk mencari tahu apa yang dilakukannya di balik sampul (jadi kejelasan tidak lagi penting sekarang karena saya memiliki kepercayaan).
David Foster
1

Ini sangat mirip dengan jawaban yang diterima, tetapi ARGV.delete_ifyang saya gunakan adalah yang saya gunakan dalam parser sederhana saya . Satu-satunya perbedaan nyata adalah bahwa opsi dengan argumen harus bersama-sama (misalnya -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
sumber
0

Rupanya @WilliamMorgan dan saya pikir sama. Saya baru saja merilis tadi malam ke Github yang sekarang saya lihat adalah perpustakaan yang mirip dengan Trollop (Dinamakan bagaimana?) Setelah melakukan pencarian untuk OptionParser di Github, lihat Switches

Ada beberapa perbedaan, tetapi filosofinya sama. Satu perbedaan yang jelas adalah bahwa Switch bergantung pada OptionParser.

thoran
sumber
0

Saya sedang mengembangkan permata pengurai opsi saya sendiri yang disebut Acclaim .

Saya menulisnya karena saya ingin membuat antarmuka baris perintah bergaya git dan dapat dengan rapi memisahkan fungsionalitas dari setiap perintah ke dalam kelas yang terpisah, tetapi juga dapat digunakan tanpa seluruh kerangka perintah:

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

Belum ada rilis stabil, tetapi saya sudah menerapkan beberapa fitur seperti:

  • pengurai opsi kustom
  • penguraian fleksibel dari argumen opsi yang memungkinkan untuk minimum dan opsional
  • dukungan untuk banyak gaya opsi
  • mengganti, menambah, atau meningkatkan beberapa contoh dari opsi yang sama
  • penangan opsi kustom
  • penangan tipe kustom
  • penangan yang telah ditentukan untuk kelas perpustakaan standar umum

Ada banyak penekanan pada perintah sehingga mungkin agak berat untuk penguraian baris perintah sederhana tetapi berfungsi dengan baik dan saya telah menggunakannya di semua proyek saya. Jika Anda tertarik dengan aspek antarmuka perintah, lihat halaman GitHub proyek untuk informasi dan contoh lebih lanjut.

Matheus Moreira
sumber
1
Saya sangat merekomendasikan Acclaim. Mudah digunakan dan memiliki semua opsi yang Anda butuhkan.
bowsersenior
0

Misalkan sebuah perintah memiliki paling banyak satu tindakan dan jumlah opsi yang berubah-ubah seperti ini:

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

Penguraian tanpa validasi mungkin seperti ini:

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

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

https://github.com/soveran/clap

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

46LOC (pada 1.0.0), tidak ada ketergantungan pada pengurai opsi eksternal. Menyelesaikan pekerjaan. Mungkin tidak berfitur lengkap seperti yang lain, tapi 46LOC.

Jika Anda memeriksa kode, Anda dapat dengan mudah menduplikasi teknik yang mendasarinya - tetapkan lambda dan gunakan arity untuk memastikan jumlah argumen yang tepat mengikuti bendera jika Anda benar-benar tidak menginginkan perpustakaan eksternal.

Sederhana. Murah.


EDIT : konsep yang mendasari diringkas karena saya kira Anda dapat menyalin / menempelkannya ke dalam skrip untuk membuat parser baris perintah yang masuk akal. Ini jelas bukan sesuatu yang ingin saya hafalkan, tetapi menggunakan lambda arity sebagai pengurai murah adalah ide baru:

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
sumber
Harap baca poin 1 di akhir pertanyaan. Jika Anda tidak dapat mengetik semua kode yang diperlukan tepat di jawaban Anda di sini, itu bukan jawaban atas pertanyaan.
cjs
Poin bagus! Saya pikir pada saat itu saya menganggap lib cukup kecil sehingga Anda dapat menyalin / menempel semuanya ke dalam skrip apa pun yang Anda kerjakan tanpa memerlukan ketergantungan eksternal, tetapi itu jelas bukan satu baris yang bersih yang akan saya komit ke memori untuk memenuhi poin # 2 Anda. Saya pikir konsep yang mendasarinya baru dan cukup keren sehingga saya melanjutkan dan membuat versi singkat yang menjawab pertanyaan Anda sedikit lebih tepat.
Ben Alavi
-1

Saya akan membagikan pengurai opsi sederhana saya sendiri yang telah saya kerjakan selama beberapa waktu. Ini hanyalah 74 baris kode, dan melakukan dasar-dasar dari apa yang dilakukan pengurai opsi internal Git. Saya mengambil OptionParser sebagai inspirasi, dan juga Git's.

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

Ini terlihat seperti ini:

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
sumber
Anda bahkan tidak memeriksa kodenya. Saya telah memberikan jawaban lain untuk mengeluarkan kode parsing.
FelipeC
Saya tidak perlu mengingat bahwa Anda mengatakan panjangnya 74 baris. Namun, saya baru saja melihatnya sekarang dan masih melanggar kalimat pertama dari persyaratan 2. (Tanggapan ini juga melanggar konvensi Stack Overflow bahwa Anda harus menyertakan kode dalam jawaban Anda daripada memberikan tautan di luar situs.)
cjs
-1

EasyOptions sama sekali tidak memerlukan kode penguraian opsi. Cukup tulis teks bantuan, minta, selesai.

## 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
sumber
EasyOptions adalah satu file Ruby tanpa pernyataan require, dan tidak ada kode parsing yang perlu diingat sama sekali. Tampaknya Anda malah menginginkan sesuatu yang dapat disematkan yang cukup kuat namun mudah diingat.
Renato Silva