Can Python's argparse permute argument order like gnu getopt?
Question: GNU getopt, and command line tools that use it, allow options and arguments to
be interleaved, known as permuting options (see
<http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using-
Getopt>). Perl's Getopt::Long module also supports this (with qw(:config
gnu_getopt)). argparse seems to not support (or even mention) permuting
options.

There are many SO questions related to arg/opt order, but none seem answer
this question: Can argparse be made to permute argument order like getopt?

The use case is a prototypical command line signature like GNU sort:

    
    
    sort [opts] [files]
    

in which 1) options and files are permuted, and 2) the file list may contain
zero or more arguments.

For example:

    
    
    import argparse
    p = argparse.ArgumentParser();
    p.add_argument('files',nargs='*',default=['-']);
    p.add_argument('-z',action='store_true')
    
    p.parse_args(['-z','bar','foo']) # ok
    p.parse_args(['bar','foo','-z']) # ok
    p.parse_args(['bar','-z','foo']) # not okay
    usage: ipython [-h] [-z] [files [files ...]]
    

I've tried:

  * p.parse_known_args -- doesn't complain, but doesn't actually permute either and it doesn't balk about arguments that look like invalid options (e.g., --bogus or -b above).
  * p.add_argument('files',nargs=argparse.REMAINDER) -- option -z is included in files unless before positional args
  * p.add_argument('files',nargs='*',action='append');

I want to implement something close to the GNU sort prototype above. I am not
interested in a flag that can be specified for each file (e.g., -f file1 -f
file2).


Answer: Here's a quick solution which decodes the argument list one (options,
positional arguments) pair at a time.

    
    
    import argparse
    
    class ExtendAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            items = getattr(namespace, self.dest, None)
            if items is None:
                items = []
            items.extend(values)
            setattr(namespace, self.dest, items)
    
    parser = argparse.ArgumentParser()
    parser.add_argument('files', nargs='*', action=ExtendAction)
    parser.add_argument('-z', action='store_true')
    parser.add_argument('-v', action='count')
    parser.add_argument('args_tail', nargs=argparse.REMAINDER)
    
    def interleaved_parse(argv=None):
        opts = parser.parse_args(argv)
        optargs = opts.args_tail
        while optargs:
            opts = parser.parse_args(optargs, opts)
            optargs = opts.args_tail
        return opts
    
    print(interleaved_parse('-z bar foo'.split()))
    print(interleaved_parse('bar foo -z'.split()))
    print(interleaved_parse('bar -z foo'.split()))
    print(interleaved_parse('-v a -zv b -z c -vz d -v'.split()))
    

Output:

    
    
    Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
    Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
    Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
    Namespace(args_tail=[], files=['a', 'b', 'c', 'd'], v=4, z=True)
    

Note: Don't try to use this with other non-flag arguments (besides a single
`nargs='*'` argument and the `args_tail` argument). The parser won't know
about previous invocations of `parse_args` so it will store the wrong value
for these non-flag arguments. As a workaround, you can parse the `nargs='*'`
argument manually after using `interleaved_parse`.

