From 44aeea8e454a31e1fda593cf06c0fbe22db2269f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 10 Jan 2010 01:11:38 -0500 Subject: [PATCH 01/15] Hooks - starting... - Tried to include an autoconf Lua detector. Failed. Waiting on mailing list response. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index b04bee072..c6ededf12 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,7 @@ AC_SUBST(CFLAGS) # Checks for libraries. AC_CHECK_LIB(ncurses,initscr) +AC_CHECK_LIB(lua,lua_open) # Checks for header files. AC_HEADER_STDC From c66d6b0500d687afd48eb75a747441f5aa237897 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 17 Jan 2010 01:12:28 -0500 Subject: [PATCH 02/15] Enhancement - Hooks - Added bare bones autoconf changes to support Lua. - Added API class from metatask.git. - Added skeleton Hooks class. - Added 'with_lua' helper script. --- configure.ac | 35 ++++ src/API.cpp | 450 ++++++++++++++++++++++++++++++++++++++++++++++++ src/API.h | 57 ++++++ src/Context.h | 2 + src/Hooks.cpp | 40 +++++ src/Hooks.h | 43 +++++ src/Makefile.am | 23 +-- with_lua | 6 + 8 files changed, 645 insertions(+), 11 deletions(-) create mode 100644 src/API.cpp create mode 100644 src/API.h create mode 100644 src/Hooks.cpp create mode 100644 src/Hooks.h create mode 100755 with_lua diff --git a/configure.ac b/configure.ac index c6ededf12..5368e1cde 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,41 @@ else AC_DEFINE([UNKNOWN], [], [Compiling on Unknown]) fi +# Check for Lua. +echo '----------------------------------------------------------' + +AC_ARG_ENABLE(lua, [--disable-lua Turn off building of Lua library (default=compiled if found)],enable_lua=$enableval,enable_lua=yes) +AC_ARG_WITH(lua-inc, [--with-lua-inc=DIR, Lua include files are in DIR],lua_inc=$withval,lua_inc='') +AC_ARG_WITH(lua-lib, [--with-lua-lib=DIR, Lua library files are in DIR],lua_lib=$withval,lua_lib='') + +if test "x$enable_lua" = "xyes" ; then + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_CFLAGS="$CFLAGS" + ac_save_LDFLAGS="$LDFLAGS" + + LUA_CFLAGS="" + LUA_LFLAGS="" + + if test -n "$lua_inc"; then + CFLAGS="$CFLAGS -I$lua_inc" + CPPFLAGS="$CPPFLAGS -I$lua_inc" + fi + if test -n "$lua_lib"; then + LDFLAGS="$LDFLAGS -L$lua_lib" + LUA_LFLAGS="-llua" + fi +fi + +if test "x$enable_lua" = "xyes" ; then + AC_SUBST(LUA_CFLAGS) + AC_SUBST(LUA_LFLAGS) + AC_DEFINE([HAVE_LIBLUA], [1], [Building with Lua support]) +fi + +AM_CONDITIONAL(HAVE_LIBLUA, test "$enable_lua" = "yes") + +echo '----------------------------------------------------------' + AM_INIT_AUTOMAKE AC_CONFIG_SRCDIR([src/main.cpp]) AC_CONFIG_HEADER([auto.h]) diff --git a/src/API.cpp b/src/API.cpp new file mode 100644 index 000000000..1547364ab --- /dev/null +++ b/src/API.cpp @@ -0,0 +1,450 @@ +//////////////////////////////////////////////////////////////////////////////// +// Task Lua API +// +// Copyright 2006 - 2010, Paul Beckingham. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "API.h" + +#ifdef HAVE_LIBLUA + +//////////////////////////////////////////////////////////////////////////////// +// Returns a string representing the task version number, such as '1.9.0'. +static int api_task_version (lua_State* L) +{ + lua_pushstring (L, PACKAGE_VERSION); + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns a string representing the Lua version number, such as '5.1.4'. +// Lua 5.2.0 has a 'lua_version' call, but 5.1.4 is the target. +static int api_task_lua_version (lua_State* L) +{ + // Convert "Lua 5.1.4" -> "5.1.4" + std::string ver = LUA_RELEASE; + lua_pushstring (L, ver.substr (4, std::string::npos).c_str ()); + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns the type of OS that task is running on. +static int api_task_os (lua_State* L) +{ +#if defined (DARWIN) + lua_pushstring (L, "darwin"); +#elif defined (SOLARIS) + lua_pushstring (L, "solaris"); +#elif defined (CYGWIN) + lua_pushstring (L, "cygwin"); +#elif defined (OPENBSD) + lua_pushstring (L, "openbsd"); +#elif defined (HAIKU) + lua_pushstring (L, "haiku"); +#elif defined (FREEBSD) + lua_pushstring (L, "freebsd"); +#elif defined (LINUX) + lua_pushstring (L, "linux"); +#else + lua_pushstring (L, "unknown"); +#endif + + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_feature (lua_State* L) +{ + std::string name = luaL_checkstring (L, 1); + bool value = false; + + if (name == "readline") + { +#ifdef HAVE_READLINE + value = true; +#endif + } + + else if (name == "ncurses") + { +#ifdef HAVE_NCURSES + value = true; +#endif + } + + lua_pushnumber (L, value ? 1 : 0); + return 1; +} + +/* +//////////////////////////////////////////////////////////////////////////////// +static int api_task_aliases () +{ + return {}; +} + +//////////////////////////////////////////////////////////////////////////////// +-- Returns values from .taskrc, by name. +static int api_task_get_config (name) +{ + return "foo" +} + +//////////////////////////////////////////////////////////////////////////////// +-- Temporarily sets .taskrc values, by name. +static int api_task_set_config (name, value) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +-- Returns an internationalized string, by string ID, from the appropriate +-- locale-based strings file. +static int api_task_i18n_string (id) +{ + return "le foo" +} + +//////////////////////////////////////////////////////////////////////////////// +-- Returns a list of tips, from the appropriate locale-based tips file. +static int api_task_i18n_tips () +{ + return {} +} + +//////////////////////////////////////////////////////////////////////////////// +-- Returns the name of the current command. +static int api_task_get_command () +{ + return "list" +} + +//////////////////////////////////////////////////////////////////////////////// +-- Returns a list of string messages generated so far. +static int api_task_get_header_messages () +{ + return {} +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_footnote_messages () +{ + return {} +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_debug_messages () +{ + return {} +} + +//////////////////////////////////////////////////////////////////////////////// +-- Records additional messages, for subsequent display. +static int api_task_header_message (text) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_footnote_message (text) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_debug_message (text) +{ +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +// Causes the shell or interactive mode task to exit. Ordinarily this does not +// occur. +static int api_task_exit (lua_State* L) +{ + // TODO Is this the correct exception? How does the shell handle this? + throw std::string ("Exiting."); + return 0; +} + +/* +//////////////////////////////////////////////////////////////////////////////// +-- Shuts off the hook system for any subsequent hook calls for this command. +static int api_task_inhibit_further_hooks () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +-- Returns a table that contains a complete copy of the task. +static int api_task_get (id) +{ + return task +} + +//////////////////////////////////////////////////////////////////////////////// +-- Creates a new task from the data specified in the table t. +static int api_task_add (t) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +-- Modifies the task described in the table t. +static int api_task_modify (t) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +-- 'id' is the task id passed to the hook function. Date attributes are +-- returned as a numeric epoch offset. Tags and annotations are returned +-- as tables. A nil value indicates a missing value. +static int api_task_get_uuid (id) +{ + return task.uuid +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_description (id) +{ + return task.description +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_annotations (id) +{ + return task.annotations +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_project (id) +{ + return task.project +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_priority (id) +{ + return task.priority +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_tags (id) +{ + return task.tags +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_status (id) +{ + return task.status +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_due (id) +{ + return task.due_date +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_entry (id) +{ + return task.entry_date +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_start (id) +{ + return task.start_date +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_end (id) +{ + return task.end_date +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_recur (id) +{ + return task.recur +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_until (id) +{ + return task.until_date +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_get_wait (id) +{ + return task.wait_date +} + +//////////////////////////////////////////////////////////////////////////////// +-- 'id' is the task id passed to the hook function. Date attributes are +-- expected as numeric epoch offsets. Tags and annotations are expected +-- as tables. A nil value indicates a missing value. +static int api_task_set_description (id, value) +{ + task.description = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_annotations (id, value) +{ + task.annotations = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_project (id, value) +{ + task.project = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_priority (id, value) +{ + task.priority = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_tags (id, value) +{ + task.tags = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_status (id, value) +{ + task.status = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_due (id, value) +{ + task.due_date = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_start (id, value) +{ + task.start_date = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_recur (id, value) +{ + task.recur = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_until (id, value) +{ + task.until_date = value +} + +//////////////////////////////////////////////////////////////////////////////// +static int api_task_set_wait (id, value) +{ + task.wait_date = value +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +API::API () +: L (NULL) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +API::~API () +{ + if (L) + { + lua_close (L); + L = NULL; + } +} + +//////////////////////////////////////////////////////////////////////////////// +void API::initialize () +{ + // Initialize Lua. + L = lua_open (); + luaL_openlibs (L); + + // Register all the API functions in Lua global space. + lua_pushcfunction (L, api_task_version); lua_setglobal (L, "task_version"); + lua_pushcfunction (L, api_task_lua_version); lua_setglobal (L, "task_lua_version"); + lua_pushcfunction (L, api_task_os); lua_setglobal (L, "task_os"); + lua_pushcfunction (L, api_task_feature); lua_setglobal (L, "task_feature"); +/* + lua_pushcfunction (L, api_task_aliases); lua_setglobal (L, "api_task_aliases"); + lua_pushcfunction (L, api_task_get_config); lua_setglobal (L, "api_task_get_config"); + lua_pushcfunction (L, api_task_set_config); lua_setglobal (L, "api_task_set_config"); + lua_pushcfunction (L, api_task_i18n_string); lua_setglobal (L, "api_task_i18n_string"); + lua_pushcfunction (L, api_task_i18n_tips); lua_setglobal (L, "api_task_i18n_tips"); + lua_pushcfunction (L, api_task_get_command); lua_setglobal (L, "api_task_get_command"); + lua_pushcfunction (L, api_task_get_header_messages); lua_setglobal (L, "api_task_get_header_messages"); + lua_pushcfunction (L, api_task_get_footnote_messages); lua_setglobal (L, "api_task_get_footnote_messages"); + lua_pushcfunction (L, api_task_get_debug_messages); lua_setglobal (L, "api_task_get_debug_messages"); + lua_pushcfunction (L, api_task_header_message); lua_setglobal (L, "api_task_header_message"); + lua_pushcfunction (L, api_task_footnote_message); lua_setglobal (L, "api_task_footnote_message"); + lua_pushcfunction (L, api_task_debug_message); lua_setglobal (L, "api_task_debug_message"); +*/ + lua_pushcfunction (L, api_task_exit); lua_setglobal (L, "task_exit"); +/* + lua_pushcfunction (L, api_task_inhibit_further_hooks); lua_setglobal (L, "api_task_inhibit_further_hooks"); + lua_pushcfunction (L, api_task_get); lua_setglobal (L, "api_task_get"); + lua_pushcfunction (L, api_task_add); lua_setglobal (L, "api_task_add"); + lua_pushcfunction (L, api_task_modify); lua_setglobal (L, "api_task_modify"); + lua_pushcfunction (L, api_task_get_uuid); lua_setglobal (L, "api_task_get_uuid"); + lua_pushcfunction (L, api_task_get_description); lua_setglobal (L, "api_task_get_description"); + lua_pushcfunction (L, api_task_get_annotations); lua_setglobal (L, "api_task_get_annotations"); + lua_pushcfunction (L, api_task_get_project); lua_setglobal (L, "api_task_get_project"); + lua_pushcfunction (L, api_task_get_priority); lua_setglobal (L, "api_task_get_priority"); + lua_pushcfunction (L, api_task_get_tags); lua_setglobal (L, "api_task_get_tags"); + lua_pushcfunction (L, api_task_get_status); lua_setglobal (L, "api_task_get_status"); + lua_pushcfunction (L, api_task_get_due); lua_setglobal (L, "api_task_get_due"); + lua_pushcfunction (L, api_task_get_entry); lua_setglobal (L, "api_task_get_entry"); + lua_pushcfunction (L, api_task_get_start); lua_setglobal (L, "api_task_get_start"); + lua_pushcfunction (L, api_task_get_end); lua_setglobal (L, "api_task_get_end"); + lua_pushcfunction (L, api_task_get_recur); lua_setglobal (L, "api_task_get_recur"); + lua_pushcfunction (L, api_task_get_until); lua_setglobal (L, "api_task_get_until"); + lua_pushcfunction (L, api_task_get_wait); lua_setglobal (L, "api_task_get_wait"); + lua_pushcfunction (L, api_task_set_description); lua_setglobal (L, "api_task_set_description"); + lua_pushcfunction (L, api_task_set_annotations); lua_setglobal (L, "api_task_set_annotations"); + lua_pushcfunction (L, api_task_set_project); lua_setglobal (L, "api_task_set_project"); + lua_pushcfunction (L, api_task_set_priority); lua_setglobal (L, "api_task_set_priority"); + lua_pushcfunction (L, api_task_set_tags); lua_setglobal (L, "api_task_set_tags"); + lua_pushcfunction (L, api_task_set_status); lua_setglobal (L, "api_task_set_status"); + lua_pushcfunction (L, api_task_set_due); lua_setglobal (L, "api_task_set_due"); + lua_pushcfunction (L, api_task_set_start); lua_setglobal (L, "api_task_set_start"); + lua_pushcfunction (L, api_task_set_recur); lua_setglobal (L, "api_task_set_recur"); + lua_pushcfunction (L, api_task_set_until); lua_setglobal (L, "api_task_set_until"); + lua_pushcfunction (L, api_task_set_wait); lua_setglobal (L, "api_task_set_wait"); +*/ +} + +//////////////////////////////////////////////////////////////////////////////// + +#endif + diff --git a/src/API.h b/src/API.h new file mode 100644 index 000000000..c6f3c9846 --- /dev/null +++ b/src/API.h @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_API +#define INCLUDED_API + +#include "auto.h" +#ifdef HAVE_LIBLUA + +#include +extern "C" +{ + #include "lua.h" + #include "lualib.h" + #include "lauxlib.h" +} + +class API +{ +public: + API (); + API (const API&); + API& operator= (const API&); + ~API (); + + void initialize (); + +public: + lua_State* L; +}; + +#endif +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Context.h b/src/Context.h index dfd1ba279..d7a202f99 100644 --- a/src/Context.h +++ b/src/Context.h @@ -36,6 +36,7 @@ #include "Task.h" #include "TDB.h" #include "StringTable.h" +#include "Hooks.h" class Context { @@ -89,6 +90,7 @@ public: std::map aliases; std::vector tagAdditions; std::vector tagRemovals; + Hooks hooks; private: std::vector headers; diff --git a/src/Hooks.cpp b/src/Hooks.cpp new file mode 100644 index 000000000..46bd1ea69 --- /dev/null +++ b/src/Hooks.cpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include "Hooks.h" + +//////////////////////////////////////////////////////////////////////////////// +Hooks::Hooks () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Hooks::~Hooks () +{ +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Hooks.h b/src/Hooks.h new file mode 100644 index 000000000..08a26fe12 --- /dev/null +++ b/src/Hooks.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////// +// task - a command line task list manager. +// +// Copyright 2006 - 2010, Paul Beckingham. +// All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation; either version 2 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the +// +// Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, +// Boston, MA +// 02110-1301 +// USA +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_HOOKS +#define INCLUDED_HOOKS + +class Hooks +{ +public: + Hooks (); // Default constructor + ~Hooks (); // Destructor + + Hooks (const Hooks&); + Hooks& operator= (const Hooks&); + +private: +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Makefile.am b/src/Makefile.am index 4bc6c9a73..accbed95c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,14 +1,15 @@ bin_PROGRAMS = task -task_SOURCES = Att.cpp Cmd.cpp Color.cpp Config.cpp Context.cpp Date.cpp \ - Directory.cpp Duration.cpp File.cpp Filter.cpp Grid.cpp \ - Keymap.cpp Location.cpp Nibbler.cpp Path.cpp Permission.cpp \ - Record.cpp Sequence.cpp StringTable.cpp Subst.cpp TDB.cpp \ - Table.cpp Task.cpp Timer.cpp command.cpp custom.cpp edit.cpp \ - import.cpp interactive.cpp main.cpp recur.cpp report.cpp \ - rules.cpp text.cpp util.cpp \ - Att.h Cmd.h Color.h Config.h Context.h Date.h Directory.h \ - Duration.h File.h Filter.h Grid.h Keymap.h Location.h \ - Nibbler.h Path.h Permission.h Record.h Sequence.h \ +task_SOURCES = API.cpp Att.cpp Cmd.cpp Color.cpp Config.cpp Context.cpp \ + Date.cpp Directory.cpp Duration.cpp File.cpp Filter.cpp \ + Grid.cpp Hooks.cpp Keymap.cpp Location.cpp Nibbler.cpp \ + Path.cpp Permission.cpp Record.cpp Sequence.cpp \ + StringTable.cpp Subst.cpp TDB.cpp Table.cpp Task.cpp Timer.cpp \ + command.cpp custom.cpp edit.cpp import.cpp interactive.cpp \ + main.cpp recur.cpp report.cpp rules.cpp text.cpp util.cpp \ + API.h Att.h Cmd.h Color.h Config.h Context.h Date.h \ + Directory.h Duration.h File.h Filter.h Grid.h Hooks.h Keymap.h \ + Location.h Nibbler.h Path.h Permission.h Record.h Sequence.h \ StringTable.h Subst.h TDB.h Table.h Task.h Timer.h i18n.h \ main.h text.h util.h - +task_CPPFLAGS=$(LUA_CFLAGS) +task_LDFLAGS=$(LUA_LFLAGS) diff --git a/with_lua b/with_lua new file mode 100755 index 000000000..5b24f5e97 --- /dev/null +++ b/with_lua @@ -0,0 +1,6 @@ +# This is currently what is required to build with Lua. + +#./configure --enable-lua=yes --with-lua --with-lua-inc=/usr/local/include --with-lua-lib=/usr/local/lib +#./configure --enable-lua --with-lua --with-lua-inc=/usr/local/include --with-lua-lib=/usr/local/lib + ./configure --enable-lua --with-lua-inc=/usr/local/include --with-lua-lib=/usr/local/lib + From 57e94585e8f9d761a83db65fd824b051cf8f85be Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 17 Jan 2010 14:20:29 -0500 Subject: [PATCH 03/15] Enhancement - Hooks - Added Lua copyright notice to the version command. - Added Lua copyright details to API.cpp, which is the only file that interacts with Lua. - Added hook-type detection. - Added a hook-type calling mechanism in Hooks. - Implemented the post-start and pre-exit event triggers. - Context::initialize now calls Hooks::initialize, which in turn calls API::initialize. We have liftoff! --- src/API.cpp | 24 +++++++++++++- src/Context.cpp | 6 ++++ src/Hooks.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Hooks.h | 23 ++++++++++--- src/command.cpp | 6 +++- 5 files changed, 141 insertions(+), 6 deletions(-) diff --git a/src/API.cpp b/src/API.cpp index 1547364ab..11279b369 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -23,6 +23,28 @@ // 02110-1301 // USA // +// ------------- +// +// Copyright © 1994–2008 Lua.org, PUC-Rio. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// //////////////////////////////////////////////////////////////////////////////// #include "API.h" @@ -389,7 +411,7 @@ void API::initialize () { // Initialize Lua. L = lua_open (); - luaL_openlibs (L); + luaL_openlibs (L); // TODO Error handling // Register all the API functions in Lua global space. lua_pushcfunction (L, api_task_version); lua_setglobal (L, "task_version"); diff --git a/src/Context.cpp b/src/Context.cpp index a4a9485ad..aaca35086 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -83,6 +83,11 @@ void Context::initialize (int argc, char** argv) } initialize (); + + // Hook system init, plus post-start event occurring at the first possible + // moment after hook initialization. + hooks.initialize (); + hooks.trigger ("post-start"); } //////////////////////////////////////////////////////////////////////////////// @@ -180,6 +185,7 @@ int Context::run () else std::cout << *f << std::endl; + hooks.trigger ("pre-exit"); return rc; } diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 46bd1ea69..1cf399747 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -25,6 +25,7 @@ // //////////////////////////////////////////////////////////////////////////////// +#include #include "Hooks.h" //////////////////////////////////////////////////////////////////////////////// @@ -38,3 +39,90 @@ Hooks::~Hooks () } //////////////////////////////////////////////////////////////////////////////// +void Hooks::initialize () +{ + api.initialize (); +} + +//////////////////////////////////////////////////////////////////////////////// +bool Hooks::trigger (const std::string& event) +{ + // 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)) + { + 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); + } + else + throw std::string ("Unrecognized hook event '") + event + "'"; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Hooks::eventType (const std::string& event, std::string& type) +{ + if (event == "post-start" || + event == "pre-exit") + { + type = "program"; + return true; + } + else if (event == "?") + { + type = "list"; + return true; + } + else if (event == "?") + { + type = "task"; + return true; + } + else if (event == "?") + { + type = "field"; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +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 false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Hooks::triggerListEvent (const std::string& event) +{ + std::cout << "Hooks::triggerListEvent " << event << std::endl; + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Hooks::triggerTaskEvent (const std::string& event) +{ + std::cout << "Hooks::triggerTaskEvent " << event << std::endl; + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Hooks::triggerFieldEvent (const std::string& event) +{ + std::cout << "Hooks::triggerFieldEvent " << event << std::endl; + return false; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Hooks.h b/src/Hooks.h index 08a26fe12..ad596637e 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -27,16 +27,31 @@ #ifndef INCLUDED_HOOKS #define INCLUDED_HOOKS +#include +#include +#include "API.h" + class Hooks { public: - Hooks (); // Default constructor - ~Hooks (); // Destructor + Hooks (); // Default constructor + ~Hooks (); // Destructor + Hooks (const Hooks&); // Deliberately unimplemented + Hooks& operator= (const Hooks&); // Deliberately unimplemented - Hooks (const Hooks&); - Hooks& operator= (const Hooks&); + void initialize (); + bool trigger (const std::string&); + bool eventType (const std::string&, std::string&); private: + bool triggerProgramEvent (const std::string&); + bool triggerListEvent (const std::string&); + bool triggerTaskEvent (const std::string&); + bool triggerFieldEvent (const std::string&); + +private: + API api; + std::vector scripts; }; #endif diff --git a/src/command.cpp b/src/command.cpp index cfadb7a36..2d7c2b733 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -490,8 +490,12 @@ int handleVersion (std::string &outs) #endif << std::endl - << "Copyright (C) 2006 - 2010, P. Beckingham, F. Hernandez." + << "Copyright (C) 2006 - 2010 P. Beckingham, F. Hernandez." << std::endl +#ifdef HAVE_LIBLUA + << "Portions of this software Copyright (C) 1994 – 2008 Lua.org, PUC-Rio." + << std::endl +#endif << disclaimer.render () << link.render () << std::endl; From 77e98c8c03eae08f50efbf681d0c9c792746bbb6 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 17 Jan 2010 23:41:02 -0500 Subject: [PATCH 04/15] Enhancement - Hooks - Improved the conditional compilation. --- src/Hooks.cpp | 15 ++++++++++----- src/Hooks.h | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 1cf399747..6afa0f05d 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -47,6 +47,7 @@ void Hooks::initialize () //////////////////////////////////////////////////////////////////////////////// bool Hooks::trigger (const std::string& event) { +#ifdef HAVE_LIBLUA // TODO Look up scripts/functions hooking this event. // TODO Load the scripts if necessary. @@ -61,8 +62,9 @@ bool Hooks::trigger (const std::string& event) } else throw std::string ("Unrecognized hook event '") + event + "'"; +#endif - return false; + return true; } //////////////////////////////////////////////////////////////////////////////// @@ -94,6 +96,8 @@ 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; @@ -101,28 +105,29 @@ bool Hooks::triggerProgramEvent (const std::string& event) // TODO Is this event hooked? // TODO Is the associated script loaded? // TODO Call the function - return false; + return true; } //////////////////////////////////////////////////////////////////////////////// bool Hooks::triggerListEvent (const std::string& event) { std::cout << "Hooks::triggerListEvent " << event << std::endl; - return false; + return true; } //////////////////////////////////////////////////////////////////////////////// bool Hooks::triggerTaskEvent (const std::string& event) { std::cout << "Hooks::triggerTaskEvent " << event << std::endl; - return false; + return true; } //////////////////////////////////////////////////////////////////////////////// bool Hooks::triggerFieldEvent (const std::string& event) { std::cout << "Hooks::triggerFieldEvent " << event << std::endl; - return false; + return true; } +#endif //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Hooks.h b/src/Hooks.h index ad596637e..34aade486 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -30,6 +30,7 @@ #include #include #include "API.h" +#include "auto.h" class Hooks { @@ -44,13 +45,17 @@ public: 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; }; From eeefc8a99227ccba81bc88f749baa789ea15dd99 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 18 Jan 2010 00:53:01 -0500 Subject: [PATCH 05/15] Color - Removed coloring of only the due date, which is operating outside of the colorization rules. - Removed commented out code. --- src/Context.cpp | 2 -- src/custom.cpp | 30 ++---------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/src/Context.cpp b/src/Context.cpp index aaca35086..3846593b2 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -363,8 +363,6 @@ void Context::loadCorrectConfigFile () "Could not read home directory from the passwd file.")); std::string home = pw->pw_dir; -// std::string rc = home + "/.taskrc"; -// std::string data = home + "/.task"; File rc (home + "/.taskrc"); Directory data (home + "./task"); diff --git a/src/custom.cpp b/src/custom.cpp index 1de5b6149..eda344272 100644 --- a/src/custom.cpp +++ b/src/custom.cpp @@ -583,39 +583,13 @@ int runCustomReport ( } // Now auto colorize all rows. - std::string due; - Color color_due (context.config.get ("color.due")); - Color color_overdue (context.config.get ("color.overdue")); - - bool imminent; - bool overdue; - for (unsigned int row = 0; row < tasks.size (); ++row) + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) { - imminent = false; - overdue = false; - due = tasks[row].get ("due"); - if (due.length ()) - { - switch (getDueState (due)) - { - case 2: overdue = true; break; - case 1: imminent = true; break; - case 0: - default: break; - } - } - - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + for (unsigned int row = 0; row < tasks.size (); ++row) { Color c (tasks[row].get ("fg") + " " + tasks[row].get ("bg")); autoColorize (tasks[row], c); table.setRowColor (row, c); - - if (dueColumn != -1) - { - c.blend (overdue ? color_overdue : color_due); - table.setCellColor (row, columnCount, c); - } } } From 69cae7731f9d71b8082fa06820ecf4c97f0cfd95 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 18 Jan 2010 18:03:31 -0500 Subject: [PATCH 06/15] Enhancement - Hooks - First fully functioning Lua hooks. Woohoo. --- src/API.cpp | 111 +++++++++++++++++++++++++++++++++++++++++++++ src/API.h | 9 ++++ src/Context.cpp | 7 ++- src/Hooks.cpp | 116 ++++++++++++++++++++++++++++-------------------- src/Hooks.h | 62 ++++++++++++++++++++------ src/command.cpp | 19 ++++---- 6 files changed, 252 insertions(+), 72 deletions(-) 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. From 78063c4df763858cfea5e25b5685e545658a0306 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Mon, 18 Jan 2010 20:19:51 -0500 Subject: [PATCH 07/15] Enhancement - Hooks - Multiple hooks for the same event are now triggered, in the sequence they are found in .taskrc - Program hooks can now cause task to exit. --- src/API.cpp | 7 +++---- src/Context.cpp | 3 +-- src/Hooks.cpp | 13 +++++++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/API.cpp b/src/API.cpp index 3e2624a3c..718103d72 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -490,10 +490,9 @@ bool API::callProgramHook ( 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"; + +// TODO This doesn't seem to know that 'nil' was returned instead of a string. // if (!lua_isstring (L, -1)) throw std::string ("Error: '") + function + "' did not return a message"; int rc = lua_tointeger (L, -2); @@ -507,7 +506,7 @@ bool API::callProgramHook ( else { if (message) - throw std::string ("Error: ") + message; + throw std::string (message); } lua_pop (L, 1); diff --git a/src/Context.cpp b/src/Context.cpp index 8233fceb7..d45e157c4 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -194,8 +194,7 @@ int Context::dispatch (std::string &out) Timer t ("Context::dispatch"); - if (! hooks.trigger ("pre-dispatch")) - return rc; + hooks.trigger ("pre-dispatch"); // TODO Just look at this thing. It cries out for a dispatch table. if (cmd.command == "projects") { rc = handleProjects (out); } diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 99bbbfdde..90715df58 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -101,17 +101,22 @@ bool Hooks::trigger (const std::string& event) { if (it->event == event) { + bool rc = true; 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"); + if (type == "program") rc = api.callProgramHook (it->file, it->function); + else if (type == "list") rc = api.callListHook (it->file, it->function/*, tasks*/); + else if (type == "task") rc = api.callTaskHook (it->file, it->function, 0); + else if (type == "field") rc = api.callFieldHook (it->file, it->function, "field", "value"); } else throw std::string ("Unrecognized hook event '") + event + "'"; + + // If any hook returns false, stop. + if (!rc) + return false; } } #endif From e3c28f3fb3930941cbf8512be8e66255a9e6851c Mon Sep 17 00:00:00 2001 From: Federico Hernandez Date: Wed, 20 Jan 2010 00:31:41 +0100 Subject: [PATCH 08/15] Include statement for Linux --- src/API.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/API.cpp b/src/API.cpp index 718103d72..fa10f321e 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -48,6 +48,7 @@ //////////////////////////////////////////////////////////////////////////////// #include // TODO Remove +#include #include "Context.h" #include "API.h" From 8540cab0a6109134be6dd71a586ee5bb14ccbf4e Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 19 Jan 2010 22:19:40 -0500 Subject: [PATCH 09/15] Enhancement - hooks - Improved diagnostics of C++ side of the Lua API. --- src/API.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/API.cpp b/src/API.cpp index fa10f321e..566692710 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -491,10 +491,11 @@ bool API::callProgramHook ( throw std::string ("Error calling '") + function + "' - " + lua_tostring (L, -1); // Call successful - get return values. - if (!lua_isnumber (L, -2)) throw std::string ("Error: '") + function + "' did not return a success indicator"; + if (!lua_isnumber (L, -2)) + throw std::string ("Error: '") + function + "' did not return a success indicator"; -// TODO This doesn't seem to know that 'nil' was returned instead of a string. -// if (!lua_isstring (L, -1)) throw std::string ("Error: '") + function + "' did not return a message"; + if (!lua_isstring (L, -1) && !lua_isnil (L, -1)) + throw std::string ("Error: '") + function + "' did not return a message or nil"; int rc = lua_tointeger (L, -2); const char* message = lua_tostring (L, -1); From 1cd6d4c7e7960b1f47751c3dd32a42d0296725d0 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 19 Jan 2010 23:01:52 -0500 Subject: [PATCH 10/15] Enhancement - Hooks - Implemented pre-debug, post-debug. - Implemented pre-header, post-header. - Implemented pre-output, post-output. - Implemented pre-footnote, post-footnote. - Implemented pre-gc, post-gc. - Implemented pre-undo, post-undo. - Implemented pre-add-command, post-add-command. --- src/Context.cpp | 8 ++++++++ src/Hooks.cpp | 13 ++++++++++--- src/TDB.cpp | 6 ++++++ src/command.cpp | 2 ++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Context.cpp b/src/Context.cpp index d45e157c4..640c75d4c 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -159,29 +159,37 @@ int Context::run () } // Dump all debug messages. + hooks.trigger ("pre-debug"); if (config.getBoolean ("debug")) foreach (d, debugMessages) if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeDebug (*d) << std::endl; else std::cout << *d << std::endl; + hooks.trigger ("post-debug"); // Dump all headers. + hooks.trigger ("pre-header"); foreach (h, headers) if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeHeader (*h) << std::endl; else std::cout << *h << std::endl; + hooks.trigger ("post-header"); // Dump the report output. + hooks.trigger ("pre-output"); std::cout << output; + hooks.trigger ("post-output"); // Dump all footnotes. + hooks.trigger ("pre-footnote"); foreach (f, footnotes) if (config.getBoolean ("color") || config.getBoolean ("_forcecolor")) std::cout << colorizeFootnote (*f) << std::endl; else std::cout << *f << std::endl; + hooks.trigger ("post-footnote"); hooks.trigger ("pre-exit"); return rc; diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 90715df58..72ef8d840 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -127,9 +127,16 @@ bool Hooks::trigger (const std::string& event) //////////////////////////////////////////////////////////////////////////////// bool Hooks::eventType (const std::string& event, std::string& type) { - if (event == "post-start" || - event == "pre-exit" || - event == "pre-dispatch" || event == "post-dispatch") + if (event == "post-start" || + event == "pre-exit" || + event == "pre-debug" || event == "post-debug" || + event == "pre-header" || event == "post-header" || + event == "pre-footnote" || event == "post-footnote" || + event == "pre-output" || event == "post-output" || + event == "pre-dispatch" || event == "post-dispatch" || + event == "pre-gc" || event == "post-gc" || + event == "pre-undo" || event == "post-undo" || + event == "pre-add-command" || event == "post-add-command") { type = "program"; return true; diff --git a/src/TDB.cpp b/src/TDB.cpp index f2c8d4a4a..49794aca1 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -343,6 +343,7 @@ void TDB::update (const Task& task) int TDB::commit () { Timer t ("TDB::commit"); + context.hooks.trigger ("pre-gc"); int quantity = mNew.size () + mModified.size (); @@ -362,6 +363,7 @@ int TDB::commit () writeUndo (*task, mLocations[0].undo); mNew.clear (); + context.hooks.trigger ("post-gc"); return quantity; } @@ -408,6 +410,7 @@ int TDB::commit () mNew.clear (); } + context.hooks.trigger ("post-gc"); return quantity; } @@ -506,6 +509,7 @@ int TDB::nextId () //////////////////////////////////////////////////////////////////////////////// void TDB::undo () { + context.hooks.trigger ("pre-undo"); Directory location (context.config.get ("data.location")); std::string undoFile = location.data + "/undo.data"; @@ -670,6 +674,7 @@ void TDB::undo () // Rewrite files. File::write (pendingFile, p); File::write (undoFile, u); + context.hooks.trigger ("post-undo"); return; } } @@ -708,6 +713,7 @@ void TDB::undo () } std::cout << "Undo complete." << std::endl; + context.hooks.trigger ("post-undo"); return; } } diff --git a/src/command.cpp b/src/command.cpp index 8c6b6bdeb..e325c7962 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -52,6 +52,7 @@ extern Context context; //////////////////////////////////////////////////////////////////////////////// int handleAdd (std::string &outs) { + context.hooks.trigger ("pre-add-command"); std::stringstream out; context.task.set ("uuid", uuid ()); @@ -112,6 +113,7 @@ int handleAdd (std::string &outs) context.tdb.unlock (); outs = out.str (); + context.hooks.trigger ("post-add-command"); return 0; } From 21d5607af22b5b82340f8adb8bbc23c40fbd7f9a Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Tue, 19 Jan 2010 23:24:24 -0500 Subject: [PATCH 11/15] Unit Tests - post-start, pre-exit - Implemented unit tests for the post-start and pre-exit hooks. --- src/tests/hook.post-start.t | 67 +++++++++++++++++++++++++++++++++++++ src/tests/hook.pre-exit.t | 67 +++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100755 src/tests/hook.post-start.t create mode 100755 src/tests/hook.pre-exit.t diff --git a/src/tests/hook.post-start.t b/src/tests/hook.post-start.t new file mode 100755 index 000000000..a9e83f0ee --- /dev/null +++ b/src/tests/hook.post-start.t @@ -0,0 +1,67 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2010, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 7; + +# Create the rc file. +if (open my $fh, '>', 'hook.rc') +{ + print $fh "data.location=.\n", + "hook.post-start=" . $ENV{'PWD'} . "/hook:test\n"; + close $fh; + ok (-r 'hook.rc', 'Created hook.rc'); +} + +if (open my $fh, '>', 'hook') +{ + print $fh "function test () print ('marker') return 0, nil end\n"; + close $fh; + ok (-r 'hook', 'Created hook'); +} + +# Test the hook. +my $output = qx{../task rc:hook.rc _version}; +like ($output, qr/^marker.+\n\d\.\d+\.\d+\n$/ms, 'Found marker before output'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'undo.data'; +ok (!-r 'undo.data', 'Removed undo.data'); + +unlink 'hook'; +ok (!-r 'hook', 'Removed hook'); + +unlink 'hook.rc'; +ok (!-r 'hook.rc', 'Removed hook.rc'); + +exit 0; + diff --git a/src/tests/hook.pre-exit.t b/src/tests/hook.pre-exit.t new file mode 100755 index 000000000..9093a9719 --- /dev/null +++ b/src/tests/hook.pre-exit.t @@ -0,0 +1,67 @@ +#! /usr/bin/perl +################################################################################ +## task - a command line task list manager. +## +## Copyright 2006 - 2010, Paul Beckingham. +## All rights reserved. +## +## This program is free software; you can redistribute it and/or modify it under +## the terms of the GNU General Public License as published by the Free Software +## Foundation; either version 2 of the License, or (at your option) any later +## version. +## +## This program is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## this program; if not, write to the +## +## Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, +## Boston, MA +## 02110-1301 +## USA +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 7; + +# Create the rc file. +if (open my $fh, '>', 'hook.rc') +{ + print $fh "data.location=.\n", + "hook.pre-exit=" . $ENV{'PWD'} . "/hook:test\n"; + close $fh; + ok (-r 'hook.rc', 'Created hook.rc'); +} + +if (open my $fh, '>', 'hook') +{ + print $fh "function test () print ('marker') return 0, nil end\n"; + close $fh; + ok (-r 'hook', 'Created hook'); +} + +# Test the hook. +my $output = qx{../task rc:hook.rc _version}; +like ($output, qr/\n\d\.\d+\.\d+\nmarker\n$/ms, 'Found marker after output'); + +# Cleanup. +unlink 'pending.data'; +ok (!-r 'pending.data', 'Removed pending.data'); + +unlink 'undo.data'; +ok (!-r 'undo.data', 'Removed undo.data'); + +unlink 'hook'; +ok (!-r 'hook', 'Removed hook'); + +unlink 'hook.rc'; +ok (!-r 'hook.rc', 'Removed hook.rc'); + +exit 0; + From 03f7e0686f215512cff6a57acedd944d450d6b44 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 23 Jan 2010 12:47:48 -0500 Subject: [PATCH 12/15] Enhancement - Hooks - Implemented API::callTaskHook. - Implemented Hook object inside Hooks.cpp, not Hooks.h. - Implemented Hooks.setTaskId to provide context for task hooks. - Implemented pre-tag, post-tag, pre-detag, post-detag events. - Implemented pre-file-lock, post-file-lock, pre-file-unlock, post-file-unlock events. --- src/API.cpp | 43 ++++++++++++++++++++++++++++++++++----- src/Hooks.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++--- src/Hooks.h | 47 +++++++++++++----------------------------- src/TDB.cpp | 31 ++++++++++++++++------------ src/Task.cpp | 3 +++ src/command.cpp | 17 ++++++++++++---- 6 files changed, 137 insertions(+), 58 deletions(-) diff --git a/src/API.cpp b/src/API.cpp index 566692710..a991e413c 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -539,12 +539,44 @@ bool API::callTaskHook ( { loadFile (file); - // TODO Get function. - // TODO Prepare args. - // TODO Make call. - // TODO Get exit status. + // 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."; + } - return true; + // Prepare args. + lua_pushnumber (L, id); + + // Make call. + if (lua_pcall (L, 1, 2, 0) != 0) + throw std::string ("Error calling '") + function + "' - " + lua_tostring (L, -1); + + // Call successful - get return values. + if (!lua_isnumber (L, -2)) + throw std::string ("Error: '") + function + "' did not return a success indicator"; + + if (!lua_isstring (L, -1) && !lua_isnil (L, -1)) + throw std::string ("Error: '") + function + "' did not return a message or nil"; + + 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 (message); + } + + lua_pop (L, 1); + return rc == 0 ? true : false; } //////////////////////////////////////////////////////////////////////////////// @@ -567,6 +599,7 @@ bool API::callFieldHook ( //////////////////////////////////////////////////////////////////////////////// void API::loadFile (const std::string& file) { + // If the file is not loaded. if (std::find (loaded.begin (), loaded.end (), file) == loaded.end ()) { // Load the file, if possible. diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 72ef8d840..495e41866 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -31,6 +31,43 @@ extern Context context; +//////////////////////////////////////////////////////////////////////////////// +Hook::Hook () +: event ("") +, file ("") +, function ("") +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Hook::Hook (const std::string& e, const std::string& f, const std::string& fn) +: event (e) +, file (f) +, function (fn) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Hook::Hook (const Hook& other) +{ + event = other.event; + file = other.file; + function = other.function; +} + +//////////////////////////////////////////////////////////////////////////////// +Hook& Hook::operator= (const Hook& other) +{ + if (this != &other) + { + event = other.event; + file = other.file; + function = other.function; + } + + return *this; +} + //////////////////////////////////////////////////////////////////////////////// Hooks::Hooks () { @@ -92,6 +129,14 @@ void Hooks::initialize () } } +//////////////////////////////////////////////////////////////////////////////// +void Hooks::setTaskId (int id) +{ +#ifdef HAVE_LIBLUA + task_id = id; +#endif +} + //////////////////////////////////////////////////////////////////////////////// bool Hooks::trigger (const std::string& event) { @@ -105,10 +150,10 @@ bool Hooks::trigger (const std::string& event) std::string type; if (eventType (event, type)) { - // TODO Figure out where to get the calling-context info from. + // Figure out where to get the calling-context info from. if (type == "program") rc = api.callProgramHook (it->file, it->function); else if (type == "list") rc = api.callListHook (it->file, it->function/*, tasks*/); - else if (type == "task") rc = api.callTaskHook (it->file, it->function, 0); + else if (type == "task") rc = api.callTaskHook (it->file, it->function, task_id); else if (type == "field") rc = api.callFieldHook (it->file, it->function, "field", "value"); } else @@ -136,6 +181,8 @@ bool Hooks::eventType (const std::string& event, std::string& type) event == "pre-dispatch" || event == "post-dispatch" || event == "pre-gc" || event == "post-gc" || event == "pre-undo" || event == "post-undo" || + event == "pre-file-lock" || event == "post-file-lock" || + event == "pre-file-unlock" || event == "post-file-unlock" || event == "pre-add-command" || event == "post-add-command") { type = "program"; @@ -146,7 +193,8 @@ bool Hooks::eventType (const std::string& event, std::string& type) type = "list"; return true; } - else if (event == "?") + else if (event == "pre-tag" || event == "post-tag" || + event == "pre-detag" || event == "post-detag") { type = "task"; return true; diff --git a/src/Hooks.h b/src/Hooks.h index bf0c8df93..43c809244 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -32,42 +32,14 @@ #include "API.h" #include "auto.h" -// Hook class representing a single hook. +// Hook class representing a single hook, which is just a three-way map. 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; - } + Hook (); + Hook (const std::string&, const std::string&, const std::string&); + Hook (const Hook&); + Hook& operator= (const Hook&); public: std::string event; @@ -85,7 +57,13 @@ public: Hooks& operator= (const Hooks&); // Deliberately unimplemented void initialize (); + + void setTaskId (int); +// void setField (const std::string&, const std::string&); +// void setTaskList (const std::vector &); bool trigger (const std::string&); + +private: bool eventType (const std::string&, std::string&); private: @@ -93,6 +71,9 @@ private: API api; #endif std::vector all; // All current hooks. +#ifdef HAVE_LIBLUA + int task_id; +#endif }; #endif diff --git a/src/TDB.cpp b/src/TDB.cpp index 49794aca1..baa97ed70 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -121,26 +121,30 @@ void TDB::location (const std::string& path) //////////////////////////////////////////////////////////////////////////////// void TDB::lock (bool lockFile /* = true */) { - mLock = lockFile; - - mPending.clear (); - mNew.clear (); - mPending.clear (); - - foreach (location, mLocations) + if (context.hooks.trigger ("pre-file-lock")) { - location->pending = openAndLock (location->path + "/pending.data"); - location->completed = openAndLock (location->path + "/completed.data"); - location->undo = openAndLock (location->path + "/undo.data"); - } + mLock = lockFile; - mAllOpenAndLocked = true; + mPending.clear (); + mNew.clear (); + mPending.clear (); + + foreach (location, mLocations) + { + location->pending = openAndLock (location->path + "/pending.data"); + location->completed = openAndLock (location->path + "/completed.data"); + location->undo = openAndLock (location->path + "/undo.data"); + } + + mAllOpenAndLocked = true; + context.hooks.trigger ("post-file-lock"); + } } //////////////////////////////////////////////////////////////////////////////// void TDB::unlock () { - if (mAllOpenAndLocked) + if (mAllOpenAndLocked && context.hooks.trigger ("pre-file-unlock")) { mPending.clear (); mNew.clear (); @@ -162,6 +166,7 @@ void TDB::unlock () } mAllOpenAndLocked = false; + context.hooks.trigger ("post-file-unlock"); } } diff --git a/src/Task.cpp b/src/Task.cpp index 27f6258fd..15d51a864 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -27,6 +27,7 @@ #include #include +#include "Context.h" #include "Nibbler.h" #include "Date.h" #include "Duration.h" @@ -34,6 +35,8 @@ #include "text.h" #include "util.h" +extern Context context; + //////////////////////////////////////////////////////////////////////////////// Task::Task () : id (0) diff --git a/src/command.cpp b/src/command.cpp index e325c7962..ee94247c5 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -1701,20 +1701,29 @@ int deltaDescription (Task& task) int deltaTags (Task& task) { int changes = 0; + context.hooks.setTaskId (task.id); // Apply or remove tags, if any. std::vector tags; context.task.getTags (tags); foreach (tag, tags) { - task.addTag (*tag); - ++changes; + if (context.hooks.trigger ("pre-tag")) + { + task.addTag (*tag); + ++changes; + context.hooks.trigger ("post-tag"); + } } foreach (tag, context.tagRemovals) { - task.removeTag (*tag); - ++changes; + if (context.hooks.trigger ("pre-detag")) + { + task.removeTag (*tag); + ++changes; + context.hooks.trigger ("post-detag"); + } } return changes; From b02374c3f59b4a550e093e0c21981e4fc1153d57 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 23 Jan 2010 13:12:49 -0500 Subject: [PATCH 13/15] Enhancement - Hooks - Implemented pre-completed, post-completed events. - Added debug info for event triggers. - Removed support for pre-file-unlock, post-file-unlock, as they are called from TDB::~TDB in Context::~Context, which is after Hooks::~Hooks, which means segfault. --- src/Hooks.cpp | 8 +++++--- src/TDB.cpp | 36 ++++++++++++++++-------------------- src/command.cpp | 32 +++++++++++++++++++++----------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 495e41866..3c9cb28e1 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -150,6 +150,8 @@ bool Hooks::trigger (const std::string& event) std::string type; if (eventType (event, type)) { + context.debug (std::string ("Event ") + event + " triggered"); + // Figure out where to get the calling-context info from. if (type == "program") rc = api.callProgramHook (it->file, it->function); else if (type == "list") rc = api.callListHook (it->file, it->function/*, tasks*/); @@ -182,7 +184,6 @@ bool Hooks::eventType (const std::string& event, std::string& type) event == "pre-gc" || event == "post-gc" || event == "pre-undo" || event == "post-undo" || event == "pre-file-lock" || event == "post-file-lock" || - event == "pre-file-unlock" || event == "post-file-unlock" || event == "pre-add-command" || event == "post-add-command") { type = "program"; @@ -193,8 +194,9 @@ bool Hooks::eventType (const std::string& event, std::string& type) type = "list"; return true; } - else if (event == "pre-tag" || event == "post-tag" || - event == "pre-detag" || event == "post-detag") + else if (event == "pre-tag" || event == "post-tag" || + event == "pre-detag" || event == "post-detag" || + event == "pre-completed" || event == "post-completed") { type = "task"; return true; diff --git a/src/TDB.cpp b/src/TDB.cpp index baa97ed70..4506e0062 100644 --- a/src/TDB.cpp +++ b/src/TDB.cpp @@ -144,30 +144,26 @@ void TDB::lock (bool lockFile /* = true */) //////////////////////////////////////////////////////////////////////////////// void TDB::unlock () { - if (mAllOpenAndLocked && context.hooks.trigger ("pre-file-unlock")) + mPending.clear (); + mNew.clear (); + mModified.clear (); + + foreach (location, mLocations) { - mPending.clear (); - mNew.clear (); - mModified.clear (); + fflush (location->pending); + fclose (location->pending); + location->pending = NULL; - foreach (location, mLocations) - { - fflush (location->pending); - fclose (location->pending); - location->pending = NULL; + fflush (location->completed); + fclose (location->completed); + location->completed = NULL; - fflush (location->completed); - fclose (location->completed); - location->completed = NULL; - - fflush (location->undo); - fclose (location->undo); - location->completed = NULL; - } - - mAllOpenAndLocked = false; - context.hooks.trigger ("post-file-unlock"); + fflush (location->undo); + fclose (location->undo); + location->completed = NULL; } + + mAllOpenAndLocked = false; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/command.cpp b/src/command.cpp index ee94247c5..839d14e5b 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -994,20 +994,28 @@ int handleDone (std::string &outs) if (taskDiff (before, *task)) { - if (permission.confirmed (before, taskDifferences (before, *task) + "Proceed with change?")) + context.hooks.setTaskId (task->id); + if (context.hooks.trigger ("pre-completed")) { - context.tdb.update (*task); + if (permission.confirmed (before, taskDifferences (before, *task) + "Proceed with change?")) + { + context.tdb.update (*task); - if (context.config.getBoolean ("echo.command")) - out << "Completed " - << task->id - << " '" - << task->get ("description") - << "'" - << std::endl; + if (context.config.getBoolean ("echo.command")) + out << "Completed " + << task->id + << " '" + << task->get ("description") + << "'" + << std::endl; - ++count; + ++count; + } + + context.hooks.trigger ("post-completed"); } + else + continue; } updateRecurrenceMask (all, *task); @@ -1024,7 +1032,9 @@ int handleDone (std::string &outs) rc = 1; } - context.tdb.commit (); + if (count) + context.tdb.commit (); + context.tdb.unlock (); if (context.config.getBoolean ("echo.command")) From d6daa336ca85862a12b8a874d25b121cf11a2f50 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 23 Jan 2010 13:43:50 -0500 Subject: [PATCH 14/15] Enhancement - Hooks - Implemented pre-delete, post-delete events. - Implemented pre-delete-command, post-delete-command events. --- src/Hooks.cpp | 24 ++++--- src/command.cpp | 178 ++++++++++++++++++++++++++---------------------- 2 files changed, 108 insertions(+), 94 deletions(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 3c9cb28e1..e2f8f9bf1 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -174,17 +174,18 @@ bool Hooks::trigger (const std::string& event) //////////////////////////////////////////////////////////////////////////////// bool Hooks::eventType (const std::string& event, std::string& type) { - if (event == "post-start" || - event == "pre-exit" || - event == "pre-debug" || event == "post-debug" || - event == "pre-header" || event == "post-header" || - event == "pre-footnote" || event == "post-footnote" || - event == "pre-output" || event == "post-output" || - event == "pre-dispatch" || event == "post-dispatch" || - event == "pre-gc" || event == "post-gc" || - event == "pre-undo" || event == "post-undo" || - event == "pre-file-lock" || event == "post-file-lock" || - event == "pre-add-command" || event == "post-add-command") + if (event == "post-start" || + event == "pre-exit" || + event == "pre-debug" || event == "post-debug" || + event == "pre-header" || event == "post-header" || + event == "pre-footnote" || event == "post-footnote" || + event == "pre-output" || event == "post-output" || + event == "pre-dispatch" || event == "post-dispatch" || + event == "pre-gc" || event == "post-gc" || + event == "pre-undo" || event == "post-undo" || + event == "pre-file-lock" || event == "post-file-lock" || + event == "pre-add-command" || event == "post-add-command" || + event == "pre-delete-command" || event == "post-delete-command") { type = "program"; return true; @@ -196,6 +197,7 @@ bool Hooks::eventType (const std::string& event, std::string& type) } else if (event == "pre-tag" || event == "post-tag" || event == "pre-detag" || event == "post-detag" || + event == "pre-delete" || event == "post-delete" || event == "pre-completed" || event == "post-completed") { type = "task"; diff --git a/src/command.cpp b/src/command.cpp index 839d14e5b..6aa1ea26b 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -742,104 +742,116 @@ int handleConfig (std::string &outs) int handleDelete (std::string &outs) { int rc = 0; - std::stringstream out; - context.disallowModification (); - - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - Filter filter; - context.tdb.loadPending (tasks, filter); - - // Filter sequence. - std::vector all = tasks; - context.filter.applySequence (tasks, context.sequence); - - // Determine the end date. - char endTime[16]; - sprintf (endTime, "%u", (unsigned int) time (NULL)); - - foreach (task, tasks) + if (context.hooks.trigger ("pre-delete-command")) { - std::stringstream question; - question << "Permanently delete task " - << task->id - << " '" - << task->get ("description") - << "'?"; + std::stringstream out; - if (!context.config.getBoolean ("confirmation") || confirm (question.str ())) + context.disallowModification (); + + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + Filter filter; + context.tdb.loadPending (tasks, filter); + + // Filter sequence. + std::vector all = tasks; + context.filter.applySequence (tasks, context.sequence); + + // Determine the end date. + char endTime[16]; + sprintf (endTime, "%u", (unsigned int) time (NULL)); + + foreach (task, tasks) { - // Check for the more complex case of a recurring task. If this is a - // recurring task, get confirmation to delete them all. - std::string parent = task->get ("parent"); - if (parent != "") + context.hooks.setTaskId (task->id); + if (context.hooks.trigger ("pre-delete")) { - if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?")) - { - // Scan all pending tasks for siblings of this task, and the parent - // itself, and delete them. - foreach (sibling, all) - { - if (sibling->get ("parent") == parent || - sibling->get ("uuid") == parent) - { - sibling->setStatus (Task::deleted); - sibling->set ("end", endTime); - context.tdb.update (*sibling); + std::stringstream question; + question << "Permanently delete task " + << task->id + << " '" + << task->get ("description") + << "'?"; - if (context.config.getBoolean ("echo.command")) - out << "Deleting recurring task " - << sibling->id - << " '" - << sibling->get ("description") - << "'" - << std::endl; + if (!context.config.getBoolean ("confirmation") || confirm (question.str ())) + { + // Check for the more complex case of a recurring task. If this is a + // recurring task, get confirmation to delete them all. + std::string parent = task->get ("parent"); + if (parent != "") + { + if (confirm ("This is a recurring task. Do you want to delete all pending recurrences of this same task?")) + { + // Scan all pending tasks for siblings of this task, and the parent + // itself, and delete them. + foreach (sibling, all) + { + if (sibling->get ("parent") == parent || + sibling->get ("uuid") == parent) + { + sibling->setStatus (Task::deleted); + sibling->set ("end", endTime); + context.tdb.update (*sibling); + + if (context.config.getBoolean ("echo.command")) + out << "Deleting recurring task " + << sibling->id + << " '" + << sibling->get ("description") + << "'" + << std::endl; + } + } + } + else + { + // Update mask in parent. + task->setStatus (Task::deleted); + updateRecurrenceMask (all, *task); + + task->set ("end", endTime); + context.tdb.update (*task); + + out << "Deleting recurring task " + << task->id + << " '" + << task->get ("description") + << "'" + << std::endl; } } + else + { + task->setStatus (Task::deleted); + task->set ("end", endTime); + context.tdb.update (*task); + + if (context.config.getBoolean ("echo.command")) + out << "Deleting task " + << task->id + << " '" + << task->get ("description") + << "'" + << std::endl; + } } - else - { - // Update mask in parent. - task->setStatus (Task::deleted); - updateRecurrenceMask (all, *task); - - task->set ("end", endTime); - context.tdb.update (*task); - - out << "Deleting recurring task " - << task->id - << " '" - << task->get ("description") - << "'" - << std::endl; + else { + out << "Task not deleted." << std::endl; + rc = 1; } - } - else - { - task->setStatus (Task::deleted); - task->set ("end", endTime); - context.tdb.update (*task); - if (context.config.getBoolean ("echo.command")) - out << "Deleting task " - << task->id - << " '" - << task->get ("description") - << "'" - << std::endl; + context.hooks.trigger ("post-delete"); } } - else { - out << "Task not deleted." << std::endl; - rc = 1; - } + + context.tdb.commit (); + context.tdb.unlock (); + + outs = out.str (); + context.hooks.trigger ("post-delete-command"); } - context.tdb.commit (); - context.tdb.unlock (); - - outs = out.str (); return rc; } From c8d208b9be9b2a4ab035d16cdab19e52fd3e66cb Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 23 Jan 2010 13:57:38 -0500 Subject: [PATCH 15/15] Enhancement - Hooks - Implemented pre-info-command, post-info-command hook. --- src/Hooks.cpp | 3 +- src/command.cpp | 101 ++++++------ src/report.cpp | 398 ++++++++++++++++++++++++------------------------ 3 files changed, 256 insertions(+), 246 deletions(-) diff --git a/src/Hooks.cpp b/src/Hooks.cpp index e2f8f9bf1..80b2bfe3d 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -185,7 +185,8 @@ bool Hooks::eventType (const std::string& event, std::string& type) event == "pre-undo" || event == "post-undo" || event == "pre-file-lock" || event == "post-file-lock" || event == "pre-add-command" || event == "post-add-command" || - event == "pre-delete-command" || event == "post-delete-command") + event == "pre-delete-command" || event == "post-delete-command" || + event == "pre-info-command" || event == "post-info-command") { type = "program"; return true; diff --git a/src/command.cpp b/src/command.cpp index 6aa1ea26b..3b47e6877 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -52,68 +52,71 @@ extern Context context; //////////////////////////////////////////////////////////////////////////////// int handleAdd (std::string &outs) { - context.hooks.trigger ("pre-add-command"); - std::stringstream out; - - context.task.set ("uuid", uuid ()); - context.task.setEntry (); - - // Recurring tasks get a special status. - if (context.task.has ("due") && - context.task.has ("recur")) + if (context.hooks.trigger ("pre-add-command")) { - context.task.setStatus (Task::recurring); - context.task.set ("mask", ""); - } - else if (context.task.has ("wait")) - context.task.setStatus (Task::waiting); - else - context.task.setStatus (Task::pending); + std::stringstream out; - // Override with default.project, if not specified. - if (context.task.get ("project") == "") - context.task.set ("project", context.config.get ("default.project")); + context.task.set ("uuid", uuid ()); + context.task.setEntry (); - // Override with default.priority, if not specified. - if (context.task.get ("priority") == "") - { - std::string defaultPriority = context.config.get ("default.priority"); - if (Att::validNameValue ("priority", "", defaultPriority)) - context.task.set ("priority", defaultPriority); - } + // Recurring tasks get a special status. + if (context.task.has ("due") && + context.task.has ("recur")) + { + context.task.setStatus (Task::recurring); + context.task.set ("mask", ""); + } + else if (context.task.has ("wait")) + context.task.setStatus (Task::waiting); + else + context.task.setStatus (Task::pending); - // Include tags. - foreach (tag, context.tagAdditions) - context.task.addTag (*tag); + // Override with default.project, if not specified. + if (context.task.get ("project") == "") + context.task.set ("project", context.config.get ("default.project")); - // Perform some logical consistency checks. - if (context.task.has ("recur") && - !context.task.has ("due")) - throw std::string ("You cannot specify a recurring task without a due date."); + // Override with default.priority, if not specified. + if (context.task.get ("priority") == "") + { + std::string defaultPriority = context.config.get ("default.priority"); + if (Att::validNameValue ("priority", "", defaultPriority)) + context.task.set ("priority", defaultPriority); + } - if (context.task.has ("until") && - !context.task.has ("recur")) - throw std::string ("You cannot specify an until date for a non-recurring task."); + // Include tags. + foreach (tag, context.tagAdditions) + context.task.addTag (*tag); - // Only valid tasks can be added. - context.task.validate (); + // Perform some logical consistency checks. + if (context.task.has ("recur") && + !context.task.has ("due")) + throw std::string ("You cannot specify a recurring task without a due date."); - context.tdb.lock (context.config.getBoolean ("locking")); - context.tdb.add (context.task); + if (context.task.has ("until") && + !context.task.has ("recur")) + throw std::string ("You cannot specify an until date for a non-recurring task."); + + // Only valid tasks can be added. + context.task.validate (); + + context.tdb.lock (context.config.getBoolean ("locking")); + context.tdb.add (context.task); #ifdef FEATURE_NEW_ID - // All this, just for an id number. - std::vector all; - Filter none; - context.tdb.loadPending (all, none); - out << "Created task " << context.tdb.nextId () << std::endl; + // All this, just for an id number. + std::vector all; + Filter none; + context.tdb.loadPending (all, none); + out << "Created task " << context.tdb.nextId () << std::endl; #endif - context.tdb.commit (); - context.tdb.unlock (); + context.tdb.commit (); + context.tdb.unlock (); + + outs = out.str (); + context.hooks.trigger ("post-add-command"); + } - outs = out.str (); - context.hooks.trigger ("post-add-command"); return 0; } diff --git a/src/report.cpp b/src/report.cpp index 69708aa15..18c0d17fd 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -300,231 +300,237 @@ int longUsage (std::string &outs) int handleInfo (std::string &outs) { int rc = 0; - // Get all the tasks. - std::vector tasks; - context.tdb.lock (context.config.getBoolean ("locking")); - handleRecurrence (); - context.tdb.loadPending (tasks, context.filter); - context.tdb.commit (); - context.tdb.unlock (); - // Filter sequence. - context.filter.applySequence (tasks, context.sequence); - - // Find the task. - std::stringstream out; - foreach (task, tasks) + if (context.hooks.trigger ("pre-info-command")) { - Table table; - table.setTableWidth (context.getWidth ()); - table.setDateFormat (context.config.get ("dateformat")); + // Get all the tasks. + std::vector tasks; + context.tdb.lock (context.config.getBoolean ("locking")); + handleRecurrence (); + context.tdb.loadPending (tasks, context.filter); + context.tdb.commit (); + context.tdb.unlock (); - table.addColumn ("Name"); - table.addColumn ("Value"); + // Filter sequence. + context.filter.applySequence (tasks, context.sequence); - if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && - context.config.getBoolean ("fontunderline")) + // Find the task. + std::stringstream out; + foreach (task, tasks) { - table.setColumnUnderline (0); - table.setColumnUnderline (1); - } - else - table.setTableDashedUnderline (); + Table table; + table.setTableWidth (context.getWidth ()); + table.setDateFormat (context.config.get ("dateformat")); - table.setColumnWidth (0, Table::minimum); - table.setColumnWidth (1, Table::flexible); + table.addColumn ("Name"); + table.addColumn ("Value"); - table.setColumnJustification (0, Table::left); - table.setColumnJustification (1, Table::left); - Date now; - - int row = table.addRow (); - table.addCell (row, 0, "ID"); - table.addCell (row, 1, task->id); - - std::string status = ucFirst (Task::statusToText (task->getStatus ())); - - if (task->has ("parent")) - status += " (Recurring)"; - - row = table.addRow (); - table.addCell (row, 0, "Status"); - table.addCell (row, 1, status); - - row = table.addRow (); - table.addCell (row, 0, "Description"); - table.addCell (row, 1, getFullDescription (*task)); - - if (task->has ("project")) - { - row = table.addRow (); - table.addCell (row, 0, "Project"); - table.addCell (row, 1, task->get ("project")); - } - - if (task->has ("priority")) - { - row = table.addRow (); - table.addCell (row, 0, "Priority"); - table.addCell (row, 1, task->get ("priority")); - } - - if (task->getStatus () == Task::recurring || - task->has ("parent")) - { - if (task->has ("recur")) + if ((context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) && + context.config.getBoolean ("fontunderline")) { - row = table.addRow (); - table.addCell (row, 0, "Recurrence"); - table.addCell (row, 1, task->get ("recur")); + table.setColumnUnderline (0); + table.setColumnUnderline (1); } + else + table.setTableDashedUnderline (); - if (task->has ("until")) - { - row = table.addRow (); - table.addCell (row, 0, "Recur until"); - table.addCell (row, 1, task->get ("until")); - } + table.setColumnWidth (0, Table::minimum); + table.setColumnWidth (1, Table::flexible); - if (task->has ("mask")) - { - row = table.addRow (); - table.addCell (row, 0, "Mask"); - table.addCell (row, 1, task->get ("mask")); - } + table.setColumnJustification (0, Table::left); + table.setColumnJustification (1, Table::left); + Date now; + + int row = table.addRow (); + table.addCell (row, 0, "ID"); + table.addCell (row, 1, task->id); + + std::string status = ucFirst (Task::statusToText (task->getStatus ())); if (task->has ("parent")) + status += " (Recurring)"; + + row = table.addRow (); + table.addCell (row, 0, "Status"); + table.addCell (row, 1, status); + + row = table.addRow (); + table.addCell (row, 0, "Description"); + table.addCell (row, 1, getFullDescription (*task)); + + if (task->has ("project")) { row = table.addRow (); - table.addCell (row, 0, "Parent task"); - table.addCell (row, 1, task->get ("parent")); + table.addCell (row, 0, "Project"); + table.addCell (row, 1, task->get ("project")); } - row = table.addRow (); - table.addCell (row, 0, "Mask Index"); - table.addCell (row, 1, task->get ("imask")); - } - - // due (colored) - bool imminent = false; - bool overdue = false; - if (task->has ("due")) - { - row = table.addRow (); - table.addCell (row, 0, "Due"); - - Date dt (atoi (task->get ("due").c_str ())); - std::string format = context.config.get ("reportdateformat"); - if (format == "") - format = context.config.get ("dateformat"); - - std::string due = getDueDate (*task, format); - table.addCell (row, 1, due); - - overdue = (dt < now) ? true : false; - int imminentperiod = context.config.getInteger ("due"); - Date imminentDay = now + imminentperiod * 86400; - imminent = dt < imminentDay ? true : false; - - if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + if (task->has ("priority")) { - if (overdue) - table.setCellColor (row, 1, Color (context.config.get ("color.overdue"))); - else if (imminent) - table.setCellColor (row, 1, Color (context.config.get ("color.due"))); + row = table.addRow (); + table.addCell (row, 0, "Priority"); + table.addCell (row, 1, task->get ("priority")); } - } - // wait - if (task->has ("wait")) - { + if (task->getStatus () == Task::recurring || + task->has ("parent")) + { + if (task->has ("recur")) + { + row = table.addRow (); + table.addCell (row, 0, "Recurrence"); + table.addCell (row, 1, task->get ("recur")); + } + + if (task->has ("until")) + { + row = table.addRow (); + table.addCell (row, 0, "Recur until"); + table.addCell (row, 1, task->get ("until")); + } + + if (task->has ("mask")) + { + row = table.addRow (); + table.addCell (row, 0, "Mask"); + table.addCell (row, 1, task->get ("mask")); + } + + if (task->has ("parent")) + { + row = table.addRow (); + table.addCell (row, 0, "Parent task"); + table.addCell (row, 1, task->get ("parent")); + } + + row = table.addRow (); + table.addCell (row, 0, "Mask Index"); + table.addCell (row, 1, task->get ("imask")); + } + + // due (colored) + bool imminent = false; + bool overdue = false; + if (task->has ("due")) + { + row = table.addRow (); + table.addCell (row, 0, "Due"); + + Date dt (atoi (task->get ("due").c_str ())); + std::string format = context.config.get ("reportdateformat"); + if (format == "") + format = context.config.get ("dateformat"); + + std::string due = getDueDate (*task, format); + table.addCell (row, 1, due); + + overdue = (dt < now) ? true : false; + int imminentperiod = context.config.getInteger ("due"); + Date imminentDay = now + imminentperiod * 86400; + imminent = dt < imminentDay ? true : false; + + if (context.config.getBoolean ("color") || context.config.getBoolean ("_forcecolor")) + { + if (overdue) + table.setCellColor (row, 1, Color (context.config.get ("color.overdue"))); + else if (imminent) + table.setCellColor (row, 1, Color (context.config.get ("color.due"))); + } + } + + // wait + if (task->has ("wait")) + { + row = table.addRow (); + table.addCell (row, 0, "Waiting until"); + Date dt (atoi (task->get ("wait").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + } + + // start + if (task->has ("start")) + { + row = table.addRow (); + table.addCell (row, 0, "Start"); + Date dt (atoi (task->get ("start").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + } + + // end + if (task->has ("end")) + { + row = table.addRow (); + table.addCell (row, 0, "End"); + Date dt (atoi (task->get ("end").c_str ())); + table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + } + + // tags ... + std::vector tags; + task->getTags (tags); + if (tags.size ()) + { + std::string allTags; + join (allTags, " ", tags); + + row = table.addRow (); + table.addCell (row, 0, "Tags"); + table.addCell (row, 1, allTags); + } + + // uuid row = table.addRow (); - table.addCell (row, 0, "Waiting until"); - Date dt (atoi (task->get ("wait").c_str ())); - table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); - } + table.addCell (row, 0, "UUID"); + table.addCell (row, 1, task->get ("uuid")); - // start - if (task->has ("start")) - { + // entry row = table.addRow (); - table.addCell (row, 0, "Start"); - Date dt (atoi (task->get ("start").c_str ())); - table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + table.addCell (row, 0, "Entered"); + Date dt (atoi (task->get ("entry").c_str ())); + std::string entry = dt.toString (context.config.get ("dateformat")); + + std::string age; + std::string created = task->get ("entry"); + if (created.length ()) + { + Date dt (atoi (created.c_str ())); + age = formatSeconds ((time_t) (now - dt)); + } + + table.addCell (row, 1, entry + " (" + age + ")"); + + // fg + std::string color = task->get ("fg"); + if (color != "") + { + row = table.addRow (); + table.addCell (row, 0, "Foreground color"); + table.addCell (row, 1, color); + } + + // bg + color = task->get ("bg"); + if (color != "") + { + row = table.addRow (); + table.addCell (row, 0, "Background color"); + table.addCell (row, 1, color); + } + + out << optionalBlankLine () + << table.render () + << std::endl; } - // end - if (task->has ("end")) - { - row = table.addRow (); - table.addCell (row, 0, "End"); - Date dt (atoi (task->get ("end").c_str ())); - table.addCell (row, 1, dt.toString (context.config.get ("dateformat"))); + if (! tasks.size ()) { + out << "No matches." << std::endl; + rc = 1; } - // tags ... - std::vector tags; - task->getTags (tags); - if (tags.size ()) - { - std::string allTags; - join (allTags, " ", tags); - - row = table.addRow (); - table.addCell (row, 0, "Tags"); - table.addCell (row, 1, allTags); - } - - // uuid - row = table.addRow (); - table.addCell (row, 0, "UUID"); - table.addCell (row, 1, task->get ("uuid")); - - // entry - row = table.addRow (); - table.addCell (row, 0, "Entered"); - Date dt (atoi (task->get ("entry").c_str ())); - std::string entry = dt.toString (context.config.get ("dateformat")); - - std::string age; - std::string created = task->get ("entry"); - if (created.length ()) - { - Date dt (atoi (created.c_str ())); - age = formatSeconds ((time_t) (now - dt)); - } - - table.addCell (row, 1, entry + " (" + age + ")"); - - // fg - std::string color = task->get ("fg"); - if (color != "") - { - row = table.addRow (); - table.addCell (row, 0, "Foreground color"); - table.addCell (row, 1, color); - } - - // bg - color = task->get ("bg"); - if (color != "") - { - row = table.addRow (); - table.addCell (row, 0, "Background color"); - table.addCell (row, 1, color); - } - - out << optionalBlankLine () - << table.render () - << std::endl; + outs = out.str (); + context.hooks.trigger ("post-info-command"); } - if (! tasks.size ()) { - out << "No matches." << std::endl; - rc = 1; - } - - outs = out.str (); return rc; }