Add pedantic argument to allow returning a non-zero exit code in case of errors

This is especially useful to check whether a file is complete, e.g. one might
use `tageditor info --validate --pedantic --files …` to check whether the
specified files are ok. (If they were truncated there's be an error about it
and the command would return a non-zero exit code. Without pedantic this would
just return in a non-zero exit code if the file couldn't be parsed at all.)
This commit is contained in:
Martchus 2023-04-25 23:16:15 +02:00
parent 1e77e0b9e1
commit b7016f98a2
5 changed files with 65 additions and 37 deletions

View File

@ -29,9 +29,10 @@ using namespace QtUtilities;
namespace Cli { namespace Cli {
SetTagInfoArgs::SetTagInfoArgs(Argument &filesArg, Argument &verboseArg) SetTagInfoArgs::SetTagInfoArgs(Argument &filesArg, Argument &verboseArg, Argument &pedanticArg)
: filesArg(filesArg) : filesArg(filesArg)
, verboseArg(verboseArg) , verboseArg(verboseArg)
, pedanticArg(pedanticArg)
, quietArg("quiet", 'q', "suppress printing progress information") , quietArg("quiet", 'q', "suppress printing progress information")
, docTitleArg("doc-title", 'd', "specifies the document title (has no affect if not supported by the container)", , docTitleArg("doc-title", 'd', "specifies the document title (has no affect if not supported by the container)",
{ "title of first segment", "title of second segment" }) { "title of first segment", "title of second segment" })
@ -152,6 +153,13 @@ int main(int argc, char *argv[])
timeSpanFormatArg.setPreDefinedCompletionValues("measures colons seconds"); timeSpanFormatArg.setPreDefinedCompletionValues("measures colons seconds");
// verbose option // verbose option
ConfigValueArgument verboseArg("verbose", 'v', "be verbose, print debug and info messages"); ConfigValueArgument verboseArg("verbose", 'v', "be verbose, print debug and info messages");
// pedantic options
ConfigValueArgument pedanticArg("pedantic", '\0',
"return non-zero exit code if a non-fatal problem has been encountered that is at least as severe as the specified severity (or critical if "
"none specified)",
{ "critical/warning/info/debug" });
pedanticArg.setRequiredValueCount(Argument::varValueCount);
pedanticArg.setPreDefinedCompletionValues("error warning info debug");
// input/output file/files // input/output file/files
ConfigValueArgument fileArg("file", 'f', "specifies the path of the file to be opened", { "path" }); ConfigValueArgument fileArg("file", 'f', "specifies the path of the file to be opened", { "path" });
ConfigValueArgument defaultFileArg(fileArg); ConfigValueArgument defaultFileArg(fileArg);
@ -167,8 +175,9 @@ int main(int argc, char *argv[])
ConfigValueArgument validateArg( ConfigValueArgument validateArg(
"validate", 'c', "validates the file integrity as accurately as possible; the structure of the file will be parsed completely"); "validate", 'c', "validates the file integrity as accurately as possible; the structure of the file will be parsed completely");
OperationArgument displayFileInfoArg("info", 'i', "displays general file information", PROJECT_NAME " info -f /some/dir/*.m4a"); OperationArgument displayFileInfoArg("info", 'i', "displays general file information", PROJECT_NAME " info -f /some/dir/*.m4a");
displayFileInfoArg.setCallback(std::bind(Cli::displayFileInfo, _1, std::cref(filesArg), std::cref(verboseArg), std::cref(validateArg))); displayFileInfoArg.setCallback(
displayFileInfoArg.setSubArguments({ &filesArg, &validateArg, &verboseArg }); std::bind(Cli::displayFileInfo, _1, std::cref(filesArg), std::cref(verboseArg), std::cref(pedanticArg), std::cref(validateArg)));
displayFileInfoArg.setSubArguments({ &filesArg, &validateArg, &verboseArg, &pedanticArg });
// display tag info // display tag info
ConfigValueArgument fieldsArg("fields", 'n', "specifies the field names to be displayed", { "title", "album", "artist", "trackpos" }); ConfigValueArgument fieldsArg("fields", 'n', "specifies the field names to be displayed", { "title", "album", "artist", "trackpos" });
fieldsArg.setRequiredValueCount(Argument::varValueCount); fieldsArg.setRequiredValueCount(Argument::varValueCount);
@ -177,11 +186,11 @@ int main(int argc, char *argv[])
OperationArgument displayTagInfoArg("get", 'g', "displays the values of all specified tag fields (displays all fields if none specified)", OperationArgument displayTagInfoArg("get", 'g', "displays the values of all specified tag fields (displays all fields if none specified)",
PROJECT_NAME " get title album artist -f /some/dir/*.m4a"); PROJECT_NAME " get title album artist -f /some/dir/*.m4a");
ConfigValueArgument showUnsupportedArg("show-unsupported", 'u', "shows unsupported fields (has only effect when no field names specified)"); ConfigValueArgument showUnsupportedArg("show-unsupported", 'u', "shows unsupported fields (has only effect when no field names specified)");
displayTagInfoArg.setCallback( displayTagInfoArg.setCallback(std::bind(Cli::displayTagInfo, std::cref(fieldsArg), std::cref(showUnsupportedArg), std::cref(filesArg),
std::bind(Cli::displayTagInfo, std::cref(fieldsArg), std::cref(showUnsupportedArg), std::cref(filesArg), std::cref(verboseArg))); std::cref(verboseArg), std::cref(pedanticArg)));
displayTagInfoArg.setSubArguments({ &fieldsArg, &showUnsupportedArg, &filesArg, &verboseArg }); displayTagInfoArg.setSubArguments({ &fieldsArg, &showUnsupportedArg, &filesArg, &verboseArg, &pedanticArg });
// set tag info // set tag info
Cli::SetTagInfoArgs setTagInfoArgs(filesArg, verboseArg); Cli::SetTagInfoArgs setTagInfoArgs(filesArg, verboseArg, pedanticArg);
// extract cover // extract cover
ConfigValueArgument fieldArg("field", 'n', "specifies the field to be extracted", { "field name" }); ConfigValueArgument fieldArg("field", 'n', "specifies the field to be extracted", { "field name" });
fieldArg.setImplicit(true); fieldArg.setImplicit(true);

View File

@ -160,40 +160,58 @@ string incremented(const string &str, unsigned int toIncrement)
return res; return res;
} }
void printDiagMessages(const Diagnostics &diag, const char *head, bool beVerbose) void printDiagMessages(const Diagnostics &diag, const char *head, bool beVerbose, const CppUtilities::Argument *pedanticArg)
{ {
if (diag.empty()) { if (diag.empty()) {
return; return;
} }
if (!beVerbose) {
for (const auto &message : diag) { // set exit code to failure if there are diag messages considered bad enough
switch (message.level()) { auto minLevel = beVerbose ? DiagLevel::Information : DiagLevel::Warning;
case DiagLevel::Debug: auto badExitLevel = DiagLevel::Fatal;
case DiagLevel::Information: if (pedanticArg && pedanticArg->isPresent()) {
break; const auto &values = pedanticArg->values();
default: if (values.empty() || values.front() == "error"sv || values.front() == "critical"sv) {
goto printDiagMsg; badExitLevel = DiagLevel::Critical;
} } else if (values.front() == "warning"sv) {
badExitLevel = DiagLevel::Warning;
} else if (values.front() == "info"sv) {
badExitLevel = minLevel = DiagLevel::Information;
} else {
badExitLevel = minLevel = DiagLevel::Debug;
} }
return;
} }
printDiagMsg: // set exit code if there are severe enough messages and check whether there's something to print
auto hasAnythingToPrint = false;
for (const auto &message : diag) {
if (message.level() >= badExitLevel) {
exitCode = EXIT_PARSING_FAILURE;
}
if (message.level() >= minLevel) {
hasAnythingToPrint = true;
}
if (exitCode != EXIT_SUCCESS && hasAnythingToPrint) {
break;
}
}
// print diag messages if there's anything to print
if (!hasAnythingToPrint) {
return;
}
if (head) { if (head) {
cerr << " - " << head << endl; cerr << " - " << head << endl;
} }
for (const auto &message : diag) { for (const auto &message : diag) {
if (message.level() < minLevel) {
continue;
}
switch (message.level()) { switch (message.level()) {
case DiagLevel::Debug: case DiagLevel::Debug:
if (!beVerbose) {
continue;
}
cerr << " Debug "; cerr << " Debug ";
break; break;
case DiagLevel::Information: case DiagLevel::Information:
if (!beVerbose) {
continue;
}
cerr << " Information "; cerr << " Information ";
break; break;
case DiagLevel::Warning: case DiagLevel::Warning:
@ -210,9 +228,6 @@ printDiagMsg:
setStyle(cerr, TextAttribute::Bold); setStyle(cerr, TextAttribute::Bold);
cerr << " Error "; cerr << " Error ";
setStyle(cerr, TextAttribute::Reset); setStyle(cerr, TextAttribute::Reset);
if (message.level() == DiagLevel::Fatal && exitCode == EXIT_SUCCESS) {
exitCode = EXIT_PARSING_FAILURE;
}
break; break;
default:; default:;
} }

View File

@ -258,7 +258,8 @@ constexpr bool isDigit(char c)
std::string incremented(const std::string &str, unsigned int toIncrement = 1); std::string incremented(const std::string &str, unsigned int toIncrement = 1);
void printDiagMessages(const TagParser::Diagnostics &diag, const char *head = nullptr, bool beVerbose = false); void printDiagMessages(
const TagParser::Diagnostics &diag, const char *head = nullptr, bool beVerbose = false, const CppUtilities::Argument *pedanticArg = nullptr);
void printProperty(const char *propName, std::string_view value, const char *suffix = nullptr, CppUtilities::Indentation indentation = 4); void printProperty(const char *propName, std::string_view value, const char *suffix = nullptr, CppUtilities::Indentation indentation = 4);
void printProperty(const char *propName, ElementPosition elementPosition, const char *suffix = nullptr, CppUtilities::Indentation indentation = 4); void printProperty(const char *propName, ElementPosition elementPosition, const char *suffix = nullptr, CppUtilities::Indentation indentation = 4);

View File

@ -167,7 +167,8 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg,
#endif #endif
} }
void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const Argument &verboseArg, const Argument &validateArg) void displayFileInfo(
const ArgumentOccurrence &, const Argument &filesArg, const Argument &verboseArg, const Argument &pedanticArg, const Argument &validateArg)
{ {
CMD_UTILS_START_CONSOLE; CMD_UTILS_START_CONSOLE;
@ -348,12 +349,13 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
exitCode = EXIT_IO_FAILURE; exitCode = EXIT_IO_FAILURE;
} }
printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent(), &pedanticArg);
cout << endl; cout << endl;
} }
} }
void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedArg, const Argument &filesArg, const Argument &verboseArg) void displayTagInfo(
const Argument &fieldsArg, const Argument &showUnsupportedArg, const Argument &filesArg, const Argument &verboseArg, const Argument &pedanticArg)
{ {
CMD_UTILS_START_CONSOLE; CMD_UTILS_START_CONSOLE;
@ -412,7 +414,7 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedAr
cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\"." << Phrases::EndFlush; cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\"." << Phrases::EndFlush;
exitCode = EXIT_IO_FAILURE; exitCode = EXIT_IO_FAILURE;
} }
printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent(), &pedanticArg);
cout << endl; cout << endl;
} }
} }
@ -952,7 +954,7 @@ void setTagInfo(const SetTagInfoArgs &args)
exitCode = EXIT_IO_FAILURE; exitCode = EXIT_IO_FAILURE;
} }
printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent()); printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent(), &args.pedanticArg);
// continue with next file // continue with next file
++fileIndex; ++fileIndex;

