Splitting sys.argv
before hand is a good solution. But it can also be done while parsing using an argument with nargs=argparse.REMAINDER
. This type of argument gets the rest of the strings, regardless of whether they look like flags or not.
Replacing the parse_known_args
with this code:
...
build_parser.add_argument('rest', nargs=argparse.REMAINDER)
check_parser.add_argument('rest', nargs=argparse.REMAINDER)
extras = 'executeBuild --name foobar1 setupEnv executeBuild --name foobar2'.split()
# or extras = sys.argv[1:]
while extras:
args = main_parser.parse_args(extras)
extras = args.rest
delattr(args,'rest')
print args
# collect args as needed
prints:
Namespace(build_name=['foobar1'], command='executeBuild')
Namespace(command='setupEnv')
Namespace(build_name=['foobar2'], command='executeBuild')
In the documentation:
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities:
A problem with REMAINDER
is that can be too greedy. http://bugs.python.org/issue14174. As a result build_parser
and check_parser
can't have other positional arguments.
A way around the greedy REMAINDER
is to use argparse.PARSER
. This is the nargs
value that subparsers
uses (undocumented). It's like REMAINDER
, except that the first string must look like an 'argument' (no '-'), and is matched against choices
(if given). PARSER
isn't as greedy as REMAINDER
, so the subparsers can have other positional arguments.
There's some extra code involving an 'exit' string and dummy parser. This is to get around the fact that the PARSER
argument is 'required' (somewhat like nargs='+'
)
from argparse import ArgumentParser, PARSER, SUPPRESS
main_parser = ArgumentParser(prog='MAIN')
parsers = {'exit': None}
main_parser.add_argument('rest',nargs=PARSER, choices=parsers)
build_parser = ArgumentParser(prog='BUILD')
parsers['executeBuild'] = build_parser
build_parser.add_argument('cmd')
build_parser.add_argument('--name', action='store', nargs=1, dest='build_name')
build_parser.add_argument('rest',nargs=PARSER, choices=parsers, help=SUPPRESS)
check_parser = ArgumentParser(prog='CHECK')
parsers['setupEnv'] = check_parser
check_parser.add_argument('cmd')
check_parser.add_argument('foo')
check_parser.add_argument('rest',nargs=PARSER, choices=parsers, help=SUPPRESS)
argv = sys.argv[1:]
if len(argv)==0:
argv = 'executeBuild --name foobar1 setupEnv foo executeBuild --name foobar2'.split()
argv.append('exit') # extra string to properly exit the loop
parser = main_parser
while parser:
args = parser.parse_args(argv)
argv = args.rest
delattr(args,'rest')
print(parser.prog, args)
parser = parsers.get(argv[0], None)
sample output:
('MAIN', Namespace())
('BUILD', Namespace(build_name=['foobar1'], cmd='executeBuild'))
('CHECK', Namespace(cmd='setupEnv', foo='foo'))
('BUILD', Namespace(build_name=['foobar2'], cmd='executeBuild'))
Another possibility is to use '--'
to separate command blocks:
'executeBuild --name foobar1 -- setupEnv -- executeBuild --name foobar2'
However there is problem when there are several '--'
: http://bugs.python.org/issue13922
extra
because it can't be parsed (i.e.build_parser
does not accept a positional). But the 2nd '--name' is recognized by thebuild_parser
, and treated just like the 1st. In other words, a parser puts things it can't recognized in 'extra' but keeps going. 'optionals' can be in any order and can be repeated. – Uncanny