factor out getopt processing
This commit is contained in:
parent
d63e62f1a7
commit
586b3244a4
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user