GithubHelp home page GithubHelp logo

leejarvis / slop Goto Github PK

View Code? Open in Web Editor NEW
1.1K 17.0 72.0 1.2 MB

Simple Lightweight Option Parsing - ✨ new contributors welcome ✨

License: MIT License

Ruby 100.00%
command-line option-parser simple easy-to-use ruby beginner-friendly

slop's Introduction

Slop

Slop is a simple option parser with an easy to remember syntax and friendly API.

Build Status

Installation

gem install slop

Usage

opts = Slop.parse do |o|
  o.string '-h', '--host', 'a hostname'
  o.integer '--port', 'custom port', default: 80
  o.string '-l', '--login', required: true
  o.symbol '-m', '--method', default: :get
  o.bool '-v', '--verbose', 'enable verbose mode'
  o.bool '-q', '--quiet', 'suppress output (quiet mode)'
  o.bool '-c', '--check-ssl-certificate', 'check SSL certificate for host'
  o.bool '-k', '--use-keychain', 'store passphrase in OS keychain'
  o.on '--version', 'print the version' do
    puts Slop::VERSION
    exit
  end
end

ARGV #=> -v --login alice --host 192.168.0.1 -m post --check-ssl-certificate --use-keychain false

opts[:host]         #=> 192.168.0.1
opts[:login]        #=> alice
opts[:method]       #=> :post
opts[:use_keychain] #=> false

# We can also check if a flag was passed (this has no bearing on the options default value):
opts.use_keychain?          #=> true
opts.verbose?               #=> true
opts.quiet?                 #=> false
opts.check_ssl_certificate? #=> true

opts.to_hash  #=> { host: "192.168.0.1", port: 80, login: "alice", method: :post, verbose: true, quiet: false, check_ssl_certificate: true }

Note that the block we've added to the --version flag will be executed during parse time. Therefore these blocks should be reserved for immediately reacting to the presence of a flag. If you want to access other options or mutate values, check out the "Custom option types" section below and implement the #finish method.

Option types

Built in Option types are as follows:

o.string  #=> Slop::StringOption, expects an argument
o.bool    #=> Slop::BoolOption, argument optional, aliased to BooleanOption
o.integer #=> Slop::IntegerOption, expects an argument, aliased to IntOption
o.float   #=> Slop::FloatOption, expects an argument
o.array   #=> Slop::ArrayOption, expects an argument
o.regexp  #=> Slop::RegexpOption, expects an argument
o.symbol  #=> Slop::SymbolOption, expects an argument
o.null    #=> Slop::NullOption, no argument and ignored from `to_hash`
o.on      #=> alias for o.null

You can see all built in types in slop/types.rb. Suggestions or pull requests for more types are welcome.

Advanced Usage

This example is really just to describe how the underlying API works. It's not necessarily the best way to do it.

opts = Slop::Options.new
opts.banner = "usage: connect [options] ..."
opts.separator ""
opts.separator "Connection options:"
opts.string "-H", "--hostname", "a hostname"
opts.int "-p", "--port", "a port", default: 80
opts.separator ""
opts.separator "Extra options:"
opts.array "--files", "a list of files to import"
opts.bool "-v", "--verbose", "enable verbose mode", default: true

parser = Slop::Parser.new(opts)
result = parser.parse(["--hostname", "192.168.0.1", "--no-verbose"])

result.to_hash #=> { hostname: "192.168.0.1", port: 80,
                 #     files: [], verbose: false }

puts opts # prints out help

Arguments

It's common to want to retrieve an array of arguments that were not processed by the parser (i.e options or consumed arguments). You can do that with the Result#arguments method:

args = %w(connect --host google.com GET)
opts = Slop.parse args do |o|
  o.string '--host'
end

p opts.arguments #=> ["connect", "GET"] # also aliased to `args`

This is particularly useful when writing scripts with ARGF:

opts = Slop.parse do |blah|
  # ...
end

# make sure sloptions aren't consumed by ARGF
ARGV.replace opts.arguments

ARGF.each { |line|
  # ...
}

Arrays

Slop has a built in ArrayOption for handling array values:

opts = Slop.parse do |o|
  # the delimiter defaults to ','
  o.array '--files', 'a list of files', delimiter: ','
end

# Both of these will return o[:files] as ["foo.txt", "bar.rb"]:
# --files foo.txt,bar.rb
# --files foo.txt --files bar.rb
# This will return o[:files] as []:
# --files ""

If you want to disable the built-in string-splitting, set the delimiter to nil.

Custom option types

Slop uses option type classes for every new option added. They default to the NullOption. When you type o.array Slop looks for an option called Slop::ArrayOption. This class must contain at least 1 method, call. This method is executed at parse time, and the return value of this method is used for the option value. We can use this to build custom option types:

module Slop
  class PathOption < Option
    def call(value)
      Pathname.new(value)
    end
  end
end

opts = Slop.parse %w(--path ~/) do |o|
  o.path '--path', 'a custom path name'
end

p opts[:path] #=> #<Pathname:~/>

Custom options can also implement a finish method. This method by default does nothing, but it's executed once all options have been parsed. This allows us to go back and mutate state without having to rely on options being parsed in a particular order. Here's an example:

module Slop
  class FilesOption < ArrayOption
    def finish(opts)
      if opts.expand?
        self.value = value.map { |f| File.expand_path(f) }
      end
    end
  end
end

opts = Slop.parse %w(--files foo.txt,bar.rb -e) do |o|
  o.files '--files', 'an array of files'
  o.bool '-e', '--expand', 'if used, list of files will be expanded'
