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