2009-10-09 24 views
42

Tôi đang viết một tập lệnh và tôi muốn yêu cầu chuyển đổi --host có giá trị, nhưng nếu chuyển đổi --host không được chỉ định, tôi muốn phân tích tùy chọn thất bại.Làm thế nào để bạn chỉ định một công tắc cần thiết (không phải đối số) với Ruby OptionParser?

Tôi dường như không thể tìm ra cách để làm điều đó. Các tài liệu dường như chỉ xác định làm thế nào để làm cho giá trị đối số bắt buộc, không phải là chuyển đổi chính nó.

+1

Bạn cần phải xây dựng nhiều hơn, đưa ra một ví dụ .... – khelll

Trả lời

56

Tôi giả sử bạn đang sử dụng optparse ở đây, mặc dù cùng một kỹ thuật sẽ hoạt động cho các thư viện phân tích cú pháp tùy chọn khác.

Phương pháp đơn giản nhất có thể là phân tích các tham số bằng cách sử dụng thư viện phân tích cú pháp tùy chọn đã chọn của bạn và sau đó tăng OptionParser :: MissingArgument Exception nếu giá trị của máy chủ là không.

Các mã sau minh họa

#!/usr/bin/env ruby 
require 'optparse' 

options = {} 

optparse = OptionParser.new do |opts| 
    opts.on('-h', '--host HOSTNAME', "Mandatory Host Name") do |f| 
    options[:host] = f 
    end 
end 

optparse.parse! 

#Now raise an exception if we have not found a host option 
raise OptionParser::MissingArgument if options[:host].nil? 


puts "Host = #{options[:host]}" 

Chạy ví dụ này với một dòng lệnh của

./program -h somehost 

hiển thị đơn giản "Host = somehost"

Trong khi đang chạy với một thiếu -h và không tên tệp tạo ra kết quả sau đây

./program:15: missing argument: (OptionParser::MissingArgument) 

Và chạy với một dòng lệnh của ./program -h sản xuất