end

p opts[:files] #=> ["/full/path/foo.txt", "/full/path/bar.rb"]

Errors

Slop will raise errors for the following:

  • An option used without an argument when it expects one: Slop::MissingArgument
  • An option used that Slop doesn't know about: Slop::UnknownOption
  • An option marked as required when not provided: Slop::MissingRequiredOption
  • An option marked as validate_types, with an argument that does not match its type (i.e. bla for integer): Slop::InvalidOptionValue

These errors inherit from Slop::Error, so you can rescue them all. Alternatively you can suppress these errors with the suppress_errors config option:

opts = Slop.parse suppress_errors: true do
  o.string '-name'
end

# or per option:

opts = Slop.parse do
  o.string '-host', suppress_errors: true
  o.int '-port'
end

Validating Types

By default, Slop does not validate whether an argument is a valid value for a given option; instead, if the option has a default value, it will be used over the invalid argument provided. In order to have types (such as integer and float) validate and indicate that the provided value is invalid, an extra option can be either provided to the argument itself, or its option set:

opts = Slop::Options.new
opts.int "-p", "--port", "a port", default: 80, validate_types: true

parser = Slop::Parser.new(opts)
result = parser.parse(["--port", "bla"])
# invalid value for -p, --port (Slop::InvalidOptionValue)

# Or to the option set...
opts = Slop::Options.new(validate_types: true)
opts.int "-p", "--port", "a port", default: 80

parser = Slop::Parser.new(opts)
result = parser.parse(["--port", "bla"])
# invalid value for -p, --port (Slop::InvalidOptionValue)

Printing help

The return value of Slop.parse is a Slop::Result which provides a nice help string to display your options. Just puts opts or call opts.to_s:

opts = Slop.parse do |o|
  o.string '-h', '--host', 'hostname'
  o.int '-p', '--port', 'port (default: 80)', default: 80
  o.string '--username'
  o.separator ''
  o.separator 'other options:'
  o.bool '--quiet', 'suppress output'
  o.on '-v', '--version' do
    puts "1.1.1"
  end
end

puts opts

Output:

% ruby run.rb
usage: run.rb [options]
    -h, --host     hostname
    -p, --port     port (default: 80)
    --username

other options:
    --quiet        suppress output
    -v, --version

This method takes an optional prefix value, which defaults to " " * 4:

puts opts.to_s(prefix: "  ")

It'll deal with aligning your descriptions according to the longest option flag.

Here's an example of adding your own help option:

o.on '--help' do
  puts o
  exit
end

Commands

Slop no longer has built in support for git-style subcommands.

slop's People

Contributors

amon-sha avatar basicxman avatar brbrady avatar chsonnu avatar conradirwin avatar denisdefreyne avatar dominikh avatar eric1234 avatar flavono123 avatar heyvito avatar jimmycuadra avatar jylitalo avatar kachick avatar koic avatar kyrylo avatar leejarvis avatar nigorojr avatar no6v avatar olleolleolle avatar petergoldstein avatar rickhull avatar sigurdsvela avatar spk avatar threemachines avatar tmatilai avatar tylermercier avatar unpublishedworks avatar utkarshkukreti avatar whitequark avatar woodruffw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

slop's Issues

internal error when providing bad options

Not sure if you consider this a bug, but the error i get when providing bad options ls -mj (where 'j' is not a defined option) results in a pretty low-level error:

[3] (pry) main: 0> ls -mj
NoMethodError: undefined method `count' for nil:NilClass
from /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:495:in `block in execute_multiple_switches'
[4] (pry) main: 0> cat --ex
Exception: NoMethodError: undefined method `count' for nil:NilClass
--
From: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb @ line 495 @ level: 0 of backtrace (of 58).

    490:   # Returns nothing.
    491:   def execute_multiple_switches(option, argument, index)
    492:     execute_option(option, nil, index)
    493:     argument.split('').each do |key|
    494:       opt = fetch_option(key)
 => 495:       opt.count += 1
    496:       execute_option(opt, nil, index, key)
    497:     end
    498:   end
    499: 
    500:   # Extract an option from a flag.
[5] (pry) main: 0> wtf??
Exception: NoMethodError: undefined method `count' for nil:NilClass
--
 0: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:495:in `block in execute_multiple_switches'
 1: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:493:in `each'
 2: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:493:in `execute_multiple_switches'
 3: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:442:in `process_item'
 4: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:387:in `block in parse_items'
 5: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:384:in `each'
 6: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:384:in `each_with_index'
 7: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:384:in `parse_items'
 8: /Users/john/.rvm/gems/ruby-1.9.3-p194/gems/slop-3.3.1/lib/slop.rb:202:in `parse!'
 9: /Users/john/ruby/projects/pry/lib/pry/command.rb:467:in `call'
10: /Users/john/ruby/projects/pry/lib/pry/command.rb:394:in `call_with_hooks'
11: /Users/john/ruby/projects/pry/lib/pry/command.rb:372:in `call_safely'
12: /Users/john/ruby/projects/pry/lib/pry/command.rb:319:in `process_line'
13: /Users/john/ruby/projects/pry/lib/pry/command_set.rb:343:in `process_line'
14: /Users/john/ruby/projects/pry/lib/pry/pry_instance.rb:423:in `process_command'
15: /Users/john/ruby/projects/pry/lib/pry/pry_instance.rb:407:in `retrieve_line'
16: /Users/john/ruby/projects/pry/lib/pry/pry_instance.rb:304:in `block in r'
17: /Users/john/ruby/projects/pry/lib/pry/pry_instance.rb:301:in `loop'
18: /Users/john/ruby/projects/pry/lib/pry/pry_instance.rb:301:in `r'
19: /Users/john/ruby/projects/pry/lib/pry/pry_instance.rb:271:in `re'
[6] (pry) main: 0> 

