pythoncommand-line-arguments

Does ArgumentParser support different arguments per file, ffmpeg style?


I want my application to work on several files and have a different set of options for each input file:

python my.py -a file_a -b file_b --do-stuff=x file_c

ffmpeg uses this idea for its command line arguments.

I tried the following:

parser = argparse.ArgumentParser()
parser.add_argument("-a", action="store_true")
parser.add_argument("-b", action="store_true")
parser.add_argument("--do-stuff", type=str)
parser.add_argument("files", type=pathlib.Path, nargs="+")
args = parser.parse_args()

And I get an error:

error: unrecognized arguments: file_b file_c

Can I use ArgumentParser to parse such a command line?

The goal is to get a mapping like this:

file_a: {a: True}
file_b: {b: True}
file_c: {do_stuff: 'x'}

Solution

  • No, ArgumentParser doesn't support having different options for different files. It doesn't take the order of options (relative to positional arguments) into account.

    As noted by jonrsharpe, https://docs.python.org/3/library/argparse.html#intermixed-parsing is as close as it gets. And it is not good enough, because a call to parse_known_intermixed_args or parse_intermixed_args will extract all options, regardless of their position relative to file_a.


    It's possible to "prepare" arguments for the parser and parse them for each file separately. First, collect file names (or "positional arguments" as they are called) from the command line. For each file name, extract arguments which precede it. Then parse these arguments.

    # Change the type to str so each file name could be found later
    parser.add_argument("files", type=str, nargs="+")
    
    # Collect file names; ignore all options
    files = parser.parse_intermixed_args().files
    
    args_per_file = {}
    args_left = sys.argv[1:]
    for file in files:
        # Find the file name (guaranteed to succeed)
        p = args_left.index(file) + 1
        # Parse only the arguments which are relevant to this file
        args_per_file[pathlib.Path(file)] = parser.parse_args(args_left[:p])
        # Delete what was parsed
        del args_left[:p]