web-dev-qa-db-fra.com

Comment spécifier un commutateur requis (pas un argument) avec Ruby OptionParser?

J'écris un script et je veux exiger un --Host changer avec la valeur, mais si le --Host le commutateur n'est pas spécifié, je veux que l'analyse de l'option échoue.

Je n'arrive pas à comprendre comment faire ça. Les documents semblent spécifier uniquement comment rendre la valeur d'argument obligatoire, pas le commutateur lui-même.

47
Teflon Ted

Je suppose que vous utilisez optparse ici, bien que la même technique fonctionne pour d'autres bibliothèques d'analyse d'options.

La méthode la plus simple consiste probablement à analyser les paramètres à l'aide de la bibliothèque d'analyse d'options choisie, puis à lever une exception OptionParser :: MissingArgument si la valeur de Host est nulle.

Le code suivant illustre

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

Exécution de cet exemple avec une ligne de commande de

./program -h somehost

affiche simplement "Host = somehost"

Lors de l'exécution avec un -h manquant et aucun nom de fichier ne produit la sortie suivante

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

Et courir avec une ligne de commande de ./program -h produit

/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
59
Steve Weet

Une approche utilisant optparse qui fournit une sortie conviviale sur les commutateurs manquants:

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

Courir sans le -t ou -f commutateurs affiche la sortie suivante:

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

L'exécution de la méthode d'analyse dans une clause begin/rescue permet un formatage convivial sur d'autres échecs tels que des arguments manquants ou des valeurs de commutateur non valides, par exemple, essayez de passer une chaîne pour le -n commutateur.

92
volund

J'ai transformé cela en un joyau que vous pouvez télécharger et installer à partir de rubygems.org:

gem install pickled_optparse

Et vous pouvez extraire le code source du projet mis à jour sur github:
http://github.com/PicklePumpers/pickled_optparse

- Info post plus ancienne -

C'était vraiment, vraiment un bug pour moi donc je l'ai corrigé et gardé l'utilisation super SEC.

Pour rendre un commutateur requis, ajoutez simplement un symbole: required n'importe où dans le tableau d'options comme ceci:

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

Ensuite, à la fin de votre bloc OptionParser, ajoutez l'un d'eux pour imprimer les commutateurs manquants et les instructions d'utilisation:

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

Et enfin, pour que tout fonctionne, vous devez ajouter le fichier "optparse_required_switches.rb" suivant à votre projet quelque part et l'exiger lorsque vous effectuez votre analyse de ligne de commande.

J'ai écrit un petit article avec un exemple sur mon blog: http://picklepumpers.com/wordpress/?p=949

Et voici le fichier OptionParser modifié avec un exemple de son utilisation:

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
10
Mike Bethany

J'ai trouvé une solution claire et concise qui résume vos contributions. Il soulève un OptionParser::MissingArgument exception avec les arguments manquants comme message. Cette exception est interceptée dans le bloc rescue avec le reste des exceptions provenant de 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

Exécuter cet exemple:

 ./program            
missing argument: Host
Usage: program [options]
    -h, --Host hostname              Host name
6
pj.martorell

Si Host est requis, alors ce n'est sûrement pas une option , c'est un argument .

Dans cet esprit, voici un moyen de résoudre votre problème. Vous pouvez interroger le tableau ARGV pour voir si un hôte a été spécifié et, s'il ne l'a pas été, puis appeler abort("You must specify a Host!"), ou similaire, pour faire quitter votre programme avec une erreur statut.

4
sampablokuper

Si vous faites quelque chose comme ça:

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

Puis le someoptions[:Host] sera soit la valeur de la ligne de commande ou nil (si vous ne fournissez pas --Host et/ou aucune valeur après --Host) et vous pouvez la tester facilement (et échouer conditionnellement) après l'analyse:

fail "Hostname not provided" unless someoptions[:Host]
2
Uraki66

L'idée est de définir un OptionParser, puis parse! it, et puts it si certains champs sont manquants. Définir filename sur une chaîne vide par défaut n'est probablement pas la meilleure solution, mais vous avez eu l'idée.

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}'..."

Si -i filename est manquant, il affiche:

~/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
1
superarts.org

La réponse de unknown (google) est bonne, mais contient une erreur mineure.

rescue OptionParser::InvalidArgument, OptionParser::MissingArgument

devrait être

OptionParser::InvalidOption, OptionParser::MissingArgument

Autrement, optparse.parse! déclenchera la sortie d'erreur standard pour OptionParser::InvalidOption, pas le message personnalisé.

1
neilfws