From b26b972e257b11619ec26e38a4a134360479c8ed Mon Sep 17 00:00:00 2001 From: Martchus Date: Sat, 21 May 2022 13:06:34 +0200 Subject: [PATCH] Return non-zero exit code in all fatal error cases See https://github.com/Martchus/tageditor/issues/83 --- application/main.cpp | 2 +- cli/helper.cpp | 5 +++ cli/mainfeatures.cpp | 79 ++++++++++++++++++++++++++++---------------- cli/mainfeatures.h | 4 +++ 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/application/main.cpp b/application/main.cpp index e5c1070..901f3c4 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -231,5 +231,5 @@ int main(int argc, char *argv[]) // invoke specified CLI operation via callbacks parser.invokeCallbacks(); } - return 0; + return Cli::exitCode; } diff --git a/cli/helper.cpp b/cli/helper.cpp index 04aa277..54456d0 100644 --- a/cli/helper.cpp +++ b/cli/helper.cpp @@ -1,5 +1,6 @@ #include "./helper.h" #include "./fieldmapping.h" +#include "./mainfeatures.h" #include #include @@ -196,11 +197,15 @@ printDiagMsg: setStyle(cout, TextAttribute::Reset); break; case DiagLevel::Critical: + case DiagLevel::Fatal: setStyle(cout, Color::Red, ColorContext::Foreground, TextAttribute::Bold); setStyle(cout, TextAttribute::Reset); setStyle(cout, TextAttribute::Bold); cout << " Error "; setStyle(cout, TextAttribute::Reset); + if (message.level() == DiagLevel::Fatal && exitCode == EXIT_SUCCESS) { + exitCode = EXIT_PARSING_FAILURE; + } break; default:; } diff --git a/cli/mainfeatures.cpp b/cli/mainfeatures.cpp index b403c08..d85a4da 100644 --- a/cli/mainfeatures.cpp +++ b/cli/mainfeatures.cpp @@ -51,6 +51,7 @@ #endif #include +#include #include #include #include @@ -99,6 +100,7 @@ namespace Cli { const char *const fieldNames = FIELD_NAMES; const char *const fieldNamesForSet = TAG_MODIFIER " " FIELD_NAMES " " TRACK_MODIFIER " " TRACK_ATTRIBUTE_NAMES " " TARGET_MODIFIER; +int exitCode = EXIT_SUCCESS; void printFieldNames(const ArgumentOccurrence &) { @@ -146,19 +148,23 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg, cerr << Phrases::Error << "An IO error occurred when writing the file \"" << outputFileArg.values().front() << "\": " << std::string_view(errorMessage.data(), static_cast(errorMessage.size())) << Phrases::EndFlush; + exitCode = EXIT_IO_FAILURE; } } catch (const TagParser::Failure &) { cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << inputFileArg.values().front() << "\"." << Phrases::EndFlush; + exitCode = EXIT_PARSING_FAILURE; } catch (const std::ios_base::failure &e) { cerr << Phrases::Error << "An IO error occurred when reading the file \"" << inputFileArg.values().front() << "\": " << e.what() << Phrases::EndFlush; + exitCode = EXIT_IO_FAILURE; } #else CPP_UTILITIES_UNUSED(inputFileArg); CPP_UTILITIES_UNUSED(outputFileArg); CPP_UTILITIES_UNUSED(validateArg); cerr << Phrases::Error << "Generating HTML info is only available if built with Qt support." << Phrases::EndFlush; + exitCode = EXIT_FAILURE; #endif } @@ -169,7 +175,7 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const // check whether files have been specified if (!filesArg.isPresent() || filesArg.values().empty()) { cerr << Phrases::Error << "No files have been specified." << Phrases::End; - exit(-1); + std::exit(EXIT_FAILURE); } MediaFileInfo fileInfo; @@ -337,8 +343,10 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const } catch (const TagParser::Failure &) { cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::EndFlush; + exitCode = EXIT_PARSING_FAILURE; } catch (const std::ios_base::failure &) { cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\"" << Phrases::EndFlush; + exitCode = EXIT_IO_FAILURE; } printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); @@ -352,8 +360,8 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedAr // check whether files have been specified if (!filesArg.isPresent() || filesArg.values().empty()) { - cerr << Phrases::Error << "No files have been specified." << Phrases::End; - exit(-1); + std::cerr << Phrases::Error << "No files have been specified." << Phrases::End; + std::exit(EXIT_FAILURE); } // parse specified fields @@ -400,8 +408,10 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &showUnsupportedAr } } catch (const TagParser::Failure &) { cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::EndFlush; + exitCode = EXIT_PARSING_FAILURE; } catch (const std::ios_base::failure &) { cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\"." << Phrases::EndFlush; + exitCode = EXIT_IO_FAILURE; } printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); cout << endl; @@ -466,12 +476,12 @@ void setTagInfo(const SetTagInfoArgs &args) // check whether files have been specified if (!args.filesArg.isPresent() || args.filesArg.values().empty()) { - cerr << Phrases::Error << "No files have been specified." << Phrases::EndFlush; - exit(-1); + std::cerr << Phrases::Error << "No files have been specified." << Phrases::EndFlush; + std::exit(EXIT_FAILURE); } if (args.outputFilesArg.isPresent() && args.outputFilesArg.values().size() != args.filesArg.values().size()) { - cerr << Phrases::Error << "The number of output files does not match the number of input files." << Phrases::EndFlush; - exit(-1); + std::cerr << Phrases::Error << "The number of output files does not match the number of input files." << Phrases::EndFlush; + std::exit(EXIT_FAILURE); } // get output files @@ -487,14 +497,14 @@ void setTagInfo(const SetTagInfoArgs &args) && (!args.docTitleArg.isPresent() || args.docTitleArg.values().empty()) && !args.id3v1UsageArg.isPresent() && !args.id3v2UsageArg.isPresent() && !args.id3v2VersionArg.isPresent()) { if (!args.layoutOnlyArg.isPresent()) { - cerr << Phrases::Error << "No fields/attachments have been specified." << Phrases::End - << "note: This is usually a mistake. Use --layout-only to prevent this error and apply file layout options only." << endl; - exit(-1); + std::cerr << Phrases::Error << "No fields/attachments have been specified." << Phrases::End + << "note: This is usually a mistake. Use --layout-only to prevent this error and apply file layout options only." << endl; + std::exit(EXIT_FAILURE); } } else if (args.layoutOnlyArg.isPresent()) { - cerr << Phrases::Error << "Fields/attachments and --layout-only have been specified." << Phrases::End - << "note: Don't use --layout-only if you actually want to alter tag information or attachments." << endl; - exit(-1); + std::cerr << Phrases::Error << "Fields/attachments and --layout-only have been specified." << Phrases::End + << "note: Don't use --layout-only if you actually want to alter tag information or attachments." << endl; + std::exit(EXIT_FAILURE); } auto settings = TagCreationSettings(); @@ -506,8 +516,8 @@ void setTagInfo(const SetTagInfoArgs &args) auto &target = targetsToRemove.emplace_back(); for (const auto &targetDenotation : args.removeTargetArg.values(i)) { if (!applyTargetConfiguration(target, targetDenotation)) { - cerr << Phrases::Error << "The given target specification \"" << targetDenotation << "\" is invalid." << Phrases::EndFlush; - exit(-1); + std::cerr << Phrases::Error << "The given target specification \"" << targetDenotation << "\" is invalid." << Phrases::EndFlush; + std::exit(EXIT_FAILURE); } } } @@ -521,9 +531,9 @@ void setTagInfo(const SetTagInfoArgs &args) } } catch (const ConversionException &) { settings.id3v2MajorVersion = 3; - cerr << Phrases::Error << "The specified ID3v2 version \"" << args.id3v2VersionArg.values().front() << "\" is invalid." << Phrases::End - << "note: Valid versions are 1, 2, 3 and 4." << endl; - exit(-1); + std::cerr << Phrases::Error << "The specified ID3v2 version \"" << args.id3v2VersionArg.values().front() << "\" is invalid." + << Phrases::End << "note: Valid versions are 1, 2, 3 and 4." << endl; + std::exit(EXIT_FAILURE); } } @@ -689,8 +699,7 @@ void setTagInfo(const SetTagInfoArgs &args) const FieldScope &denotedScope = fieldDenotation.first; // skip values which scope does not match the current tag if (denotedScope.isTrack() || !(denotedScope.tagType == TagType::Unspecified || (denotedScope.tagType & tagType)) - || !(!targetSupported - || (tagType == TagType::OggVorbisComment && denotedScope.tagTarget.isEmpty()) + || !(!targetSupported || (tagType == TagType::OggVorbisComment && denotedScope.tagTarget.isEmpty()) || (denotedScope.exactTargetMatching ? denotedScope.tagTarget == tagTarget : denotedScope.tagTarget.matches(tagTarget)))) { continue; @@ -921,6 +930,7 @@ void setTagInfo(const SetTagInfoArgs &args) } catch (const TagParser::Failure &) { finalizeLog(); cerr << " - " << Phrases::Error << "Failed to apply changes." << Phrases::EndFlush; + exitCode = EXIT_PARSING_FAILURE; } if (args.preserveModificationTimeArg.isPresent()) { if (!modificationDateError) { @@ -933,10 +943,12 @@ void setTagInfo(const SetTagInfoArgs &args) } catch (const TagParser::Failure &) { finalizeLog(); cerr << " - " << Phrases::Error << "A parsing failure occurred when reading/writing the file \"" << file << "\"." << Phrases::EndFlush; + exitCode = EXIT_PARSING_FAILURE; } catch (const std::ios_base::failure &e) { finalizeLog(); cerr << " - " << Phrases::Error << "An IO error occurred when reading/writing the file \"" << file << "\": " << e.what() << Phrases::EndFlush; + exitCode = EXIT_IO_FAILURE; } printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent()); @@ -962,12 +974,12 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const } if (((fieldDenotations.size() != 1) || (!attachmentInfo.hasId && !attachmentInfo.name)) && ((fieldDenotations.size() == 1) && (attachmentInfo.hasId || attachmentInfo.name))) { - cerr << Phrases::Error << "Exactly one field or attachment needs to be specified." << Phrases::EndFlush; - exit(-1); + std::cerr << Phrases::Error << "Exactly one field or attachment needs to be specified." << Phrases::EndFlush; + std::exit(EXIT_FAILURE); } if (!inputFilesArg.isPresent() || inputFilesArg.values().empty()) { - cerr << Phrases::Error << "No files have been specified." << Phrases::EndFlush; - exit(-1); + std::cerr << Phrases::Error << "No files have been specified." << Phrases::EndFlush; + std::exit(EXIT_FAILURE); } static constexpr auto noIndex = std::numeric_limits::max(); @@ -978,8 +990,8 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const throw ConversionException(); } } catch (const ConversionException &) { - cerr << Phrases::Error << "Specified index is no valid unsigned integer." << Phrases::EndFlush; - exit(-1); + std::cerr << Phrases::Error << "Specified index is no valid unsigned integer." << Phrases::EndFlush; + std::exit(EXIT_FAILURE); } } @@ -1045,8 +1057,10 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const } } catch (const TagParser::Failure &) { cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::End; + exitCode = EXIT_PARSING_FAILURE; } catch (const std::ios_base::failure &e) { cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\": " << e.what() << Phrases::End; + exitCode = EXIT_IO_FAILURE; } } @@ -1054,9 +1068,11 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const if (!fieldDenotations.empty()) { if (values.empty()) { cerr << Phrases::Error << "None of the specified files has a (supported) " << fieldArg.values().front() << " field." << Phrases::End; + exitCode = exitCode != EXIT_SUCCESS ? exitCode : EXIT_FAILURE; } else if (index != noIndex && index >= values.size()) { cerr << Phrases::Error << "The specified index is out of range as the specified files/fields have only " << values.size() << " values." << Phrases::End; + exitCode = exitCode != EXIT_SUCCESS ? exitCode : EXIT_FAILURE; } else if (outputFileArg.isPresent()) { if (index != noIndex) { if (index) { @@ -1081,6 +1097,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const cout << "Value has been saved to \"" << path << "\"." << endl; } catch (const std::ios_base::failure &e) { cerr << Phrases::Error << "An IO error occurred when writing the file \"" << path << "\": " << e.what() << Phrases::End; + exitCode = exitCode != EXIT_SUCCESS ? exitCode : EXIT_IO_FAILURE; } } } else { @@ -1092,9 +1109,11 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const } else { if (attachments.empty()) { cerr << Phrases::Error << "None of the specified files has a (supported) attachment with the specified ID/name." << Phrases::End; + exitCode = exitCode != EXIT_SUCCESS ? exitCode : EXIT_FAILURE; } else if (index != noIndex && index >= values.size()) { cerr << Phrases::Error << "The specified index is out of range as the specified files have only " << attachments.size() << " attachments." << Phrases::End; + exitCode = exitCode != EXIT_SUCCESS ? exitCode : EXIT_FAILURE; } else if (outputFileArg.isPresent()) { if (index != noIndex) { if (index) { @@ -1119,6 +1138,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const cout << "Value has been saved to \"" << path << "\"." << endl; } catch (const std::ios_base::failure &e) { cerr << Phrases::Error << "An IO error occurred when writing the file \"" << path << "\": " << e.what() << Phrases::EndFlush; + exitCode = exitCode != EXIT_SUCCESS ? exitCode : EXIT_IO_FAILURE; } } } else { @@ -1138,8 +1158,8 @@ void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Ar #ifdef TAGEDITOR_JSON_EXPORT // check whether files have been specified if (!filesArg.isPresent() || filesArg.values().empty()) { - cerr << Phrases::Error << "No files have been specified." << Phrases::End; - exit(-1); + std::cerr << Phrases::Error << "No files have been specified." << Phrases::End; + std::exit(EXIT_FAILURE); } RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kArrayType); @@ -1160,8 +1180,10 @@ void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Ar jsonData.emplace_back(fileInfo, document.GetAllocator()); } catch (const TagParser::Failure &) { cerr << Phrases::Error << "A parsing failure occurred when reading the file \"" << file << "\"." << Phrases::EndFlush; + exitCode = EXIT_PARSING_FAILURE; } catch (const std::ios_base::failure &e) { cerr << Phrases::Error << "An IO error occurred when reading the file \"" << file << "\": " << e.what() << Phrases::EndFlush; + exitCode = EXIT_IO_FAILURE; } } @@ -1183,6 +1205,7 @@ void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Ar CPP_UTILITIES_UNUSED(filesArg); CPP_UTILITIES_UNUSED(prettyArg); cerr << Phrases::Error << "JSON export has not been enabled when building the tag editor." << Phrases::EndFlush; + exitCode = EXIT_FAILURE; #endif } diff --git a/cli/mainfeatures.h b/cli/mainfeatures.h index 5debcf4..8656cfd 100644 --- a/cli/mainfeatures.h +++ b/cli/mainfeatures.h @@ -3,6 +3,9 @@ #include +#define EXIT_IO_FAILURE (EXIT_FAILURE + 1) +#define EXIT_PARSING_FAILURE (EXIT_FAILURE + 2) + namespace CppUtilities { class Argument; } @@ -49,6 +52,7 @@ struct SetTagInfoArgs { extern const char *const fieldNames; extern const char *const fieldNamesForSet; +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,