#include "__whiledir.h" #include "Exception.h" #include "__mysql.h" #include #include #include #include namespace jet { __whiledir::__whiledir(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, parent, this) { // Real 1.4 content (barantmusic.page/davidarant.page's own // bae1996.section) writes , // not . Accept "dir" as an // alias for "path" and "name" as an alias for "filename" so that // content runs unmodified, matching the same "accept either" // precedent already used for /. // Resolved once into real local VALUES (not a dynamically-chosen // keyword NAME threaded through repeated keywordDefined()/ // resolveKeyword() calls) - an earlier version of this fix passed // a ZString holding just the chosen attribute name around and hit // real, unexplained corruption (a whiledir on a real, correctly // specified directory resolved to a garbage nonsense path instead). // This form fixed it in isolation (a standalone whiledir_test.jet // against this exact directory/attributes works perfectly) but the // SAME garbage path still recurs when this exact content // (bae1996.section) is reached through real content's actual deep // // nesting (40+ stack frames by that // point) - genuinely not yet root-caused; flagged in project // memory as still open. The try/catch below at least converts // whatever throws there into the same graceful, catchable error // page every other tag failure already gets, instead of a raw // std::exception unwinding past everything and dropping the // connection with no HTTP response at all. coreutils::MString dirPath; if(keywordDefined("path")) dirPath = resolveKeyword("path"); else if(keywordDefined("dir")) dirPath = resolveKeyword("dir"); else throw coreutils::Exception("whiledir tag must specify a path."); // Every one of these attributes - which output variable name to // assign into for a given field, and whether that field was even // requested - is a static property of this invocation, // fixed at construction, never changing across its own loop. The // per-iteration body used to call keywordDefined()+resolveKeyword() // (2 map lookups each) for fullpath/filename/filenamenoextension // on every single directory entry - resolved once here instead, // same reasoning as __whilerow.cpp's session-pointer hoist. coreutils::MString filenameVarName; bool haveFilenameVar = false; if(keywordDefined("filename")) { filenameVarName = resolveKeyword("filename"); haveFilenameVar = true; } else if(keywordDefined("name")) { filenameVarName = resolveKeyword("name"); haveFilenameVar = true; } coreutils::MString fullpathVarName; bool haveFullpathVar = keywordDefined("fullpath"); if(haveFullpathVar) fullpathVarName = resolveKeyword("fullpath"); coreutils::MString noExtVarName; bool haveNoExtVar = keywordDefined("filenamenoextension"); if(haveNoExtVar) noExtVarName = resolveKeyword("filenamenoextension"); bool sorted = keywordDefined("sort") && (resolveKeyword("sort") == "true"); try { if(sorted) { std::vector entries; for(auto const &entry : std::filesystem::directory_iterator(dirPath.str())) entries.push_back(entry); std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b) { return a.path() < b.path(); }); for(const auto &entry : entries) { if(haveFullpathVar) global.variables[fullpathVarName] = entry.path(); if(haveFilenameVar) global.variables[filenameVarName] = entry.path().filename(); if(haveNoExtVar) global.variables[noExtVarName] = entry.path().stem(); processContainer(container); container.reset(); } } else { for(auto const &entry : std::filesystem::directory_iterator(dirPath.str())) { if(haveFullpathVar) global.variables[fullpathVarName] = entry.path(); if(haveFilenameVar) global.variables[filenameVarName] = entry.path().filename(); if(haveNoExtVar) global.variables[noExtVarName] = entry.path().stem(); processContainer(container); container.reset(); } } } catch(std::exception &e) { coreutils::MString msg("whiledir failed: "); msg << e.what(); throw coreutils::Exception(msg.str()); } } }