View File

@ -13,9 +13,10 @@ class Argument;
namespace Cli { namespace Cli {
struct SetTagInfoArgs { struct SetTagInfoArgs {
SetTagInfoArgs(CppUtilities::Argument &filesArg, CppUtilities::Argument &verboseArg); SetTagInfoArgs(CppUtilities::Argument &filesArg, CppUtilities::Argument &verboseArg, CppUtilities::Argument &pedanticArg);
CppUtilities::Argument &filesArg; CppUtilities::Argument &filesArg;
CppUtilities::Argument &verboseArg; CppUtilities::Argument &verboseArg;
CppUtilities::Argument &pedanticArg;
CppUtilities::ConfigValueArgument quietArg; CppUtilities::ConfigValueArgument quietArg;
CppUtilities::ConfigValueArgument docTitleArg; CppUtilities::ConfigValueArgument docTitleArg;
CppUtilities::ConfigValueArgument removeOtherFieldsArg; CppUtilities::ConfigValueArgument removeOtherFieldsArg;
@ -56,11 +57,11 @@ extern int exitCode;
void applyGeneralConfig(const CppUtilities::Argument &timeSapnFormatArg); void applyGeneralConfig(const CppUtilities::Argument &timeSapnFormatArg);
void printFieldNames(const CppUtilities::ArgumentOccurrence &occurrence); void printFieldNames(const CppUtilities::ArgumentOccurrence &occurrence);
void displayFileInfo(const CppUtilities::ArgumentOccurrence &, const CppUtilities::Argument &filesArg, const CppUtilities::Argument &verboseArg, void displayFileInfo(const CppUtilities::ArgumentOccurrence &, const CppUtilities::Argument &filesArg, const CppUtilities::Argument &verboseArg,
const CppUtilities::Argument &validateArg); const CppUtilities::Argument &pedanticArg, const CppUtilities::Argument &validateArg);
void generateFileInfo(const CppUtilities::ArgumentOccurrence &, const CppUtilities::Argument &inputFileArg, void generateFileInfo(const CppUtilities::ArgumentOccurrence &, const CppUtilities::Argument &inputFileArg,
const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &validateArg); const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &validateArg);
void displayTagInfo(const CppUtilities::Argument &fieldsArg, const CppUtilities::Argument &showUnsupportedArg, const CppUtilities::Argument &filesArg, void displayTagInfo(const CppUtilities::Argument &fieldsArg, const CppUtilities::Argument &showUnsupportedArg, const CppUtilities::Argument &filesArg,
const CppUtilities::Argument &verboseArg); const CppUtilities::Argument &verboseArg, const CppUtilities::Argument &pedanticArg);
void setTagInfo(const Cli::SetTagInfoArgs &args); void setTagInfo(const Cli::SetTagInfoArgs &args);
void extractField(const CppUtilities::Argument &fieldArg, const CppUtilities::Argument &attachmentArg, const CppUtilities::Argument &inputFilesArg, void extractField(const CppUtilities::Argument &fieldArg, const CppUtilities::Argument &attachmentArg, const CppUtilities::Argument &inputFilesArg,
const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &indexArg, const CppUtilities::Argument &verboseArg); const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &indexArg, const CppUtilities::Argument &verboseArg);