You can get close to parsing what you want, but you would need to apply a transformation on the resulting namespace returned by the parser. The two differences you would need to make is that instead of --do-stuff=x file_c you have --do-stuff x file_c (NB. no equals sign is allowed). And you would need to apply some post processing to verify x and file_c were valid arguments to --do-stuff.
Example:
import argparse
from collections import defaultdict
from pathlib import Path
def valid_file(name: str) -> str:
"""Checks if a filename exists"""
path = Path(name)
if not path.exists():
raise argparse.ArgumentTypeError(f'{name} does not exist')
return name # can conver to another type if desired
# use the "append" action to store all uses of each option
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=valid_file, action='append')
parser.add_argument("-b", type=valid_file, action='append')
# use nargs=2 to enforce that --do-stuff must always have two arguments
parser.add_argument("--do-stuff", type=str, nargs=2, action='append')
# parsing example usage
ns = parser.parse_args(
'-a file_a -b file_b -a file_c --do-stuff x file_d --do-stuff y file_e'
.split()
)
assert vars(ns) == {
'a': ['file_a', 'file_c'],
'b': ['file_b'],
'do_stuff': [
['x', 'file_d'],
['y', 'file_e'],
],
}
# verify do_stuff usage that cannot easily be done with argparse.
# ie. enforce first argument is a valid option and second argument is a valid file
for first, second in ns.do_stuff:
if first not in ('x', 'y', 'z'):
print('invalid option to do_stuff')
parser.print_help()
exit(1)
if not Path(second).exists():
print(f'do_stuff error: {second} does not exist')
parser.print_help()
exit(1)
# convert argparse namespace into the desired dictionary
# uses a defaultdict(list) so that it is possible to spot if some files received
# multiple options. eg. -a foo --do-stuff x foo
result = defaultdict(list)
for option, values in vars(ns).items():
for value in values:
if isinstance(value, list):
assert len(value) == 2
result[value[1]].append({option: value[0]})
else:
result[value].append({option: True})
# optional final validation -- enforce that each file received at most one option
too_many_options = [file_ for file_, options in result.items() if len(options) > 1]
if too_many_options:
print('the following input files received multiple options. Only 1 is allowed')
print(too_many_options)
parser.print_help()
exit(1)
assert result == {
'file_a': [{'a': True}],
'file_b': [{'b': True}],
'file_c': [{'a': True}],
'file_d': [{'do_stuff': 'x'}],
'file_e': [{'do_stuff': 'y'}],
}
-aand-bas flags which are either set or not set, they do not take a value. Hencefile_aandfile_bdon't belong to anything and aren't recognised as anything. Does that explain the problem? What else did you want this to mean? Assume we do not know of the top off our heads how ffmpeg would treat this…