2.4.0 command not working

I am trying the example

require 'slop'

opts = Slop.new do
  command :clean do
    on :v, :verbose, do
      puts 'Enabled verbose mode for clean'
    end
  end

  # non-command specific options
  on :v, :version do
    puts 'version 1'
  end
end

puts opts.to_hash

and I see nothing being outputted

~$ ruby slop.rb 
{:version=>nil}
~$ ruby slop.rb -v
{:version=>nil}
~$ ruby slop.rb clean -v
{:version=>nil}
~s$ ruby slop.rb clean -v
{:version=>nil}

I am using ruby 1.9.2 on Ubuntu 10.04

default option values are overridden by successive options

I have the following options defined for a show-stack command:

opt.on :v, :verbose, "Include extra information."
opt.on :c, :current, "Display N frames either side of current frame (default to 5).", :optional => true, :as => Integer, :default => 5

When I invoke my command as follows:

show-stack -c -v

The 5 default for the c option is nulled out, as if i had passed in -c 0

However if i reverse the order of option passing:

show-stack -v -c

Then the default of 5 works again, and I see the expected result.

It would be nice, IMO, if show-stack -c -v and show-stack -v -c were equivalent :)

Clarify documentation on compulsory arguments

This might be a bit of semantic wrangling but in the Usage section of the readme, there's the following:

on :n, :name, 'Your name', true              # compulsory argument

In my interpretation -n|--name is the argument (i.e ARGV argument) being referred to and not the argument to the option itself.

I'm honestly not sure which wording would clarify that but I spent a good amount of time looking at the tests and the code before I realized that "compulsory argument" meant that -n|--name itself required an argument and not that the option itself was required.

If anything, maybe include a small example of how to use Slop::MissingArgumentError and what it covers and doesn't?

Question re: Slop Help

Using slop's help message, is there an easy way to indicate:

  • which parameters are optional?
  • which parameters take an argument?

Small warning in 1.8.7

Ruby 1.8.7 throws a warning:

options.push (@arguments || extras[:argument]) ? true : (args.shift ? true : false)

Should be changed to:

options.push(@arguments || extras[:argument]) ? true : (args.shift ? true : false)

Then the warning disappears:

/Programs/Ruby/1.8.7p334/lib/ruby/site_ruby/1.8/slop.rb:1019: warning: don't put space before argument parentheses

Problem with :default => true

There's a bug in the current version of slop where setting an option like:

on :f, :foo, 'Set foo option', :default => true

and invoking Slop without any options will erroneously return:

opts.foo? => false <== This is wrong! (I think it should be automatically defaulted to true)
opts['foo'] => true

For now, I'm using the latter form for options that need :default => true

Commands should support arguments

Currently, there is no way to implement parsing of commands that accept arguments. Let's consider a basic Slop v3 command set:

commands = Slop::Commands.new do
  on 'install'
end

Let's try to parse some input:

commands.parse %w( install geronimo )
#=> ["geronimo"]

The problem with current code is that it doesn't support arguments for install subcommand. Of course, you can save the return value of Commands#parse and use it in your program, but let's have a look at the situation when our command set have more than one command:

commands = Slop::Commands.new do
  on :install
  on :list
end

commands.parse %w( install geronimo )
#=> ["geronimo"]

commands.parse %w( list geronimo )
#=> ["geronimo"]

Both calls return the same value. Given a stroppy user of our CLI tool, it's pretty obvious that we can't tell, which of the subcommands has been executed from program's point of view: install or list?

It seems like the only way to go is to use command options:

commands = Slop::Commands.new do
  on :install do
    on :n, :name
  end

  on :list do
    on :n, :name
  end
end

commands.parse %w( install --name geronimo )
#=> ["--name", "geronimo"]
commands[:install].present? :name
#=> true

commands.parse %w( list --name geronimo )
#=> ["--name", "geronimo"]
commands[:list].present? :name
#=> true

Quite frankly, this pretty verbose and simply sucks.

Possible solutions

Add :with_argument parameter hash to Commands#on

This parameter should be specific for each command.

commands = Slop::Commands.new do
  on :install, :with_argument => true
  on :list
end

commands.parse %w( install geronimo )
#=> ["install", "geronimo"]
commands.present? :install
#=> true
commands.present? :list
#=> false
commands[:list].argument
#=> "geronimo"

Treat one of subcommand options as a default option

commands = Slop::Commands.new do
  on :install do
    as_default(on(:n, :name))
    on :v, :verbose
  end

  on :list
end

commands.parse %w( install --name geronimo )
#=> ["--name", "geronimo"]
commands.parse %w( install geronimo )
#=> ["install", "geronimo"]
commands[:list].argument
#=> "geronimo"

Slop doesn't parse correctly with multiple_switches

Hi,

