118

EDIT: Please, please, please read the two requirements listed at the bottom of this post before replying. People keep posting their new gems and libraries and whatnot, which clearly don't meet the requirements.

Sometimes I want to very cheaply hack some command line options into a simple script. A fun way to do it, without dealing with getopts or parsing or anything like that, is:

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

It's not quite the normal Unix options syntax, because it will accept options non-option command line parameters, as in "myprog -i foo bar -q", but I can live with that. (Some people, such as the Subversion developers, prefer this. Sometimes I do too.)

An option that's just present or absent can't be implemented much more simply than the above. (One assignment, one function call, one side effect.) Is there an equally simple way to deal with options that take a parameter, such as "-f filename"?

EDIT:

One point I didn't make earlier on, because it hadn't become clear to me until the author of Trollop mentioned that the library fit "in one [800-line] file," is that I'm looking not only for clean syntax, but for a technique that has the following characteristics:

  1. The entirety of the code can be included in the script file (without overwhelming the actual script itself, which may be only a couple of dozen lines), so that one can drop a single file in a bin dir on any system with a standard Ruby 1.8.[5-7] installation and use it. If you can't write a Ruby script that has no require statements and where the code to parse a couple of options is under a dozen lines or so, you fail this requirement.

  2. The code is small and simple enough that one can remember enough of it to directly type in code that will do the trick, rather than cutting and pasting from somewhere else. Think of the situation where you're on the console of a firewalled sever with no Internet access, and you want to toss together a quick script for a client to use. I don't know about you, but (besides failing the requirement above) memorizing even the 45 lines of simplified micro-optparse is not something I care to do.

cjs
  • 25,752
  • 9
  • 89
  • 101
  • 2
    Just curious to the objection against getoptlong? – Mark Carey Dec 23 '09 at 07:30
  • 1
    The verbosity of it. With getoptlog, sometimes the options parsing code is longer than the part of the script that actually does the work. This is not just an aesthetic issue, but a maintenance cost issue. – cjs Jan 27 '10 at 00:43
  • 8
    I don't understand the script inclusion requirement - both `getoptlong` and `optparse` are in the standard ruby library, so you don't NEED to copy them when deploying your script - if ruby works on that machine, then `require 'optparse'` or `require 'getoptlong'` will work too. – rampion Apr 29 '10 at 13:53
  • See http://stackoverflow.com/questions/21357953/how-do-i-get-started-with-trollop-the-command-line-parser-package, as well as William Morgan's answer below about Trollop. – fearless_fool Jan 26 '14 at 00:15
  • @CurtSampson I cant believe how many people didn't answer your question. Either way, finally got a good answer about 3 posts down XD XD – OneChillDude Jan 29 '16 at 23:37

19 Answers19

237

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.

