factor out getopt processing

This commit is contained in:
Eric Mertens 2023-02-01 09:40:22 -08:00
parent d63e62f1a7
commit 586b3244a4

View File

@ -4,6 +4,7 @@
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <utility> #include <utility>
#include <optional>
#include <libgen.h> #include <libgen.h>
#include <unistd.h> #include <unistd.h>
@ -11,54 +12,119 @@
#define DOCTEST_CONFIG_IMPLEMENT #define DOCTEST_CONFIG_IMPLEMENT
#include <doctest.h> #include <doctest.h>
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>
{
Options result{};
int ch; int ch;
while ((ch = getopt(argc, argv, ":hi:o:t")) != -1) { while ((ch = getopt(argc, argv, ":hi:o:t")) != -1)
switch (ch) { {
case 'i': in_name = optarg; break; switch (ch)
case 'o': out_name = optarg; break; {
case 't': run_tests = true; break; case 'i':
result.in_name = optarg;
break;
case 'o':
result.out_name = optarg;
break;
case 't':
result.run_tests = true;
break;
case ':': case ':':
std::cerr << "Missing argument to -" << char(optopt) << std::endl; std::cerr << "Missing argument to -" << char(optopt) << std::endl;
return 1; return {};
case 'h': case 'h':
std::cerr << "Usage: " << basename(argv[0]) << " [-i INPUT] [-o OUTPUT] [-t]" << std::endl; result.print_help = true;
return 0; break;
case '?': case '?':
default: default:
std::cerr << "Unknown flag -" << char(optopt) << std::endl; std::cerr << "Unknown flag -" << char(optopt) << std::endl;
return 1; return {};
} }
} }
if (run_tests) { // Consume the option command line arguments to prepare for
// additional subsequent consumers (like doctest)
argc -= optind;
argv += optind;
return result;
}
} // namespace
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(); return doctest::Context{argc, argv}.run();
} }
// Establish input streams
std::ifstream fin; std::ifstream fin;
std::ofstream fout; std::ofstream fout;
std::istream & in {in_name ? fin = std::ifstream{in_name } : std::cin }; std::istream &in{options.in_name ? fin = std::ifstream{options.in_name} : std::cin};
std::ostream & out {out_name ? fout = std::ofstream{out_name} : std::cout}; std::ostream &out{options.out_name ? fout = std::ofstream{options.out_name} : std::cout};
if (in.fail()) { if (in.fail())
{
std::cerr << "Bad input file" << std::endl; std::cerr << "Bad input file" << std::endl;
return 1; return EXIT_FAILURE;
} }
if (out.fail()) { if (out.fail())
{
std::cerr << "Bad output file" << std::endl; std::cerr << "Bad output file" << std::endl;
return 1; return EXIT_FAILURE;
} }
try
{
Main(in, out); Main(in, out);
} catch (std::exception const& e) { return EXIT_SUCCESS;
std::cerr << "Program failed: " << e.what() << std::endl; }
return 1; catch (std::exception const &e)
{
std::cerr << "Solution failed: " << e.what() << std::endl;
return EXIT_FAILURE;
} }
} }