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 {
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);

View File

@ -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:;
}

View File

@ -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);

View File

@ -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;

View File

@ -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);