Freedom_Ben
  • 11,247
  • 10
  • 69
  • 89
  • 39
    BTW, +1 for having written Trollop (which had already been mentioned here), but feel free to tone down the first paragraph a bit. – cjs Jun 19 '09 at 10:22
  • 34
    He has a right to complain in this case I'm afraid. When you look at the alternatives: [[1](http://rubyforge.org/docman/view.php/632/233/posted-docs.index.html)] [[2](http://ruby-doc.org/core/classes/OptionParser.html)] [[3](http://ruby-doc.org/core/classes/GetoptLong.html)], for what is basically just processing a simple string array (no really, let that sink in), you can't help but wonder WHY? What do you gain from all that bloat? This is not C, where strings are, "problematic". Of course to each his own. :) – srcspider Aug 22 '10 at 21:40
  • 51
    Please don't tone this down a bit. It's a righteous screed, brother. – William Pietri Dec 09 '10 at 18:36
  • 1
    Not sure I would recommend this. I spent 10 minutes trying to figure out how to use it for my common usage and the docs didn't seem to help. FYI I have `foo bar` where *foo* is the utility and *bar* is a file, and then want to add a flag `-t`, I can't figure out how to get Trollop to put the filename into some part of the `opts` hash. The filename arg has no flag and I don't want to have to specify one. Docs are unclear about how to do this, or if I should just bite the bullet and force it to have a flag. – mxcl Aug 22 '13 at 17:32
  • 1
    This answer has made me turn to Trollop for all of my option parsing needs and never look back. However, the best documentation can be found via the RubyForge link provided in this answer, but [RubyForge has been taken down](http://news.softpedia.com/news/RubyForge-org-Taken-Offline-After-Reportedly-Being-Hacked-397496.shtml) for some time now. Therefore, you're gonna wanna use the [cached version of the Trollop documentation](http://webcache.googleusercontent.com/search?q=cache:uHf1VK8UFOoJ:trollop.rubyforge.org/+&cd=1&hl=en&ct=clnk&gl=us) instead. – seane Dec 17 '13 at 22:14
  • +1 Trollop. If I could +1 again, it would be for, "The type will be inferred from the default if given, so no need to specify both." In the rare cases that we have to specify type information in ruby, I'm a huge fan of only requiring it if it can't otherwise be inferred. They've even gotten generic arguments in JAVA to figure this out, yet I still see the occasional requirement for both and a runtime error if they don't match. – Eric Haynes May 02 '15 at 06:17
76

I share your distaste for require 'getopts', mainly due to the awesomeness that is 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
  • 87,131
  • 49
  • 199
  • 315
  • 6
    Thanks, I can't see how this doesn't fit the OPs request, especially considering its all in standard lib, compared to the need of installing/vendoring any non standard code – Ev Dolzhenko Mar 09 '12 at 07:37
  • 3
    this looks just like the trollop version except it doesn't need the extra file. – Claudiu Jul 23 '12 at 19:46
60

Here's the standard technique I usually use:

#!/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}")
jtpereyda
  • 6,987
  • 10
  • 51
  • 80
cjs
  • 25,752
  • 9
  • 89
  • 101
  • 4
    Answers the question, but man, Trollop seems to be a lot easier to deal with. Why reinvent the wheel when the premade wheel is so much smoother? – Mikey T.K. Nov 28 '12 at 17:55
  • 7
    The premade wheel isn't smoother. Read the question again carefully, paying careful attention to the requirements. – cjs Dec 03 '12 at 07:13
  • 2
    +1 Sometimes you need to reinvent the wheel, because you don't want or simply *can't* use other dependencies like Trollop. – lzap Feb 24 '14 at 09:56
  • Trollop does not need to be installed as a gem. You can simply drop one file it in you `lib` folder or code and use it without even touching rubygems. – Overbryd Jun 01 '15 at 16:40
  • For me, I had to change `when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")` to `when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")` or it would get into an infinite usage loop – casey Jun 10 '15 at 20:11
  • @casey: It sounds like you left out the `exit(2)` line from `usage()`. – cjs Jul 14 '18 at 00:49
  • @overbyrd Precisely. In some situations you will be asking someone not just to copy a file to a dir and execute it, but copy two files to their machine and figure out how to get the second one into a place where the first will read it. You may never have seen a situation where that's not trivial; I have seen many. – cjs Jul 14 '18 at 00:49
  • alternatively `for arg in ARGV` :) – Jose V Mar 12 '21 at 18:20
  • you can do `if for arg in ARGV; case arg; ...when.... ; end.empty?; handle-no-args-case; end;` – Jose V Mar 12 '21 at 18:22
35

Since nobody appeared to mention it, and the title does refer to cheap command-line parsing, why not just let the Ruby interpreter do the work for you? If you pass the -s switch (in your shebang, for example), you get dirt-simple switches for free, assigned to single-letter global variables. Here's your example using that switch:

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

And here's the output when I save that as ./test and chmod it +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"]

See ruby -h for details.

That must be as cheap as it gets. It will raise a NameError if you try a switch like -:, so there's some validation there. Of course, you can't have any switches after a non-switch argument, but if you need something fancy, you really should be using at the minimum OptionParser. In fact, the only thing that annoys me about this technique is that you'll get a warning (if you've enabled them) when accessing an unset global variable, but it's still falsey, so it works just fine for throwaway tools and quick scripts.

One caveat pointed out by FelipeC in the comments in "How to do really cheap command-line option parsing in Ruby", is that your shell might not support the 3-token shebang; you may need to replace /usr/bin/env ruby -w with the actual path to your ruby (like /usr/local/bin/ruby -w), or run it from a wrapper script, or something.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
bjjb
  • 481
  • 4
  • 9
  • 2
    Thanks :) I sure hope he hasn't been waiting for this answer for the last two years. –  Nov 27 '13 at 05:23
  • 4
    I have indeed been waiting for this answer for the last two years. :-) More seriously, this is the sort of clever thinking I was looking for. The warning thing is a bit annoying, but I can think of ways to mitigate that. – cjs Dec 04 '14 at 08:17
  • Glad I could (eventually) help, @CurtSampson, The MRI's flags are ripped straight out of Perl, where they tend to be used gratuitously in shell one-liners. Feel free to accept, if the answer's still useful to you. :) – bjjb Dec 06 '14 at 17:08
  • 1
    You can't use multiple arguments in a shebang in Linux. /usr/bin/env: ‘ruby -s’: No such file or directory – FelipeC Jan 17 '18 at 23:22
