Skip to content
Browse files

Merge pull request #8 from njoh/experimental

catch up with docopt v0.5.0
  • Loading branch information...
2 parents 057d9a0 + 48217a0 commit b095a7c72775b1e10d9350fd568a4e698c1eab1e @johari johari committed Sep 1, 2012
View
37 .gitignore
@@ -1,34 +1,3 @@
-*.py[co]
-
-# Vim
-*.swp
-
-# Packages
-*.egg
-*.egg-info
-dist
-build
-eggs
-parts
-bin
-var
-sdist
-develop-eggs
-.installed.cfg
-
-# Installer logs
-pip-log.txt
-
-# Unit test / coverage reports
-.coverage
-.tox
-nosetests.xml
-
-#Translations
-*.mo
-
-#Mr Developer
-.mr.developer.cfg
-
-# Sphinx
-docs/_*
+pkg/*
+*.gem
+.bundle
View
1 LICENSE-MIT → LICENSE
@@ -1,6 +1,7 @@
Copyright (c) 2012 Vladimir Keleshev <vladimir@keleshev.com>
Blake Williams <code@shabbyrobe.org>
Alex Speller <alex@alexspeller.com>
+ Nima Johari
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
View
291 README.md
@@ -1,6 +1,14 @@
-`docopt` – command line option parser, that will make you smile
+`docopt.rb` – command line option parser, that will make you smile
===============================================================================
+This is the ruby port of [`docopt`](https://github.com/docopt/docopt),
+the awesome option parser written originally in python.
+
+> New in version 0.5.0:
+>
+> Repeatable flags and commands are counted if repeated (a-la ssh `-vvv`).
+> Repeatable options with arguments are accumulated into list.
+
Isn't it awesome how `optparse` and `argparse` generate help messages
based on your code?!
@@ -12,78 +20,56 @@ write only the help message--*the way you want it*.
`docopt` helps you create most beautiful command-line interfaces *easily*:
```ruby
-doc = "Usage: example.rb [options] <arguments>...
+require "docopt"
+doc = <<DOCOPT
+Naval Fate.
+
+Usage:
+ #{__FILE__} ship new <name>...
+ #{__FILE__} ship <name> move <x> <y> [--speed=<kn>]
+ #{__FILE__} ship shoot <x> <y>
+ #{__FILE__} mine (set|remove) <x> <y> [--moored|--drifting]
+ #{__FILE__} -h | --help
+ #{__FILE__} --version
Options:
- -h --help show this help message and exit
- --version show version and exit
- -v --verbose print status messages
- -q --quiet report only file names
- -r --repeat show all occurrences of the same error
- --exclude=patterns exclude files or directories which match these comma
- separated patterns [default: .svn,CVS,.bzr,.hg,.git]
- --filename=patterns when parsing directories, only check filenames matching
- these comma separated patterns [default: *.rb]
- --select=errors select errors and warnings (e.g. E,W6)
- --ignore=errors skip errors and warnings (e.g. E4,W)
- --show-source show source code for each error
- --statistics count errors and warnings
- --count print total number of errors and warnings to standard
- error and set exit code to 1 if total is not null
- --benchmark measure processing speed
- --testsuite=dir run regression tests from dir
- --doctest run doctest on myself"
-
-require 'docopt'
-
-if __FILE__ == $0
- options = Docopt(doc, {:version => '1.0.0'})
- puts options.inspect
- puts ARGV.inspect
+ -h --help Show this screen.
+ --version Show version.
+ --speed=<kn> Speed in knots [default: 10].
+ --moored Moored (anchored) mine.
+ --drifting Drifting mine.
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
end
```
Beat that! The option parser is generated based on the docstring above that is
passed to `docopt` function. `docopt` parses the usage pattern
-(`"Usage: ..."`) and option descriptions (lines starting with dash "`-`") and
+(`Usage: ...`) and option descriptions (lines starting with dash "`-`") and
ensures that the program invocation matches the usage pattern; it parses
options, arguments and commands based on that. The basic idea is that
*a good help message has all necessary information in it to make a parser*.
-```ruby
-require 'docopt'
-doc = "Usage: your_program.rb [options]
-
- -h --help Show this.
- -v --verbose Print more text.
- --quiet Print less text.
- -o FILE Specify output file [default: ./test.txt]"
-
-options = Docopt(doc, { :version => nil, :help => true })`
-
-options['--help'] # returns true or false depending on option given
-
-```
-
-
Installation
===============================================================================
-~~Docopt is available through rubygems:~~
+Docopt is available via rubygems:
gem install docopt
-*Please note: the gem provides an out of date version. We are working on getting
-it updated.*
-
-Alternatively, you can just drop `docopt.rb` file into your project--it is
+Alternatively, you can just drop `lib/docopt.rb` file into your project--it is
self-contained. [Get source on github](http://github.com/docopt/docopt.rb).
`docopt` has been confirmed to work with 1.8.7p370 and 1.9.3p194. If you have
noticed it working (or not working) with an earlier version, please raise an
issue and we will investigate support.
-
API
===============================================================================
@@ -127,179 +113,74 @@ with keys spelled exactly like in a help message
(long versions of options are given priority). For example, if you invoke
the top example as::
- naval_fate.py ship Guardian move 100 150 --speed=15
+ naval_fate.rb ship Guardian move 100 150 --speed=15
the return dictionary will be::
-```python
-{'--drifting' => False, 'mine' => False,
- '--help' => False, 'move' => True,
- '--moored' => False, 'new' => False,
- '--speed' => '15', 'remove' => False,
- '--version' => False, 'set' => False,
- '<name>' => ['Guardian'], 'ship' => True,
- '<x>' => '100', 'shoot' => False,
- '<y>' => '150'}
+```ruby
+{"ship"=>true,
+ "new"=>false,
+ "<name>"=>["Guardian"],
+ "move"=>true,
+ "<x>"=>"100",
+ "<y>"=>"150",
+ "--speed"=>"15",
+ "shoot"=>false,
+ "mine"=>false,
+ "set"=>false,
+ "remove"=>false,
+ "--moored"=>false,
+ "--drifting"=>false,
+ "--help"=>false,
+ "--version"=>false}
```
Help message format
===============================================================================
-Help message consists of 2 parts:
-
-- Usage pattern, e.g.::
-
- Usage: my_program.py [-hso FILE] [--quiet | --verbose] [INPUT ...]
-
-- Option descriptions, e.g.::
+docopt.rb follows the docopt help message format.
+You can find more details at
+[official docopt git repo](https://github.com/docopt/docopt#help-message-format)
- -h --help show this
- -s --sorted sorted output
- -o FILE specify output file [default: ./test.txt]
- --quiet print less text
- --verbose print more text
-Their format is described below; other text is ignored.
-Also, take a look at the
-[beautiful examples](https://github.com/docopt/docopt/tree/master/examples>).
-
-Usage pattern format
+Examples
-------------------------------------------------------------------------------
-**Usage pattern** is a substring of `doc` that starts with
-`usage:` (case-*in*sensitive) and ends with a *visibly* empty line.
-Minimum example::
-
-```python
-"""Usage: my_program.py
-
-"""
-```
-
-The first word after `usage:` is interpreted as your program's name.
-You can specify your program's name several times to signify several
-exclusive patterns::
-
-```python
-"""Usage: my_program.py FILE
- my_program.py COUNT FILE
-
-"""
-```
+We have an extensive list of
+[examples](https://github.com/docopt/docopt.rb/tree/master/examples)
+which cover every aspect of functionality of `docopt`. Try them out,
+read the source if in doubt.
-Each pattern can consist of the following elements:
-
-- **<arguments>**, **ARGUMENTS**. Arguments are specified as either
- upper-case words, e.g.
- `my_program.py CONTENT-PATH`
- or words surrounded by angular brackets:
- `my_program.py <content-path>`.
-- **--options**.
- Options are words started with dash (`-`), e.g. `--output`, `-o`.
- You can "stack" several of one-letter options, e.g. `-oiv` which will
- be the same as `-o -i -v`. The options can have arguments, e.g.
- `--input=FILE` or
- `-i FILE` or even `-iFILE`. However it is important that you specify
- option descriptions if you want for option to have an argument, a
- default value, or specify synonymous short/long versions of option
- (see next section on option descriptions).
-- **commands** are words that do *not* follow the described above conventions
- of `--options` or `<arguments>` or `ARGUMENTS`, plus two special
- commands: dash "`-`" and double dash "`--`" (see below).
-
-Use the following constructs to specify patterns:
-
-- **[ ]** (brackets) **optional** elements.
- e.g.: `my_program.py [-hvqo FILE]`
-- **( )** (parens) **required** elements.
- All elements that are *not* put in **[ ]** are also required,
- e.g.: `my_program.py --path=<path> <file>...` is the same as
- `my_program.py (--path=<path> <file>...)`.
- (Note, "required options" might be not a good idea for your users).
-- **|** (pipe) **mutualy exclussive** elements. Group them using **( )** if
- one of the mutually exclussive elements is required:
- `my_program.py (--clockwise | --counter-clockwise) TIME`. Group them using
- **[ ]** if none of the mutually-exclusive elements are required:
- `my_program.py [--left | --right]`.
-- **...** (ellipsis) **one or more** elements. To specify that arbitrary
- number of repeating elements could be accepted, use ellipsis (`...`), e.g.
- `my_program.py FILE ...` means one or more `FILE`-s are accepted.
- If you want to accept zero or more elements, use brackets, e.g.:
- `my_program.py [FILE ...]`. Ellipsis works as a unary operator on the
- expression to the left.
-- **[options]** (case sensitive) shortcut for any options.
- You can use it if you want to specify that the usage
- pattern could be provided with any options defined below in the
- option-descriptions and do not want to enumerate them all in pattern.
-- "`[--]`". Double dash "`--`" is used by convention to separate
- positional arguments that can be mistaken for options. In order to
- support this convention add "`[--]`" to you usage patterns.
-- "`[-]`". Single dash "`-`" is used by convention to signify that
- `stdin` is used instead of a file. To support this add "`[-]`" to
- you usage patterns. "`-`" act as a normal command.
-
-If your pattern allows to match argument-less option (a flag) several times:
-
- Usage: my_program.py [-v | -vv | -vvv]
-
-then number of occurences of the option will be counted. I.e. `args['-v']`
-will be `2` if program was invoked as `my_program -vv`. Same works for
-commands.
-
-If your usage patterns allows to match same-named option with argument
-or positional argument several times, the matched arguments will be
-collected into a list:
-
- Usage: my_program.py <file> <file> --path=<path>...
-
-I.e. invoked with `my_program.py file1 file2 --path=./here --path=./there`
-the returned dict will contain `args['<file>'] == ['file1', 'file2']` and
-`args['--path'] == ['./here', './there']`.
-
-
-Option descriptions format
+Data validation
-------------------------------------------------------------------------------
-**Option descriptions** consist of a list of options that you put below your
-usage patterns.
+`docopt` does one thing and does it well: it implements your command-line
+interface. However it does not validate the input data. We are looking
+for ruby validation libraries to make your option parsing experiene
+even more awesome!
+If you've got any suggestions or think your awesome schema validation gem
+fits well with `docopt.rb`, open an issue on github and enjoy the eternal glory!
-It is necessary to list option descriptions in order to specify:
-
-- synonymous short and long options,
-- if an option has an argument,
-- if option's argument has a default value.
-
-The rules are as follows:
-
-- Every line in `doc` that starts with `-` or `--` (not counting spaces)
- is treated as an option description, e.g.:
-
- Options:
- --verbose # GOOD
- -o FILE # GOOD
- Other: --bad # BAD, line does not start with dash "-"
-
-- To specify that option has an argument, put a word describing that
- argument after space (or equals "`=`" sign) as shown below. Follow
- either <angular-brackets> or UPPER-CASE convention for options' arguments.
- You can use comma if you want to separate options. In the example below, both
- lines are valid, however you are recommended to stick to a single style. :
-
- -o FILE --output=FILE # without comma, with "=" sign
- -i <file>, --input <file> # with comma, wihtout "=" sing
+Contribution
+===============================================================================
-- Use two spaces to separate options with their informal description.
+We would *love* to hear what you think about `docopt.rb`.
+Contribute, make pull requrests, report bugs, suggest ideas and discuss
+`docopt.rb` on
+[issues page](http://github.com/docopt/docopt.rb/issues).
- --verbose More text. # BAD, will be treated as if verbose option had
- # an argument "More", so use 2 spaces instead
- -q Quit. # GOOD
- -o FILE Output file. # GOOD
- --stdout Use stdout. # GOOD, 2 spaces
+If you want to discuss the original `docopt` reference,
+point to [it's home](http://github.com/docopt/docopt) or
+drop a line directly to vladimir@keleshev.com!
-- If you want to set a default value for an option with an argument, put it
- into the option-description, in form `[default: <my-default-value>]`.
+Porting `docopt` to other languages
+===============================================================================
- --coefficient=K The K coefficient [default: 2.95]
- --output=FILE Output file [default: test.txt]
- --directory=DIR Some directory [default: ./]
+Docopt is an interlinguistic (?) effort,
+and this is the ruby port of `docopt`.
+We coordinate our efforts with docopt community and try our best to
+keep in sync with the python reference.
+Docopt community *loves* to hear what you think about `docopt`, `docopt.rb`
+and other sister projects on docopt's
+[issues page](http://github.com/docopt/docopt/issues).
View
150 Rakefile
@@ -1,8 +1,150 @@
-require 'rake/testtask'
-require 'test/unit'
+require 'rubygems'
+require 'rake'
+require 'date'
+
+#############################################################################
+#
+# Helper functions
+#
+#############################################################################
+
+def name
+ @name ||= Dir['*.gemspec'].first.split('.').first
+end
+
+def version
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
+end
+
+def date
+ Date.today.to_s
+end
+
+def rubyforge_project
+ name
+end
+
+def gemspec_file
+ "#{name}.gemspec"
+end
+
+def gem_file
+ "#{name}-#{version}.gem"
+end
+
+def replace_header(head, header_name)
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
+end
+
+#############################################################################
+#
+# Standard tasks
+#
+#############################################################################
task :default => :test
-Rake::TestTask.new do |t|
- t.test_files = FileList['test/test*.rb']
+require 'rake/testtask'
+Rake::TestTask.new(:test) do |test|
+ test.libs << 'lib' << 'test'
+ test.pattern = 'test/**/test_*.rb'
+ test.verbose = true
+end
+
+desc "Generate RCov test coverage and open in your browser"
+task :coverage do
+ require 'rcov'
+ sh "rm -fr coverage"
+ sh "rcov test/test_*.rb"
+ sh "open coverage/index.html"
+end
+
+require 'rdoc/task'
+Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = "#{name} #{version}"
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+desc "Open an irb session preloaded with this library"
+task :console do
+ sh "irb -rubygems -r ./lib/#{name}.rb"
+end
+
+#############################################################################
+#
+# Custom tasks (add your own tasks here)
+#
+#############################################################################
+
+
+
+#############################################################################
+#
+# Packaging tasks
+#
+#############################################################################
+
+desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
+task :release => :build do
+ unless `git branch` =~ /^\* master$/
+ puts "You must be on the master branch to release!"
+ exit!
+ end
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
+ sh "git tag v#{version}"
+ sh "git push origin master"
+ sh "git push origin v#{version}"
+ sh "gem push pkg/#{name}-#{version}.gem"
+end
+
+desc "Build #{gem_file} into the pkg directory"
+task :build => :gemspec do
+ sh "mkdir -p pkg"
+ sh "gem build #{gemspec_file}"
+ sh "mv #{gem_file} pkg"
+end
+
+desc "Generate #{gemspec_file}"
+task :gemspec => :validate do
+ # read spec file and split out manifest section
+ spec = File.read(gemspec_file)
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
+
+ # replace name version and date
+ replace_header(head, :name)
+ replace_header(head, :version)
+ replace_header(head, :date)
+ #comment this out if your rubyforge_project has a different name
+ replace_header(head, :rubyforge_project)
+
+ # determine file list from git ls-files
+ files = `git ls-files`.
+ split("\n").
+ sort.
+ reject { |file| file =~ /^\./ }.
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
+ map { |file| " #{file}" }.
+ join("\n")
+
+ # piece file back together and write
+ manifest = " s.files = %w[\n#{files}\n ]\n"
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
+ puts "Updated #{gemspec_file}"
+end
+
+desc "Validate #{gemspec_file}"
+task :validate do
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
+ unless libfiles.empty?
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
+ exit!
+ end
+ unless Dir['VERSION*'].empty?
+ puts "A `VERSION` file at root level violates Gem best practices."
+ exit!
+ end
end
View
93 docopt.gemspec
@@ -1,18 +1,83 @@
-# -*- encoding: utf-8 -*-
-
+## This is the rakegem gemspec template. Make sure you read and understand
+## all of the comments. Some sections require modification, and others can
+## be deleted if you don't need them. Once you understand the contents of
+## this file, feel free to delete any comments that begin with two hash marks.
+## You can find comprehensive Gem::Specification documentation, at
+## http://docs.rubygems.org/read/chapter/20
Gem::Specification.new do |s|
- s.name = "docopt"
- s.version = "0.5.0"
- s.required_ruby_version = '>= 1.8.7'
- s.required_rubygems_version = ">= 1.3.6"
- s.platform = Gem::Platform::RUBY
- s.require_path = 'lib'
- s.license = 'MIT'
- s.authors = ["Blake Williams", "Vladimir Keleshev", "Alex Speller"]
+ s.specification_version = 2 if s.respond_to? :specification_version=
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.rubygems_version = '1.3.5'
+ s.required_ruby_version = '>= 1.8.7'
+ ## Leave these as is they will be modified for you by the rake gemspec task.
+ ## If your rubyforge_project name is different, then edit it and comment out
+ ## the sub! line in the Rakefile
+ s.name = 'docopt'
+ s.version = '0.5.0'
+ s.date = '2012-09-01'
+ # s.rubyforge_project = 'docopt'
+
+ ## Make sure your summary is short. The description may be as long
+ ## as you like.
+ s.summary = "A command line option parser, that will make you smile."
+ s.description = "Isn't it awesome how `optparse` and other option parsers generate help and usage-messages based on your code?! Hell no!\nYou know what's awesome? It's when the option parser *is* generated based on the help and usage-message that you write in a docstring! That's what docopt does!"
+
+ ## List the primary authors. If there are a bunch of authors, it's probably
+ ## better to set the email to an email list or something. If you don't have
+ ## a custom homepage, consider using your GitHub URL or the like.
+ s.authors = ["Blake Williams", "Vladimir Keleshev", "Alex Speller", "Nima Johari"]
s.email = "code@shabbyrobe.org"
- s.date = "2012-08-20"
- s.description = "A command line option parser, that will make you smile. Isn't it awesome how `optparse` and other option parsers generate help and usage-messages based on your code?! Hell no! You know what's awesome? It's when the option parser *is* generated based on the help and usage-message that you write in a docstring!"
- s.files = ["README.md", "LICENSE-MIT", "example.rb", "lib/docopt.rb"]
s.homepage = "http://github.com/docopt/docopt.rb"
- s.summary = "A command line option parser, that will make you smile."
+ s.license = 'MIT'
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
+ s.require_paths = %w[lib]
+
+ ## This sections is only necessary if you have C extensions.
+ # s.require_paths << 'ext'
+ # s.extensions = %w[ext/extconf.rb]
+
+ ## If your gem includes any executables, list them here.
+ # s.executables = ["name"]
+
+ ## Specify any RDoc options here. You'll want to add your README and
+ ## LICENSE files to the extra_rdoc_files list.
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.extra_rdoc_files = %w[README.md LICENSE]
+
+ ## List your runtime dependencies here. Runtime dependencies are those
+ ## that are needed for an end user to actually USE your code.
+ # s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
+
+ ## List your development dependencies here. Development dependencies are
+ ## those that are only needed during development
+ s.add_development_dependency('json', "~> 1.6.5")
+
+ ## Leave this section as-is. It will be automatically generated from the
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
+ # = MANIFEST =
+ s.files = %w[
+ Gemfile
+ LICENSE
+ README.md
+ Rakefile
+ docopt.gemspec
+ examples/any_options_example.rb
+ examples/calculator.rb
+ examples/counted_example.rb
+ examples/example_options.rb
+ examples/git_example.rb
+ examples/naval_fate.rb
+ examples/odd_even_example.rb
+ examples/quick_example.rb
+ lib/docopt.rb
+ test/test_docopt.rb
+ test/testee.rb
+ ]
+ # = MANIFEST =
+
+ ## Test files will be grabbed from the file list. Make sure the path glob
+ ## matches what you actually use.
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
end
View
30 example.rb
@@ -1,30 +0,0 @@
-$DOC = "Usage: example.py [options] <arguments>...
-
-Options:
- -h --help show this help message and exit
- --version show version and exit
- -v --verbose print status messages
- -q --quiet report only file names
- -r --repeat show all occurrences of the same error
- --exclude=patterns exclude files or directories which match these comma
- separated patterns [default: .svn,CVS,.bzr,.hg,.git]
- --filename=patterns when parsing directories, only check filenames matching
- these comma separated patterns [default: *.rb]
- --select=errors select errors and warnings (e.g. E,W6)
- --ignore=errors skip errors and warnings (e.g. E4,W)
- --show-source show source code for each error
- --statistics count errors and warnings
- --count print total number of errors and warnings to standard
- error and set exit code to 1 if total is not null
- --benchmark measure processing speed
- --testsuite=dir run regression tests from dir
- --doctest run doctest on myself"
-
-require './lib/docopt'
-
-if __FILE__ == $0
- options = Docopt($DOC, { :version => '1.0.0' }) # parse options based on doc above
- options.sort.each { |key, value|
- puts key + ': ' + value.inspect
- }
-end
View
24 examples/any_options_example.rb
@@ -0,0 +1,24 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Example of program which uses [options] shortcut in pattern.
+
+Usage:
+ #{__FILE__} [options] <port>
+
+Options:
+ -h --help show this help message and exit
+ --version show version and exit
+ -n, --number N use N as a number
+ -t, --timeout TIMEOUT set timeout TIMEOUT seconds
+ --apply apply changes to database
+ -q operate in quiet mode
+
+DOCOPT
+
+
+begin
+ puts Docopt::docopt(doc, version: '1.0.0rc2').to_s
+rescue Docopt::Exit => e
+ puts e.message
+end
View
16 examples/calculator.rb
@@ -0,0 +1,16 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Usage:
+ #{__FILE__} tcp <host> <port> [--timeout=<seconds>]
+ #{__FILE__} serial <port> [--baud=9600] [--timeout=<seconds>]
+ #{__FILE__} -h | --help | --version
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
22 examples/counted_example.rb
@@ -0,0 +1,22 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Usage: #{__FILE__} --help
+ #{__FILE__} -v...
+ #{__FILE__} go [go]
+ #{__FILE__} (--path=<path>)...
+ #{__FILE__} <file> <file>
+
+Try: #{__FILE__} -vvvvvvvvvv
+ #{__FILE__} go go
+ #{__FILE__} --path ./here --path ./there
+ #{__FILE__} this.txt that.txt
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
44 examples/example_options.rb
@@ -0,0 +1,44 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Example of program with many options using docopt.
+
+Usage:
+ #{__FILE__} [-hvqrf NAME] [--exclude=PATTERNS]
+ [--select=ERRORS | --ignore=ERRORS] [--show-source]
+ [--statistics] [--count] [--benchmark] PATH...
+ #{__FILE__} (--doctest | --testsuite=DIR)
+ #{__FILE__} --version
+
+Arguments:
+ PATH destination path
+
+Options:
+ -h --help show this help message and exit
+ --version show version and exit
+ -v --verbose print status messages
+ -q --quiet report only file names
+ -r --repeat show all occurrences of the same error
+ --exclude=PATTERNS exclude files or directories which match these comma
+ separated patterns [default: .svn,CVS,.bzr,.hg,.git]
+ -f NAME --file=NAME when parsing directories, only check filenames matching
+ these comma separated patterns [default: *#{__FILE__}]
+ --select=ERRORS select errors and warnings (e.g. E,W6)
+ --ignore=ERRORS skip errors and warnings (e.g. E4,W)
+ --show-source show source code for each error
+ --statistics count errors and warnings
+ --count print total number of errors and warnings to standard
+ error and set exit code to 1 if total is not null
+ --benchmark measure processing speed
+ --testsuite=DIR run regression tests from dir
+ --doctest run doctest on myself
+
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
44 examples/git_example.rb
@@ -0,0 +1,44 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Usage:
+ #{__FILE__} remote [-v | --verbose]
+ #{__FILE__} remote add [-t <branch>] [-m <master>] [-f]
+ [--tags|--no-tags] [--mirror] <name> <url>
+ #{__FILE__} remote rename <old> <new>
+ #{__FILE__} remote rm <name>
+ #{__FILE__} remote set-head <name> (-a | -d | <branch>)
+ #{__FILE__} remote set-branches <name> [--add] <branch>...
+ #{__FILE__} remote set-url [--push] <name> <newurl> [<oldurl>]
+ #{__FILE__} remote set-url --add [--push] <name> <newurl>
+ #{__FILE__} remote set-url --delete [--push] <name> <url>
+ #{__FILE__} remote [-v | --verbose] show [-n] <name>
+ #{__FILE__} remote prune [-n | --dry-run] <name>
+ #{__FILE__} remote [-v | --verbose] update [-p | --prune]
+ [(<group> | <remote>)...]
+
+Options:
+ -v, --verbose
+ -t <branch>
+ -m <master>
+ -f
+ --tags
+ --no-tags
+ --mittor
+ -a
+ -d
+ -n, --dry-run
+ -p, --prune
+ --add
+ --delete
+ --push
+ --mirror
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
30 examples/naval_fate.rb
@@ -0,0 +1,30 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+#The *popular* naval fate example
+
+doc = <<DOCOPT
+Naval Fate.
+
+Usage:
+ #{__FILE__} ship new <name>...
+ #{__FILE__} ship <name> move <x> <y> [--speed=<kn>]
+ #{__FILE__} ship shoot <x> <y>
+ #{__FILE__} mine (set|remove) <x> <y> [--moored|--drifting]
+ #{__FILE__} -h | --help
+ #{__FILE__} --version
+
+Options:
+ -h --help Show this screen.
+ --version Show version.
+ --speed=<kn> Speed in knots [default: 10].
+ --moored Moored (anchored) mine.
+ --drifting Drifting mine.
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
19 examples/odd_even_example.rb
@@ -0,0 +1,19 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Usage: #{__FILE__} [-h | --help] (ODD EVEN)...
+
+Example, try:
+ #{__FILE__} 1 2 3 4
+
+Options:
+ -h, --help
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
16 examples/quick_example.rb
@@ -0,0 +1,16 @@
+require File.expand_path("../../lib/docopt.rb", __FILE__)
+
+doc = <<DOCOPT
+Usage:
+ #{__FILE__} tcp <host> <port> [--timeout=<seconds>]
+ #{__FILE__} serial <port> [--baud=9600] [--timeout=<seconds>]
+ #{__FILE__} -h | --help | --version
+
+DOCOPT
+
+begin
+ require "pp"
+ pp Docopt::docopt(doc)
+rescue Docopt::Exit => e
+ puts e.message
+end
View
232 lib/docopt.rb
@@ -1,12 +1,11 @@
module Docopt
+ VERSION = '0.5.0'
+end
+module Docopt
class DocoptLanguageError < SyntaxError
end
-
class Exit < RuntimeError
- # TODO: try and derive this from SystemExit
- @@usage = ''
-
def self.usage
@@usage
end
@@ -15,12 +14,15 @@ def self.set_usage(usage)
@@usage = usage ? usage : ''
end
+ def message
+ @@message
+ end
+
def initialize(message='')
- super((message && message != '' ? (message + "\n") : '') + @@usage)
+ @@message = ((message && message != '' ? (message + "\n") : '') + @@usage)
end
end
-
class Pattern
attr_accessor :children
@@ -46,9 +48,7 @@ def fix_identities(uniq=nil)
if not instance_variable_defined?(:@children)
return self
end
- if not uniq
- uniq = self.flat.uniq
- end
+ uniq ||= flat.uniq
@children.each_with_index do |c, i|
if not c.instance_variable_defined?(:@children)
@@ -63,10 +63,8 @@ def fix_identities(uniq=nil)
end
def fix_list_arguments
- either = self.either.children.map { |c| c.children }
-
- for case_ in either
- for e in case_.select { |c| case_.count(c) > 1 }
+ either.children.map { |c| c.children }.each do |case_|
+ case_.select { |c| case_.count(c) > 1 }.each do |e|
if e.class == Argument or (e.class == Option and e.argcount > 0)
e.value = []
end
@@ -92,7 +90,6 @@ def either
for c in either.children
groups << [c] + children
end
-
elsif types.include?(Required)
required = children.select { |c| c.class == Required }[0]
children.slice!(children.index(required))
@@ -107,14 +104,14 @@ def either
oneormore = children.select { |c| c.class == OneOrMore }[0]
children.slice!(children.index(oneormore))
groups << (oneormore.children * 2) + children
-
+
else
ret << children
end
end
- args = ret.map { |e| Required.new(e) }
- return Either.new(args)
+ args = ret.map { |e| Required.new(*e) }
+ return Either.new(*args)
end
end
@@ -128,19 +125,19 @@ def initialize(name, value=nil)
end
def inspect()
- return "<#{self.class.name}>(<#{self.name}>, <#{self.value}>)"
+ "#{self.class.name}(#{self.name}, #{self.value})"
end
-
+
def flat
- return [self]
+ [self]
end
-
+
def match(left, collected=nil)
- collected = [] if !collected
+ collected ||= []
pos, match = self.single_match(left)
if match == nil
- return false, left, collected
+ return [false, left, collected]
end
left_ = left.dup
@@ -151,21 +148,19 @@ def match(left, collected=nil)
increment = @value.is_a?(Integer) ? 1 : [match.value]
if same_name.count == 0
match.value = increment
- return true, left_, collected + [match]
+ return [true, left_, collected + [match]]
end
-
same_name[0].value += increment
- return true, left_, collected
+ return [true, left_, collected]
end
- return true, left_, collected + [match]
+ return [true, left_, collected + [match]]
end
end
-
class ParentPattern < Pattern
attr_accessor :children
- def initialize(children)
+ def initialize(*children)
@children = children
end
@@ -179,43 +174,40 @@ def flat
end
end
-
class Argument < ChildPattern
- attr_accessor :argcount
- def initialize(*args)
- super(*args)
- @argcount = 0
- end
+ # def initialize(*args)
+ # super(*args)
+ # end
def single_match(left)
- left.each_with_index { |p, n|
+ left.each_with_index do |p, n|
if p.class == Argument
- return n, Argument.new(self.name, p.value)
+ return [n, Argument.new(self.name, p.value)]
end
- }
- return nil, nil
+ end
+ return [nil, nil]
end
end
class Command < Argument
def initialize(name, value=false)
- super(name, value)
+ @name = name
+ @value = value
end
def single_match(left)
- left.each_with_index { |p, n|
+ left.each_with_index do |p, n|
if p.class == Argument
if p.value == self.name
return n, Command.new(self.name, true)
else
break
end
end
- }
-
- return nil, nil
+ end
+ return [nil, nil]
end
end
@@ -225,23 +217,24 @@ class Option < ChildPattern
attr_accessor :argcount
def initialize(short=nil, long=nil, argcount=0, value=false)
- unless argcount == 0 or argcount == 1
+ unless [0, 1].include? argcount
raise RuntimeError
end
@short, @long = short, long
- @argcount = argcount
-
+ @argcount, @value = argcount, value
+
if value == false and argcount > 0
- value = nil
+ @value = nil
+ else
+ @value = value
end
- @value = value
end
def self.parse(option_description)
short, long, argcount, value = nil, nil, 0, false
options, _, description = option_description.strip.partition(' ')
-
+
options.gsub!(",", " ")
options.gsub!("=", " ")
@@ -263,60 +256,56 @@ def self.parse(option_description)
end
def single_match(left)
- left.each_with_index { |p, n|
+ left.each_with_index do |p, n|
if self.name == p.name
- return n, p
+ return [n, p]
end
- }
- return nil, nil
+ end
+ return [nil, nil]
end
def name
return self.long ? self.long : self.short
end
def inspect
- return "#{self.class.name}('#{self.short}', '#{self.long}', #{self.argcount}, '#{self.value}')"
+ return "Option(#{self.short}, #{self.long}, #{self.argcount}, #{self.value})"
end
end
-
class Required < ParentPattern
def match(left, collected=nil)
- collected = [] if !collected
+ collected ||= []
l = left
c = collected
for p in self.children
matched, l, c = p.match(l, c)
if not matched
- return false, left, collected
+ return [false, left, collected]
end
end
- return true, l, c
+ return [true, l, c]
end
end
-
class Optional < ParentPattern
def match(left, collected=nil)
- collected = [] if !collected
+ collected ||= []
for p in self.children
m, left, collected = p.match(left, collected)
end
- return true, left, collected
+ return [true, left, collected]
end
end
-
class OneOrMore < ParentPattern
def match(left, collected=nil)
if self.children.count != 1
raise RuntimeError
end
- collected = [] if !collected
-
+ collected ||= []
l = left
c = collected
l_ = nil
@@ -331,18 +320,16 @@ def match(left, collected=nil)
end
l_ = l
end
-
if times >= 1
- return true, l, c
+ return [true, l, c]
end
- return false, left, collected
+ return [false, left, collected]
end
end
-
class Either < ParentPattern
def match(left, collected=nil)
- collected = [] if !collected
+ collected ||= []
outcomes = []
for p in self.children
matched, _, _ = outcome = p.match(left, collected)
@@ -351,15 +338,16 @@ def match(left, collected=nil)
end
end
- if outcomes and outcomes.count > 0
- ret = outcomes.min_by { |outcome| outcome[1] == nil ? 0 : outcome[1].count }
+ if outcomes.count > 0
+ ret = outcomes.min_by do |outcome|
+ outcome[1] == nil ? 0 : outcome[1].count
+ end
return ret
end
- return false, left, collected
+ return [false, left, collected]
end
end
-
class TokenStream < Array
attr_reader :error
@@ -382,7 +370,6 @@ def current
end
end
-
class << self
def parse_long(tokens, options)
raw, eq, value = tokens.move().partition('=')
@@ -403,12 +390,10 @@ def parse_long(tokens, options)
return [o]
end
end
-
if opt.count > 1
ostr = opt.map { |o| o.long }.join(', ')
raise tokens.error, "#{raw} is not a unique prefix: #{ostr}?"
end
-
o = opt[0]
opt = Option.new(o.short, o.long, o.argcount, o.value)
if opt.argcount == 1
@@ -418,7 +403,6 @@ def parse_long(tokens, options)
end
value = tokens.move()
end
-
elsif value != nil
raise tokens.error, "#{opt.name} must not have an argument"
end
@@ -428,11 +412,9 @@ def parse_long(tokens, options)
else
opt.value = value ? nil : false
end
-
return [opt]
end
-
def parse_shorts(tokens, options)
raw = tokens.move()[1..-1]
parsed = []
@@ -478,7 +460,6 @@ def parse_shorts(tokens, options)
end
parsed << opt
end
-
return parsed
end
@@ -490,7 +471,7 @@ def parse_pattern(source, options)
if tokens.current() != nil
raise tokens.error, "unexpected ending: #{tokens.join(" ")}"
end
- return Required.new(result)
+ return Required.new(*result)
end
@@ -499,38 +480,35 @@ def parse_expr(tokens, options)
if tokens.current() != '|'
return seq
end
- result = seq.count > 1 ? [Required.new(seq)] : seq
+ result = seq.count > 1 ? [Required.new(*seq)] : seq
while tokens.current() == '|'
tokens.move()
seq = parse_seq(tokens, options)
- result += seq.count > 1 ? [Required.new(seq)] : seq
+ result += seq.count > 1 ? [Required.new(*seq)] : seq
end
-
- return result.count > 1 ? [Either.new(result)] : result
+ return result.count > 1 ? [Either.new(*result)] : result
end
-
def parse_seq(tokens, options)
result = []
stop = [nil, ']', ')', '|']
while !stop.include?(tokens.current)
atom = parse_atom(tokens, options)
if tokens.current() == '...'
- atom = [OneOrMore.new(atom)]
+ atom = [OneOrMore.new(*atom)]
tokens.move()
end
result += atom
end
return result
end
-
def parse_atom(tokens, options)
token = tokens.current()
result = []
- if token == '(' or token == '['
+ if ['(' , '['].include? token
tokens.move()
if token == '('
matching = ')'
@@ -539,59 +517,47 @@ def parse_atom(tokens, options)
matching = ']'
pattern = Optional
end
- result = pattern.new(parse_expr(tokens, options))
+ result = pattern.new(*parse_expr(tokens, options))
if tokens.move() != matching
raise tokens.error, "unmatched '#{token}'"
end
-
return [result]
-
elsif token == 'options'
tokens.move()
return options
-
elsif token.start_with?('--') and token != '--'
return parse_long(tokens, options)
-
- elsif token.start_with?('-') and token != '-' and token != '--'
+ elsif token.start_with?('-') and not ['-', '--'].include? token
return parse_shorts(tokens, options)
elsif token.start_with?('<') and token.end_with?('>') or token.upcase == token
return [Argument.new(tokens.move())]
-
else
return [Command.new(tokens.move())]
end
end
-
def parse_argv(source, options)
tokens = TokenStream.new(source, Exit)
parsed = []
while tokens.current() != nil
if tokens.current() == '--'
return parsed + tokens.map { |v| Argument.new(nil, v) }
-
elsif tokens.current().start_with?('--')
parsed += parse_long(tokens, options)
-
elsif tokens.current().start_with?('-') and tokens.current() != '-'
parsed += parse_shorts(tokens, options)
-
else
parsed << Argument.new(nil, tokens.move())
end
end
-
return parsed
end
-
def parse_doc_options(doc)
return doc.split(/^ *-|\n *-/)[1..-1].map { |s| Option.parse('-' + s) }
end
-
def printable_usage(doc)
usage_split = doc.split(/([Uu][Ss][Aa][Gg][Ee]:)/)
if usage_split.count < 3
@@ -603,10 +569,9 @@ def printable_usage(doc)
return usage_split[1..-1].join().split(/\n\s*\n/)[0].strip
end
-
def formal_usage(printable_usage)
pu = printable_usage.split()[1..-1] # split and drop "usage:"
-
+
ret = []
for s in pu[1..-1]
if s == pu[0]
@@ -619,7 +584,6 @@ def formal_usage(printable_usage)
return '( ' + ret.join(' ') + ' )'
end
-
def dump_patterns(pattern, indent=0)
ws = " " * 4 * indent
out = ""
@@ -647,7 +611,6 @@ def dump_patterns(pattern, indent=0)
return out
end
-
def extras(help, version, options, doc)
ofound = false
vfound = false
@@ -671,41 +634,30 @@ def extras(help, version, options, doc)
end
def docopt(doc, params={})
- begin
- default = { :version => nil, :argv => nil, :help => true }
- params = default.merge(params)
- params[:argv] = ARGV if !params[:argv]
-
- Exit.set_usage(printable_usage(doc))
- options = parse_doc_options(doc)
- pattern = parse_pattern(formal_usage(Exit.usage), options)
- argv = parse_argv(params[:argv], options)
- extras(params[:help], params[:version], argv, doc)
-
- matched, left, collected = pattern.fix().match(argv)
- collected = [] if !collected
-
- if matched and (!left or left.count == 0)
- ret = {}
- for a in pattern.flat + options + collected
- name = a.name
- if name and name != ''
- ret[name] = a.value
- end
+ default = {:version => nil, :argv => nil, :help => true}
+ params = default.merge(params)
+ params[:argv] = ARGV if !params[:argv]
+
+ Exit.set_usage(printable_usage(doc))
+ options = parse_doc_options(doc)
+ pattern = parse_pattern(formal_usage(Exit.usage), options)
+ argv = parse_argv(params[:argv], options)
+ extras(params[:help], params[:version], argv, doc)
+
+ matched, left, collected = pattern.fix().match(argv)
+ collected ||= []
+
+ if matched and (!left or left.count == 0)
+ ret = {}
+ for a in pattern.flat + options + collected
+ name = a.name
+ if name and name != ''
+ ret[name] = a.value
end
- return ret
end
- raise Exit
-
- rescue Exit => ex
- puts ex.message.rstrip + "\n"
- exit
+ return ret
end
+ raise Exit
end
end
end
-
-# Convenience method for Docopt.docopt
-def Docopt *args
- Docopt.docopt *args
-end
View
5 testee.rb → test/testee.rb
@@ -1,10 +1,9 @@
#!/usr/bin/env ruby
-require File.expand_path(File.dirname(__FILE__) + "/lib/docopt.rb")
+require File.expand_path("../../lib/docopt.rb", __FILE__)
-require 'rubygems'
require 'json'
-doc = $stdin.read
+doc = STDIN.read
begin
puts Docopt::docopt(doc).to_json

0 comments on commit b095a7c

Please sign in to comment.
Something went wrong with that request. Please try again.