Really Cheap Command-Line Option Parsing in Ruby

前端 未结 20 1075
野的像风
野的像风 2020-11-30 16:57

EDIT: Please, please, please read the two requirements listed at the bottom of this post before replying. People keep posting their new gems and li

相关标签:
20条回答
  • 2020-11-30 17:06

    Trollop is pretty cheap.

    0 讨论(0)
  • 2020-11-30 17:06

    https://github.com/soveran/clap

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

    46LOC (at 1.0.0), no dependency on external option parser. Gets the job done. Probably not as full featured as others, but it's 46LOC.

    If you check the code you can pretty easily duplicate the underlying technique -- assign lambdas and use the arity to ensure the proper number of args follow the flag if you really don't want an external library.

    Simple. Cheap.


    EDIT: the underlying concept boiled down as I suppose you might copy/paste it into a script to make a reasonable command line parser. It's definitely not something I would commit to memory, but using the lambda arity as a cheap parser is a novel idea:

    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...
    
    0 讨论(0)
  • 2020-11-30 17:07

    Suppose a command has at most one action and arbitrary number of options like this:

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

    The parsing without validation may be like this:

    ACTION = ARGV.shift
    OPTIONS = ARGV.join.tr('-', '')
    
    if ACTION == '***'
      ...
      if OPTIONS.include? '*'
        ...
      end
      ...
    end
    
    0 讨论(0)
  • 2020-11-30 17:08

    As the author of Trollop, I cannot BELIEVE the stuff that people think is reasonable in an option parser. Seriously. It boggles the mind.

    Why should I have to make a module that extends some other module to parse options? Why should I have to subclass anything? Why should I have to subscribe to some "framework" just to parse the command line?

    Here's the Trollop version of the above:

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

    And that's it. opts is now a hash with keys :quiet, :interactive, and :filename. You can do whatever you want with it. And you get a beautiful help page, formatted to fit your screen width, automatic short argument names, type checking... everything you need.

    It's one file, so you can drop it in your lib/ directory if you don't want a formal dependency. It has a minimal DSL that is easy to pick up.

    LOC per option people. It matters.

    0 讨论(0)
  • 2020-11-30 17:11

    This is very similar to the accepted answer, but using ARGV.delete_if which is what I use in my simple parser. The only real difference is that options with arguments must be together (e.g. -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}"
    
    0 讨论(0)
  • 2020-11-30 17:13

    Here's the code snippet I use at the top of most of my scripts:

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

    I also hate to require additional files in my quick-and-dirty scripts. My solution is very nearly what you're asking for. I paste a 10 line snippet of code at the top of any of my scripts that parses the command line and sticks positional args and switches into a Hash object (usually assigned to a object that I've named arghash in the examples below).

    Here's an example command line you might want to parse...

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

    Which would become a Hash like this.

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

    In addition to that, two convenience methods are added to the Hash:

    • argc() will return the count of non-switch arguments.
    • switches() will return an array containing the keys for switches that are present

    This is mean to allow some quick and dirty stuff like...

    • Validate I've got the right number of positional arguments regardless of the switches passed in ( arghash.argc == 2 )
    • Access positional arguments by their relative position, regardless of switches appearing before or interspersed with positional arguments ( e.g. arghash[1] always gets the second non-switch argument).
    • Support value-assigned switches in the command line such as "--max=15" which can be accessed by arghash['--max='] which yields a value of '15' given the example command line.
    • Test for the presence or absence of a switch in the command line using a very simple notation such as arghash['-s'] which evaluates to true if it's present and nil if its absent.
    • Test for the presence of a switch or alternatives of switches using set operations like

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

    • Identify use of invalid switches using set operations such as

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

    • Specify default values for missing arguments using a simple Hash.merge() such as the below example that fills in a value for -max= if one was not set and adds a 4th positional argument if one was not passed.

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

    0 讨论(0)
提交回复
热议问题