13

I built micro-optparse to fill this obvious need for a short, but easy to use option-parser. It has a syntax similar to Trollop and is 70 lines short. If you don't need validations and can do without empty lines you can cut it down to 45 lines. I think that's exactly what you were looking for.

Short example:

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!

Calling the script with -h or --help will print

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

It checks if input is of same type as the default value, generates short and long accessors, prints descriptive error messages if invalid arguments are given and more.

I compared several option-parser by using each option-parser for the problem I had. You can use these examples and my summary to make an informative decision. Feel free to add more implementations to the list. :)

Florian Pilz
  • 8,002
  • 4
  • 22
  • 30
  • The library itself looks like it may be great. However, isn't it disingenuous to compare line counts with Trollop since you depend on and require `optparse` which is (give or take) 1937 lines. – Telemachus May 24 '12 at 12:43
  • 6
    Comparing line counts is absolutely OK, since `optparse` is a default library, i.e. it ships with every ruby installation. `Trollop` is a third party library, hence you must import the complete code every time you want to include it in a project. µ-optparse always only requires the ~70 lines, since `optparse` is already there. – Florian Pilz May 27 '12 at 13:26
8

I totally understand why you want to avoid optparse - it can get too much. But there are a few far "lighter" solutions (compared to OptParse) that come as libraries but are simple enough to make a single gem installation worthwhile.

For example, check out this OptiFlag example. Just a few lines for the processing. A slightly truncated example tailored to your case:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

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

There are tons of customized examples too. I recall using another that was even easier, but it has escaped me for now but I will come back and add a comment here if I find it.

Peter Cooper
  • 2,907
  • 28
  • 67
  • 87
4

This is what I use for really, really cheap args:

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

main

so if you run programname foo bar it calls foo and then bar. It's handy for throwaway scripts.

chrismealy
  • 4,830
  • 2
  • 23
  • 24
4

You can try something like:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end
Stefan
  • 9,289
  • 7
  • 38
  • 46
  • This is by kilometres the best answer. Simple, effective. Sure, you can't combine two options, but how onerous is that for users? Can we agree to deprecate all optionparsers? – Graham Nicholls Aug 27 '22 at 14:25
3

Here's my favorite quick-and-dirty option parser:

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

The options are regular expressions, so "-h" also would match "--help".

Readable, easy to remember, no external library, and minimal code.

EdwardTeach
  • 615
  • 6
  • 18
3

Have you considered Thor by wycats? I think it's a lot cleaner than optparse. If you already have a script written, it might be some more work to format it or refactor it for thor, but it does make handling options very simple.

Here's the example snippet from the 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 automatically maps commands as such:

app install myname --force

That gets converted to:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Inherit from Thor to turn a class into an option mapper
  2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
  3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description.
  4. Provide any additional options. These will be marshaled from -- and - params. In this case, a --force and a -f option is added.
Jack Chu
  • 6,791
  • 4
  • 38
  • 44
  • I like the command-mapping thing, since a single binary with a bunch of subcommands is something I do often. Still, though you've gone a ways from 'light'. Could you find an even simpler way to express that same functionality? What if you didn't need to print `--help` output? What if "head myprogram.rb" was the help output? – cjs May 25 '09 at 22:36
2

If you want a simple command line parser for key/value commands without the use of gems:

But this only works if you always have key/value pairs.

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

If you don't' need any checking you could just use:

opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end
Roger
  • 7,535
  • 5
  • 41
  • 63
2

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. -lfile).

def usage
  "usage: #{File.basename($0)}: [-l<logfile>] [-q] file ..."
end

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
FelipeC
  • 9,123
  • 4
  • 44
  • 38
2

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)

