How do you specify a required switch (not argument) with Ruby OptionParser?

前端 未结 8 2067
时光取名叫无心
时光取名叫无心 2020-12-12 23:58

I\'m writing a script and I want to require a --host switch with value, but if the --host switch isn\'t specified, I want the option parsing to fai

相关标签:
8条回答
  • 2020-12-13 00:41

    I turned this into a gem you can download and install from rubygems.org:

    gem install pickled_optparse
    

    And you can checkout the updated project source code on github:
    http://github.com/PicklePumpers/pickled_optparse

    -- Older post info --

    This was really, really bugging me so I fixed it and kept the usage super DRY.

    To make a switch required just add a :required symbol anywhere in the array of options like so:

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

    Then at the end of your OptionParser block add one of these to print out the missing switches and the usage instructions:

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

    And finally to make it all work you need to add the following "optparse_required_switches.rb" file to your project somewhere and require it when you do your command line parsing.

    I wrote up a little article with an example on my blog: http://picklepumpers.com/wordpress/?p=949

    And here's the modified OptionParser file with an example of its usage:

    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?
        !@missing_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
    
    0 讨论(0)
  • 2020-12-13 00:44

    I am assuming you are using optparse here, although the same technique will work for other option parsing libraries.

    The simplest method is probably to parse the parameters using your chosen option parsing library and then raise an OptionParser::MissingArgument Exception if the value of host is nil.

    The following code illustrates

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

    Running this example with a command line of

    ./program -h somehost
    

    simple displays "Host = somehost"

    Whilst running with a missing -h and no file name produces the following output

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

    And running with a command line of ./program -h produces

    /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
    
    0 讨论(0)
提交回复
热议问题