Using the multiple_switches feature, Slop doesn't work as expected. For example, with the following code where args is ["-mLv", "-C", "Ripper"] :

    opts = Slop.parse! args, :multiple_switches => true do
      on :v, :verbose, "Show value of variables and constants and locations of methods"
      on :L, :less, "Only show methods set by the receiver"
      on :a, :more, "Show all of the methods, including those defined in Object"
      on :f, :filter, "Regular expression to filter methods and variables",
        :optional => false, :default => ""
      on :C, :context, "Changes self", :optional => false do |arg|
        p arg
        ctxt.target = Pry.binding_for(target.eval(context))
      end

      on :l, :locals, "Show local variables"
      on :g, :globals, "Show global variables"
      on :i, 'instance-variables', "Show instance variables"
      on :k, 'class-variables', "Show class variables"

      on :c, :constants, "Show constants"

      on :m, :methods, "Show methods"
      on :M, 'instance-methods', "Show instance methods"

      on :h, :help, 'Print this help message', :tail => true
    end

args still contains the flags passed to Slop and the block passed to the context flag doesn't get called.

as: :int produces odd results without argument: true

Using as: :int produces odd results when the user forgets to specify that there is an optional or mandatory argument.

do :l, :loops, as: int

opts[:l]

produces 0 when -l n or -l is provided. With optional: true, the return value is int n for -l n, and nil for -l (which is more what I would expect).

This is really more of a user bug, but I thought the behaviour was a little weird.

Completion thoughts

