mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 02:02:06 +00:00
Use colored/styled text output for diagnostics and usage info (#1775)
This commit is contained in:
1
Makefile
1
Makefile
@@ -52,6 +52,7 @@ all: rgbasm rgblink rgbfix rgbgfx
|
||||
common_obj := \
|
||||
src/extern/getopt.o \
|
||||
src/diagnostics.o \
|
||||
src/style.o \
|
||||
src/usage.o
|
||||
|
||||
rgbasm_obj := \
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#define STDERR_FILENO 2
|
||||
#define ssize_t int
|
||||
#define SSIZE_MAX INT_MAX
|
||||
#define isatty _isatty
|
||||
#else
|
||||
#include <fcntl.h> // IWYU pragma: export
|
||||
#include <limits.h> // IWYU pragma: export
|
||||
|
||||
42
include/style.hpp
Normal file
42
include/style.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_STYLE_HPP
|
||||
#define RGBDS_STYLE_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
#define STYLE_ANSI 0
|
||||
#else
|
||||
#define STYLE_ANSI 1
|
||||
#endif
|
||||
|
||||
enum StyleColor {
|
||||
#if STYLE_ANSI
|
||||
// Values analogous to ANSI foreground and background SGR colors
|
||||
STYLE_BLACK,
|
||||
STYLE_RED,
|
||||
STYLE_GREEN,
|
||||
STYLE_YELLOW,
|
||||
STYLE_BLUE,
|
||||
STYLE_MAGENTA,
|
||||
STYLE_CYAN,
|
||||
STYLE_GRAY,
|
||||
#else
|
||||
// Values analogous to `FOREGROUND_*` constants from `windows.h`
|
||||
STYLE_BLACK,
|
||||
STYLE_BLUE, // bit 0
|
||||
STYLE_GREEN, // bit 1
|
||||
STYLE_CYAN, // STYLE_BLUE | STYLE_GREEN
|
||||
STYLE_RED, // bit 2
|
||||
STYLE_MAGENTA, // STYLE_BLUE | STYLE_RED
|
||||
STYLE_YELLOW, // STYLE_GREEN | STYLE_RED
|
||||
STYLE_GRAY, // STYLE_BLUE | STYLE_GREEN | STYLE_RED
|
||||
#endif
|
||||
};
|
||||
|
||||
void style_Enable(bool enable);
|
||||
void style_Set(FILE *file, StyleColor color, bool bold);
|
||||
void style_Reset(FILE *file);
|
||||
|
||||
#endif // RGBDS_STYLE_HPP
|
||||
@@ -4,12 +4,14 @@
|
||||
#define RGBDS_USAGE_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class Usage {
|
||||
char const *usage;
|
||||
|
||||
public:
|
||||
Usage(char const *usage_) : usage(usage_) {}
|
||||
struct Usage {
|
||||
std::string name;
|
||||
std::vector<std::string> flags;
|
||||
std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> options;
|
||||
|
||||
[[noreturn]]
|
||||
void printAndExit(int code) const;
|
||||
|
||||
13
man/rgbasm.1
13
man/rgbasm.1
@@ -10,6 +10,7 @@
|
||||
.Nm
|
||||
.Op Fl EhVvw
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
.Op Fl I Ar path
|
||||
@@ -65,6 +66,18 @@ letters,
|
||||
.Sq # ,
|
||||
or
|
||||
.Sq @ .
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
|
||||
Add a string symbol to the compiled source code.
|
||||
This is equivalent to
|
||||
|
||||
21
man/rgbfix.1
21
man/rgbfix.1
@@ -10,6 +10,7 @@
|
||||
.Nm
|
||||
.Op Fl hjOsVvw
|
||||
.Op Fl C | c
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
.Op Fl k Ar licensee_str
|
||||
@@ -46,13 +47,13 @@ can be a path to a file, or
|
||||
to read from standard input.
|
||||
.Pp
|
||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||
.Fl \-color-o
|
||||
.Fl \-verb
|
||||
is
|
||||
.Fl \-color-only ,
|
||||
.Fl \-verbose ,
|
||||
but
|
||||
.Fl \-color
|
||||
.Fl \-ver
|
||||
is invalid because it could also be
|
||||
.Fl \-color-compatible .
|
||||
.Fl \-version .
|
||||
Options later in the command line override those set earlier.
|
||||
Accepted options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
@@ -70,6 +71,18 @@ to 0x80.
|
||||
This overrides
|
||||
.Fl c
|
||||
if it was set prior.
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl f Ar fix_spec , Fl \-fix-spec Ar fix_spec
|
||||
Fix certain header values that the Game Boy checks for correctness.
|
||||
Alternatively, intentionally trash these values by writing their binary inverse instead.
|
||||
|
||||
13
man/rgbgfx.1
13
man/rgbgfx.1
@@ -15,6 +15,7 @@
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
.Op Fl c Ar pal_spec
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl d Ar depth
|
||||
.Op Fl i Ar input_tiles
|
||||
.Op Fl L Ar slice
|
||||
@@ -195,6 +196,18 @@ See
|
||||
.Sx PALETTE SPECIFICATION FORMATS
|
||||
for a list of formats and their descriptions.
|
||||
.El
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dhMtVvwx
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl l Ar linker_script
|
||||
.Op Fl m Ar map_file
|
||||
.Op Fl n Ar sym_file
|
||||
@@ -63,6 +64,18 @@ is invalid because it could also be
|
||||
.Fl \-version .
|
||||
The arguments are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl d , Fl \-dmg
|
||||
Enable DMG mode.
|
||||
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||
|
||||
@@ -5,6 +5,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
||||
set(common_src
|
||||
"extern/getopt.cpp"
|
||||
"diagnostics.cpp"
|
||||
"style.cpp"
|
||||
"usage.cpp"
|
||||
"_version.cpp"
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ bool charmap_ForEach(
|
||||
});
|
||||
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
for (auto const &[nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "helpers.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp" // S_ISDIR (stat macro)
|
||||
#include "style.hpp"
|
||||
#include "verbosity.hpp"
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
@@ -63,19 +64,27 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
fputs(reptChain().c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return lastName;
|
||||
} else {
|
||||
if (parent) {
|
||||
parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
}
|
||||
std::string const &nodeName = name();
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(nodeName.c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return nodeName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "style.hpp"
|
||||
#include "util.hpp"
|
||||
#include "verbosity.hpp"
|
||||
|
||||
@@ -845,9 +846,15 @@ void lexer_DumpStringExpansions() {
|
||||
for (Expansion &exp : lexerState->expansions) {
|
||||
// Only register EQUS expansions, not string args
|
||||
if (exp.name) {
|
||||
fprintf(stderr, "while expanding symbol \"%s\"\n", exp.name->c_str());
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs("while expanding symbol \"", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(exp.name->c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs("\"\n", stderr);
|
||||
}
|
||||
}
|
||||
style_Reset(stderr);
|
||||
}
|
||||
|
||||
// Functions to discard non-tokenized characters
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "extern/getopt.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "parser.hpp" // Generated from parser.y
|
||||
#include "style.hpp"
|
||||
#include "usage.hpp"
|
||||
#include "util.hpp" // UpperMap
|
||||
#include "verbosity.hpp"
|
||||
@@ -36,7 +37,7 @@ static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
static int longOpt; // `--color` and variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
@@ -53,11 +54,6 @@ static option const longopts[] = {
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MC", no_argument, &depType, 'C'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MQ", required_argument, &depType, 'Q'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
@@ -68,27 +64,34 @@ static option const longopts[] = {
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"max-errors", required_argument, nullptr, 'X'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"color", required_argument, &longOpt, 'c'},
|
||||
{"MC", no_argument, &longOpt, 'C'},
|
||||
{"MG", no_argument, &longOpt, 'G'},
|
||||
{"MP", no_argument, &longOpt, 'P'},
|
||||
{"MQ", required_argument, &longOpt, 'Q'},
|
||||
{"MT", required_argument, &longOpt, 'T'},
|
||||
{nullptr, no_argument, nullptr, 0 },
|
||||
};
|
||||
|
||||
// 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"
|
||||
" <file>\n"
|
||||
"Useful options:\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
" -o, --output <path> set the output object file\n"
|
||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||
" -s, --state <features>:<path> set an output state file\n"
|
||||
" -V, --version print RGBASM version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n"
|
||||
);
|
||||
// clang-format off: nested initializers
|
||||
static Usage usage = {
|
||||
.name = "rgbasm",
|
||||
.flags = {
|
||||
"[-EhVvw]", "[-b chars]", "[-D name[=value]]", "[-g chars]", "[-I path]",
|
||||
"[-M depend_file]", "[-MC]", "[-MG]", "[-MP]", "[-MT target_file]", "[-MQ target_file]",
|
||||
"[-o out_file]", "[-P include_file]", "[-p pad_value]", "[-Q precision]", "[-r depth]",
|
||||
"[-s features:state_file]", "[-W warning]", "[-X max_errors]", "<file>",
|
||||
},
|
||||
.options = {
|
||||
{{"-E", "--export-all"}, {"export all labels"}},
|
||||
{{"-M", "--dependfile <path>"}, {"set the output dependency file"}},
|
||||
{{"-o", "--output <path>"}, {"set the output object file"}},
|
||||
{{"-p", "--pad-value <value>"}, {"set the value to use for `ds'"}},
|
||||
{{"-s", "--state <features>:<path>"}, {"set an output state file"}},
|
||||
{{"-V", "--version"}, {"print RGBASM version and exit"}},
|
||||
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// LCOV_EXCL_START
|
||||
@@ -160,7 +163,7 @@ static void verboseOutputConfig(int argc, char *argv[]) {
|
||||
"char",
|
||||
"macro",
|
||||
};
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
for (auto const &[name, features] : stateFileSpecs) {
|
||||
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
|
||||
for (size_t i = 0; i < features.size(); ++i) {
|
||||
if (i > 0) {
|
||||
@@ -449,7 +452,17 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Long-only options
|
||||
case 0:
|
||||
switch (depType) {
|
||||
switch (longOpt) {
|
||||
case 'c':
|
||||
if (!strcasecmp(musl_optarg, "always")) {
|
||||
style_Enable(true);
|
||||
} else if (!strcasecmp(musl_optarg, "never")) {
|
||||
style_Enable(false);
|
||||
} else if (strcasecmp(musl_optarg, "auto")) {
|
||||
fatal("Invalid argument for option '--color'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
options.missingIncludeState = GEN_CONTINUE;
|
||||
break;
|
||||
@@ -465,7 +478,7 @@ int main(int argc, char *argv[]) {
|
||||
case 'Q':
|
||||
case 'T': {
|
||||
std::string newTarget = musl_optarg;
|
||||
if (depType == 'Q') {
|
||||
if (longOpt == 'Q') {
|
||||
newTarget = escapeMakeChars(newTarget);
|
||||
}
|
||||
if (!options.targetFileName.empty()) {
|
||||
@@ -549,7 +562,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
for (auto const &[name, features] : stateFileSpecs) {
|
||||
out_WriteState(name, features);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "style.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
@@ -64,14 +65,24 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
// clang-format on
|
||||
|
||||
static void printDiag(
|
||||
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
|
||||
char const *fmt,
|
||||
va_list args,
|
||||
char const *type,
|
||||
StyleColor color,
|
||||
char const *flagfmt,
|
||||
char const *flag
|
||||
) {
|
||||
fputs(type, stderr);
|
||||
fputs(": ", stderr);
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, "%s: ", type);
|
||||
if (fstk_DumpCurrent()) {
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
putc(':', stderr);
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
}
|
||||
fputs("\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
putc('\n', stderr);
|
||||
|
||||
@@ -82,13 +93,16 @@ static void incrementErrors() {
|
||||
// This intentionally makes 0 act as "unlimited"
|
||||
warnings.incrementErrors();
|
||||
if (warnings.nbErrors == options.maxErrors) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Assembly aborted after the maximum of %" PRIu64 " error%s! (configure with "
|
||||
"'-X/--max-errors')\n",
|
||||
"Assembly aborted after the maximum of %" PRIu64 " error%s!",
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Set(stderr, STYLE_RED, false);
|
||||
fputs(" (configure with '-X/--max-errors')\n", stderr);
|
||||
style_Reset(stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -97,17 +111,19 @@ void error(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
printDiag(fmt, args, "error", ":", nullptr);
|
||||
printDiag(fmt, args, "error", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
incrementErrors();
|
||||
}
|
||||
|
||||
void error(std::function<void()> callback) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
if (fstk_DumpCurrent()) {
|
||||
fputs(":\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
callback();
|
||||
putc('\n', stderr);
|
||||
lexer_DumpStringExpansions();
|
||||
@@ -120,7 +136,7 @@ void fatal(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
printDiag(fmt, args, "FATAL", ":", nullptr);
|
||||
printDiag(fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
exit(1);
|
||||
@@ -128,12 +144,14 @@ void fatal(char const *fmt, ...) {
|
||||
|
||||
void requireZeroErrors() {
|
||||
if (warnings.nbErrors != 0) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Assembly aborted with %" PRIu64 " error%s!\n",
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Reset(stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -149,11 +167,11 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
printDiag(fmt, args, "warning", ": [-W%s]", flag);
|
||||
printDiag(fmt, args, "warning", STYLE_YELLOW, " [-W%s]", flag);
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
|
||||
printDiag(fmt, args, "error", STYLE_RED, " [-Werror=%s]", flag);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
void warnx(char const *fmt, ...) {
|
||||
|
||||
102
src/fix/main.cpp
102
src/fix/main.cpp
@@ -16,6 +16,7 @@
|
||||
#include "extern/getopt.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "style.hpp"
|
||||
#include "usage.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
@@ -27,6 +28,9 @@ static constexpr off_t BANK_SIZE = 0x4000;
|
||||
// Short options
|
||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int longOpt; // `--color`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
@@ -35,47 +39,54 @@ static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"color-only", no_argument, nullptr, 'C'},
|
||||
{"color-compatible", no_argument, nullptr, 'c'},
|
||||
{"fix-spec", required_argument, nullptr, 'f'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"game-id", required_argument, nullptr, 'i'},
|
||||
{"non-japanese", no_argument, nullptr, 'j'},
|
||||
{"new-licensee", required_argument, nullptr, 'k'},
|
||||
{"logo", required_argument, nullptr, 'L'},
|
||||
{"old-licensee", required_argument, nullptr, 'l'},
|
||||
{"mbc-type", required_argument, nullptr, 'm'},
|
||||
{"rom-version", required_argument, nullptr, 'n'},
|
||||
{"overwrite", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"ram-size", required_argument, nullptr, 'r'},
|
||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||
{"title", required_argument, nullptr, 't'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"validate", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"color-only", no_argument, nullptr, 'C'},
|
||||
{"color-compatible", no_argument, nullptr, 'c'},
|
||||
{"fix-spec", required_argument, nullptr, 'f'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"game-id", required_argument, nullptr, 'i'},
|
||||
{"non-japanese", no_argument, nullptr, 'j'},
|
||||
{"new-licensee", required_argument, nullptr, 'k'},
|
||||
{"logo", required_argument, nullptr, 'L'},
|
||||
{"old-licensee", required_argument, nullptr, 'l'},
|
||||
{"mbc-type", required_argument, nullptr, 'm'},
|
||||
{"rom-version", required_argument, nullptr, 'n'},
|
||||
{"overwrite", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"ram-size", required_argument, nullptr, 'r'},
|
||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||
{"title", required_argument, nullptr, 't'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"validate", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"color", required_argument, &longOpt, 'c'},
|
||||
{nullptr, no_argument, nullptr, 0 },
|
||||
};
|
||||
|
||||
// clang-format off: long string literal
|
||||
static Usage usage(
|
||||
"Usage: rgbfix [-hjOsVvw] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||
" [-L <logo_file>] [-l <licensee_byte>] [-m <mbc_type>]\n"
|
||||
" [-n <rom_version>] [-p <pad_value>] [-r <ram_size>] [-t <title_str>]\n"
|
||||
" [-W warning] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mbc-type <value> set the MBC type byte to this value; `-m help'\n"
|
||||
" or `-m list' prints the accepted values\n"
|
||||
" -p, --pad-value <value> pad to the next valid size using this value\n"
|
||||
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
|
||||
" -o, --output <path> 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"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbfix' or go to https://rgbds.gbdev.io/docs/\n"
|
||||
);
|
||||
// clang-format off: nested initializers
|
||||
static Usage usage = {
|
||||
.name = "rgbfix",
|
||||
.flags = {
|
||||
"[-hjOsVvw]", "[-C | -c]", "[-f <fix_spec>]", "[-i <game_id>]", "[-k <licensee>]",
|
||||
"[-L <logo_file>]", "[-l <licensee_byte>]", "[-m <mbc_type>]", "[-n <rom_version>]",
|
||||
"[-p <pad_value>]", "[-r <ram_size>]", "[-t <title_str>]", "[-W warning]", "<file> ...",
|
||||
},
|
||||
.options = {
|
||||
{
|
||||
{"-m", "--mbc-type <value>"},
|
||||
{
|
||||
"set the MBC type byte to this value; `-m help'",
|
||||
"or `-m list' prints the accepted values",
|
||||
},
|
||||
},
|
||||
{{"-p", "--pad-value <value>"}, {"pad to the next valid size using this value"}},
|
||||
{{"-r", "--ram-size <code>"}, {"set the cart RAM size byte to this value"}},
|
||||
{{"-o", "--output <path>"}, {"set the output file"}},
|
||||
{{"-V", "--version"}, {"print RGBFIX version and exit"}},
|
||||
{{"-v", "--validate"}, {"fix the header logo and both checksums (`-f lhg')"}},
|
||||
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static uint8_t tpp1Rev[2];
|
||||
@@ -817,6 +828,19 @@ int main(int argc, char *argv[]) {
|
||||
warnings.state.warningsEnabled = false;
|
||||
break;
|
||||
|
||||
// Long-only options
|
||||
case 0:
|
||||
if (longOpt == 'c') {
|
||||
if (!strcasecmp(musl_optarg, "always")) {
|
||||
style_Enable(true);
|
||||
} else if (!strcasecmp(musl_optarg, "never")) {
|
||||
style_Enable(false);
|
||||
} else if (strcasecmp(musl_optarg, "auto")) {
|
||||
fatal("Invalid argument for option '--color'");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "fix/mbc.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "fix/warning.hpp"
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
// clang-format off: nested initializers
|
||||
Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
.metaWarnings = {
|
||||
@@ -20,6 +24,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
|
||||
uint32_t checkErrors(char const *filename) {
|
||||
if (warnings.nbErrors > 0) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Fixing \"%s\" failed with %" PRIu64 " error%s\n",
|
||||
@@ -27,13 +32,16 @@ uint32_t checkErrors(char const *filename) {
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Reset(stderr);
|
||||
}
|
||||
return warnings.nbErrors;
|
||||
}
|
||||
|
||||
void error(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -44,7 +52,9 @@ void error(char const *fmt, ...) {
|
||||
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("FATAL: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -62,7 +72,9 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
style_Set(stderr, STYLE_YELLOW, true);
|
||||
fprintf(stderr, "warning: [-W%s]\n ", flag);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -70,7 +82,9 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(stderr, "error: [-Werror=%s]\n ", flag);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
120
src/gfx/main.cpp
120
src/gfx/main.cpp
@@ -18,6 +18,7 @@
|
||||
#include "extern/getopt.hpp"
|
||||
#include "file.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "style.hpp"
|
||||
#include "usage.hpp"
|
||||
#include "verbosity.hpp"
|
||||
#include "version.hpp"
|
||||
@@ -44,6 +45,9 @@ static struct LocalOptions {
|
||||
// Short options
|
||||
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int longOpt; // `--color`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
@@ -52,59 +56,62 @@ static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"background-color", required_argument, nullptr, 'B'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"input-tileset", required_argument, nullptr, 'i'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"base-palette", required_argument, nullptr, 'l'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
{"nb-tiles", required_argument, nullptr, 'N'},
|
||||
{"nb-palettes", required_argument, nullptr, 'n'},
|
||||
{"group-outputs", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"auto-palette", no_argument, nullptr, 'P'},
|
||||
{"palette", required_argument, nullptr, 'p'},
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"palette-size", required_argument, nullptr, 's'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
{"unique-tiles", no_argument, nullptr, 'u'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"mirror-x", no_argument, nullptr, 'X'},
|
||||
{"trim-end", required_argument, nullptr, 'x'},
|
||||
{"mirror-y", no_argument, nullptr, 'Y'},
|
||||
{"columns", no_argument, nullptr, 'Z'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"background-color", required_argument, nullptr, 'B'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"input-tileset", required_argument, nullptr, 'i'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"base-palette", required_argument, nullptr, 'l'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
{"nb-tiles", required_argument, nullptr, 'N'},
|
||||
{"nb-palettes", required_argument, nullptr, 'n'},
|
||||
{"group-outputs", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"auto-palette", no_argument, nullptr, 'P'},
|
||||
{"palette", required_argument, nullptr, 'p'},
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"palette-size", required_argument, nullptr, 's'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
{"unique-tiles", no_argument, nullptr, 'u'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"mirror-x", no_argument, nullptr, 'X'},
|
||||
{"trim-end", required_argument, nullptr, 'x'},
|
||||
{"mirror-y", no_argument, nullptr, 'Y'},
|
||||
{"columns", no_argument, nullptr, 'Z'},
|
||||
{"color", required_argument, &longOpt, 'c'},
|
||||
{nullptr, no_argument, nullptr, 0 },
|
||||
};
|
||||
|
||||
// clang-format off: long string literal
|
||||
static Usage usage(
|
||||
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-i <tileset_file>]\n"
|
||||
" [-L <slice>] [-l <base_pal>] [-N <nb_tiles>] [-n <nb_pals>]\n"
|
||||
" [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> output the tile data to this path\n"
|
||||
" -t, --tilemap <path> output the tile map to this path\n"
|
||||
" -u, --unique-tiles optimize out identical tiles\n"
|
||||
" -V, --version print RGBGFX version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n"
|
||||
);
|
||||
// clang-format off: nested initializers
|
||||
static Usage usage = {
|
||||
.name = "rgbgfx",
|
||||
.flags = {
|
||||
"[-r stride]", "[-ChmOuVXYZ]", "[-v [-v ...]]", "[-a <attr_map> | -A]", "[-b <base_ids>]",
|
||||
"[-c <colors>]", "[-d <depth>]", "[-i <tileset_file>]", "[-L <slice>]", "[-l <base_pal>]",
|
||||
"[-N <nb_tiles>]", "[-n <nb_pals>]", "[-o <out_file>]", "[-p <pal_file> | -P]",
|
||||
"[-q <pal_map> | -Q]", "[-s <nb_colors>]", "[-t <tile_map> | -T]", "[-x <nb_tiles>]",
|
||||
"<file>",
|
||||
},
|
||||
.options = {
|
||||
{{"-m", "--mirror-tiles"}, {"optimize out mirrored tiles"}},
|
||||
{{"-o", "--output <path>"}, {"output the tile data to this path"}},
|
||||
{{"-t", "--tilemap <path>"}, {"output the tile map to this path"}},
|
||||
{{"-u", "--unique-tiles"}, {"optimize out identical tiles"}},
|
||||
{{"-V", "--version"}, {"print RGBGFX version and exit"}},
|
||||
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||
@@ -552,6 +559,17 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
case 'Z':
|
||||
options.columnMajor = true;
|
||||
break;
|
||||
case 0: // Long-only options
|
||||
if (longOpt == 'c') {
|
||||
if (!strcasecmp(musl_optarg, "always")) {
|
||||
style_Enable(true);
|
||||
} else if (!strcasecmp(musl_optarg, "never")) {
|
||||
style_Enable(false);
|
||||
} else if (strcasecmp(musl_optarg, "auto")) {
|
||||
fatal("Invalid argument for option '--color'");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: // Positional argument, requested by leading `-` in opt string
|
||||
if (musl_optarg[0] == '@') {
|
||||
// Instruct the caller to process that at-file
|
||||
|
||||
@@ -630,7 +630,7 @@ static void outputUnoptimizedTileData(
|
||||
uint64_t nbKeptTiles = nbTiles > options.trim ? nbTiles - options.trim : 0;
|
||||
uint64_t tileIdx = 0;
|
||||
|
||||
for (auto [tile, attr] : zip(image.visitAsTiles(), attrmap)) {
|
||||
for (auto const &[tile, attr] : zip(image.visitAsTiles(), attrmap)) {
|
||||
// Do not emit fully-background tiles.
|
||||
if (attr.isBackgroundTile()) {
|
||||
++tileIdx;
|
||||
@@ -799,7 +799,7 @@ static UniqueTiles dedupTiles(
|
||||
}
|
||||
|
||||
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
|
||||
for (auto [tile, attr] : zip(image.visitAsTiles(), attrmap)) {
|
||||
for (auto const &[tile, attr] : zip(image.visitAsTiles(), attrmap)) {
|
||||
if (attr.isBackgroundTile()) {
|
||||
attr.xFlip = false;
|
||||
attr.yFlip = false;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
// clang-format off: nested initializers
|
||||
Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
.metaWarnings = {
|
||||
@@ -25,12 +27,14 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
|
||||
[[noreturn]]
|
||||
void giveUp() {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Conversion aborted after %" PRIu64 " error%s\n",
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Reset(stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -42,7 +46,9 @@ void requireZeroErrors() {
|
||||
|
||||
void error(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -54,7 +60,9 @@ void error(char const *fmt, ...) {
|
||||
[[noreturn]]
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("FATAL: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -73,7 +81,9 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
style_Set(stderr, STYLE_YELLOW, true);
|
||||
fprintf(stderr, "warning: [-W%s]\n ", flag);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -81,7 +91,9 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(stderr, "error: [-Werror=%s]\n ", flag);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "itertools.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "script.hpp" // Generated from script.y
|
||||
#include "style.hpp"
|
||||
#include "usage.hpp"
|
||||
#include "util.hpp" // UpperMap, printChar
|
||||
#include "verbosity.hpp"
|
||||
@@ -37,6 +38,9 @@ static char const *linkerScriptName = nullptr; // -l
|
||||
// Short options
|
||||
static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int longOpt; // `--color`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
@@ -45,42 +49,44 @@ static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvW:wx";
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"dmg", no_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"linkerscript", required_argument, nullptr, 'l'},
|
||||
{"map", required_argument, nullptr, 'm'},
|
||||
{"no-sym-in-map", no_argument, nullptr, 'M'},
|
||||
{"sym", required_argument, nullptr, 'n'},
|
||||
{"overlay", required_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad", required_argument, nullptr, 'p'},
|
||||
{"scramble", required_argument, nullptr, 'S'},
|
||||
{"tiny", no_argument, nullptr, 't'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"wramx", no_argument, nullptr, 'w'},
|
||||
{"nopad", no_argument, nullptr, 'x'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"dmg", no_argument, nullptr, 'd'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"linkerscript", required_argument, nullptr, 'l'},
|
||||
{"map", required_argument, nullptr, 'm'},
|
||||
{"no-sym-in-map", no_argument, nullptr, 'M'},
|
||||
{"sym", required_argument, nullptr, 'n'},
|
||||
{"overlay", required_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"pad", required_argument, nullptr, 'p'},
|
||||
{"scramble", required_argument, nullptr, 'S'},
|
||||
{"tiny", no_argument, nullptr, 't'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"wramx", no_argument, nullptr, 'w'},
|
||||
{"nopad", no_argument, nullptr, 'x'},
|
||||
{"color", required_argument, &longOpt, 'c'},
|
||||
{nullptr, no_argument, nullptr, 0 },
|
||||
};
|
||||
|
||||
// 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] <file> ...\n"
|
||||
"Useful options:\n"
|
||||
" -l, --linkerscript <path> set the input linker script\n"
|
||||
" -m, --map <path> set the output map file\n"
|
||||
" -n, --sym <path> set the output symbol list file\n"
|
||||
" -o, --output <path> set the output file\n"
|
||||
" -p, --pad <value> set the value to pad between sections with\n"
|
||||
" -x, --nopad disable padding of output binary\n"
|
||||
" -V, --version print RGBLINK version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgblink' or go to https://rgbds.gbdev.io/docs/\n"
|
||||
);
|
||||
// clang-format off: nested initializers
|
||||
static Usage usage = {
|
||||
.name = "rgblink",
|
||||
.flags = {
|
||||
"[-dhMtVvwx]", "[-l script]", "[-m map_file]", "[-n sym_file]", "[-O overlay_file]",
|
||||
"[-o out_file]", "[-p pad_value]", "[-S spec]", "<file> ...",
|
||||
},
|
||||
.options = {
|
||||
{{"-l", "--linkerscript <path>"}, {"set the input linker script"}},
|
||||
{{"-m", "--map <path>"}, {"set the output map file"}},
|
||||
{{"-n", "--sym <path>"}, {"set the output symbol list file"}},
|
||||
{{"-o", "--output <path>"}, {"set the output file"}},
|
||||
{{"-p", "--pad <value>"}, {"set the value to pad between sections with"}},
|
||||
{{"-x", "--nopad"}, {"disable padding of output binary"}},
|
||||
{{"-V", "--version"}, {"print RGBLINK version and exit"}},
|
||||
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// LCOV_EXCL_START
|
||||
@@ -174,21 +180,29 @@ std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
for (uint32_t iter : iters()) {
|
||||
fprintf(stderr, "::REPT~%" PRIu32, iter);
|
||||
}
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return lastName;
|
||||
} else {
|
||||
if (parent) {
|
||||
parent->dump(lineNo);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fputs(" -> ", stderr);
|
||||
}
|
||||
std::string const &nodeName = name();
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(nodeName.c_str(), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
style_Reset(stderr);
|
||||
return nodeName;
|
||||
}
|
||||
}
|
||||
@@ -388,6 +402,17 @@ int main(int argc, char *argv[]) {
|
||||
// implies tiny mode
|
||||
options.is32kMode = true;
|
||||
break;
|
||||
case 0: // Long-only options
|
||||
if (longOpt == 'c') {
|
||||
if (!strcasecmp(musl_optarg, "always")) {
|
||||
style_Enable(true);
|
||||
} else if (!strcasecmp(musl_optarg, "never")) {
|
||||
style_Enable(false);
|
||||
} else if (strcasecmp(musl_optarg, "auto")) {
|
||||
fatal("Invalid argument for option '--color'");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
|
||||
// clang-format off: nested initializers
|
||||
@@ -33,24 +35,29 @@ static void printDiag(
|
||||
char const *fmt,
|
||||
va_list args,
|
||||
char const *type,
|
||||
StyleColor color,
|
||||
char const *flagfmt,
|
||||
char const *flag
|
||||
) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, "%s: ", type);
|
||||
if (src) {
|
||||
src->dump(lineNo);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
fputs("\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
static void abortLinking(char const *verb) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Linking %s with %" PRIu64 " error%s\n",
|
||||
@@ -58,27 +65,28 @@ static void abortLinking(char const *verb) {
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Reset(stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warning(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printDiag(src, lineNo, fmt, args, "warning", nullptr, 0);
|
||||
printDiag(src, lineNo, fmt, args, "warning", STYLE_YELLOW, nullptr, nullptr);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void warning(char const *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printDiag(nullptr, 0, fmt, args, "warning", nullptr, 0);
|
||||
printDiag(nullptr, 0, fmt, args, "warning", STYLE_YELLOW, nullptr, nullptr);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printDiag(src, lineNo, fmt, args, "error", nullptr, 0);
|
||||
printDiag(src, lineNo, fmt, args, "error", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
warnings.incrementErrors();
|
||||
@@ -87,7 +95,7 @@ void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
|
||||
void error(char const *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printDiag(nullptr, 0, fmt, args, "error", nullptr, 0);
|
||||
printDiag(nullptr, 0, fmt, args, "error", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
warnings.incrementErrors();
|
||||
@@ -95,7 +103,9 @@ void error(char const *fmt, ...) {
|
||||
|
||||
void errorNoDump(char const *fmt, ...) {
|
||||
va_list args;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
@@ -104,7 +114,13 @@ void errorNoDump(char const *fmt, ...) {
|
||||
}
|
||||
|
||||
void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list args) {
|
||||
fprintf(stderr, "error: %s(%" PRIu32 "): ", name, lineNo);
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(name, stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 "): ", lineNo);
|
||||
style_Reset(stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
putc('\n', stderr);
|
||||
|
||||
@@ -115,7 +131,7 @@ void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list arg
|
||||
void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printDiag(src, lineNo, fmt, args, "FATAL", nullptr, 0);
|
||||
printDiag(src, lineNo, fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
warnings.incrementErrors();
|
||||
@@ -126,7 +142,7 @@ void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...) {
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
printDiag(nullptr, 0, fmt, args, "FATAL", nullptr, 0);
|
||||
printDiag(nullptr, 0, fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
warnings.incrementErrors();
|
||||
@@ -150,11 +166,11 @@ void warning(FileStackNode const *src, uint32_t lineNo, WarningID id, char const
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
printDiag(src, lineNo, fmt, args, "warning", "[-W%s]", flag);
|
||||
printDiag(src, lineNo, fmt, args, "warning", STYLE_RED, "[-W%s]", flag);
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
printDiag(src, lineNo, fmt, args, "error", "[-Werror=%s]", flag);
|
||||
printDiag(src, lineNo, fmt, args, "error", STYLE_YELLOW, "[-Werror=%s]", flag);
|
||||
|
||||
warnings.incrementErrors();
|
||||
break;
|
||||
|
||||
@@ -14,7 +14,7 @@ SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
||||
.size = 0x2000, // Patched to 0x1000 if !isWRAM0Mode
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "VRAM"s,
|
||||
.startAddr = 0x8000,
|
||||
@@ -28,42 +28,42 @@ SectionTypeInfo sectionTypeInfo[SECTTYPE_INVALID] = {
|
||||
.size = 0x4000,
|
||||
.firstBank = 1,
|
||||
.lastBank = 65535,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "ROM0"s,
|
||||
.startAddr = 0x0000,
|
||||
.size = 0x8000, // Patched to 0x4000 if !is32kMode
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "HRAM"s,
|
||||
.startAddr = 0xFF80,
|
||||
.size = 0x007F,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "WRAMX"s,
|
||||
.startAddr = 0xD000,
|
||||
.size = 0x1000,
|
||||
.firstBank = 1,
|
||||
.lastBank = 7,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "SRAM"s,
|
||||
.startAddr = 0xA000,
|
||||
.size = 0x2000,
|
||||
.firstBank = 0,
|
||||
.lastBank = 255,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "OAM"s,
|
||||
.startAddr = 0xFE00,
|
||||
.size = 0x00A0,
|
||||
.firstBank = 0,
|
||||
.lastBank = 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
98
src/style.cpp
Normal file
98
src/style.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This implementation was based on https://github.com/agauniyal/rang/
|
||||
// and adapted for RGBDS.
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
#include <stdlib.h> // getenv
|
||||
#include <string.h>
|
||||
|
||||
#include "platform.hpp" // isatty
|
||||
|
||||
#if !STYLE_ANSI
|
||||
// clang-format off: maintain `include` order
|
||||
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
|
||||
#include <windows.h>
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
enum Tribool { TRI_NO, TRI_YES, TRI_MAYBE };
|
||||
|
||||
#if !STYLE_ANSI
|
||||
static const HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
static const HANDLE errHandle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
static const WORD defaultAttrib = []() {
|
||||
if (CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(outHandle, &info)
|
||||
|| GetConsoleScreenBufferInfo(errHandle, &info)) {
|
||||
return info.wAttributes;
|
||||
}
|
||||
return static_cast<WORD>((STYLE_BLACK << 4) | (STYLE_GRAY | 8));
|
||||
}();
|
||||
|
||||
static HANDLE getHandle(FILE *file) {
|
||||
return file == stdout ? outHandle : file == stderr ? errHandle : INVALID_HANDLE_VALUE;
|
||||
}
|
||||
#endif // !STYLE_ANSI
|
||||
|
||||
static Tribool forceStyle = []() {
|
||||
if (char const *forceColor = getenv("FORCE_COLOR");
|
||||
forceColor && strcmp(forceColor, "") && strcmp(forceColor, "0")) {
|
||||
return TRI_YES;
|
||||
}
|
||||
if (char const *noColor = getenv("NO_COLOR");
|
||||
noColor && strcmp(noColor, "") && strcmp(noColor, "0")) {
|
||||
return TRI_NO;
|
||||
}
|
||||
return TRI_MAYBE;
|
||||
}();
|
||||
|
||||
static bool isTerminal(FILE *file) {
|
||||
static bool isOutTerminal = isatty(STDOUT_FILENO);
|
||||
static bool isErrTerminal = isatty(STDERR_FILENO);
|
||||
|
||||
return (file == stdout && isOutTerminal) || (file == stderr && isErrTerminal);
|
||||
}
|
||||
|
||||
static bool allowStyle(FILE *file) {
|
||||
return forceStyle == TRI_YES || (forceStyle == TRI_MAYBE && isTerminal(file));
|
||||
}
|
||||
|
||||
void style_Enable(bool enable) {
|
||||
forceStyle = enable ? TRI_YES : TRI_NO;
|
||||
}
|
||||
|
||||
void style_Set(FILE *file, StyleColor color, bool bold) {
|
||||
if (!allowStyle(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
#if STYLE_ANSI
|
||||
fprintf(file, "\033[%dm", static_cast<int>(color) + (bold ? 90 : 30));
|
||||
#else
|
||||
if (HANDLE handle = getHandle(file); handle != INVALID_HANDLE_VALUE) {
|
||||
fflush(file);
|
||||
SetConsoleTextAttribute(handle, (defaultAttrib & ~0xF) | (color | (bold ? 8 : 0)));
|
||||
}
|
||||
#endif
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
void style_Reset(FILE *file) {
|
||||
if (!allowStyle(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
#if STYLE_ANSI
|
||||
fputs("\033[m", file);
|
||||
#else
|
||||
if (HANDLE handle = getHandle(file); handle != INVALID_HANDLE_VALUE) {
|
||||
fflush(file);
|
||||
SetConsoleTextAttribute(handle, defaultAttrib);
|
||||
}
|
||||
#endif
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
@@ -2,17 +2,111 @@
|
||||
|
||||
#include "usage.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "style.hpp"
|
||||
|
||||
static constexpr size_t maxLineLen = 79;
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
void Usage::printAndExit(int code) const {
|
||||
fputs(usage, stderr);
|
||||
FILE *file = code ? stderr : stdout;
|
||||
|
||||
// Print "Usage: <program name>"
|
||||
style_Set(file, STYLE_GREEN, true);
|
||||
fputs("Usage: ", file);
|
||||
style_Set(file, STYLE_CYAN, true);
|
||||
fputs(name.c_str(), file);
|
||||
size_t padFlags = literal_strlen("Usage: ") + name.length();
|
||||
|
||||
// Print the flags after the program name, indented to the same level
|
||||
style_Set(file, STYLE_CYAN, false);
|
||||
size_t flagsWidth = padFlags;
|
||||
for (std::string const &flag : flags) {
|
||||
if (flagsWidth + 1 + flag.length() > maxLineLen) {
|
||||
fprintf(file, "\n%*c", static_cast<int>(padFlags), ' ');
|
||||
flagsWidth = padFlags;
|
||||
}
|
||||
fprintf(file, " %s", flag.c_str());
|
||||
flagsWidth += 1 + flag.length();
|
||||
}
|
||||
fputs("\n\n", file);
|
||||
|
||||
// Measure the options' flags
|
||||
size_t padOpts = 0;
|
||||
for (auto const &item : options) {
|
||||
size_t pad = 0;
|
||||
for (size_t i = 0; i < item.first.size(); ++i) {
|
||||
if (i > 0) {
|
||||
pad += literal_strlen(", ");
|
||||
}
|
||||
pad += item.first[i].length();
|
||||
}
|
||||
if (pad > padOpts) {
|
||||
padOpts = pad;
|
||||
}
|
||||
}
|
||||
int optIndent = static_cast<int>(literal_strlen(" ") + padOpts);
|
||||
|
||||
// Print the options
|
||||
if (!options.empty()) {
|
||||
style_Set(file, STYLE_GREEN, true);
|
||||
fputs("Useful options:\n", file);
|
||||
}
|
||||
for (auto const &[opts, description] : options) {
|
||||
fputs(" ", file);
|
||||
|
||||
// Print the comma-separated options
|
||||
size_t optWidth = 0;
|
||||
for (size_t i = 0; i < opts.size(); ++i) {
|
||||
if (i > 0) {
|
||||
style_Reset(file);
|
||||
fputs(", ", file);
|
||||
optWidth += literal_strlen(", ");
|
||||
}
|
||||
style_Set(file, STYLE_CYAN, false);
|
||||
fputs(opts[i].c_str(), file);
|
||||
optWidth += opts[i].length();
|
||||
}
|
||||
if (optWidth < padOpts) {
|
||||
fprintf(file, "%*c", static_cast<int>(padOpts - optWidth), ' ');
|
||||
}
|
||||
|
||||
// Print the description lines, indented to the same level
|
||||
for (size_t i = 0; i < description.size(); ++i) {
|
||||
style_Reset(file);
|
||||
if (i > 0) {
|
||||
fprintf(file, "\n%*c", optIndent, ' ');
|
||||
}
|
||||
fprintf(file, " %s", description[i].c_str());
|
||||
}
|
||||
putc('\n', file);
|
||||
}
|
||||
|
||||
// Print the link for further help information
|
||||
style_Reset(file);
|
||||
fputs("\nFor more help, use `", file);
|
||||
style_Set(file, STYLE_CYAN, true);
|
||||
fprintf(file, "man %s", name.c_str());
|
||||
style_Reset(file);
|
||||
fputs("' or go to ", file);
|
||||
style_Set(file, STYLE_BLUE, true);
|
||||
fputs("https://rgbds.gbdev.io/docs/", file);
|
||||
style_Reset(file);
|
||||
putc('\n', file);
|
||||
|
||||
exit(code);
|
||||
}
|
||||
|
||||
void Usage::printAndExit(char const *fmt, ...) const {
|
||||
va_list args;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("FATAL: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
va_end(args);
|
||||
@@ -20,3 +114,5 @@ void Usage::printAndExit(char const *fmt, ...) const {
|
||||
|
||||
printAndExit(1);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "verbosity.hpp"
|
||||
|
||||
#include <array>
|
||||
@@ -8,16 +10,18 @@
|
||||
|
||||
static Verbosity verbosity = VERB_NONE;
|
||||
|
||||
bool checkVerbosity(Verbosity level) {
|
||||
return verbosity >= level;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
void incrementVerbosity() {
|
||||
if (verbosity < VERB_VVVVVV) {
|
||||
verbosity = static_cast<Verbosity>(verbosity + 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool checkVerbosity(Verbosity level) {
|
||||
return verbosity >= level;
|
||||
}
|
||||
|
||||
void printVVVVVVerbosity() {
|
||||
if (!checkVerbosity(VERB_VVVVVV)) {
|
||||
return;
|
||||
@@ -67,3 +71,5 @@ void printVVVVVVerbosity() {
|
||||
}
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
Reference in New Issue
Block a user