From 9603864924ef573269515c2873e62ee380f29b84 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Fri, 24 Jun 2011 00:58:26 -0400 Subject: [PATCH] Commands - add, log - Implemented add and log commands using new infrastructure. - Relaxed restriction about creating completed tasks with no dependencies. - Localized CmdAdd.cpp CmdLog.cpp. - Implemented Command::modify_task to apply command line arguments to a specified task. - Implemented Command::apply_defaults to apply various initial values to a specified task, such as entry date. --- src/Arguments.h | 1 - src/commands/CmdAdd.cpp | 92 +++++--------------------- src/commands/CmdLog.cpp | 82 +++++++++-------------- src/commands/Command.cpp | 139 ++++++++++++++++++++++++++++++++++++++- src/commands/Command.h | 4 ++ src/en-US.h | 4 ++ 6 files changed, 193 insertions(+), 129 deletions(-) diff --git a/src/Arguments.h b/src/Arguments.h index e7d1999c4..6637035da 100644 --- a/src/Arguments.h +++ b/src/Arguments.h @@ -72,7 +72,6 @@ public: static bool is_modifier (const std::string&); static bool is_expression (const std::string&); - // TODO Decide if these are really useful. static bool extract_attr (const std::string&, std::string&, std::string&); static bool extract_attmod (const std::string&, std::string&, std::string&, std::string&, std::string&); static bool extract_subst (const std::string&, std::string&, std::string&, bool&); diff --git a/src/commands/CmdAdd.cpp b/src/commands/CmdAdd.cpp index 412241479..b7a3b88f9 100644 --- a/src/commands/CmdAdd.cpp +++ b/src/commands/CmdAdd.cpp @@ -25,10 +25,13 @@ // //////////////////////////////////////////////////////////////////////////////// +#define L10N // Localization complete. + #include #include #include #include +#include #include #include #include @@ -40,7 +43,7 @@ CmdAdd::CmdAdd () { _keyword = "add"; _usage = "task add [tags] [attrs] desc..."; - _description = "Adds a new task."; + _description = STRING_CMD_ADD_USAGE; _read_only = false; _displays_id = false; } @@ -49,100 +52,39 @@ CmdAdd::CmdAdd () int CmdAdd::execute (std::string& output) { int rc = 0; -/* - 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")) - { - context.task.setStatus (Task::recurring); - context.task.set ("mask", ""); - } - - // Tasks with a wait: date get a special status. - else if (context.task.has ("wait")) - context.task.setStatus (Task::waiting); - - // By default, tasks are pending. - else - context.task.setStatus (Task::pending); - - // Override with default.project, if not specified. - if (context.task.get ("project") == "") - context.task.set ("project", context.config.get ("default.project")); - - // 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); - } - - // Override with default.due, if not specified. - if (context.task.get ("due") == "") - { - std::string defaultDue = context.config.get ("default.due"); - if (defaultDue != "" && - Att::validNameValue ("due", "", defaultDue)) - context.task.set ("due", defaultDue); - } - - // Include tags. - std::vector ::iterator tag; - for (tag = context.tagAdditions.begin (); - tag != context.tagAdditions.end (); - ++tag) - context.task.addTag (*tag); // Must load pending to resolve dependencies, and to provide a new ID. context.tdb.lock (context.config.getBoolean ("locking")); std::vector all; - Filter none; - context.tdb.loadPending (all, none); + context.tdb.loadPending (all); - // Resolve dependencies. - if (context.task.has ("depends")) - { - // Convert ID to UUID. - std::vector deps; - split (deps, context.task.get ("depends"), ','); + // Every task needs a UUID. + Task task; + task.set ("uuid", uuid ()); - // Eliminate the ID-based set. - context.task.set ("depends", ""); - - std::vector ::iterator i; - for (i = deps.begin (); i != deps.end (); i++) - { - int id = strtol (i->c_str (), NULL, 10); - if (id < 0) - context.task.removeDependency (-id); - else - context.task.addDependency (id); - } - } + // Apply the command line modifications to the new task. + Arguments modifications = context.args.extract_modifications (); + modify_task (task, modifications); + apply_defaults (task); // Only valid tasks can be added. - context.task.validate (); + task.validate (); - context.tdb.add (context.task); + context.tdb.add (task); + std::stringstream out; + // TODO This should be a call in to feedback.cpp. #ifdef FEATURE_NEW_ID out << "Created task " << context.tdb.nextId () << ".\n"; #endif - context.footnote (onProjectChange (context.task)); + context.footnote (onProjectChange (task)); context.tdb.commit (); context.tdb.unlock (); output = out.str (); -*/ return rc; } diff --git a/src/commands/CmdLog.cpp b/src/commands/CmdLog.cpp index 62403dd0d..92cb3b692 100644 --- a/src/commands/CmdLog.cpp +++ b/src/commands/CmdLog.cpp @@ -25,9 +25,12 @@ // //////////////////////////////////////////////////////////////////////////////// +#define L10N // Localization complete. + #include #include #include +#include #include #include #include @@ -39,7 +42,7 @@ CmdLog::CmdLog () { _keyword = "log"; _usage = "task log [tags] [attrs] desc..."; - _description = "Adds a new task that is already completed."; + _description = STRING_CMD_LOG_USAGE; _read_only = false; _displays_id = false; } @@ -48,70 +51,49 @@ CmdLog::CmdLog () int CmdLog::execute (std::string& output) { int rc = 0; -/* - std::stringstream out; - context.task.setStatus (Task::completed); - context.task.set ("uuid", uuid ()); - context.task.setEntry (); + // Must load pending to resolve dependencies, and to provide a new ID. + context.tdb.lock (context.config.getBoolean ("locking")); - // Add an end date. - char entryTime[16]; - sprintf (entryTime, "%u", (unsigned int) time (NULL)); - context.task.set ("end", entryTime); + std::vector all; + context.tdb.loadPending (all); + + // Every task needs a UUID. + Task task; + task.set ("uuid", uuid ()); + + // Apply the command line modifications to the new task. + Arguments modifications = context.args.extract_modifications (); + modify_task (task, modifications); + apply_defaults (task); // Recurring tasks get a special status. - if (context.task.has ("recur")) + if (task.has ("recur")) throw std::string ("You cannot log recurring tasks."); - if (context.task.has ("wait")) + if (task.has ("wait")) throw std::string ("You cannot log waiting tasks."); - // It makes no sense to add dependencies to an already-completed task. - if (context.task.get ("depends") != "") - throw std::string ("You cannot specify dependencies on a completed task."); + // Override with log-specific changes. + task.setStatus (Task::completed); - // Override with default.project, if not specified. - if (context.task.get ("project") == "") - context.task.set ("project", context.config.get ("default.project")); - - // 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); - } - - // Override with default.due, if not specified. - if (context.task.get ("due") == "") - { - std::string defaultDue = context.config.get ("default.due"); - if (defaultDue != "" && - Att::validNameValue ("due", "", defaultDue)) - context.task.set ("due", defaultDue); - } - - // Include tags. - std::vector ::iterator tag; - for (tag = tagAdditions.begin 90; tag != tagAdditions.end (); ++tag) - context.task.addTag (*tag); + // Provide an end date unless user already specified one. + if (task.get ("end") == "") + task.set ("end", task.get ("entry")); // Only valid tasks can be added. - context.task.validate (); + task.validate (); + + context.tdb.add (task); + + context.footnote (onProjectChange (task)); - context.tdb.lock (context.config.getBoolean ("locking")); - context.tdb.add (context.task); context.tdb.commit (); - - if (context.config.getBoolean ("echo.command")) - out << "Logged task.\n"; - - context.footnote (onProjectChange (context.task)); context.tdb.unlock (); - output = out.str (); -*/ + if (context.config.getBoolean ("echo.command")) + output = "Logged task.\n"; + return rc; } diff --git a/src/commands/Command.cpp b/src/commands/Command.cpp index f0e433e44..f2ea8fdd2 100644 --- a/src/commands/Command.cpp +++ b/src/commands/Command.cpp @@ -25,10 +25,15 @@ // //////////////////////////////////////////////////////////////////////////////// +#define L10N // Localization complete. + #include #include #include +#include #include +#include +#include #include #include @@ -77,6 +82,10 @@ #include #include +#include +#include +#include + extern Context context; //////////////////////////////////////////////////////////////////////////////// @@ -162,9 +171,7 @@ void Command::factory (std::map & all) { // Make sure a custom report does not clash with a built-in command. if (all.find (*report) != all.end ()) - throw std::string ("Custom report '") - + *report - + "' conflicts with built-in task command."; + throw format (STRING_CMD_CONFLICT, *report); c = new CmdCustom ( *report, @@ -271,3 +278,129 @@ void Command::filter (std::vector & input, std::vector & output) } //////////////////////////////////////////////////////////////////////////////// +// Apply the modifications in arguments to the task. +void Command::modify_task (Task& task, Arguments& arguments) +{ + std::string description; + + std::vector >::iterator arg; + for (arg = arguments.begin (); arg != arguments.end (); ++arg) + { + // Attributes are essentially name:value pairs, and correspond directly + // to stored attributes. + if (arg->second == "attr") + { + std::string name; + std::string value; + Arguments::extract_attr (arg->first, name, value); + + // Dependencies must be resolved to UUIDs. + if (name == "depends") + { + // Convert ID to UUID. + std::vector deps; + split (deps, value, ','); + + // Apply or remove dendencies in turn. + std::vector ::iterator i; + for (i = deps.begin (); i != deps.end (); i++) + { + int id = strtol (i->c_str (), NULL, 10); + if (id < 0) + task.removeDependency (-id); + else + task.addDependency (id); + } + } + + // By default, just add it. + else + task.set (name, value); + } + + // Tags need special handling because they are essentially a vector stored + // in a single string, therefore Task::{add,remove}Tag must be called as + // appropriate. + else if (arg->second == "tag") + { + char type; + std::string value; + Arguments::extract_tag (arg->first, type, value); + + if (type == '+') + task.addTag (value); + else + task.removeTag (value); + } + + // Words and operators are aggregated into a description. + else if (arg->second == "word" || + arg->second == "op") + { + if (description.length ()) + description += " "; + + description += arg->first; + } + + // Any additional argument types are indicative of a failure in + // Arguments::extract_modifications. + else + throw format (STRING_CMD_MOD_UNEXPECTED, arg->first); + } + + task.set ("description", description); +} + +//////////////////////////////////////////////////////////////////////////////// +void Command::apply_defaults (Task& task) +{ + // Provide an entry date unless user already specified one. + if (task.get ("entry") == "") + task.setEntry (); + + // Recurring tasks get a special status. + if (task.has ("due") && + task.has ("recur")) + { + task.setStatus (Task::recurring); + task.set ("mask", ""); + } + + // Tasks with a wait: date get a special status. + else if (task.has ("wait")) + task.setStatus (Task::waiting); + + // By default, tasks are pending. + else + task.setStatus (Task::pending); + + // Override with default.project, if not specified. + if (task.get ("project") == "") + { + std::string defaultProject = context.config.get ("default.project"); + if (defaultProject != "" && + context.columns["project"]->validate (defaultProject)) + task.set ("project", defaultProject); + } + + // Override with default.priority, if not specified. + if (task.get ("priority") == "") + { + std::string defaultPriority = context.config.get ("default.priority"); + if (defaultPriority != "" && + context.columns["priority"]->validate (defaultPriority)) + task.set ("priority", defaultPriority); + } + + // Override with default.due, if not specified. + if (task.get ("due") == "") + { + std::string defaultDue = context.config.get ("default.due"); + if (defaultDue != "" && + context.columns["due"]->validate (defaultDue)) + task.set ("due", defaultDue); + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/Command.h b/src/commands/Command.h index 7b5123e98..4379551e1 100644 --- a/src/commands/Command.h +++ b/src/commands/Command.h @@ -32,6 +32,7 @@ #include #include #include +#include class Command { @@ -51,7 +52,10 @@ public: bool displays_id () const; virtual int execute (std::string&) = 0; +protected: void filter (std::vector &, std::vector &); + void modify_task (Task&, Arguments&); + void apply_defaults (Task&); protected: std::string _keyword; diff --git a/src/en-US.h b/src/en-US.h index 62b23af6f..c810bbc4f 100644 --- a/src/en-US.h +++ b/src/en-US.h @@ -143,6 +143,7 @@ #define STRING_COLUMN_LABEL_URGENCY "Urgency" // commands/Cmd* +#define STRING_CMD_CONFLICT "Custom report '{1}' conflicts with built-in task command." #define STRING_CMD_VERSION_USAGE "Shows the taskwarrior version number." #define STRING_CMD_VERSION_USAGE2 "Shows only the taskwarrior version number." #define STRING_CMD_VERSION_GPL "Taskwarrior may be copied only under the terms of the GNU General Public License, which may be found in the taskwarrior source kit." @@ -157,6 +158,9 @@ #define STRING_CMD_URGENCY_USAGE "Displays the urgency measure of a task." #define STRING_CMD_URGENCY_NO_TASKS "No tasks specified." #define STRING_CMD_URGENCY_RESULT "task {1} urgency {2}" +#define STRING_CMD_ADD_USAGE "Adds a new task." +#define STRING_CMD_MOD_UNEXPECTED "Unexpected argument '{1}' found while modifying a task." +#define STRING_CMD_LOG_USAGE "Adds a new task that is already completed." // Config #define STRING_CONFIG_OVERNEST "Configuration file nested to more than 10 levels deep - this has to be a mistake."