/usr/lib/ruby/1.8/optparse.rb:451:in `parse': missing argument: -h (OptionParser::MissingArgument) 
    from /usr/lib/ruby/1.8/optparse.rb:1288:in `parse_in_order' 
    from /usr/lib/ruby/1.8/optparse.rb:1247:in `catch' 
    from /usr/lib/ruby/1.8/optparse.rb:1247:in `parse_in_order' 
    from /usr/lib/ruby/1.8/optparse.rb:1241:in `order!' 
    from /usr/lib/ruby/1.8/optparse.rb:1332:in `permute!' 
    from /usr/lib/ruby/1.8/optparse.rb:1353:in `parse!' 
    from ./program:13 
+15

Nó stinks đó không phải là một tính năng có nguồn gốc của thư viện - nó không phải là rất DRY nếu bạn có thêm một vài công tắc được yêu cầu. Cảm ơn. –

+4

Thay vì đưa ra một ngoại lệ khó hiểu, tôi muốn thoát với trạng thái khác 0 bằng cách gọi 'Kernel.abort'. Phải có một đối số tùy chọn mà bạn có thể sử dụng để xác định lý do hủy bỏ. –

+4

Đồng ý Ted. Nó không phải là DRY ở tất cả và nên xấu hổ rằng nó không phải là. Ứng dụng dòng lệnh đầu tiên của tôi, tôi cần một công tắc bắt buộc nên không thể chấp nhận được rằng điều này không được bao gồm. –

82

Một cách tiếp cận sử dụng optparse cung cấp sản lượng thân thiện trên switch mất tích:

#!/usr/bin/env ruby 
require 'optparse' 

options = {} 

optparse = OptionParser.new do |opts| 
    opts.on('-f', '--from SENDER', 'username of sender') do |sender| 
    options[:from] = sender 
    end 

    opts.on('-t', '--to RECIPIENTS', 'comma separated list of recipients') do |recipients| 
    options[:to] = recipients 
    end 

    options[:number_of_files] = 1 
    opts.on('-n', '--num_files NUMBER', Integer, "number of files to send (default #{options[:number_of_files]})") do |number_of_files| 
    options[:number_of_files] = number_of_files 
    end 

    opts.on('-h', '--help', 'Display this screen') do 
    puts opts 
    exit 
    end 
end 

begin 
    optparse.parse! 
    mandatory = [:from, :to]           # Enforce the presence of 
    missing = mandatory.select{ |param| options[param].nil? }  # the -t and -f switches 
    unless missing.empty?           # 
    raise OptionParser::MissingArgument.new(missing.join(', ')) # 
    end                # 
rescue OptionParser::InvalidOption, OptionParser::MissingArgument  # 
    puts $!.to_s               # Friendly output when parsing fails 
    puts optparse               # 
    exit                 # 
end                  # 

puts "Performing task with options: #{options.inspect}" 

Chạy mà không -t hoặc -f chuyển show sản lượng sau:

Missing options: from, to 
Usage: test_script [options] 
    -f, --from SENDER    username of sender 
    -t, --to RECIPIENTS    comma separated list of recipients 
    -n, --num_files NUMBER   number of files to send (default 1) 
    -h, --help 

Chạy phương pháp phân tích cú pháp trong một mệnh đề bắt đầu/cứu hộ cho phép định dạng thân thiện khi các lỗi khác chẳng hạn như các đối số bị thiếu hoặc các giá trị chuyển đổi không hợp lệ, ví dụ, thử truyền một chuỗi cho công tắc -n.

+0

cố định theo ý kiến ​​của neilfws – volund

+1

Điều đó không tồi nhưng nó vẫn không thật sự khô. Bạn phải làm rất nhiều công việc ở cuối đó và phải chỉ định công tắc của bạn ở hai nơi. Kiểm tra sửa chữa của tôi dưới đây là đơn giản hơn nhiều và nhiều DRY'er. Cũng trên blog của tôi: http://picklepumpers.com/wordpress/?p=949 –

+0

Đó là một câu trả lời tuyệt vời, cảm ơn volund. –

1

Câu trả lời từ không xác định (google) là tốt, nhưng có lỗi nhỏ.

rescue OptionParser::InvalidArgument, OptionParser::MissingArgument 

nên

OptionParser::InvalidOption, OptionParser::MissingArgument 

Nếu không, optparse.parse! sẽ kích hoạt đầu ra sai số chuẩn cho OptionParser::InvalidOption, chứ không phải thông báo tùy chỉnh.

9

Tôi đã biến điều này thành đá quý, bạn có thể tải xuống và cài đặt từ rubygems.org:

gem install pickled_optparse 

Và bạn có thể kiểm tra các mã nguồn dự án cập nhật trên github:
http://github.com/PicklePumpers/pickled_optparse

- bài cũ hơn thông tin -

Đây là thực sự, thực sự bugging tôi vì vậy tôi cố định nó giữ mức sử dụng siêu DRY.

Để thực hiện một chuyển đổi cần thiết chỉ cần thêm một: biểu tượng đòi hỏi bất cứ nơi nào trong mảng của các tùy chọn như vậy:

opts.on("-f", "--foo [Bar]", String, :required, "Some required option") do |option| 
    @options[:foo] = option 
end 

Sau đó, ở phần cuối của khối OptionParser bạn thêm một trong những để in ra các switch mất tích và hướng dẫn sử dụng:

if opts.missing_switches? 
    puts opts.missing_switches 
    puts opts 
    exit 
end 

và cuối cùng để làm cho nó tất cả việc bạn cần phải thêm "optparse_required_switches.rb" tập tin sau đây để dự án của bạn ở đâu đó và yêu cầu nó khi bạn làm phân tích cú pháp dòng lệnh của bạn.

Tôi đã viết lên một chút bài viết với một ví dụ trên blog của tôi: http://picklepumpers.com/wordpress/?p=949

Và đây là file OptionParser sửa đổi với một ví dụ về việc sử dụng của nó:

required_switches_example.rb

#!/usr/bin/env ruby 
require 'optparse' 
require_relative 'optparse_required_switches' 

# Configure options based on command line options 
@options = {} 
OptionParser.new do |opts| 
    opts.banner = "Usage: test [options] in_file[.srt] out_file[.srt]" 

    # Note that :required can be anywhere in the parameters 

    # Also note that OptionParser is bugged and will only check 
    # for required parameters on the last option, not my bug. 

    # required switch, required parameter 
    opts.on("-s Short", String, :required, "a required switch with just a short") do |operation| 
    @options[:operation] = operation 
    end 

    # required switch, optional parameter 
    opts.on(:required, "--long [Long]", String, "a required switch with just a long") do |operation| 
    @options[:operation] = operation 
    end 

    # required switch, required parameter 
    opts.on("-b", "--both ShortAndLong", String, "a required switch with short and long", :required) do |operation| 
    @options[:operation] = operation 
    end 

    # optional switch, optional parameter 
    opts.on("-o", "--optional [Whatever]", String, "an optional switch with short and long") do |operation| 
    @options[:operation] = operation 
    end 

    # Now we can see if there are any missing required 
    # switches so we can alert the user to what they 
    # missed and how to use the program properly. 
    if opts.missing_switches? 
    puts opts.missing_switches 
    puts opts 
    exit 
    end 

end.parse! 

optparse_required_switches.rb

# Add required switches to OptionParser 
class OptionParser 

    # An array of messages describing the missing required switches 
    attr_reader :missing_switches 

    # Convenience method to test if we're missing any required switches 
    def missing_switches? 
    [email protected]_switches.nil? 
    end 

    def make_switch(opts, block = nil) 
    short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] 
    ldesc, sdesc, desc, arg = [], [], [] 
    default_style = Switch::NoArgument 
    default_pattern = nil 
    klass = nil 
    n, q, a = nil 

    # Check for required switches 
    required = opts.delete(:required) 

    opts.each do |o| 

     # argument class 
     next if search(:atype, o) do |pat, c| 
     klass = notwice(o, klass, 'type') 
     if not_style and not_style != Switch::NoArgument 
      not_pattern, not_conv = pat, c 
     else 
      default_pattern, conv = pat, c 
     end 
     end 

     # directly specified pattern(any object possible to match) 
     if (!(String === o || Symbol === o)) and o.respond_to?(:match) 
     pattern = notwice(o, pattern, 'pattern') 
     if pattern.respond_to?(:convert) 
      conv = pattern.method(:convert).to_proc 
     else 
      conv = SPLAT_PROC 
     end 
     next 
     end 

     # anything others 
     case o 
     when Proc, Method 
      block = notwice(o, block, 'block') 
     when Array, Hash 
      case pattern 
      when CompletingHash 
      when nil 
       pattern = CompletingHash.new 
       conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert) 
      else 
       raise ArgumentError, "argument pattern given twice" 
      end 
      o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} 
     when Module 
      raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) 
     when *ArgumentStyle.keys 
      style = notwice(ArgumentStyle[o], style, 'style') 
     when /^--no-([^\[\]=\s]*)(.+)?/ 
      q, a = $1, $2 
      o = notwice(a ? Object : TrueClass, klass, 'type') 
      not_pattern, not_conv = search(:atype, o) unless not_style 
      not_style = (not_style || default_style).guess(arg = a) if a 
      default_style = Switch::NoArgument 
      default_pattern, conv = search(:atype, FalseClass) unless default_pattern 
      ldesc << "--no-#{q}" 
      long << 'no-' + (q = q.downcase) 
      nolong << q 
     when /^--\[no-\]([^\[\]=\s]*)(.+)?/ 
      q, a = $1, $2 
      o = notwice(a ? Object : TrueClass, klass, 'type') 
      if a 
      default_style = default_style.guess(arg = a) 
      default_pattern, conv = search(:atype, o) unless default_pattern 
      end 
      ldesc << "--[no-]#{q}" 
      long << (o = q.downcase) 
      not_pattern, not_conv = search(:atype, FalseClass) unless not_style 
      not_style = Switch::NoArgument 
      nolong << 'no-' + o 
     when /^--([^\[\]=\s]*)(.+)?/ 
      q, a = $1, $2 
      if a 
      o = notwice(NilClass, klass, 'type') 
      default_style = default_style.guess(arg = a) 
      default_pattern, conv = search(:atype, o) unless default_pattern 
      end 
      ldesc << "--#{q}" 
      long << (o = q.downcase) 
     when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ 
      q, a = $1, $2 
      o = notwice(Object, klass, 'type') 
      if a 
      default_style = default_style.guess(arg = a) 
      default_pattern, conv = search(:atype, o) unless default_pattern 
      end 
      sdesc << "-#{q}" 
      short << Regexp.new(q) 
     when /^-(.)(.+)?/ 
      q, a = $1, $2 
      if a 
      o = notwice(NilClass, klass, 'type') 
      default_style = default_style.guess(arg = a) 
      default_pattern, conv = search(:atype, o) unless default_pattern 
      end 
      sdesc << "-#{q}" 
      short << q 
     when /^=/ 
      style = notwice(default_style.guess(arg = o), style, 'style') 
      default_pattern, conv = search(:atype, Object) unless default_pattern 
     else 
      desc.push(o) 
     end 

    end 

    default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern 
    if !(short.empty? and long.empty?) 
     s = (style || default_style).new(pattern || default_pattern, conv, sdesc, ldesc, arg, desc, block) 
    elsif !block 
     if style or pattern 
     raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) 
     end 
     s = desc 
    else 
     short << pattern 
     s = (style || default_style).new(pattern, conv, nil, nil, arg, desc, block) 
    end 

    # Make sure required switches are given 
    if required && !(default_argv.include?("-#{short[0]}") || default_argv.include?("--#{long[0]}")) 
     @missing_switches ||= [] # Should be placed in initialize if incorporated into Ruby proper 

     # This is more clear but ugly and long. 
     #missing = "-#{short[0]}" if !short.empty? 
     #missing = "#{missing} or " if !short.empty? && !long.empty? 
     #missing = "#{missing}--#{long[0]}" if !long.empty? 

     # This is less clear and uglier but shorter. 
     missing = "#{"-#{short[0]}" if !short.empty?}#{" or " if !short.empty? && !long.empty?}#{"--#{long[0]}" if !long.empty?}" 

     @missing_switches << "Missing switch: #{missing}" 
    end 

    return s, short, long, 
     (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), 
     nolong 
    end 

end 
+1

'pickled_optparse yêu cầu phiên bản Ruby ~> 1.9.2.': ( – Nowaker

+0

@DamianNowak - Sao chép repo github và sửa đổi dòng 36 của tệp pickled_optparse.gemspec. Sau đó, hãy tạo' gem build. \ Pickled_optparse.gemspec && gem install. \ Pickled_optparse -0.1.1.gem' và bạn tốt để đi. – Pakman

3

Nếu chủ là cần thiết, thì chắc chắn nó không phải là một lựa chọn , nó là một luận.

Với ý nghĩ đó, đây là cách để giải quyết vấn đề của bạn. Bạn có thể thẩm vấn mảng ARGV để xem liệu một máy chủ đã được chỉ định chưa, sau đó gọi số abort("You must specify a host!") hoặc tương tự để làm cho chương trình của bạn thoát khỏi trạng thái lỗi.

2

Nếu bạn làm điều gì đó như thế này:

opts.on('-h', '--host', 
      'required host name [STRING]') do |h| 
    someoptions[:host] = h || nil 
    end 

Sau đó someoptions[:host] sẽ hoặc là giá trị từ dòng lệnh hoặc nil (nếu bạn không cung cấp giá trị --host và/hoặc không sau --host) và bạn có thể thử nghiệm nó một cách dễ dàng (và có điều kiện không thành công) sau khi phân tích cú pháp:

fail "Hostname not provided" unless someoptions[:host] 
4

Tôi đã đưa ra một giải pháp rõ ràng và súc tích tổng hợp những đóng góp của bạn. Nó làm tăng một ngoại lệ OptionParser::MissingArgument với các đối số bị thiếu làm thông báo. Ngoại lệ này được bắt trong khối rescue cùng với phần còn lại của các ngoại lệ đến từ OptionParser.

#!/usr/bin/env ruby 
require 'optparse' 

options = {} 

optparse = OptionParser.new do |opts| 
    opts.on('-h', '--host hostname', "Host name") do |host| 
    options[:host] = host 
    end 
end 

begin 
    optparse.parse! 
    mandatory = [:host] 
    missing = mandatory.select{ |param| options[param].nil? } 
    raise OptionParser::MissingArgument, missing.join(', ') unless missing.empty? 
rescue OptionParser::ParseError => e 
    puts e 
    puts optparse 
    exit 
end 

Chạy ví dụ này:

 ./program    
missing argument: host 
Usage: program [options] 
    -h, --host hostname    Host name 
0

Ý tưởng là để xác định một OptionParser, sau đó parse! nó, và puts nó nếu một số lĩnh vực đang thiếu. Đặt filename thành chuỗi trống theo mặc định có lẽ không phải là cách tốt nhất để đi, nhưng bạn có ý tưởng.

require 'optparse' 

filename = '' 
options = OptionParser.new do |opts| 
    opts.banner = "Usage: swift-code-style.rb [options]" 

    opts.on("-iNAME", "--input-filename=NAME", "Input filename") do |name| 
     filename = name 
    end 
    opts.on("-h", "--help", "Prints this help") do 
     puts opts 
     exit 
    end 
end 

options.parse! 

if filename == '' 
    puts "Missing filename.\n---\n" 
    puts options 
    exit 
end 

puts "Processing '#{filename}'..." 

Nếu -i filename là mất tích, nó sẽ hiển thị:

~/prj/gem/swift-code-kit ./swift-code-style.rb 
Missing filename. 
--- 
Usage: swift-code-style.rb [options] 
    -i, --input-filename=NAME  Input filename 
    -h, --help      Prints this help 
Các vấn đề liên quan