piranha/opster — pull request #23 by oscarbenjamin (2012-04-25 13:21:48+00:00)
Title: Proper preparsing by reimplementing getopt

Hi again,

I've found that opster's current preparsing method fails in a number of situations. It can:
1) give a traceback if the first non-global option is in the form ``--x=y`` whether it comes before or after the subcommand argument
2) attempt to execute the wrong command if a non-global option comes before the subcommand argument and has the form ``--x y``

I think that there are only two sane ways to handle options that come before the subcommand argument:
1) Don't allow any
2) Only allow global options (like this patch does)
The advantage of 2) is being able to alias global options e.g. ``alias git="git --no-pager"``. This is actually what git does.:

$ git --cached diff
Unknown option: --cached
usage: git [--version] [--exec-path[=]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|--no-pager] [--no-replace-objects]
[--bare] [--git-dir=] [--work-tree=]
[-c name=value] [--help]
[]

This patch applies the rule that only global options can go before the command argument. Unfortunately, the only way to do this is to reimplement ``getopt`` in ``opster``. This doesn't add as much code as you might think, since it simplifies the code that uses ``getopt``. Since there needed to be a ``find_match`` function for abbreviated options, I rewrote the ``findcmd`` functions to reuse this. The other advantage of this patch is that preparsing only does parsing and does not attempt to convert the options like ``process`` does. AFAICT the only thing that ``getopt`` does that this patch doesn't is to use ``POSIXLY_CORRECT`` environment variable. It wouldn't be hard though to add an option to the ``getopts`` function here that would stop at the first non-option argument.

The patch also adds tests (that would fail without this patch) and docs to explain that global options can go before a subcommand. I also had to add flush() calls to multicommands.py to make the tests consistent between python and python3.

Examples::

Given multicommands.py:

$ python tests/multicommands.py help
usage: tests/multicommands.py [options]

commands:

help Show help for a given help topic or a help overview.
nodoc (no help text available)
simple Just simple command to print keys of received arguments.

$ python tests/multicommands.py help complex
tests/multicommands.py complex [-p] [--exit value] ...

That's more complex command intended to do something

ðÿ Ðüð¦ð+ð¥ðÁ ð¦ð+ð¦ð¦ð¢ð¥ðÁ - ð+Ðï ÐéÐâÐé ð¢ðÁð+ð¢ð¥ðÂðÁÐçð¦ð¥ ÐéðÁð¦ÐüÐéð¦ ð¢ðÁ ð¦ ascii ð¢ð¦ð+ð©ÐêðÁð+
ð© ð+ð¥Ðüð+ð¥ÐéÐÇð©ð+, ÐçÐéð¥ ð¦Ðâð¦ðÁÐé. :)

options:

-p --pass don't run the command
--exit exit with supplied code (default: 100)
-n --name optional name
-v --verbose enable additional output
-q --quiet suppress output
-h --help display help

We have the following (using current master branch of opster):

$ python tests/multicommands.py complex --name dave
write
info
warn

That works but this gets a traceback even though it is valid (works with the patch):

$ python tests/multicommands.py complex --name=dave
Traceback (most recent call last):
...
File "q:\current\opster\opster.py", line 541, in process
args.insert(args.index(prefix + e.opt), '--')
ValueError: list.index(x): x not in list

The remaining examples are not allowed with the patch (opster will give an error message to the user).
This gets a traceback:

$ python tests/multicommands.py --name=dave complex
Traceback (most recent call last):
...
File "q:\current\opster\opster.py", line 541, in process
args.insert(args.index(prefix + e.opt), '--')
ValueError: list.index(x): x not in list

Opster can get confused about the command that you want to run:

$ python tests/multicommands.py --name dave complex
unknown command: 'dave'

We may end up running the wrong command altogether:

$ python tests/multicommands.py --name simple complex
error: option --name not recognized

tests/multicommands.py simple [-t]

Just simple command to print keys of received arguments.

I assure you! Nothing to look here. ;-)

options:

-t --test just test execution
-v --verbose enable additional output
-q --quiet suppress output
-h --help display help