diff --git a/src/API.cpp b/src/API.cpp index 11279b369..3e2624a3c 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -47,8 +47,12 @@ // //////////////////////////////////////////////////////////////////////////////// +#include // TODO Remove +#include "Context.h" #include "API.h" +extern Context context; + #ifdef HAVE_LIBLUA //////////////////////////////////////////////////////////////////////////////// @@ -466,6 +470,113 @@ void API::initialize () */ } +//////////////////////////////////////////////////////////////////////////////// +bool API::callProgramHook ( + const std::string& file, + const std::string& function) +{ + loadFile (file); + + // Get function. + lua_getglobal (L, function.c_str ()); + if (!lua_isfunction (L, -1)) + { + lua_pop (L, 1); + throw std::string ("The Lua function '") + function + "' was not found."; + } + + // Make call. + if (lua_pcall (L, 0, 2, 0) != 0) + throw std::string ("Error calling '") + function + "' - " + lua_tostring (L, -1); + + // Call successful - get return values. + // 0, nil -> success + // 0, string -> warning + // 1, string -> error + if (!lua_isnumber (L, -2)) throw std::string ("Error: '") + function + "' did not return a success indicator"; +// if (!lua_isstring (L, -1)) throw std::string ("Error: '") + function + "' did not return a message"; + + int rc = lua_tointeger (L, -2); + const char* message = lua_tostring (L, -1); + + if (rc == 0) + { + if (message) + context.footnote (std::string ("Warning: ") + message); + } + else + { + if (message) + throw std::string ("Error: ") + message; + } + + lua_pop (L, 1); + return rc == 0 ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool API::callListHook ( + const std::string& file, + const std::string& function/*, + iterator i*/) +{ + loadFile (file); + + // TODO Get function. + // TODO Prepare args. + // TODO Make call. + // TODO Get exit status. + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool API::callTaskHook ( + const std::string& file, + const std::string& function, + int id) +{ + loadFile (file); + + // TODO Get function. + // TODO Prepare args. + // TODO Make call. + // TODO Get exit status. + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool API::callFieldHook ( + const std::string& file, + const std::string& function, + const std::string& field, + const std::string& value) +{ + loadFile (file); + + // TODO Get function. + // TODO Prepare args. + // TODO Make call. + // TODO Get exit status. + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +void API::loadFile (const std::string& file) +{ + if (std::find (loaded.begin (), loaded.end (), file) == loaded.end ()) + { + // Load the file, if possible. + if (luaL_loadfile (L, file.c_str ()) || lua_pcall (L, 0, 0, 0)) + throw std::string ("Error: ") + std::string (lua_tostring (L, -1)); + + // Mark this as loaded, so as to not bother again. + loaded.push_back (file); + } +} + //////////////////////////////////////////////////////////////////////////////// #endif diff --git a/src/API.h b/src/API.h index c6f3c9846..ec808085d 100644 --- a/src/API.h +++ b/src/API.h @@ -30,6 +30,7 @@ #include "auto.h" #ifdef HAVE_LIBLUA +#include #include extern "C" { @@ -47,9 +48,17 @@ public: ~API (); void initialize (); + bool callProgramHook (const std::string&, const std::string&); + bool callListHook (const std::string&, const std::string& /*, iterator */); + bool callTaskHook (const std::string&, const std::string&, int); + bool callFieldHook (const std::string&, const std::string&, const std::string&, const std::string&); + +private: + void loadFile (const std::string&); public: lua_State* L; + std::vector loaded; }; #endif diff --git a/src/Context.cpp b/src/Context.cpp index 3846593b2..8233fceb7 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -139,8 +139,6 @@ void Context::initialize () int Context::run () { int rc; - Timer t ("Context::run"); - std::string output; try { @@ -193,8 +191,12 @@ int Context::run () int Context::dispatch (std::string &out) { int rc = 0; + Timer t ("Context::dispatch"); + if (! hooks.trigger ("pre-dispatch")) + return rc; + // TODO Just look at this thing. It cries out for a dispatch table. if (cmd.command == "projects") { rc = handleProjects (out); } else if (cmd.command == "tags") { rc = handleTags (out); } @@ -245,6 +247,7 @@ int Context::dispatch (std::string &out) if (cmd.isWriteCommand () && !inShadow) shadow (); + hooks.trigger ("post-dispatch"); return rc; } diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 6afa0f05d..99bbbfdde 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -26,8 +26,11 @@ //////////////////////////////////////////////////////////////////////////////// #include +#include "Context.h" #include "Hooks.h" +extern Context context; + //////////////////////////////////////////////////////////////////////////////// Hooks::Hooks () { @@ -42,26 +45,75 @@ Hooks::~Hooks () void Hooks::initialize () { api.initialize (); + + // TODO Enumerate all hooks, and tell API about the script files it must load + // in order to call them. Note that API will perform a deferred read, + // which means that if it isn't called, a script will not be loaded. + + std::vector vars; + context.config.all (vars); + + std::vector ::iterator it; + for (it = vars.begin (); it != vars.end (); ++it) + { + std::string type; + std::string name; + std::string value; + + // "." + Nibbler n (*it); + if (n.getUntil ('.', type) && + type == "hook" && + n.skip ('.') && + n.getUntilEOS (name)) + { + std::string value = context.config.get (*it); + Nibbler n (value); + + // : [, ...] + while (!n.depleted ()) + { + std::string file; + std::string function; + if (n.getUntil (':', file) && + n.skip (':') && + n.getUntil (',', function)) + { + context.debug (std::string ("Event '") + name + "' hooked by " + file + ", function " + function); + Hook h (name, Path::expand (file), function); + all.push_back (h); + + (void) n.skip (','); + } + else + throw std::string ("Malformed hook definition '") + *it + "'"; + } + } + } } //////////////////////////////////////////////////////////////////////////////// bool Hooks::trigger (const std::string& event) { #ifdef HAVE_LIBLUA - // TODO Look up scripts/functions hooking this event. - // TODO Load the scripts if necessary. - - // TODO Call each function. - std::string type; - if (eventType (event, type)) + std::vector ::iterator it; + for (it = all.begin (); it != all.end (); ++it) { - if (type == "program") return triggerProgramEvent (event); - else if (type == "list") return triggerListEvent (event); - else if (type == "task") return triggerTaskEvent (event); - else if (type == "field") return triggerFieldEvent (event); + if (it->event == event) + { + std::string type; + if (eventType (event, type)) + { + // TODO Figure out where to get the calling-context info from. + if (type == "program") return api.callProgramHook (it->file, it->function); + else if (type == "list") return api.callListHook (it->file, it->function/*, tasks*/); + else if (type == "task") return api.callTaskHook (it->file, it->function, 0); + else if (type == "field") return api.callFieldHook (it->file, it->function, "field", "value"); + } + else + throw std::string ("Unrecognized hook event '") + event + "'"; + } } - else - throw std::string ("Unrecognized hook event '") + event + "'"; #endif return true; @@ -70,8 +122,9 @@ bool Hooks::trigger (const std::string& event) //////////////////////////////////////////////////////////////////////////////// bool Hooks::eventType (const std::string& event, std::string& type) { - if (event == "post-start" || - event == "pre-exit") + if (event == "post-start" || + event == "pre-exit" || + event == "pre-dispatch" || event == "post-dispatch") { type = "program"; return true; @@ -96,38 +149,3 @@ bool Hooks::eventType (const std::string& event, std::string& type) } //////////////////////////////////////////////////////////////////////////////// -#ifdef HAVE_LIBLUA - -bool Hooks::triggerProgramEvent (const std::string& event) -{ - std::cout << "Hooks::triggerProgramEvent " << event << std::endl; - - // TODO Is this event hooked? - // TODO Is the associated script loaded? - // TODO Call the function - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool Hooks::triggerListEvent (const std::string& event) -{ - std::cout << "Hooks::triggerListEvent " << event << std::endl; - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool Hooks::triggerTaskEvent (const std::string& event) -{ - std::cout << "Hooks::triggerTaskEvent " << event << std::endl; - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool Hooks::triggerFieldEvent (const std::string& event) -{ - std::cout << "Hooks::triggerFieldEvent " << event << std::endl; - return true; -} - -#endif -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Hooks.h b/src/Hooks.h index 34aade486..bf0c8df93 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -32,31 +32,67 @@ #include "API.h" #include "auto.h" +// Hook class representing a single hook. +class Hook +{ +public: + Hook () + : event ("") + , file ("") + , function ("") + { + } + + Hook (const std::string& e, const std::string& f, const std::string& fn) + : event (e) + , file (f) + , function (fn) + { + } + + Hook (const Hook& other) + { + event = other.event; + file = other.file; + function = other.function; + } + + Hook& operator= (const Hook& other) + { + if (this != &other) + { + event = other.event; + file = other.file; + function = other.function; + } + + return *this; + } + +public: + std::string event; + std::string file; + std::string function; +}; + +// Hooks class for managing the loading and calling of hook functions. class Hooks { public: - Hooks (); // Default constructor - ~Hooks (); // Destructor - Hooks (const Hooks&); // Deliberately unimplemented - Hooks& operator= (const Hooks&); // Deliberately unimplemented + Hooks (); // Default constructor + ~Hooks (); // Destructor + Hooks (const Hooks&); // Deliberately unimplemented + Hooks& operator= (const Hooks&); // Deliberately unimplemented void initialize (); bool trigger (const std::string&); bool eventType (const std::string&, std::string&); -private: -#ifdef HAVE_LIBLUA - bool triggerProgramEvent (const std::string&); - bool triggerListEvent (const std::string&); - bool triggerTaskEvent (const std::string&); - bool triggerFieldEvent (const std::string&); -#endif - private: #ifdef HAVE_LIBLUA API api; #endif - std::vector scripts; + std::vector all; // All current hooks. }; #endif diff --git a/src/command.cpp b/src/command.cpp index 2d7c2b733..8c6b6bdeb 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -648,14 +648,16 @@ int handleConfig (std::string &outs) // These are the regular configuration variables. // Note that there is a leading and trailing space, to make searching easier. std::string recognized = - " annotation.details blanklines bulk calendar.details calendar.details.report color " - "color.active color.due color.overdue color.pri.H color.pri.L color.pri.M color.pri.none " - "color.recurring color.tagged color.footnote color.header color.debug color.alternate " - "color.calendar.today color.calendar.due color.calendar.overdue color.calendar.weekend " - "confirmation curses data.location dateformat reportdateformat debug default.command " - "default.priority default.project defaultwidth due locale displayweeknumber " - "echo.command locking monthsperline nag next project shadow.command shadow.file " - "shadow.notify weekstart editor import.synonym.id import.synonym.uuid " + " annotation.details blanklines bulk calendar.details " + "calendar.details.report color color.active color.due color.overdue " + "color.pri.H color.pri.L color.pri.M color.pri.none color.recurring " + "color.tagged color.footnote color.header color.debug color.alternate " + "color.calendar.today color.calendar.due color.calendar.overdue " + "color.calendar.weekend confirmation curses data.location dateformat " + "reportdateformat debug default.command default.priority default.project " + "defaultwidth due locale displayweeknumber echo.command locking " + "monthsperline nag next project shadow.command shadow.file shadow.notify " + "weekstart editor import.synonym.id import.synonym.uuid " "complete.all.projects complete.all.tags " #ifdef FEATURE_SHELL "shell.prompt " @@ -705,6 +707,7 @@ int handleConfig (std::string &outs) out << context.config.checkForDeprecatedColor (); // TODO Check for referenced but missing theme files. // TODO Check for referenced but missing string files. + // TODO Check for referenced but missing hook scripts. // Verify installation. This is mentioned in the documentation as the way to // ensure everything is properly installed.