OptParser has a horrible API, but one of the great things about it - at least in theory - is its built-in support for command completion. (I say "in theory" because it looks like you have to know to install rb_optparse.bash in your profile, and then manually define each command that might use it, and there's no documentation that would tell you that, so I'm not sure anyone's ever used it.)

I took a look at optparse.rb to see how they do it; there's a lot of code there, but it's mostly around ruby/ruby@ 644f0445e86034dde399d6db8261c82cf34b8e07 and ruby/ruby@91c0ff4. Looks like they add two secret options, --*-completion-bash=X and --*-completion-zsh=X, where X is the phrase being tab-completed, and then rb_optparse.bash sets up bash to use that option to generate the completion file.

Kinda hackish, but doable. Have you given any thought to doing that in slop eventually? I think you could/should go one further, and skip the shim. Allow me to say Slop.parse :auto_complete => true, and have slop itself install the compgen/compsys completion (if it's not present). The first time I run the script anywhere, boom - magic auto-completion.

It's a lot of work, but it'd be a killer feature ☺

Printing help from an InvalidOptionError rescue

Maybe I'm approaching this the wrong way, please advise:

begin
    opts = Slop.parse(:strict => true) do
      on :d, :destdir=, 'destination directory'
   end
rescue Slop::InvalidOptionError => x
    puts x.to_s
    # how to do the equivalent of opts.help in here?
end

two separator's don't add an extra return

Why doesn’t the following give me an extra return?

separator ''
separator '-create options'

Only if I add a 'on' then it will, like this

separator ''
on
separator '-create options'

doesn't work with 1.8.7?

I was really excited to try this out, immediately got an error on line 114 of lib/slop.rb

option = Option.new(self, *clean_options(args), options, &block)

afaik, ruby 1.8 doesn't support inlining *args into the method signature in other locations than the end. I tried the obvious modification:
option = Option.new(self, *[clean_options(args), options], &block)

but this failed with a method signature error.

Slop::Optionize

Feature thoughts:

require 'slop'

class User
  include Slop::Optionize

  option :n, :name=, 'Your name'
  option :a, :age=,  'Your age', :as => :int

  def birthday_year
    Time.now.year - options[:age]
  end

end

u = User.new
u.parse_options %w[--name lee --age 346]
p u.birthday_year #=> 1665

Diff: https://gist.github.com/88f890540376794559ab

Commit the .yardoc file

The .yardoc file is used by services like rdoc.info/rubydoc.info and users who manually want to generate documentation. As such it is best to not gitignore it but rather commit it to the repository.

Unable to use lists multiple times

Hi,

I'm trying to use lists several times as specified in the wiki. Here are my two trials:

# This one is like the wiki says

opts = Slop.new do
  on :friends, 'Your friends', true, :as => Array
end

opts.parse %w[ --friends lee foo bar --friends john,jack ]

p opts[:friends] #=> true
# This one is with a = sign after :friends

opts = Slop.new do
  on :friends=, 'Your friends', true, :as => Array
end

opts.parse %w[ --friends lee foo bar --friends john,jack ]

p opts[:friends] #=> ["john", "jack"]

In both cases the return value is not the expected one ["lee", "john", "jack"]

Command ideas

Following up ticket #38 , here is what I would like to see with command.

  1. They should work similar to git? so

git help clone => show clone options and help
git help or --help => show generic help and available commands

Maybe it could be define as:

command :one, 'description' do
    on :help  => print current  options help , combined with global options if any?
end

on :help => show list of commands?
on :v , 'Verbose'

or using the Slop.new(:help => true) , it does automatically what is describe above?

  1. I would like to be able to nest command like
command :config do

 command :one do
   on :specific_to_one
 end

 command :two do
   on :specific_to_two
 end

 on : common_opt_for_two_and_one
end

on :global_opt

Adding a `:unless` option to a Slop::Option

This allows us to do the following:

opts = Slop.new do
  on :g, :grep, true do

  end

  on :h, :head, true, :unless => :grep do |limit|
    # this will be called unless the --grep option is used
  end

  on :t, :tail, true, :unless => :grep do |limit|
    # this will be called unless the --grep option is used
  end
end

Thoughts:

  • Should opts.head? still return true if the --head option was present, but grep was also there.
  • Should opts[:head] == 10 if --head 10. Meaning Slop would only not execute it's block, it will still collect data, or should it ignore everything, meaning opts[:head].nil? #=> true even with --head 10

Regressions in 3.2.0 (vs 2.4.4)

As you know, pry has been using slop for ages. Unfortunately the new version of slop breaks in a lot of ways.

Some of these I can fix at our end:

• convert true, to :argument => true
• convert :optional => true to :optional_argument => true.

Some of these I can't:

• Argument arrays can no longer contain nil values.
We were using this facility so we could pass a regex capture groups through slop.
• Ranges can no-longer have negative argument values.
We used this in a lot of places in the same way ruby allows array[-1]
:as => :count doesn't work anymore.
I tried the obvious fix to return @count but it looks like the counting itself is also broken

I'll try to send pull-requests for these over the weekend. Another alternative we're strongly considering would just be to bundle Slop 2.4.4 inside the Pry namespace; but that will become a worse-and-worse solution as the future goes on.

shell words at option level

i wanna:

Gist a string:

gist -s "sup pig how are you"

and i also wanna:

Gist a method:

command -m "hello".chomp

Currently these are mutually exclusive as we have to specify shell words for the whole command, or not at all. It would be good if we could specify shell words on the option level.

Slop object should be reusable

At the moment, Slop uses define_method for checking an options presence. This means the return value is stored and never changes, which means this does not work:

opts.parse %w()
p opts.foo? #=> false, true that

opts.parse %w( --foo )
p opts.foo? #=> false (wtf bro, this should be true!)

method_missing should not define a method and instead lazily check for an options presence on each call

Slop "opts" object uses method_missing for "option?" methods

Using method_missing to receive "option?" messages causes problems when someone defines an "option?" method on Object -- the overridden method gets called instead of Slop's.

For example, pry has an #in? option: https://github.com/pry/pry/blob/master/lib/pry/default_commands/shell.rb#L91

Meanwhile, my library adds #in? to Object, which breaks pry thusly:

[7] pry(main)> [].each_cons :barf
TypeError: can't convert Symbol into Integer
from (pry):2:in `each_cons'
[8] pry(main)> cat --ex
ArgumentError: wrong number of arguments (0 for 1)
from /home/epi/.rvm/gems/ruby-1.9.3-rc1/gems/epitools-0.5.2/lib/epitools/basetypes.rb:817:in `in?'
[9] pry(main)>

Solutions:

  • define_method the methods
  • Inherit from BasicObject instead of Object

Wrong event fired

Note below that the help event is triggered and not the replay event. Yet when passed any other value than 1 to replay (such as --replay 2) the correct behavior is observed.

require 'slop'
require 'shellwords'

args = "--replay 1".shellsplit

opts = Slop.parse(args) do
  banner "Usage: hist [-rSTART..END]"
  on :r, :replay, 'The line (or range of lines) to replay.', true, :as => Range do |r|
    options[:r].argument_value = r.to_i if r.is_a?(String)
  end
  on :h, :help, 'Show this message.', :tail => true do
    puts help
  end        
end

# output is the standard `help` information ?!

EDIT: weird behaviour also occurs when passing --replay 0. I get the following error for --replay 0:

/home/john/.rvm/gems/ruby-1.9.2-head/gems/slop-1.5.0/lib/slop.rb:354:in `check_optional_argument!': '0' expects an   argument, none given (Slop::MissingArgumentError)

How to expect and handle non-boolean options cleanly?

I have been working with this gem, and I like it so far. One thing I'm having some trouble with is finding a clean way to expect a non-boolean value for an option. For instance:

opts = Slop.new(:strict => true) do
  banner "Usage: cmd [options]"
  on :c, :context, "Deploy to context [location]", true, :as => String
  on :a, :app, "Application Name [name]", true, :as => String
  on :s, :sinatra, "Use Sinatra?"
  on :h, :help, "Show detailed Help"
end

This works great, but if I pass to the command line cmd -cash, I will get all true values for the options, but no clean way to figure out if :app or :context were given a string or not (since the :as => String just coerces the true value to a string).

When parsing the opts, opts[:app] returns true. So, everywhere I want to work with it, I need to check two things:

if opts[:app] && opts[:app].class == String

Can you suggest any easier ways to do this? I would like to be able to roll up the Slop::InvalidOptionError for these options (since technically I require an option, but I also require a value for it, too. That would just add more complexity to every option, too).

Using default :help behaves differently to v2

Hi,

I'm not sure if this is a bug or a feature but I'm using the default :help option the following way: Slop.parse!(args, :help => true, :multiple_switches => false, &options)

In v2 when the user typed pry -h it would display help, and that's all.
In v3 when the user types pry -h it displays help AND puts them in a pry session afterwards.

So i'm guessing in v2 you broke out of option processing as soon as -h was encountered, but in v3 you do something different?

Supporting --no- prefixed for negating options

Add support to prefix --no- to an existing option, this option value will then negate the value of the option:

o = Slop.parse("--no-verbose --no-password") do
  opt(:v, :verbose, "Enable verbose mode")
  opt(:p, :password, "Disable password prompt", :default => false) # bad example
end

o[:verbose] #=> false
o[:password] #=> true

slop 2.4.0 problems

Hi,

In previous versions of slop (i only tried 2.2.0 and earlier) the code i'm about to paste worked fine - that is gist -i 1 did not raise an exception. Since i updated to 2.4.0 however I keep getting an exception, i paste the situation below:

It seems that the value of opts[:i] is always nil.

Note, it's also nil if i pass in a real Range or I leave off a paramter altogether (to rely on the default):

[4] (pry) main: 0> gist -i 1..1
NoMethodError: undefined method `>=' for nil:NilClass
from /Users/john/ruby/projects/pry/lib/pry/helpers/command_helpers.rb:254:in `absolute_index_number'
[5] (pry) main: 0> gist -i 
NoMethodError: undefined method `>=' for nil:NilClass
from /Users/john/ruby/projects/pry/lib/pry/helpers/command_helpers.rb:254:in `absolute_index_number'
[6] (pry) main: 0> 

Fetching: slop-2.4.0.gem (100%)
Successfully installed slop-2.4.0
Gems updated: slop
crow:pry john$ rake pry
(in /Users/john/ruby/projects/pry)
/Users/john/ruby/projects/pry/Rakefile:21: warning: Insecure world writable dir /Applications/MacPorts/Emacs.app/Contents in PATH, mode 040777
[1] (pry) main: 0> def hello
[1] (pry) main: 0*   puts "hi"
[1] (pry) main: 0* end  
=> nil
[2] (pry) main: 0> gist -i 1
NoMethodError: undefined method `>=' for nil:NilClass
from /Users/john/ruby/projects/pry/lib/pry/helpers/command_helpers.rb:254:in `absolute_index_number'
[3] (pry) main: 0> show-command gist

From: /Users/john/ruby/projects/pry/lib/pry/default_commands/documentation.rb @ line 58:
Number of lines: 56

command "gist", "Gist a method or expression history to github. Type `gist --help` for more info.", :requires_gem => "gist", :shellwords => false do |*args|
  require 'gist'

  target = target()

  opts = parse_options!(args) do |opt|
    opt.banner unindent <<-USAGE
      Usage: gist [OPTIONS] [METH]
      Gist method (doc or source) or input expression to github.
      Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
      e.g: gist -m my_method
      e.g: gist -d my_method
      e.g: gist -i 1..10
    USAGE

    opt.on :d, :doc, "Gist a method's documentation.", true
    opt.on :m, :method, "Gist a method's source.", true
    opt.on :p, :public, "Create a public gist (default: false)", :default => false
    opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional => true, :as => Range, :default => -5..-1
  end

  type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
  if opts.present?(:in)
    code_type = :ruby
    content = ""
    normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length)
    input_items = _pry_.input_array[normalized_range] || []

    input_items.each_with_index.map do |code, index|
      corrected_index = index + normalized_range.first
      if code && code != ""
        content << code
        content << "#{comment_expression_result_for_gist(_pry_.output_array[corrected_index].pretty_inspect)}" if code !~ /;\Z/
      end
    end
  elsif opts.present?(:doc)
    meth = get_method_or_raise(opts[:d], target, {})
    content = meth.doc
    code_type = meth.source_type

    text.no_color do
      content = process_comment_markup(content, code_type)
    end
    code_type = :plain
  elsif opts.present?(:method)
    meth = get_method_or_raise(opts[:m], target, {})
    content = meth.source
    code_type = meth.source_type
  end

  link = Gist.write([:extension => ".#{type_map[code_type]}",
                     :input => content],
                    !opts[:p])

  output.puts "Gist created at #{link}"
end

Nested commands

Is slop capable of doing nested command? So take for example a cli tool:

Usage: test.rb
*required

--user
  --get 
      --id*
  --create
      --first-name*
      --middle-name
      --last-name*
--group
  --get
     --id*
  --delete
     --id*

./test.rb --user --get --id=1 (would be a valid command)

however

./test.rb --user --delete (would not)

basically allowing each opt to have child opts which are printed in the help and handled by the parser.

I would be happy to create a fork and do the work myself, if you could maybe point me in a general direction as to how this might be implemented using slop.

Thanks:)

support argument next to short option

I switched from optparse to slop but now my program behaves like this :
mytool -f'hello' # doesn't match
mytool -f 'hello' # does match

Maybe a small issue in the way arguments are grabbed.

Default not getting set depending on order of options.

With the following script:

slop = Slop.parse do
  on :d, :dev, 'Development-mode'
  on :s, :server, 'Start server', default: 3000
end

puts slop.to_hash.inspect

Depending on order of options, the default value for the -s option doesn't get set, ex.:

$ test -s
{:dev => nil, :server => 3000}
$ test -d -s
{:dev => true, :server => 3000}
$ test -s -d
{:dev => true, :server => 0}

This seems to be caused by the second option used as the value of the first.

Also when grouping options, the default never get set:

$ test -sd
{:dev => true, :server => 0}
$ test -ds
{:dev => true, :server => 0}

Tried with slop 2.1 and 2.4

Support for Ruby 1.8.6?

Slop is the nicest option parser I have found, but it doesn't work on Ruby 1.8.6 because of this idiom

select(&:required)

which came in on 1.8.7

I'm loath to fork. Any chance of 1.8.6 being supported?

--long-option=val raises NoMethodError if using :multiple_switches (which is default)

From irb, explicitly disabling multiple_switches:

1.9.2p290 :001 > opts = Slop.parse!(['--asd=foo'], :multiple_switches => false) do on '--asd='; end
 => #<Slop {:strict=>false, :help=>false, :banner=>nil, :ignore_case=>false, :autocreate=>false, :arguments=>false, :optional_arguments=>false, :multiple_switches=>false, :longest_flag=>3} ["#<Slop::Option [- | --asd=] () {:argument=>true, :optional_argument=>false, :tail=>false, :default=>nil, :callback=>nil, :delimiter=>\",\", :limit=>0, :match=>nil, :optional=>true, :required=>false, :as=>String, :autocreated=>false}"]> 

From irb, default config:

1.9.2p290 :002 > opts = Slop.parse!(['--asd=foo']) do on :asd=; end
NoMethodError: undefined method `count=' for nil:NilClass
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:471:in `block in     execute_multiple_switches'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:469:in `each'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:469:in `execute_multiple_switches'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:407:in `process_item'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:369:in `block in parse_items'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:366:in `each'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:366:in `each_with_index'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:366:in `parse_items'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:195:in `parse!'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:122:in `init_and_parse'
    from /Users/jtjerno/.rvm/gems/ruby-1.9.2-p290@show_me/gems/slop-3.0.0/lib/slop.rb:63:in `parse!'
    from (irb):5

Parsing negative integers

At the moment, this is impossible. Slop decides anything prefixed with a - is likely to be an option, and not an option argument. To fix this Slop should check if an argument is numeric and likely to be a negative integer rather than an existing defined option

to_hash returns Strings as keys, should these be Symbols?

Example

p Slop.parse { on :name, true }.to_hash

With --name lee this will return

{"name"=>"lee"}

Should keys be symbolized so this returns

{:name=>"lee"}

instead?

It's a backwards incompatible change, and Slop follows semver, so this couldn't be the default behaviour unless I released a 2.0 (even though it would be a subtle change). Until then though I could add an optional argument to to_hash

Thoughts on this?

Slop cannot parse fuzzy options with arguments correctly

By "fuzzy options with arguments", in actual fact, I mean a presence of space between a flag and its option. Let me show some examples:

def slop
  Slop.new(:multiple_switches => false) do
    on :r, :argument => true
    on :I, :argument => true
  end
end

# Broken.
opts = slop
opts.parse %w( -Ilib -rgeronimo )
p opts[:I] # => "lib"
p opts[:r] # => nil

# Broken.
opts = slop
opts.parse %w( -Ilib -r geronimo )
p opts[:I] # => "lib"
p opts[:r] # => nil

# Works!
opts = slop
opts.parse %w( -I lib -r geronimo )
p opts[:I] # => "lib"
p opts[:r] # => "geronimo"

# Works!
opts = slop
opts.parse %w( -I lib -rgeronimo )
p opts[:I] # => "lib"
p opts[:r] # => "geronimo"

Array argument processing is broken?

I am using the latest 3.3.2 gem and I am running an example from WIki about lists:

require 'rubygems'
require 'slop'
opts = Slop.new do
  on :friends, 'Your friends', true, :as => Array
end

opts.parse %w[ --friends lee,john,barney ]

p opts[:friends] #=> ["lee", "john", "barney"]

When I run this, all I get is "true" instead of the array of arguments. Had the API for array been changed, but wiki had not been updated? Or is this a real bug?

negative number parsed as option and removed from input array

If i use a positive number Slop works as expected: args = ["2"]; Slop.parse!(args); args #=> ["2"]

But if i want to use a negative number as a non-option i get: args = ["-2"]; Slop.parse!(args); args #=> []

Slop appears to interpret the -2 as an option (though unrecognized) and removes it from the array.

AFAIK numbers are not valid options so this is incorrect behaviour,

thx

When autocreate is on, values are added as entries

When autocreate is turned on, values passed on to an option, get a key with value nil in the option Hash.

Here's the script I'm using.

require 'slop'
require 'pp'

opts = Slop.parse(:autocreate => true)

pp opts.to_hash

Here's it's output:

$ ruby slop.rb --name Lee -v
{:name=>"Lee", :Lee=>nil, :v=>true}

The :Lee key shouldn't be there, I think.

I'm on Slop version 3.3.2

on_ event blocks are instance_eval'd rather than called

This is contrary behaviour to OptParse where the blocks are called -- i think it's important that they're called rather than instance_eval'd as, esp in my case, i need to access utility functions defined in the enclosing scope inside the block. For example:

  on :h, :help, 'Show this message.', :tail => true do
    output.puts help
  end        

Iin the above the output method is not available.

Think about supporting 'bup.options' style parsing

http://apenwarr.ca/log/?m=201111#02

optspec = """
bup save [-tc] [-n name] 
--
r,remote=  hostname:/path/to/repo of remote repository
t,tree     output a tree id
c,commit   output a commit id
n,name=    name of backup set to update (if any)
d,date=    date for the commit (seconds since the epoch)
v,verbose  increase log output (can be used more than once)
q,quiet    don't show progress meter
smaller=   only back up files smaller than n bytes
bwlimit=   maximum bytes/sec to transmit to server
f,indexfile=  the name of the index file (normally BUP_DIR/bupindex)
strip      strips the path to every filename given
strip-path= path-prefix to be stripped when saving
graft=     a graft point *old_path*=*new_path* (can be used more than once)
"""
o = options.Options(optspec)
(opt, flags, extra) = o.parse(sys.argv[1:])
spec = <<-SPEC
ruby foo.rb [options]

---
r,remote=  hostname:/path/to/repo of remote repository
t,tree     output a tree id
c,commit   output a commit id
n,name=    name of backup set to update (if any)
d,date=    date for the commit (seconds since the epoch)
v,verbose  increase log output (can be used more than once)
q,quiet    don't show progress meter
smaller=   only back up files smaller than n bytes
bwlimit=   maximum bytes/sec to transmit to server
f,indexfile=  the name of the index file (normally BUP_DIR/bupindex)
strip      strips the path to every filename given
strip-path= path-prefix to be stripped when saving
graft=     a graft point *old_path*=*new_path* (can be used more than once)
SPEC

opts = Slop.spec(spec)
opts.parse

# ruby foo.rb --verbose --smaller 10 -r foo:/tmp
opts.verbose?  #=> true
opts[:smaller] #=> 10
opts[:remote]  #=> foo:/tmp

Options without dashes

It's possible to create commands without dashes? For example: boo init something

Awesome gem BTW :-)

block parameter for "execute"

Hey Lee!

I use Slop in a small project. A few days ago you introduced the "execute" method for a command context. This is great and one feature I'll want to use. So far so good ...

Now it seems that a block execution raises an error under some ruby versions. I tried it under MRI 1.8.7-p334, -p302 and 1.9.2-p180 and got the following results:

:001 > require 'rubygems'
 => true 
:002 > require 'slop'
 => true 
:003 > 
:004 >   opts = Slop.new do
:005 >       command :foo do
:006 >           on :v, :verbose
:007?>
:008 >           execute { |o| p o.verbose? }
:009?>       end
:010?>   end
 => #<Slop config_options={}>
:011 > opts.parse %w[foo --verbose] #=> true
(irb):8: warning: multiple values for a block parameter (2 for 1)
    from /Users/szimmermann/.rvm/gems/ruby-1.8.7-p334@private-jailmaster/gems/slop-1.9.1/lib/slop.rb:336
NoMethodError: undefined method `verbose?' for #<Array:0x1005557c0>
    from (irb):8
    from /Users/szimmermann/.rvm/gems/ruby-1.8.7-p334@private-jailmaster/gems/slop-1.9.1/lib/slop.rb:336:in `call'
    from /Users/szimmermann/.rvm/gems/ruby-1.8.7-p334@private-jailmaster/gems/slop-1.9.1/lib/slop.rb:336:in `execute'
    from /Users/szimmermann/.rvm/gems/ruby-1.8.7-p334@private-jailmaster/gems/slop-1.9.1/lib/slop.rb:619:in `execute_command'
    from /Users/szimmermann/.rvm/gems/ruby-1.8.7-p334@private-jailmaster/gems/slop-1.9.1/lib/slop.rb:447:in `parse_items'
    from /Users/szimmermann/.rvm/gems/ruby-1.8.7-p334@private-jailmaster/gems/slop-1.9.1/lib/slop.rb:193:in `parse'
    from (irb):11

I got this error under every 1.8.7 version, on 1.9.2 it doesn't occur and I think the problem is (as stated in the error description) on your "slop.rb" at line 336:

def execute(args=[], &block)
  if block_given?
    @execution_block = block
  elsif @execution_block.respond_to?(:call)
    @execution_block.call(self, args)
  end
end

I'm sorry, but I have no patch at this point.

But anyway: Thank you for your work on this nice helper! :-)

