diff --git a/include/GenericToolbox.Json.h b/include/GenericToolbox.Json.h index 44b8f77..f9f93d2 100644 --- a/include/GenericToolbox.Json.h +++ b/include/GenericToolbox.Json.h @@ -30,7 +30,7 @@ namespace GenericToolbox { typedef nlohmann::ordered_json JsonType; // Objects configured by a JSON file can inherit from this - typedef GenericToolbox::ConfigClass ConfigBaseClass; + typedef ConfigClass ConfigBaseClass; // IO inline JsonType readConfigJsonStr(const std::string& configJsonStr_); @@ -41,10 +41,12 @@ namespace GenericToolbox { inline std::string toReadableString(const JsonType& config_); // reading + inline JsonType cd(const JsonType& json_, const std::string& keyPath_); inline std::vector ls(const JsonType& jsonConfig_); inline bool doKeyExist(const JsonType& jsonConfig_, const std::string& keyPath_); inline std::string buildFormula(const JsonType& jsonConfig_, const std::string& keyPath_, const std::string& joinStr_); inline std::string buildFormula(const JsonType& jsonConfig_, const std::string& keyPath_, const std::string& joinStr_, const std::string& defaultFormula_); + inline void fillFormula(const JsonType& jsonConfig_, std::string& formulaToFill_, const std::string& keyPath_, const std::string& joinStr_); // reading -- templates template inline auto get(const JsonType& json_) -> T; @@ -97,7 +99,7 @@ namespace GenericToolbox { return output; } inline JsonType readConfigFile(const std::string& configFilePath_){ - if( not GenericToolbox::isFile(configFilePath_) ){ + if( not isFile(configFilePath_) ){ std::cout << "\"" << configFilePath_ << "\" could not be found." << std::endl; throw std::runtime_error("file not found."); } @@ -125,14 +127,14 @@ namespace GenericToolbox { while( config_.is_string() ){ std::cout << "Forwarding " << (className_.empty()? "": className_ + " ") << "config: \"" << config_.template get() << "\"" << std::endl; auto name = config_.template get(); - std::string expand = GenericToolbox::expandEnvironmentVariables(name); + std::string expand = expandEnvironmentVariables(name); config_ = readConfigFile(expand); } } inline void unfoldConfig(JsonType& config_){ for( auto& entry : config_ ){ if( entry.is_string() and ( - GenericToolbox::endsWith(entry.template get(), ".json", true) + endsWith(entry.template get(), ".json", true) ) ){ forwardConfig(entry); unfoldConfig(config_); // remake the loop on the unfolder config @@ -158,19 +160,19 @@ namespace GenericToolbox { if( not inQuote ){ if( c == '{' or c == '[' ){ - ss << std::endl << GenericToolbox::repeatString(" ", indentLevel) << c; + ss << std::endl << repeatString(" ", indentLevel) << c; indentLevel++; - ss << std::endl << GenericToolbox::repeatString(" ", indentLevel); + ss << std::endl << repeatString(" ", indentLevel); } else if( c == '}' or c == ']' ){ indentLevel--; - ss << std::endl << GenericToolbox::repeatString(" ", indentLevel) << c; + ss << std::endl << repeatString(" ", indentLevel) << c; } else if( c == ':' ){ ss << c << " "; } else if( c == ',' ){ - ss << c << std::endl << GenericToolbox::repeatString(" ", indentLevel); + ss << c << std::endl << repeatString(" ", indentLevel); } else if( c == '\n' ){ if( ss.str().back() != '\n' ) ss << c; @@ -199,6 +201,11 @@ namespace GenericToolbox { return false; } } + inline JsonType cd(const JsonType& json_, const std::string& keyPath_){ + JsonType out{}; + fillValue(json_, out, keyPath_); + return out; + } inline std::vector ls(const JsonType& jsonConfig_){ std::vector out{}; out.reserve( jsonConfig_.size() ); @@ -237,7 +244,7 @@ namespace GenericToolbox { } out += "("; - out += GenericToolbox::joinVectorString(conditionsList, ") " + joinStr_ + " ("); + out += joinVectorString(conditionsList, ") " + joinStr_ + " ("); out += ")"; return out; @@ -246,6 +253,9 @@ namespace GenericToolbox { if( not doKeyExist(jsonConfig_, keyPath_) ) return defaultFormula_; else return buildFormula(jsonConfig_, keyPath_, joinStr_); } + inline void fillFormula(const JsonType& jsonConfig_, std::string& formulaToFill_, const std::string& keyPath_, const std::string& joinStr_){ + formulaToFill_ = buildFormula(jsonConfig_, keyPath_, joinStr_, formulaToFill_); + } // templates template inline auto get(const JsonType& json_) -> T { @@ -266,7 +276,7 @@ namespace GenericToolbox { } template inline auto fetchValue(const JsonType& jsonConfig_, const std::string& keyPath_) -> T{ // always treats as a key path - std::vector keyPathElements{GenericToolbox::splitString(keyPath_, "/")}; + std::vector keyPathElements{splitString(keyPath_, "/")}; JsonType walkConfig(jsonConfig_); for( auto& keyName : keyPathElements ){ auto entry = walkConfig.find(keyName); @@ -285,7 +295,7 @@ namespace GenericToolbox { try{ return fetchValue(jsonConfig_, keyPath); } catch(...){} // try the next ones } - throw std::runtime_error("Could not find any json entry: " + GenericToolbox::toString(keyPathList_) + ":\n" + toReadableString(jsonConfig_)); + throw std::runtime_error("Could not find any json entry: " + toString(keyPathList_) + ":\n" + toReadableString(jsonConfig_)); } template inline auto fetchValue(const JsonType& jsonConfig_, const std::string& keyPath_, const T& defaultValue_) -> T{ try{ return fetchValue(jsonConfig_, keyPath_); } @@ -348,21 +358,22 @@ namespace GenericToolbox { // specific keys like "name" might help reference the lists std::vector listOfIdentifiers{{"name"}, {"__INDEX__"}}; + std::vector jsonPath{}; std::function overrideRecursive = [&](JsonType& outEntry_, const JsonType& overrideEntry_){ - if(debug){ std::cout << GET_VAR_NAME_VALUE(GenericToolbox::joinPath(jsonPath)) << std::endl; } + GTLogDebugIf(debug) << GET_VAR_NAME_VALUE(joinPath(jsonPath)) << std::endl; if( overrideEntry_.is_array() ){ // entry is list if( not outEntry_.is_array() ){ - std::cerr << GenericToolbox::joinPath( jsonPath ) << " is not an array: " << std::endl << outEntry_ << std::endl << std::endl << overrideEntry_; + std::cerr << joinPath( jsonPath ) << " is not an array: " << std::endl << outEntry_ << std::endl << std::endl << overrideEntry_; throw std::runtime_error("not outEntry_.is_array()"); } // is it empty? -> erase if( overrideEntry_.empty() ){ - ss << "Overriding list: " << GenericToolbox::joinPath(jsonPath) << std::endl; + ss << "Overriding list: " << joinPath(jsonPath) << std::endl; outEntry_ = overrideEntry_; return; } @@ -371,7 +382,7 @@ namespace GenericToolbox { bool isStructured{false}; for( auto& outListEntry : outEntry_.items() ){ if( outListEntry.value().is_structured() ){ isStructured = true; break; } } if( not isStructured ){ - ss << "Overriding list: " << GenericToolbox::joinPath(jsonPath) << std::endl; + ss << "Overriding list: " << joinPath(jsonPath) << std::endl; outEntry_ = overrideEntry_; return; } @@ -382,39 +393,38 @@ namespace GenericToolbox { // fetch identifier if available using override std::string identifier{}; for( auto& identifierCandidate : listOfIdentifiers ){ - if( GenericToolbox::Json::doKeyExist( overrideListEntry.value(), identifierCandidate ) ){ + if( doKeyExist( overrideListEntry.value(), identifierCandidate ) ){ identifier = identifierCandidate; } } if( not identifier.empty() ){ - // will i - if(debug) std::cout << "Will identify override list item with key \"" << identifier << "\" = " << overrideListEntry.value()[identifier] << std::endl; + if ( identifier == "__INDEX__" ){ + // make sure we don't write override identifiers + JsonType override(overrideListEntry.value()); + override.erase("__INDEX__"); - JsonType* outListEntryMatch{nullptr}; - - if( identifier == "__INDEX__" ){ if ( overrideListEntry.value()[identifier].is_string() and overrideListEntry.value()[identifier].get() == "*" ){ // applying on every entry int index{0}; for( auto& outSubEntry : outEntry_ ){ jsonPath.emplace_back(std::to_string(index++)); - overrideRecursive(outSubEntry, overrideListEntry.value()); + overrideRecursive(outSubEntry, override); jsonPath.pop_back(); } } else if( overrideListEntry.value()[identifier].get() == -1 ){ // add entry if( allowAddMissingKey ){ - ss << "Adding: " << GenericToolbox::joinPath(jsonPath, outEntry_.size()); - if( overrideListEntry.value().is_primitive() ){ ss << " -> " << overrideListEntry.value(); } + ss << "Added: " << joinPath(jsonPath, outEntry_.size()); + if( overrideListEntry.value().is_primitive() ){ ss << " -> " << override; } ss << std::endl; - outEntry_.emplace_back(overrideListEntry.value()); + outEntry_.emplace_back(override); } } else if( overrideListEntry.value()[identifier].get() < outEntry_.size() ){ jsonPath.emplace_back( overrideListEntry.key() ); - overrideRecursive( outEntry_[overrideListEntry.value()[identifier].get()], overrideListEntry.value() ); + overrideRecursive( outEntry_[overrideListEntry.value()[identifier].get()], override ); jsonPath.pop_back(); } else{ @@ -423,64 +433,84 @@ namespace GenericToolbox { } } else{ + // "name" identifier + bool matchingFound{false}; + + std::vector availableNameList{}; + + // looping over all list options for( auto& outListEntry : outEntry_ ){ - if( GenericToolbox::Json::doKeyExist( outListEntry, identifier ) - and outListEntry[identifier] == overrideListEntry.value()[identifier] ){ - outListEntryMatch = &outListEntry; - break; + // does this entry has a "name" + if( not doKeyExist( outListEntry, "name" ) ){ continue; } + + availableNameList.emplace_back(outListEntry["name"]); + + if( outListEntry["name"] == overrideListEntry.value()["name"] + or isMatching(outListEntry["name"], overrideListEntry.value()["name"]) ) { + matchingFound = true; + + GTLogDebugIf(debug) << "Found matching " << outListEntry["name"] << " with " << overrideListEntry.value()["name"] << std::endl; + + // make sure we don't write override identifiers + JsonType override(overrideListEntry.value()); + if(not override.is_array()){ override.erase("name"); } + + jsonPath.emplace_back(joinAsString("",overrideListEntry.key(),"(name:",outListEntry["name"],")")); + overrideRecursive(outListEntry, override); + jsonPath.pop_back(); } } - if( outListEntryMatch == nullptr ){ - if( allowAddMissingKey ) { - ss << "Adding: " << GenericToolbox::joinPath(jsonPath, outEntry_.size()) << "(" << identifier - << ":" << overrideListEntry.value()[identifier] << ")" << std::endl; - outEntry_.emplace_back(overrideListEntry.value()); - } - continue; + if( not matchingFound and allowAddMissingKey ){ + GTLogDebugIf(debug) << "Did NOT find any matching name entry matching " << overrideListEntry.value()["name"] + // << " among: " << GenericToolbox::toString(availableNameList) + << std::endl; + + ss << "Added: " + << joinPath(jsonPath, outEntry_.size()) + << "(name:" + << overrideListEntry.value()["name"] << ")" << std::endl; + outEntry_.emplace_back(overrideListEntry.value()); } - jsonPath.emplace_back(GenericToolbox::joinAsString("",overrideListEntry.key(),"(",identifier,":",overrideListEntry.value()[identifier],")")); - overrideRecursive(*outListEntryMatch, overrideListEntry.value()); - jsonPath.pop_back(); } } else{ - ss << "No identifier found for list def in " << GenericToolbox::joinPath(jsonPath) << std::endl; + ss << "No identifier found for list def in " << joinPath(jsonPath) << std::endl; continue; } } } else{ - if(debug) std::cout << "Not array: " << overrideEntry_.empty() << std::endl; + GTLogDebugIf(debug) << "Not array: " << overrideEntry_.empty() << std::endl; if( overrideEntry_.empty() ){ - ss << "Removing entry: " << GenericToolbox::joinPath(jsonPath) << std::endl; + ss << "Removing entry: " << joinPath(jsonPath) << std::endl; outEntry_ = overrideEntry_; return; } // entry is dictionary for( auto& overrideEntry : overrideEntry_.items() ){ - if(debug) std::cout << GET_VAR_NAME_VALUE(overrideEntry.key()) << std::endl; + GTLogDebugIf(debug) << GET_VAR_NAME_VALUE(overrideEntry.key()) << std::endl; // addition mode: - if( not GenericToolbox::Json::doKeyExist(outEntry_, overrideEntry.key()) ){ + if( not doKeyExist(outEntry_, overrideEntry.key()) ){ if( overrideEntry.key() != "__INDEX__" ){ if( allowAddMissingKey ){ - ss << "Adding: " << GenericToolbox::joinPath(jsonPath, overrideEntry.key()); + ss << "Added: " << joinPath(jsonPath, overrideEntry.key()); if( overrideEntry.value().is_primitive() ){ ss << " -> " << overrideEntry.value(); } ss << std::endl; outEntry_[overrideEntry.key()] = overrideEntry.value(); } else{ std::cerr << "Could not edit missing key \"" - << GenericToolbox::joinPath(jsonPath, overrideEntry.key()) << "\" (" + << joinPath(jsonPath, overrideEntry.key()) << "\" (" << GET_VAR_NAME_VALUE(allowAddMissingKey) << ")" << std::endl; throw std::runtime_error("Could not edit missing key."); } } else{ - if(debug) std::cout << "skipping __INDEX__ entry" << std::endl; + GTLogDebugIf(debug) << "skipping __INDEX__ entry" << std::endl; } continue; } @@ -489,7 +519,7 @@ namespace GenericToolbox { auto& outSubEntry = outEntry_[overrideEntry.key()]; if( overrideEntry.value().is_structured() ){ - if(debug) std::cout << "Is structured... going recursive..." << std::endl; + GTLogDebugIf(debug) << "Is structured... going recursive..." << std::endl; // recursive candidate jsonPath.emplace_back(overrideEntry.key()); overrideRecursive(outSubEntry, overrideEntry.value()); @@ -497,8 +527,8 @@ namespace GenericToolbox { } else{ // override - if( not GenericToolbox::isIn(overrideEntry.key(), listOfIdentifiers) ){ - ss << "Overriding: " << GenericToolbox::joinPath(jsonPath, overrideEntry.key()) << ": " + if( not isIn(overrideEntry.key(), listOfIdentifiers) ){ + ss << "Override: " << joinPath(jsonPath, overrideEntry.key()) << ": " << outSubEntry << " -> " << overrideEntry.value() << std::endl; } outSubEntry = overrideEntry.value(); @@ -515,11 +545,11 @@ namespace GenericToolbox { } inline void clearEntry(JsonType& jsonConfig_, const std::string& path_){ - auto pathEntries{ GenericToolbox::splitString(path_, "/") }; + auto pathEntries{ splitString(path_, "/") }; auto* configEntry{&jsonConfig_}; for( auto& pathEntry : pathEntries ){ - if( GenericToolbox::Json::doKeyExist( *configEntry, pathEntry ) ){ + if( doKeyExist( *configEntry, pathEntry ) ){ // next configEntry = &( configEntry->find(pathEntry).value() ); } diff --git a/include/GenericToolbox.Log.h b/include/GenericToolbox.Log.h index 1fef587..833378b 100644 --- a/include/GenericToolbox.Log.h +++ b/include/GenericToolbox.Log.h @@ -32,11 +32,16 @@ namespace GenericToolbox{ return ss.str(); } + inline std::string getFctName(const std::string &func_){ + if(func_ == "operator()"){ return ""; } + return func_; + } + } } // internals -#define GTLogBase std::string(__FILENAME__) + ":" + std::to_string(__LINE__) + "/" + __func__ + ": " +#define GTLogBase std::string(__FILENAME__) + ":" + std::to_string(__LINE__) + "/" + GenericToolbox::LoggerUtils::getFctName(__func__) + ": " // basics #define GTLogError std::cout << GenericToolbox::LoggerUtils::getTimeStr() << " ERROR " << GTLogBase @@ -46,6 +51,14 @@ namespace GenericToolbox{ #define GTLogDebug std::cout << GenericToolbox::LoggerUtils::getTimeStr() << " DEBUG " << GTLogBase #define GTLogTrace std::cout << GenericToolbox::LoggerUtils::getTimeStr() << " TRACE " << GTLogBase +// conditionals +#define GTLogErrorIf(enable_) if(enable_) GTLogError +#define GTLogAlertIf(enable_) if(enable_) GTLogAlert +#define GTLogWarningIf(enable_) if(enable_) GTLogWarning +#define GTLogInfoIf(enable_) if(enable_) GTLogInfo +#define GTLogDebugIf(enable_) if(enable_) GTLogDebug +#define GTLogTraceIf(enable_) if(enable_) GTLogTrace + // throw #define GTLogThrow(message_) GTLogError << message_ << std::endl; throw std::runtime_error("exception thrown by the logger.") #define GTLogThrowIf(condition_, message_) if(condition_){ GTLogThrow( "[" << #condition_ << "] " << message_); } diff --git a/include/GenericToolbox.Root.h b/include/GenericToolbox.Root.h index 10e90c3..5cc348b 100644 --- a/include/GenericToolbox.Root.h +++ b/include/GenericToolbox.Root.h @@ -41,6 +41,7 @@ #include #include #include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" @@ -264,13 +265,14 @@ namespace GenericToolbox{ class [[maybe_unused]] TFilePath { public: - TFilePath(TDirectory* rootDir_, std::string subDirPath_): rootDir(rootDir_), subDirPath(subDirPath_) {} - TFilePath getSubDir( const std::string& subDir_ ) const { + explicit TFilePath(TDirectory* rootDir_): rootDir(rootDir_) {} + TFilePath(TDirectory* rootDir_, std::string subDirPath_): rootDir(rootDir_), subDirPath(std::move(subDirPath_)) {} + [[nodiscard]] TFilePath getSubDir( const std::string& subDir_ ) const { TFilePath out(*this); out.subDirPath = GenericToolbox::joinPath(this->subDirPath, subDir_); return out; } - TDirectory* getDir() const { return GenericToolbox::mkdirTFile(rootDir, subDirPath); } + [[nodiscard]] TDirectory* getDir() const { return GenericToolbox::mkdirTFile(rootDir, subDirPath); } private: TDirectory* rootDir{nullptr}; @@ -396,6 +398,7 @@ namespace GenericToolbox { return correlationMatrix; } template inline T* toCorrelationMatrix(const T* matrix_){ + if( matrix_ == nullptr ){ return nullptr;} auto* out = (T*) matrix_->Clone(); for(int iRow = 0 ; iRow < matrix_->GetNrows() ; iRow++){ @@ -813,6 +816,7 @@ namespace GenericToolbox { writeInTFile(dir_, TNamed(saveName_, objToSave_.c_str()), std::move(saveName_), forceWriteFile_); } inline void triggerTFileWrite(TDirectory* dir_){ + if( dir_ == nullptr ) return; if( dir_->GetFile() != nullptr ) dir_->GetFile()->Write(); } diff --git a/include/GenericToolbox.String.h b/include/GenericToolbox.String.h index ee1c993..054dddd 100644 --- a/include/GenericToolbox.String.h +++ b/include/GenericToolbox.String.h @@ -98,6 +98,7 @@ namespace GenericToolbox{ static bool hasSubStr(const std::string& str_, const std::string& subStr_, bool ignoreCase_ = false); static bool startsWith(const std::string& str_, const std::string& subStr_, bool ignoreCase_ = false); static bool endsWith(const std::string& str_, const std::string& subStr_, bool ignoreCase_ = false); + static bool isMatching(const std::string& str_, const std::string& pattern_); // -- Conversion Tools template static std::string toString(const T& iterable_, const TT& toStrFct_, bool jumpLine_=true, bool indentLine_=true); @@ -186,6 +187,11 @@ namespace GenericToolbox { } return std::equal( subStr_.begin(), subStr_.end(), str_.end() - long(subStr_.size()) ); } + static bool isMatching(const std::string& str_, const std::string& pattern_){ + // Convert '*' wildcard into regex equivalent ".*" + std::regex pattern( std::regex_replace(pattern_, std::regex("\\*"), ".*") ); + return std::regex_match(str_, pattern); + } // -- Aesthetic static std::string addUpDownBars(const std::string& str_, bool stripUnicode_){ diff --git a/include/GenericToolbox.Time.h b/include/GenericToolbox.Time.h index 4c42259..4349874 100644 --- a/include/GenericToolbox.Time.h +++ b/include/GenericToolbox.Time.h @@ -33,6 +33,7 @@ namespace GenericToolbox{ static long long getElapsedTimeSinceLastCallInMicroSeconds(const std::string& key_); static long long getElapsedTimeSinceLastCallInMicroSeconds(int instance = -1); static std::string getNowDateString(const std::string& dateFormat_="%Y_%m_%d-%H_%M_%S"); + static std::string toString(std::chrono::duration duration_); namespace Time{ @@ -229,6 +230,37 @@ namespace GenericToolbox{ #endif return ss.str(); } + static std::string toString(std::chrono::duration duration_){ + std::stringstream ss; + using namespace std::chrono; + + auto h = duration_cast(duration_); + duration_ -= h; + auto m = duration_cast(duration_); + duration_ -= m; + auto s = duration_cast(duration_); + duration_ -= s; + auto ms = duration_cast(duration_); + + bool started = false; + + if (h.count() > 0) { + ss << h.count() << "h:"; + started = true; + } + if (m.count() > 0 || started) { // Print minutes if hours were printed + ss << m.count() << "m:"; + started = true; + } + if (s.count() > 0 || started) { // Print seconds if minutes or hours were printed + ss << s.count() << "s."; + started = true; + } + // Always print milliseconds if everything else is zero + ss << ms.count() << "ms"; + + return ss.str(); + } diff --git a/include/GenericToolbox.Vector.h b/include/GenericToolbox.Vector.h index 7926fd1..c197589 100644 --- a/include/GenericToolbox.Vector.h +++ b/include/GenericToolbox.Vector.h @@ -10,6 +10,7 @@ // *************************** #include "GenericToolbox.Macro.h" +#include "GenericToolbox.Log.h" #include #include @@ -205,9 +206,37 @@ namespace GenericToolbox { // Sorting template static std::vector getSortPermutation(const std::vector& vectorToSort_, const Lambda& firstArgGoesFirstFct_ ){ std::vector p(vectorToSort_.size()); + // 0,1,2,3,4... std::iota(p.begin(), p.end(), 0); - std::sort(p.begin(), p.end(), - [&](size_t i, size_t j){ return firstArgGoesFirstFct_(vectorToSort_.at(i), vectorToSort_.at(j)); }); + try { + std::sort(p.begin(), p.end(), + [&](size_t i, size_t j){ + if( i > vectorToSort_.size() or j > vectorToSort_.size() ) { + throw std::runtime_error("std::sort is reaching out of range."); + } + return firstArgGoesFirstFct_(vectorToSort_.at(i), vectorToSort_.at(j)); + }); + } + catch( ... ) { + // REACHING HERE MIGHT INDICATE THAT SOMETHING IS WRONG WITH firstArgGoesFirstFct_. + // "not a valid strict weak ordering": + // Typically: firstArgGoesFirstFct_(obj1, obj2) != !firstArgGoesFirstFct_(obj2, obj1) + GTLogError << "Something might be wrong with the sort function. Using std::stable_sort instead..." << std::endl; + try { + std::stable_sort(p.begin(), p.end(), + [&](size_t i, size_t j){ + if( i > vectorToSort_.size() or j > vectorToSort_.size() ) { + throw std::runtime_error("std::sort is reaching out of range."); + } + return firstArgGoesFirstFct_(vectorToSort_.at(i), vectorToSort_.at(j)); + }); + } + catch( ... ) { + GTLogError << "Couldn't use std::stable_sort either. Skip sorting." << std::endl; + } + + } + return p; } template static std::vector getSortedVector(const std::vector& unsortedVector_, const std::vector& sortPermutation_ ){