David Foster
  • 669
  • 6
  • 5
  • (I've edited this to clean improve the code formatting, mainly using alignment to make the block and control structure more clear, which I feel is especially important in something this dense with punctuation. But if you hate the new formatting, please feel free to undo the edit.) – cjs Jul 14 '18 at 00:43
  • This is pretty nice, if not the easiest thing in the world to read. I like that it also demonstrates that changing argument "syntax" (here, allowing options after positional args and disallowing option arguments except by using `=`) can make a difference in the code you need. – cjs Jul 14 '18 at 00:52
  • Thanks for the reformatting. It's definitely obscure to read and one could easily trade length of code for clarity. Now that I trust this code, more or less, I treat it like a gem and I never try to figure out what it's doing under the covers (so clarity is no longer important now that I have trust). – David Foster Jul 14 '18 at 21:57
2

Trollop is pretty cheap.

cjs
  • 25,752
  • 9
  • 89
  • 101
  • That would be, . I rather like it, I think, though I really wasn't looking for a library. – cjs May 26 '09 at 14:30
  • True, it is a library. However, at < 800 LOC, it's a pretty negligible one, at that. http://gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb –  May 28 '09 at 19:35
  • 1
    I was kinda thinking that maybe 30-50 lines would be good, if I were going so far as to use a "library." But then again, I guess once you've got as far as a separate file full of code, API design is more important than line count. Still, I'm not sure I would want to include it in a one-off script that I just want to plop into the bin directory on a random system. – cjs Jun 19 '09 at 10:21
  • Instead of plopping it into a bin directory, you might consider having a proper deployment strategy –  Aug 29 '12 at 18:39
  • 1
    You've got it backwards: the point is to avoid having to have a more complex deployment strategy. – cjs Sep 09 '12 at 02:57
  • @CurtSampson Nope. Especially with something low-entropy like Trollop, it makes sense to configure the deployment and then forget about it. –  Sep 09 '12 at 03:53
  • 1
    You're quite wrong, because you're totally misinterpreting (or ignoring) the needs and intentions of the person who asked the question. I suggest you re-read the question carefully, particularly the final two points. – cjs Sep 09 '12 at 09:12
0

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
Bohr
  • 1,566
  • 1
  • 16
  • 21
0

Apparently @WilliamMorgan and I think alike. I just released last night to Github what I now see is a similar library to Trollop (Named how?) after having done a search for OptionParser on Github, see Switches

There are a few differences, but the philosophy is the same. One obvious difference is that Switches is dependent on OptionParser.

mac
  • 42,153
  • 26
  • 121
  • 131
thoran
  • 9
  • 1
0

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...
Ben Alavi
  • 392
  • 3
  • 7
  • Please read point 1 at the end of the question. If you can't type _all_ the necessary code right in your answer here, it's not an answer to the question. – cjs Jul 14 '18 at 00:56
  • Good point! I think at the time I assumed the lib was small enough that you could copy/paste the whole thing into whatever script you were working on w/o needing an external dependency, but it's definitely not a clean one-liner I would commit to memory to fulfill your point #2. I do think the underlying concept is novel and cool enough that I went ahead and made a boiled down version that answers your question a little more appropriately. – Ben Alavi Sep 12 '18 at 23:01
0

I'm developing my own option parser gem called Acclaim.

I wrote it because I wanted to create git-style command line interfaces and be able to cleanly separate the functionality of each command into separate classes, but it can also be used without the entire command framework as well:

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

No stable release as of yet, but I've already implemented some features like:

  • custom option parser
  • flexible parsing of option's arguments that allows for both minimum and optional
  • support for many option styles
  • replace, append or raise on multiple instances of the same option
  • custom option handlers
  • custom type handlers
  • predefined handlers for the common standard library classes

There's a lot of emphasis on commands so it might be a little heavy for simple command line parsing but it works well and I've been using it on all of my projects. If you're interested in the command interface aspect then check out the project's GitHub page for more information and examples.

Matheus Moreira
  • 17,106
  • 3
  • 68
  • 107
-1

I created a very simple yet useful parser: parseopt. It uses Git's internal option parser as inspiration, and also Ruby's OptionParser.

It looks like this:

opts = ParseOpt.new
opts.usage = 'git foo'

opts.on('b', 'bool', 'Boolean') do |v|
 $bool = v
end

opts.on('s', 'string', 'String') do |v|
 $str = v
end

opts.on('n', 'number', 'Number') do |v|
 $num = v.to_i
end

opts.parse
FelipeC
  • 9,123
  • 4
  • 44
  • 38
  • You didn't even check the code. I've put another answer taking the parsing code out. – FelipeC Jan 17 '18 at 23:47
  • @cjs The first answer mentions Trollop, do they need to put the whole code of Trollop? – FelipeC May 26 '21 at 20:31
  • Yes, which is why I downvoted that answer. (That it's the "first" answer is because it appears that many people voting on answers to this question are voting based on a different set of requirements from those in the question. Not much I can do about that, but you'll note that the accepted answer is not the highest voted one.) – cjs May 29 '21 at 04:09