diff --git a/Makefile b/Makefile index c1ce48af..5d48f209 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,8 @@ all: rgbasm rgblink rgbfix rgbgfx common_obj := \ src/extern/getopt.o \ - src/diagnostics.o + src/diagnostics.o \ + src/usage.o rgbasm_obj := \ ${common_obj} \ diff --git a/include/platform.hpp b/include/platform.hpp index 8e71e71d..f8e55cc3 100644 --- a/include/platform.hpp +++ b/include/platform.hpp @@ -1,7 +1,5 @@ // SPDX-License-Identifier: MIT -// platform-specific hacks - #ifndef RGBDS_PLATFORM_HPP #define RGBDS_PLATFORM_HPP diff --git a/include/usage.hpp b/include/usage.hpp new file mode 100644 index 00000000..5ebfcc19 --- /dev/null +++ b/include/usage.hpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +#ifndef RGBDS_USAGE_HPP +#define RGBDS_USAGE_HPP + +#include + +class Usage { + char const *usage; + +public: + Usage(char const *usage_) : usage(usage_) {} + + [[noreturn]] + void printAndExit(int code) const; + + [[gnu::format(printf, 2, 3), noreturn]] + void printAndExit(char const *fmt, ...) const; +}; + +#endif // RGBDS_USAGE_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eeaa1dad..2f163cf5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES) set(common_src "extern/getopt.cpp" "diagnostics.cpp" + "usage.cpp" "_version.cpp" ) diff --git a/src/asm/main.cpp b/src/asm/main.cpp index bf634057..b9209160 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -15,6 +15,7 @@ #include "extern/getopt.hpp" #include "helpers.hpp" #include "parser.hpp" // Generated from parser.y +#include "usage.hpp" #include "version.hpp" #include "asm/charmap.hpp" @@ -83,41 +84,25 @@ static option const longopts[] = { {nullptr, no_argument, nullptr, 0 } }; -// LCOV_EXCL_START -static void printUsage() { - fputs( - "Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n" - " [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n" - " [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n" - " [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n" - " \n" - "Useful options:\n" - " -E, --export-all export all labels\n" - " -M, --dependfile set the output dependency file\n" - " -o, --output set the output object file\n" - " -p, --pad-value set the value to use for `ds'\n" - " -s, --state : set an output state file\n" - " -V, --version print RGBASM version and exit\n" - " -W, --warning enable or disable warnings\n" - "\n" - "For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n", - stderr - ); -} -// LCOV_EXCL_STOP - -[[gnu::format(printf, 1, 2), noreturn]] -static void fatalWithUsage(char const *fmt, ...) { - va_list ap; - fputs("FATAL: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - - printUsage(); - exit(1); -} +// clang-format off: long string literal +static Usage usage( + "Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n" + " [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n" + " [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n" + " [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n" + " \n" + "Useful options:\n" + " -E, --export-all export all labels\n" + " -M, --dependfile set the output dependency file\n" + " -o, --output set the output object file\n" + " -p, --pad-value set the value to use for `ds'\n" + " -s, --state : set an output state file\n" + " -V, --version print RGBASM version and exit\n" + " -W, --warning enable or disable warnings\n" + "\n" + "For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n" +); +// clang-format on // Parse a comma-separated string of '-s/--state' features static std::vector parseStateFeatures(char *str) { @@ -218,10 +203,7 @@ int main(int argc, char *argv[]) { break; case 'h': - // LCOV_EXCL_START - printUsage(); - exit(0); - // LCOV_EXCL_STOP + usage.printAndExit(0); // LCOV_EXCL_LINE case 'I': fstk_AddIncludePath(musl_optarg); @@ -382,10 +364,7 @@ int main(int argc, char *argv[]) { // Unrecognized options default: - // LCOV_EXCL_START - printUsage(); - exit(1); - // LCOV_EXCL_STOP + usage.printAndExit(1); // LCOV_EXCL_LINE } } @@ -394,9 +373,9 @@ int main(int argc, char *argv[]) { } if (argc == musl_optind) { - fatalWithUsage("Please specify an input file (pass `-` to read from standard input)"); + usage.printAndExit("Please specify an input file (pass `-` to read from standard input)"); } else if (argc != musl_optind + 1) { - fatalWithUsage("More than one input file specified"); + usage.printAndExit("More than one input file specified"); } std::string mainFileName = argv[musl_optind]; diff --git a/src/fix/main.cpp b/src/fix/main.cpp index 35a8c43e..89a962a9 100644 --- a/src/fix/main.cpp +++ b/src/fix/main.cpp @@ -16,6 +16,7 @@ #include "extern/getopt.hpp" #include "helpers.hpp" #include "platform.hpp" +#include "usage.hpp" #include "version.hpp" #include "fix/mbc.hpp" @@ -57,40 +58,24 @@ static option const longopts[] = { {nullptr, no_argument, nullptr, 0 } }; -// LCOV_EXCL_START -static void printUsage() { - fputs( - "Usage: rgbfix [-hjOsVvw] [-C | -c] [-f ] [-i ] [-k ]\n" - " [-L ] [-l ] [-m ]\n" - " [-n ] [-p ] [-r ] [-t ]\n" - " [-W warning] ...\n" - "Useful options:\n" - " -m, --mbc-type set the MBC type byte to this value; refer\n" - " to the man page for a list of values\n" - " -p, --pad-value pad to the next valid size using this value\n" - " -r, --ram-size set the cart RAM size byte to this value\n" - " -o, --output set the output file\n" - " -V, --version print RGBFIX version and exit\n" - " -v, --validate fix the header logo and both checksums (-f lhg)\n" - "\n" - "For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n", - stderr - ); -} -// LCOV_EXCL_STOP - -[[gnu::format(printf, 1, 2), noreturn]] -static void fatalWithUsage(char const *fmt, ...) { - va_list ap; - fputs("FATAL: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - - printUsage(); - exit(1); -} +// clang-format off: long string literal +static Usage usage( + "Usage: rgbfix [-hjOsVvw] [-C | -c] [-f ] [-i ] [-k ]\n" + " [-L ] [-l ] [-m ]\n" + " [-n ] [-p ] [-r ] [-t ]\n" + " [-W warning] ...\n" + "Useful options:\n" + " -m, --mbc-type set the MBC type byte to this value; refer\n" + " to the man page for a list of values\n" + " -p, --pad-value pad to the next valid size using this value\n" + " -r, --ram-size set the cart RAM size byte to this value\n" + " -o, --output set the output file\n" + " -V, --version print RGBFIX version and exit\n" + " -v, --validate fix the header logo and both checksums (-f lhg)\n" + "\n" + "For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n" +); +// clang-format on static uint8_t tpp1Rev[2]; @@ -727,10 +712,7 @@ int main(int argc, char *argv[]) { break; case 'h': - // LCOV_EXCL_START - printUsage(); - exit(0); - // LCOV_EXCL_STOP + usage.printAndExit(0); // LCOV_EXCL_LINE case 'i': gameID = musl_optarg; @@ -835,10 +817,7 @@ int main(int argc, char *argv[]) { break; default: - // LCOV_EXCL_START - printUsage(); - exit(1); - // LCOV_EXCL_STOP + usage.printAndExit(1); // LCOV_EXCL_LINE } } @@ -894,11 +873,11 @@ int main(int argc, char *argv[]) { argv += musl_optind; if (!*argv) { - fatalWithUsage("Please specify an input file (pass `-` to read from standard input)"); + usage.printAndExit("Please specify an input file (pass `-` to read from standard input)"); } if (outputFilename && argc != musl_optind + 1) { - fatalWithUsage("If `-o` is set then only a single input file may be specified"); + usage.printAndExit("If `-o` is set then only a single input file may be specified"); } bool failed = warnings.nbErrors > 0; diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 9a2b1266..ccf9f109 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -18,6 +18,7 @@ #include "extern/getopt.hpp" #include "file.hpp" #include "platform.hpp" +#include "usage.hpp" #include "version.hpp" #include "gfx/pal_spec.hpp" @@ -98,39 +99,23 @@ static option const longopts[] = { {nullptr, no_argument, nullptr, 0 } }; -// LCOV_EXCL_START -static void printUsage() { - fputs( - "Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a | -A]\n" - " [-b ] [-c ] [-d ] [-i ]\n" - " [-L ] [-l ] [-N ] [-n ]\n" - " [-o ] [-p | -P] [-q | -Q]\n" - " [-s ] [-t | -T] [-x ] \n" - "Useful options:\n" - " -m, --mirror-tiles optimize out mirrored tiles\n" - " -o, --output output the tile data to this path\n" - " -t, --tilemap output the tile map to this path\n" - " -u, --unique-tiles optimize out identical tiles\n" - " -V, --version print RGBGFX version and exit\n" - "\n" - "For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n", - stderr - ); -} -// LCOV_EXCL_STOP - -[[gnu::format(printf, 1, 2), noreturn]] -static void fatalWithUsage(char const *fmt, ...) { - va_list ap; - fputs("FATAL: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - - printUsage(); - exit(1); -} +// clang-format off: long string literal +static Usage usage( + "Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a | -A]\n" + " [-b ] [-c ] [-d ] [-i ]\n" + " [-L ] [-l ] [-N ] [-n ]\n" + " [-o ] [-p | -P] [-q | -Q]\n" + " [-s ] [-t | -T] [-x ] \n" + "Useful options:\n" + " -m, --mirror-tiles optimize out mirrored tiles\n" + " -o, --output output the tile data to this path\n" + " -t, --tilemap output the tile map to this path\n" + " -u, --unique-tiles optimize out identical tiles\n" + " -V, --version print RGBGFX version and exit\n" + "\n" + "For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n" +); +// clang-format on // Parses a number at the beginning of a string, moving the pointer to skip the parsed characters. // Returns the provided errVal on error. @@ -209,13 +194,13 @@ static void skipWhitespace(char *&arg) { static void registerInput(char const *arg) { if (!options.input.empty()) { - fatalWithUsage( + usage.printAndExit( "Input image specified more than once! (first \"%s\", then \"%s\")", options.input.c_str(), arg ); } else if (arg[0] == '\0') { // Empty input path - fatalWithUsage("Input image path cannot be empty"); + usage.printAndExit("Input image path cannot be empty"); } else { options.input = arg; } @@ -379,10 +364,7 @@ static char *parseArgv(int argc, char *argv[]) { } break; case 'h': - // LCOV_EXCL_START - printUsage(); - exit(0); - // LCOV_EXCL_STOP + usage.printAndExit(0); // LCOV_EXCL_LINE case 'i': if (!options.inputTileset.empty()) { warnx("Overriding input tileset file %s", options.inputTileset.c_str()); @@ -591,10 +573,7 @@ static char *parseArgv(int argc, char *argv[]) { } break; default: - // LCOV_EXCL_START - printUsage(); - exit(1); - // LCOV_EXCL_STOP + usage.printAndExit(1); // LCOV_EXCL_LINE } } @@ -818,7 +797,7 @@ int main(int argc, char *argv[]) { if (autoOptEnabled) { std::string &image = localOptions.groupOutputs ? options.output : options.input; if (image.empty()) { - fatalWithUsage( + usage.printAndExit( "No %s specified", localOptions.groupOutputs ? "output tile data file" : "input image" ); @@ -875,7 +854,7 @@ int main(int argc, char *argv[]) { && !localOptions.reverse) { processPalettes(); } else { - fatalWithUsage("No input image specified"); + usage.printAndExit("No input image specified"); } requireZeroErrors(); diff --git a/src/link/main.cpp b/src/link/main.cpp index 52d52133..9070d187 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -15,6 +15,7 @@ #include "itertools.hpp" #include "platform.hpp" #include "script.hpp" // Generated from script.y +#include "usage.hpp" #include "version.hpp" #include "link/assign.hpp" @@ -81,39 +82,23 @@ static option const longopts[] = { {nullptr, no_argument, nullptr, 0 } }; -// LCOV_EXCL_START -static void printUsage() { - fputs( - "Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" - " [-O overlay_file] [-o out_file] [-p pad_value]\n" - " [-S spec] ...\n" - "Useful options:\n" - " -l, --linkerscript set the input linker script\n" - " -m, --map set the output map file\n" - " -n, --sym set the output symbol list file\n" - " -o, --output set the output file\n" - " -p, --pad set the value to pad between sections with\n" - " -x, --nopad disable padding of output binary\n" - " -V, --version print RGBLINK version and exits\n" - "\n" - "For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n", - stderr - ); -} -// LCOV_EXCL_STOP - -[[gnu::format(printf, 1, 2), noreturn]] -static void fatalWithUsage(char const *fmt, ...) { - va_list ap; - fputs("FATAL: ", stderr); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - - printUsage(); - exit(1); -} +// clang-format off: long string literal +static Usage usage( + "Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n" + " [-O overlay_file] [-o out_file] [-p pad_value]\n" + " [-S spec] ...\n" + "Useful options:\n" + " -l, --linkerscript set the input linker script\n" + " -m, --map set the output map file\n" + " -n, --sym set the output symbol list file\n" + " -o, --output set the output file\n" + " -p, --pad set the value to pad between sections with\n" + " -x, --nopad disable padding of output binary\n" + " -V, --version print RGBLINK version and exits\n" + "\n" + "For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n" +); +// clang-format on enum ScrambledRegion { SCRAMBLE_ROMX, @@ -268,10 +253,7 @@ int main(int argc, char *argv[]) { options.isWRAM0Mode = true; break; case 'h': - // LCOV_EXCL_START - printUsage(); - exit(0); - // LCOV_EXCL_STOP + usage.printAndExit(0); // LCOV_EXCL_LINE case 'l': if (linkerScriptName) { warnx("Overriding linker script %s", linkerScriptName); @@ -345,10 +327,7 @@ int main(int argc, char *argv[]) { options.is32kMode = true; break; default: - // LCOV_EXCL_START - printUsage(); - exit(1); - // LCOV_EXCL_STOP + usage.printAndExit(1); // LCOV_EXCL_LINE } } @@ -356,7 +335,7 @@ int main(int argc, char *argv[]) { // If no input files were specified, the user must have screwed up if (curArgIndex == argc) { - fatalWithUsage("Please specify an input file (pass `-` to read from standard input)"); + usage.printAndExit("Please specify an input file (pass `-` to read from standard input)"); } // Patch the size array depending on command-line options diff --git a/src/usage.cpp b/src/usage.cpp new file mode 100644 index 00000000..b0e068a3 --- /dev/null +++ b/src/usage.cpp @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +#include "usage.hpp" + +#include +#include + +void Usage::printAndExit(int code) const { + fputs(usage, stderr); + exit(code); +} + +void Usage::printAndExit(char const *fmt, ...) const { + va_list args; + fputs("FATAL: ", stderr); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + putc('\n', stderr); + + printAndExit(1); +}