From fd5b4e20117d871fc9ec264747fa7cde2031d46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Tue, 12 Oct 2021 14:16:29 +0200 Subject: [PATCH] CommandLineInterface: Update control flow to accommodate the new way of reporting errors --- solc/CommandLineInterface.cpp | 70 +++---- solc/CommandLineInterface.h | 32 +++- solc/main.cpp | 7 +- test/solc/CommandLineInterface.cpp | 191 +++++++++---------- test/solc/CommandLineInterfaceAllowPaths.cpp | 6 +- test/solc/Common.cpp | 28 ++- test/solc/Common.h | 20 +- 7 files changed, 196 insertions(+), 158 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 0c13069cd..49f25a4de 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -404,7 +404,7 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) } } -bool CommandLineInterface::readInputFiles() +void CommandLineInterface::readInputFiles() { solAssert(!m_standardJsonInput.has_value(), ""); @@ -413,7 +413,7 @@ bool CommandLineInterface::readInputFiles() m_options.input.mode == InputMode::License || m_options.input.mode == InputMode::Version ) - return true; + return; m_fileReader.setBasePath(m_options.input.basePath); @@ -501,8 +501,6 @@ bool CommandLineInterface::readInputFiles() if (m_fileReader.sourceCodes().empty() && !m_standardJsonInput.has_value()) solThrow(CommandLineValidationError, "All specified input files either do not exist or are not regular files."); - - return true; } map CommandLineInterface::parseAstFromInput() @@ -568,6 +566,30 @@ void CommandLineInterface::createJson(string const& _fileName, string const& _js createFile(boost::filesystem::basename(_fileName) + string(".json"), _json); } +bool CommandLineInterface::run(int _argc, char const* const* _argv) +{ + try + { + if (!parseArguments(_argc, _argv)) + return false; + + readInputFiles(); + processInput(); + return true; + } + catch (CommandLineError const& _exception) + { + m_hasOutput = true; + + // There might be no message in the exception itself if the error output is bulky and has + // already been printed to stderr (this happens e.g. for compiler errors). + if (_exception.what() != ""s) + serr() << _exception.what() << endl; + + return false; + } +} + bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv) { CommandLineParser parser; @@ -581,22 +603,13 @@ bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv) return false; } - try - { - parser.parse(_argc, _argv); - } - catch (CommandLineValidationError const& _exception) - { - serr() << _exception.what() << endl; - return false; - } - + parser.parse(_argc, _argv); m_options = parser.options(); return true; } -bool CommandLineInterface::processInput() +void CommandLineInterface::processInput() { switch (m_options.input.mode) { @@ -619,18 +632,15 @@ bool CommandLineInterface::processInput() break; } case InputMode::Assembler: - if (!assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine)) - return false; + assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine); break; case InputMode::Linker: - if (!link()) - return false; + link(); writeLinkedFiles(); break; case InputMode::Compiler: case InputMode::CompilerWithASTImport: - if (!compile()) - return false; + compile(); outputCompilationResults(); } @@ -651,7 +661,7 @@ void CommandLineInterface::printLicense() sout() << licenseText << endl; } -bool CommandLineInterface::compile() +void CommandLineInterface::compile() { solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); @@ -714,6 +724,8 @@ bool CommandLineInterface::compile() } catch (Exception const& _exc) { + // FIXME: AST import is missing proper validations. This hack catches failing + // assertions and presents them as if they were compiler errors. solThrow(CommandLineExecutionError, "Failed to import AST: "s + _exc.what()); } } @@ -754,8 +766,6 @@ bool CommandLineInterface::compile() solThrow(CommandLineExecutionError, ""); } } - - return true; } void CommandLineInterface::handleCombinedJSON() @@ -884,7 +894,7 @@ void CommandLineInterface::handleAst() } } -bool CommandLineInterface::link() +void CommandLineInterface::link() { solAssert(m_options.input.mode == InputMode::Linker, ""); @@ -945,8 +955,6 @@ bool CommandLineInterface::link() src.second.resize(src.second.size() - 1); } m_fileReader.setSources(move(sourceCodes)); - - return true; } void CommandLineInterface::writeLinkedFiles() @@ -986,7 +994,7 @@ string CommandLineInterface::objectWithLinkRefsHex(evmasm::LinkerObject const& _ return out; } -bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine) +void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine) { solAssert(m_options.input.mode == InputMode::Assembler, ""); @@ -1072,8 +1080,6 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul: else serr() << "No text representation found." << endl; } - - return true; } void CommandLineInterface::outputCompilationResults() @@ -1110,13 +1116,9 @@ void CommandLineInterface::outputCompilationResults() ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes()); if (!m_options.output.dir.empty()) - { createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); - } else - { sout() << "EVM assembly:" << endl << ret << endl; - } } if (m_options.compiler.estimateGas) diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index ee5057468..fc584001f 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -51,12 +51,28 @@ public: m_options(_options) {} - /// Parse command line arguments and return false if we should not continue + /// Parses command-line arguments, executes the requested operation and handles validation and + /// execution errors. + /// @returns false if it catches a @p CommandLineValidationError or if the application is + /// expected to exit with a non-zero exit code despite there being no error. + bool run(int _argc, char const* const* _argv); + + /// Parses command line arguments and stores the result in @p m_options. + /// @throws CommandLineValidationError if command-line arguments are invalid. + /// @returns false if the application is expected to exit with a non-zero exit code despite + /// there being no error. bool parseArguments(int _argc, char const* const* _argv); - /// Read the content of all input files and initialize the file reader. - bool readInputFiles(); - /// Parse the files, create source code objects, print the output. - bool processInput(); + + /// Reads the content of all input files and initializes the file reader. + /// @throws CommandLineValidationError if it fails to read the input files (invalid paths, + /// non-existent files, not enough or too many input files, etc.). + void readInputFiles(); + + /// Executes the requested operation (compilation, assembling, standard JSON, etc.) and prints + /// results to the terminal. + /// @throws CommandLineExecutionError if execution fails due to errors in the input files. + /// @throws CommandLineOutputError if creating output files or writing to them fails. + void processInput(); CommandLineOptions const& options() const { return m_options; } FileReader const& fileReader() const { return m_fileReader; } @@ -65,15 +81,15 @@ public: private: void printVersion(); void printLicense(); - bool compile(); - bool link(); + void compile(); + void link(); void writeLinkedFiles(); /// @returns the ``// -> name`` hint for library placeholders. static std::string libraryPlaceholderHint(std::string const& _libraryName); /// @returns the full object with library placeholder hints in hex. static std::string objectWithLinkRefsHex(evmasm::LinkerObject const& _obj); - bool assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine); + void assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine); void outputCompilationResults(); diff --git a/solc/main.cpp b/solc/main.cpp index 874ee13ca..ce69d20a7 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -62,12 +62,7 @@ int main(int argc, char** argv) { setDefaultOrCLocale(); solidity::frontend::CommandLineInterface cli(cin, cout, cerr); - bool success = - cli.parseArguments(argc, argv) && - cli.readInputFiles() && - cli.processInput(); - - return success ? 0 : 1; + return cli.run(argc, argv) ? 0 : 1; } catch (smtutil::SMTLogicError const& _exception) { diff --git a/test/solc/CommandLineInterface.cpp b/test/solc/CommandLineInterface.cpp index 0269ec462..dbffc4788 100644 --- a/test/solc/CommandLineInterface.cpp +++ b/test/solc/CommandLineInterface.cpp @@ -19,6 +19,7 @@ /// Unit tests for solc/CommandLineInterface.h #include +#include #include @@ -114,7 +115,7 @@ BOOST_AUTO_TEST_SUITE(CommandLineInterfaceTest) BOOST_AUTO_TEST_CASE(help) { - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--help"}, "", /* _processInput */ true); + OptionsReaderAndMessages result = runCLI({"solc", "--help"}, ""); BOOST_TEST(result.success); BOOST_TEST(boost::starts_with(result.stdoutContent, "solc, the Solidity commandline compiler.")); @@ -124,7 +125,7 @@ BOOST_AUTO_TEST_CASE(help) BOOST_AUTO_TEST_CASE(license) { - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--license"}, "", /* _processInput */ true); + OptionsReaderAndMessages result = runCLI({"solc", "--license"}, ""); BOOST_TEST(result.success); BOOST_TEST(boost::starts_with(result.stdoutContent, "Most of the code is licensed under GPLv3")); @@ -134,7 +135,7 @@ BOOST_AUTO_TEST_CASE(license) BOOST_AUTO_TEST_CASE(version) { - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--version"}, "", /* _processInput */ true); + OptionsReaderAndMessages result = runCLI({"solc", "--version"}, ""); BOOST_TEST(result.success); BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n")); @@ -158,17 +159,16 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes) string expectedMessage = "The following options are mutually exclusive: " "--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. " - "Select at most one.\n"; + "Select at most one."; for (string const& mode1: inputModeOptions) for (string const& mode2: inputModeOptions) if (mode1 != mode2) - { - vector commandLine = {"solc", mode1, mode2}; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); - } + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({"solc", mode1, mode2}), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(cli_input) @@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(cli_ignore_missing_no_files_exist) "\"" + (tempDir.path() / "input2.sol").string() + "\" is not found. Skipping.\n" "All specified input files either do not exist or are not regular files.\n"; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ + OptionsReaderAndMessages result = runCLI({ "solc", (tempDir.path() / "input1.sol").string(), (tempDir.path() / "input2.sol").string(), @@ -267,11 +267,13 @@ BOOST_AUTO_TEST_CASE(cli_not_a_file) { TemporaryDirectory tempDir(TEST_CASE_NAME); - string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file.\n"; + string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file."; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(standard_json_base_path) @@ -336,24 +338,26 @@ BOOST_AUTO_TEST_CASE(standard_json_two_input_files) { string expectedMessage = "Too many input files for --standard-json.\n" - "Please either specify a single file name or provide its content on standard input.\n"; + "Please either specify a single file name or provide its content on standard input."; - vector commandLine = {"solc", "--standard-json", "input1.json", "input2.json"}; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "input2.json"}), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(standard_json_one_input_file_and_stdin) { string expectedMessage = "Too many input files for --standard-json.\n" - "Please either specify a single file name or provide its content on standard input.\n"; + "Please either specify a single file name or provide its content on standard input."; - vector commandLine = {"solc", "--standard-json", "input1.json", "-"}; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "-"}), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(standard_json_ignore_missing) @@ -362,29 +366,31 @@ BOOST_AUTO_TEST_CASE(standard_json_ignore_missing) // This option is pretty much useless Standard JSON mode. string expectedMessage = - "\"" + (tempDir.path() / "input.json").string() + "\" is not found. Skipping.\n" - "All specified input files either do not exist or are not regular files.\n"; + "All specified input files either do not exist or are not regular files."; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ - "solc", - "--standard-json", - (tempDir.path() / "input.json").string(), - "--ignore-missing", - }); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({ + "solc", + "--standard-json", + (tempDir.path() / "input.json").string(), + "--ignore-missing", + }), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(standard_json_remapping) { string expectedMessage = "Import remappings are not accepted on the command line in Standard JSON mode.\n" - "Please put them under 'settings.remappings' in the JSON input.\n"; + "Please put them under 'settings.remappings' in the JSON input."; - vector commandLine = {"solc", "--standard-json", "a=b"}; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({"solc", "--standard-json", "a=b"}), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path) @@ -997,11 +1003,7 @@ BOOST_AUTO_TEST_CASE(cli_include_paths) canonicalWorkDir / "lib", }; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( - commandLine, - "", - true /* _processInput */ - ); + OptionsReaderAndMessages result = runCLI(commandLine, ""); BOOST_TEST(result.stderrContent == ""); BOOST_TEST(result.stdoutContent == ""); @@ -1087,11 +1089,7 @@ BOOST_AUTO_TEST_CASE(standard_json_include_paths) FileReader::FileSystemPathSet expectedAllowedDirectories = {}; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( - commandLine, - standardJsonInput, - true /* _processInput */ - ); + OptionsReaderAndMessages result = runCLI(commandLine, standardJsonInput); Json::Value parsedStdout; string jsonParsingErrors; @@ -1119,18 +1117,19 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_empty_path) TemporaryWorkingDirectory tempWorkDir(tempDir); createFilesWithParentDirs({tempDir.path() / "base/main.sol"}); - string expectedMessage = "Empty values are not allowed in --include-path.\n"; + string expectedMessage = "Empty values are not allowed in --include-path."; - vector commandLine = { - "solc", - "--base-path=base/", - "--include-path", "include/", - "--include-path", "", - "base/main.sol", - }; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({ + "solc", + "--base-path=base/", + "--include-path", "include/", + "--include-path", "", + "base/main.sol", + }), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path) @@ -1139,12 +1138,13 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path) TemporaryWorkingDirectory tempWorkDir(tempDir); createFilesWithParentDirs({tempDir.path() / "contract.sol"}); - string expectedMessage = "--include-path option requires a non-empty base path.\n"; + string expectedMessage = "--include-path option requires a non-empty base path."; - vector commandLine = {"solc", "--include-path", "include/", "contract.sol"}; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(!result.success); - BOOST_TEST(result.stderrContent == expectedMessage); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({"solc", "--include-path", "include/", "contract.sol"}), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions) @@ -1173,35 +1173,37 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions { // import "contract1.sol" and import "contract2.sol" would be ambiguous: - vector commandLine = { - "solc", - "--base-path=dir1/", - "--include-path=dir2/", - "dir1/contract1.sol", - "dir2/contract1.sol", - "dir1/contract2.sol", - "dir2/contract2.sol", - }; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(result.stderrContent == expectedMessage); - BOOST_REQUIRE(!result.success); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({ + "solc", + "--base-path=dir1/", + "--include-path=dir2/", + "dir1/contract1.sol", + "dir2/contract1.sol", + "dir1/contract2.sol", + "dir2/contract2.sol", + }), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } { // import "contract1.sol" and import "contract2.sol" would be ambiguous: - vector commandLine = { - "solc", - "--base-path=dir3/", - "--include-path=dir1/", - "--include-path=dir2/", - "dir1/contract1.sol", - "dir2/contract1.sol", - "dir1/contract2.sol", - "dir2/contract2.sol", - }; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); - BOOST_TEST(result.stderrContent == expectedMessage); - BOOST_REQUIRE(!result.success); + BOOST_CHECK_EXCEPTION( + parseCommandLineAndReadInputFiles({ + "solc", + "--base-path=dir3/", + "--include-path=dir1/", + "--include-path=dir2/", + "dir1/contract1.sol", + "dir2/contract1.sol", + "dir1/contract2.sol", + "dir2/contract2.sol", + }), + CommandLineValidationError, + [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; } + ); } { @@ -1316,12 +1318,7 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_ambiguous_import) "3 | import \"contract.sol\";\n" " | ^^^^^^^^^^^^^^^^^^^^^^\n\n"; - OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( - commandLine, - mainContractSource, - true /* _processInput */ - ); - + OptionsReaderAndMessages result = runCLI(commandLine, mainContractSource); BOOST_TEST(result.stderrContent == expectedMessage); BOOST_REQUIRE(!result.success); } diff --git a/test/solc/CommandLineInterfaceAllowPaths.cpp b/test/solc/CommandLineInterfaceAllowPaths.cpp index a15199618..77f727014 100644 --- a/test/solc/CommandLineInterfaceAllowPaths.cpp +++ b/test/solc/CommandLineInterfaceAllowPaths.cpp @@ -95,11 +95,7 @@ ImportCheck checkImport( "pragma solidity >=0.0;\n" + _import + ";"; - test::OptionsReaderAndMessages cliResult = test::parseCommandLineAndReadInputFiles( - commandLine, - standardInputContent, - true /* processInput */ - ); + test::OptionsReaderAndMessages cliResult = test::runCLI(commandLine, standardInputContent); if (cliResult.success) return ImportCheck::OK(); diff --git a/test/solc/Common.cpp b/test/solc/Common.cpp index 8e40c1af6..ead9b798d 100644 --- a/test/solc/Common.cpp +++ b/test/solc/Common.cpp @@ -41,18 +41,34 @@ vector test::makeArgv(vector const& _commandLine) test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles( vector const& _commandLine, - string const& _standardInputContent, - bool _processInput + string const& _standardInputContent ) { vector argv = makeArgv(_commandLine); stringstream sin(_standardInputContent), sout, serr; CommandLineInterface cli(sin, sout, serr); bool success = cli.parseArguments(static_cast(_commandLine.size()), argv.data()); - if (success) - success = cli.readInputFiles(); - if (success && _processInput) - success = cli.processInput(); + cli.readInputFiles(); + + return { + success, + cli.options(), + cli.fileReader(), + cli.standardJsonInput(), + sout.str(), + stripPreReleaseWarning(serr.str()), + }; +} + +test::OptionsReaderAndMessages test::runCLI( + vector const& _commandLine, + string const& _standardInputContent +) +{ + vector argv = makeArgv(_commandLine); + stringstream sin(_standardInputContent), sout, serr; + CommandLineInterface cli(sin, sout, serr); + bool success = cli.run(static_cast(_commandLine.size()), argv.data()); return { success, diff --git a/test/solc/Common.h b/test/solc/Common.h index 958d63054..3575bea74 100644 --- a/test/solc/Common.h +++ b/test/solc/Common.h @@ -44,10 +44,26 @@ struct OptionsReaderAndMessages std::vector makeArgv(std::vector const& _commandLine); +/// Runs only command-line parsing, without compilation, assembling or any other input processing. +/// Lets through any @a CommandLineErrors throw by the CLI. +/// Note: This uses the @a CommandLineInterface class and does not actually spawn a new process. +/// @param _commandLine Arguments in the form of strings that would be specified on the command-line. +/// You must specify the program name as the first item. +/// @param _standardInputContent Content that the CLI will be able to read from its standard input. OptionsReaderAndMessages parseCommandLineAndReadInputFiles( std::vector const& _commandLine, - std::string const& _standardInputContent = "", - bool _processInput = false + std::string const& _standardInputContent = "" +); + +/// Runs all stages of command-line interface processing, including error handling. +/// Never throws @a CommandLineError - validation errors are included in the returned stderr content. +/// Note: This uses the @a CommandLineInterface class and does not actually spawn a new process. +/// @param _commandLine Arguments in the form of strings that would be specified on the command-line. +/// You must specify the program name as the first item. +/// @param _standardInputContent Content that the CLI will be able to read from its standard input. +OptionsReaderAndMessages runCLI( + std::vector const& _commandLine, + std::string const& _standardInputContent = "" ); std::string stripPreReleaseWarning(std::string const& _stderrContent);