Stefan

--foo=bar syntax consuming following argument

Here are two samples of using on :do_thing= syntax, and having it consume the next argument if you use --do_thing=something. If you use --do_thing something then it works fine.

It's both removed from the passed in args (in parse!) mode, and ignored in terms of parsing (so if it's an option, it's not seen).

1.9.2p290 :001 > require 'slop'; args = ["--do_thing=fourtytwo", "hey dawg"]
 => ["--do_thing=fourtytwo", "hey dawg"]
1.9.2p290 :002 > opts = Slop.parse!(args) do on :do_thing=; end
 => #<Slop {:strict=>false, :help=>false, :banner=>nil, :ignore_case=>false, :autocreate=>false, :arguments=>false, :optional_arguments=>false, :multiple_switches=>true, :longest_flag=>8} ["#<Slop::Option [- | --do_thing=] () {:argument=>true, :optional_argument=>false, :tail=>false, :default=>nil, :callback=>nil, :delimiter=>\",\", :limit=>0, :match=>nil, :optional=>true, :required=>false, :as=>String, :autocreated=>false}"]>
1.9.2p290 :003 > args
 => []
1.9.2p290 :004 > opts[:do_thing]
 => "fourtytwo"


1.9.2p290 :001 > require 'slop'; args = ["--do_thing=fourtytwo", "--yes"]
 => ["--do_thing=fourtytwo", "--yes"]
1.9.2p290 :002 > opts = Slop.parse!(args) do on :do_thing=; on :yes; end
 => #<Slop {:strict=>false, :help=>false, :banner=>nil, :ignore_case=>false, :autocreate=>false, :arguments=>false, :optional_arguments=>false, :multiple_switches=>true, :longest_flag=>8} ["#<Slop::Option [- | --do_thing=] () {:argument=>true, :optional_argument=>false, :tail=>false, :default=>nil, :callback=>nil, :delimiter=>\",\", :limit=>0, :match=>nil, :optional=>true, :required=>false, :as=>String, :autocreated=>false}", "#<Slop::Option [- | --yes] () {:argument=>false, :optional_argument=>false, :tail=>false, :default=>nil, :callback=>nil, :delimiter=>\",\", :limit=>0, :match=>nil, :optional=>true, :required=>false, :as=>String, :autocreated=>false}"]>
1.9.2p290 :003 > opts.yes?
 => false
1.9.2p290 :004 > opts[:do_thing]
 => "fourtytwo"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.