#include "__include.h" #include "Exception.h" #include "File.h" #include #include #include namespace jet { namespace { struct CachedInclude { std::string content; time_t mtime; }; // Persistent, per-worker-thread cache of file contents, // keyed by resolved filename. Real content depends on this // constantly through custom tag bodies that jetc's own compile- // time -inlining doesn't reach (only the top-level .jet // source's own includes get compiled in) - mainpagetemplate.temp // alone pulls in login.menu/menu.bar/opensource.menu/cart.menu/ // sessionvar.menu, all previously re-read from disk on every // single request even though the files are 100% static between // edits. Invalidated via mtime comparison (a stat() call, cheap // relative to a full read()) so a live edit still takes effect on // the very next request - matching the workflow already relied on // all session of editing .tag/.temp/.menu content directly and // seeing it live without a rebuild. thread_local std::unordered_map includeCache; } __include::__include(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, parent, local) { if(!keywordDefined("file")) throw coreutils::Exception("file keyword must be specified."); if(hasContainer) throw coreutils::Exception("include tag should not have a container."); hasContainer = true; coreutils::MString includeFileName = resolveKeyword("file"); std::string fileNameKey = includeFileName.str(); struct stat st; bool haveStat = (stat(fileNameKey.c_str(), &st) == 0); auto cached = includeCache.find(fileNameKey); if(cached != includeCache.end() && haveStat && cached->second.mtime == st.st_mtime) { container = coreutils::ZString(cached->second.content.data(), cached->second.content.length()); } else { coreutils::File file(includeFileName); file.read(); if(haveStat) { CachedInclude &entry = includeCache[fileNameKey]; entry.content = file.asString(); entry.mtime = st.st_mtime; container = coreutils::ZString(entry.content.data(), entry.content.length()); } else { // Couldn't stat the file (unusual - e.g. a race with deletion) - // don't cache something we can't validate the freshness of, // just render this one read directly, matching the pre-cache // behavior exactly for this edge case. container = file.asZString(); } } try { processContainer(container); } catch(coreutils::Exception e) { // global.errorCursor is whatever cursor was active in whichever // ZString buffer was being parsed at the moment the ORIGINAL // exception was thrown - not necessarily this include's own // `container` buffer at all (e.g. a keyword-resolution error // happening while interpreting a completely different .temp // file's own text). setCursor() on a pointer outside container's // own [data, data+length] range throws "Cursor out of range on // setCursor." - and since that happens INSIDE this diagnostic- // only "show a nice line number" block, it was replacing the // real error (e.text) with that unrelated cursor-range message // before it ever reached the caller, making every real error // from inside an impossible to see. This is purely // diagnostic output - if it can't compute a line number, fall // back to printing the real error text without one rather than // losing it entirely. try { container.setCursor(global.errorCursor); container.moveBackToLineStart(); std::cout << "-----------------------------" << std::endl; std::cout << "Error in included script '" << variables["file"] << "' at line " << container.getLineNumberAtCursor() << std::endl; std::cout << "Error text: " << e.text << std::endl; std::cout << "-----------------------------" << std::endl; std::cout << container.parsed() << std::endl; std::cout << "******** Error caught: " << e.text << std::endl; std::cout << container.unparsed() << std::endl; } catch(coreutils::Exception &) { std::cout << "-----------------------------" << std::endl; std::cout << "Error in included script '" << variables["file"] << "' (line number unavailable - error originated outside this include's own content)" << std::endl; std::cout << "Error text: " << e.text << std::endl; std::cout << "-----------------------------" << std::endl; } global.errorCursor = in.getCursor(); throw coreutils::Exception(e.text); } } }