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[=<path>]] [--html-path] [--man-path] [--info-path]
               [-p|--paginate|--no-pager] [--no-replace-objects]
               [--bare] [--git-dir=<path>] [--work-tree=<path>]
               [-c name=value] [--help]
               <command> [<args>]    

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 <command> [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