From 586b3244a4b93d099fac189acfe4032d4cc5fd35 Mon Sep 17 00:00:00 2001 From: Eric Mertens Date: Wed, 1 Feb 2023 09:40:22 -0800 Subject: [PATCH] factor out getopt processing --- lib/src/Startup.cpp | 142 ++++++++++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 38 deletions(-) diff --git a/lib/src/Startup.cpp b/lib/src/Startup.cpp index 193f683..695a640 100644 --- a/lib/src/Startup.cpp +++ b/lib/src/Startup.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -11,54 +12,119 @@ #define DOCTEST_CONFIG_IMPLEMENT #include -auto main(int argc, char ** argv) -> int +namespace { - bool run_tests {}; - char const* in_name {}; - char const* out_name {}; - try { + /// @brief Fields corresponding to command line options + struct Options + { + /// -i: Input file name or nullptr for stdin + char const *in_name; + /// -o: Output file name or nullptr for stdout + char const *out_name; + /// -t: Flag to run tests instead of solution + bool run_tests; + /// -h: Flag to print help and exit + bool print_help; + }; + + /// @brief Parse the basic command line options used by all solution programs + /// @param[in,out] argc argc from main + /// @param[in,out] argv argv from main + /// @return options structure on success + /// + /// `argc` and `argv` are updated to have the processed options removed from them + /// on a successful options parse. + auto ParseOptions(int &argc, char **&argv) -> std::optional + { + Options result{}; + int ch; - while ((ch = getopt(argc, argv, ":hi:o:t")) != -1) { - switch (ch) { - case 'i': in_name = optarg; break; - case 'o': out_name = optarg; break; - case 't': run_tests = true; break; - case ':': - std::cerr << "Missing argument to -" << char(optopt) << std::endl; - return 1; - case 'h': - std::cerr << "Usage: " << basename(argv[0]) << " [-i INPUT] [-o OUTPUT] [-t]" << std::endl; - return 0; - case '?': - default: - std::cerr << "Unknown flag -" << char(optopt) << std::endl; - return 1; + while ((ch = getopt(argc, argv, ":hi:o:t")) != -1) + { + switch (ch) + { + case 'i': + result.in_name = optarg; + break; + case 'o': + result.out_name = optarg; + break; + case 't': + result.run_tests = true; + break; + case ':': + std::cerr << "Missing argument to -" << char(optopt) << std::endl; + return {}; + case 'h': + result.print_help = true; + break; + case '?': + default: + std::cerr << "Unknown flag -" << char(optopt) << std::endl; + return {}; } } - if (run_tests) { - return doctest::Context{argc, argv}.run(); - } + // Consume the option command line arguments to prepare for + // additional subsequent consumers (like doctest) + argc -= optind; + argv += optind; - std::ifstream fin; - std::ofstream fout; - std::istream & in {in_name ? fin = std::ifstream{in_name } : std::cin }; - std::ostream & out {out_name ? fout = std::ofstream{out_name} : std::cout}; + return result; + } - if (in.fail()) { - std::cerr << "Bad input file" << std::endl; - return 1; - } +} // namespace - if (out.fail()) { - std::cerr << "Bad output file" << std::endl; - return 1; - } +auto main(int argc, char **argv) -> int +{ + Options options; + if (auto o = ParseOptions(argc, argv)) + { + options = *o; + } + else + { + return EXIT_FAILURE; + } + if (options.print_help) + { + std::cerr << "Usage: " << basename(argv[0]) << " [-i INPUT] [-o OUTPUT] [-t]" << std::endl; + return EXIT_SUCCESS; + } + + if (options.run_tests) + { + return doctest::Context{argc, argv}.run(); + } + + // Establish input streams + std::ifstream fin; + std::ofstream fout; + std::istream &in{options.in_name ? fin = std::ifstream{options.in_name} : std::cin}; + std::ostream &out{options.out_name ? fout = std::ofstream{options.out_name} : std::cout}; + + if (in.fail()) + { + std::cerr << "Bad input file" << std::endl; + return EXIT_FAILURE; + } + + if (out.fail()) + { + std::cerr << "Bad output file" << std::endl; + return EXIT_FAILURE; + } + + try + { Main(in, out); - } catch (std::exception const& e) { - std::cerr << "Program failed: " << e.what() << std::endl; - return 1; + return EXIT_SUCCESS; + } + catch (std::exception const &e) + { + std::cerr << "Solution failed: " << e.what() << std::endl; + return EXIT_FAILURE; } }