Skip to content

Response files

gershnik edited this page Mar 14, 2022 · 4 revisions

Sometimes it is convenient to provide command line arguments in a file and have it referenced from the real command line. You can think of it as similar to #include contents of the file in your command line arguments. This approach is especially popular on Windows where command line is (or at least used to be) very limited in length. Thus, the only way to supply a lengthy command line there is to put it into a response file.

The way it works is that whenever a command line argument starts with a given prefix (the de-facto standard on Windows is @) the rest of the argument is treated as a filename, the file is opened and its content is used instead. Details of how to split file content vary. Some utilities will treat each line as an argument, some will split each line on whitespace etc.

Following its modular architecture Argum provides support for response files via a separate ResponseFileReader (and WResponseFileReader) class. You use it as follows:

  • Create an instance of ResponseFileReader giving it a prefix or prefixes to look for.
  • Call its expand method on a range of command line arguments. The range can be argc,argv or a random access container or a range of things convertible to std::string_view
  • The expand method processes the arguments, locates response files, if present, and substitutes their content.
    • This is done recursively, that is a response file can reference another response file
    • The output is std::vector<std::string> that can be directly passed to Parser::parse.
  • You can optionally pass a Splitter object to the expand method. It will be used to decide how to split each response line into arguments.
    • By default, if you don't pass a splitter, each non empty line is trimmed of whitespace on both ends and used as a single argument
    • The Splitter is function object that is invoked as void (string && line, auto dest). The dest argument is some kind of output iterator. You copy/move the produced strings into it.
  • On any error locating or reading response files it throws ResponseFileReader::Exception derived from ParsingException. Thus expand should be called within parsing try block.

Putting it all together here is an example using default splitter

Parser parser;
...
try {
   parser.parse(ResponseFileReader('@').expand(argc, argv));   
} catch (ParsingException & ex) {
   cerr << ex.message() << '\n';
   cerr << parser.formatUsage(progname) << '\n';
   return EXIT_FAILURE;
}

And this is one where we split on whitespace

Parser parser;
...

regex whitespace("\\s+");

try {
   parser.parse(ResponseFileReader('@').expand(argc, argv, 
                                               [&](string && line, auto dest){
        copy(sregex_token_iterator(line.begin(), line.end(), whitespace, -1), 
             sregex_token_iterator(), dest);
   }));   
} catch (ParsingException & ex) {
   cerr << ex.message() << '\n';
   cerr << parser.formatUsage(progname) << '\n';
   return EXIT_FAILURE;
}