diff --git a/application/main.cpp b/application/main.cpp index 901f3c4..bb679f3 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -29,9 +29,10 @@ using namespace QtUtilities; namespace Cli { -SetTagInfoArgs::SetTagInfoArgs(Argument &filesArg, Argument &verboseArg) +SetTagInfoArgs::SetTagInfoArgs(Argument &filesArg, Argument &verboseArg, Argument &pedanticArg) : filesArg(filesArg) , verboseArg(verboseArg) + , pedanticArg(pedanticArg) , quietArg("quiet", 'q', "suppress printing progress information") , 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" }) @@ -152,6 +153,13 @@ int main(int argc, char *argv[]) timeSpanFormatArg.setPreDefinedCompletionValues("measures colons seconds"); // verbose option 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 ConfigValueArgument fileArg("file", 'f', "specifies the path of the file to be opened", { "path" }); ConfigValueArgument defaultFileArg(fileArg); @@ -167,8 +175,9 @@ int main(int argc, char *argv[]) ConfigValueArgument validateArg( "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"); - displayFileInfoArg.setCallback(std::bind(Cli::displayFileInfo, _1, std::cref(filesArg), std::cref(verboseArg), std::cref(validateArg))); - displayFileInfoArg.setSubArguments({ &filesArg, &validateArg, &verboseArg }); + displayFileInfoArg.setCallback( + 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 ConfigValueArgument fieldsArg("fields", 'n', "specifies the field names to be displayed", { "title", "album", "artist", "trackpos" }); 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)", 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)"); - displayTagInfoArg.setCallback( - std::bind(Cli::displayTagInfo, std::cref(fieldsArg), std::cref(showUnsupportedArg), std::cref(filesArg), std::cref(verboseArg))); - displayTagInfoArg.setSubArguments({ &fieldsArg, &showUnsupportedArg, &filesArg, &verboseArg }); + displayTagInfoArg.setCallback(std::bind(Cli::displayTagInfo, std::cref(fieldsArg), std::cref(showUnsupportedArg), std::cref(filesArg), + std::cref(verboseArg), std::cref(pedanticArg))); + displayTagInfoArg.setSubArguments({ &fieldsArg, &showUnsupportedArg, &filesArg, &verboseArg, &pedanticArg }); // set tag info - Cli::SetTagInfoArgs setTagInfoArgs(filesArg, verboseArg); + Cli::SetTagInfoArgs setTagInfoArgs(filesArg, verboseArg, pedanticArg); // extract cover ConfigValueArgument fieldArg("field", 'n', "specifies the field to be extracted", { "field name" }); fieldArg.setImplicit(true); diff --git a/cli/helper.cpp b/cli/helper.cpp index 6b41aee..de559e2 100644 --- a/cli/helper.cpp +++ b/cli/helper.cpp @@ -160,40 +160,58 @@ string incremented(const string &str, unsigned int toIncrement) 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()) { return; } - if (!beVerbose) { - for (const auto &message : diag) { - switch (message.level()) { - case DiagLevel::Debug: - case DiagLevel::Information: - break; - default: - goto printDiagMsg; - } + + // set exit code to failure if there are diag messages considered bad enough + auto minLevel = beVerbose ? DiagLevel::Information : DiagLevel::Warning; + auto badExitLevel = DiagLevel::Fatal; + if (pedanticArg && pedanticArg->isPresent()) { + const auto &values = pedanticArg->values(); + if (values.empty() || values.front() == "error"sv || values.front() == "critical"sv) { + 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) { cerr << " - " << head << endl; } for (const auto &message : diag) { + if (message.level() < minLevel) { + continue; + } switch (message.level()) { case DiagLevel::Debug: - if (!beVerbose) { - continue; - } cerr << " Debug "; break; case DiagLevel::Information: - if (!beVerbose) { - continue; - } cerr << " Information "; break; case DiagLevel::Warning: @@ -210,9 +228,6 @@ printDiagMsg: setStyle(cerr, TextAttribute::Bold); cerr << " Error "; setStyle(cerr, TextAttribute::Reset); - if (message.level() == DiagLevel::Fatal && exitCode == EXIT_SUCCESS) { - exitCode = EXIT_PARSING_FAILURE; - } break; default:; } diff --git a/cli/helper.h b/cli/helper.h index 082db0a..f391b1d 100644 --- a/cli/helper.h +++ b/cli/helper.h @@ -258,7 +258,8 @@ constexpr bool isDigit(char c) 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, ElementPosition elementPosition, const char *suffix = nullptr, CppUtilities::Indentation indentation = 4); diff --git a/cli/mainfeatures.cpp b/cli/mainfeatures.cpp index 55238e8..2973256 100644 --- a/cli/mainfeatures.cpp +++ b/cli/mainfeatures.cpp @@ -167,7 +167,8 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg, #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; @@ -348,12 +349,13 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const exitCode = EXIT_IO_FAILURE; } - printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent(), &pedanticArg); 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; @@ -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; exitCode = EXIT_IO_FAILURE; } - printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent(), &pedanticArg); cout << endl; } } @@ -952,7 +954,7 @@ void setTagInfo(const SetTagInfoArgs &args) exitCode = EXIT_IO_FAILURE; } - printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent(), &args.pedanticArg); // continue with next file ++fileIndex; diff --git a/cli/mainfeatures.h b/cli/mainfeatures.h index 8656cfd..7080796 100644 --- a/cli/mainfeatures.h +++ b/cli/mainfeatures.h @@ -13,9 +13,10 @@ class Argument; namespace Cli { struct SetTagInfoArgs { - SetTagInfoArgs(CppUtilities::Argument &filesArg, CppUtilities::Argument &verboseArg); + SetTagInfoArgs(CppUtilities::Argument &filesArg, CppUtilities::Argument &verboseArg, CppUtilities::Argument &pedanticArg); CppUtilities::Argument &filesArg; CppUtilities::Argument &verboseArg; + CppUtilities::Argument &pedanticArg; CppUtilities::ConfigValueArgument quietArg; CppUtilities::ConfigValueArgument docTitleArg; CppUtilities::ConfigValueArgument removeOtherFieldsArg; @@ -56,11 +57,11 @@ extern int exitCode; void applyGeneralConfig(const CppUtilities::Argument &timeSapnFormatArg); void printFieldNames(const CppUtilities::ArgumentOccurrence &occurrence); 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, const CppUtilities::Argument &outputFileArg, const CppUtilities::Argument &validateArg); 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 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);