//////////////////////////////////////////////////////////////////////////////// // taskwarrior - a command line task list manager. // // Copyright 2006-2011, Paul Beckingham, Federico Hernandez. // // 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. // // http://www.opensource.org/licenses/mit-license.php // //////////////////////////////////////////////////////////////////////////////// #define L10N // Localization complete. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_EXECUTE #include #endif #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern Context context; //////////////////////////////////////////////////////////////////////////////// void Command::factory (std::map & all) { Command* c; c = new CmdAdd (); all[c->keyword ()] = c; c = new CmdAnnotate (); all[c->keyword ()] = c; c = new CmdAppend (); all[c->keyword ()] = c; c = new CmdBurndownDaily (); all[c->keyword ()] = c; c = new CmdBurndownMonthly (); all[c->keyword ()] = c; c = new CmdBurndownWeekly (); all[c->keyword ()] = c; c = new CmdCalendar (); all[c->keyword ()] = c; c = new CmdColor (); all[c->keyword ()] = c; c = new CmdColumns (); all[c->keyword ()] = c; c = new CmdCompletionColumns (); all[c->keyword ()] = c; c = new CmdCompletionCommands (); all[c->keyword ()] = c; c = new CmdCompletionConfig (); all[c->keyword ()] = c; c = new CmdCompletionIds (); all[c->keyword ()] = c; c = new CmdCompletionProjects (); all[c->keyword ()] = c; c = new CmdCompletionTags (); all[c->keyword ()] = c; c = new CmdCompletionVersion (); all[c->keyword ()] = c; c = new CmdConfig (); all[c->keyword ()] = c; c = new CmdCount (); all[c->keyword ()] = c; c = new CmdDelete (); all[c->keyword ()] = c; c = new CmdDenotate (); all[c->keyword ()] = c; c = new CmdDiagnostics (); all[c->keyword ()] = c; c = new CmdDone (); all[c->keyword ()] = c; c = new CmdDuplicate (); all[c->keyword ()] = c; c = new CmdEdit (); all[c->keyword ()] = c; #ifdef HAVE_EXECUTE c = new CmdExec (); all[c->keyword ()] = c; #endif c = new CmdExport (); all[c->keyword ()] = c; c = new CmdGHistoryMonthly (); all[c->keyword ()] = c; c = new CmdGHistoryAnnual (); all[c->keyword ()] = c; c = new CmdHelp (); all[c->keyword ()] = c; c = new CmdHistoryMonthly (); all[c->keyword ()] = c; c = new CmdHistoryAnnual (); all[c->keyword ()] = c; c = new CmdIDs (); all[c->keyword ()] = c; c = new CmdImport (); all[c->keyword ()] = c; c = new CmdInfo (); all[c->keyword ()] = c; // c = new CmdInstall (); all[c->keyword ()] = c; c = new CmdLog (); all[c->keyword ()] = c; c = new CmdLogo (); all[c->keyword ()] = c; c = new CmdMerge (); all[c->keyword ()] = c; c = new CmdModify (); all[c->keyword ()] = c; c = new CmdPrepend (); all[c->keyword ()] = c; c = new CmdProjects (); all[c->keyword ()] = c; c = new CmdPull (); all[c->keyword ()] = c; c = new CmdPush (); all[c->keyword ()] = c; c = new CmdReports (); all[c->keyword ()] = c; c = new CmdShell (); all[c->keyword ()] = c; c = new CmdShow (); all[c->keyword ()] = c; c = new CmdStart (); all[c->keyword ()] = c; c = new CmdStatistics (); all[c->keyword ()] = c; c = new CmdStop (); all[c->keyword ()] = c; c = new CmdSummary (); all[c->keyword ()] = c; c = new CmdSynch (); all[c->keyword ()] = c; c = new CmdTags (); all[c->keyword ()] = c; c = new CmdTimesheet (); all[c->keyword ()] = c; c = new CmdUndo (); all[c->keyword ()] = c; c = new CmdUrgency (); all[c->keyword ()] = c; c = new CmdVersion (); all[c->keyword ()] = c; c = new CmdZshCommands (); all[c->keyword ()] = c; c = new CmdZshCompletionIds (); all[c->keyword ()] = c; // Instantiate a command object for each custom report. std::vector variables; context.config.all (variables); std::vector reports; std::vector ::iterator i; for (i = variables.begin (); i != variables.end (); ++i) { if (i->substr (0, 7) == "report.") { std::string report = i->substr (7); std::string::size_type columns = report.find (".columns"); if (columns != std::string::npos) reports.push_back (report.substr (0, columns)); } } std::vector ::iterator report; for (report = reports.begin (); report != reports.end (); ++report) { // Make sure a custom report does not clash with a built-in command. if (all.find (*report) != all.end ()) throw format (STRING_CMD_CONFLICT, *report); c = new CmdCustom ( *report, "task " + *report, context.config.get ("report." + *report + ".description")); all[c->keyword ()] = c; } } //////////////////////////////////////////////////////////////////////////////// Command::Command () : _usage ("") , _description ("") , _read_only (true) , _displays_id (true) , _needs_confirm (false) , _permission_quit (false) , _permission_all (false) { } //////////////////////////////////////////////////////////////////////////////// Command::Command (const Command& other) { _usage = other._usage; _description = other._description; _read_only = other._read_only; _displays_id = other._displays_id; _needs_confirm = other._needs_confirm; _permission_quit = other._permission_quit; _permission_all = other._permission_all; } //////////////////////////////////////////////////////////////////////////////// Command& Command::operator= (const Command& other) { if (this != &other) { _usage = other._usage; _description = other._description; _read_only = other._read_only; _displays_id = other._displays_id; _needs_confirm = other._needs_confirm; _permission_quit = other._permission_quit; _permission_all = other._permission_all; } return *this; } //////////////////////////////////////////////////////////////////////////////// bool Command::operator== (const Command& other) const { return _usage == other._usage && _description == other._description && _read_only == other._read_only && _displays_id == other._displays_id && _needs_confirm == other._needs_confirm; } //////////////////////////////////////////////////////////////////////////////// Command::~Command () { } //////////////////////////////////////////////////////////////////////////////// std::string Command::keyword () const { return _keyword; } //////////////////////////////////////////////////////////////////////////////// std::string Command::usage () const { return _usage; } //////////////////////////////////////////////////////////////////////////////// std::string Command::description () const { return _description; } //////////////////////////////////////////////////////////////////////////////// bool Command::read_only () const { return _read_only; } //////////////////////////////////////////////////////////////////////////////// bool Command::displays_id () const { return _displays_id; } //////////////////////////////////////////////////////////////////////////////// // Filter a specific list of tasks. void Command::filter (const std::vector & input, std::vector & output) { context.timer_filter.start (); A3 filt = context.a3.extract_filter (); filt.dump ("extract_filter"); if (filt.size ()) { E9 e (filt); std::vector ::const_iterator task; for (task = input.begin (); task != input.end (); ++task) if (e.evalFilter (*task)) output.push_back (*task); } else output = input; context.timer_filter.stop (); } //////////////////////////////////////////////////////////////////////////////// // Filter all tasks. void Command::filter (std::vector & output) { context.timer_filter.start (); A3 filt = context.a3.extract_filter (); filt.dump ("extract_filter"); if (filt.size ()) { context.timer_filter.stop (); const std::vector & pending = context.tdb2.pending.get_tasks (); context.timer_filter.start (); E9 e (filt); output.clear (); std::vector ::const_iterator task; for (task = pending.begin (); task != pending.end (); ++task) if (e.evalFilter (*task)) output.push_back (*task); if (! filter_shortcut (filt)) { context.timer_filter.stop (); const std::vector & completed = context.tdb2.completed.get_tasks (); // TODO Optional context.timer_filter.start (); for (task = completed.begin (); task != completed.end (); ++task) if (e.evalFilter (*task)) output.push_back (*task); } else context.debug ("Command::filter skipping completed.data"); } else { safety (); context.timer_filter.stop (); const std::vector & pending = context.tdb2.pending.get_tasks (); const std::vector & completed = context.tdb2.completed.get_tasks (); context.timer_filter.start (); std::vector ::const_iterator task; for (task = pending.begin (); task != pending.end (); ++task) output.push_back (*task); for (task = completed.begin (); task != completed.end (); ++task) output.push_back (*task); } context.timer_filter.stop (); } //////////////////////////////////////////////////////////////////////////////// // If the filter contains the restriction "status:pending", as the first filter // term, then completed.data does not need to be loaded. bool Command::filter_shortcut (const A3& filter) { // Postfix: <"pending"> <=> // 0 1 2 if (filter.size () >= 3 && filter[0]._raw == "status" && filter[1]._raw.find ("pending") != std::string::npos && filter[2]._raw == "=") return true; return false; } //////////////////////////////////////////////////////////////////////////////// // Apply the modifications in arguments to the task. void Command::modify_task_description_replace (Task& task, const A3& arguments) { std::string description; modify_task (task, arguments, description); if (description.length ()) task.set ("description", description); } //////////////////////////////////////////////////////////////////////////////// void Command::modify_task_description_prepend (Task& task, const A3& arguments) { std::string description; modify_task (task, arguments, description); if (description.length ()) task.set ("description", description + " " + task.get ("description")); } //////////////////////////////////////////////////////////////////////////////// void Command::modify_task_description_append (Task& task, const A3& arguments) { std::string description; modify_task (task, arguments, description); if (description.length ()) task.set ("description", task.get ("description") + " " + description); } //////////////////////////////////////////////////////////////////////////////// void Command::modify_task_annotate (Task& task, const A3& arguments) { std::string description; modify_task (task, arguments, description); if (description.length ()) task.addAnnotation (description); } //////////////////////////////////////////////////////////////////////////////// // Worker function that does all the updates, but never overwrites description. void Command::modify_task ( Task& task, const A3& arguments, std::string& description) { // Coalesce arguments together into sets to be processed as a batch. unsigned int pos = 0; Arg arg; while (next_mod_group (arguments, arg, pos)) { // Attributes are essentially name:value pairs, and correspond directly // to stored attributes. if (arg._category == Arg::cat_attr) { std::string name; std::string value; A3::extract_attr (arg._raw, name, value); if (A3::is_attribute (name, name)) // Canonicalize { //std::cout << "# Command::modify_task name='" << name << "' value='" << value << "'\n"; // Get the column info. Column* column = context.columns[name]; if (value == "") { task.remove (name); } else { // 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); } } // Priorities are converted to upper case. else if (name == "priority") { task.set (name, upperCase (value)); } // Dates are special, maybe. else if (column->type () == "date") { // All values must be eval'd first. A3 value_tokens; value_tokens.capture (value); value_tokens = value_tokens.postfix (value_tokens.tokenize (value_tokens)); E9 e (value_tokens); std::string result = e.evalExpression (task); context.debug (std::string ("Eval '") + value + "' --> '" + result + "'"); // If the date value is less than 5 years, it is a duration, not a // date, therefore add 'now'. long l = strtol (result.c_str (), NULL, 10); if (labs (l) < 5 * 365 * 86400) { Date now; now += l; task.set (name, now.toEpochString ()); } else task.set (name, result); } // By default, just add/remove it. else task.set (name, value); // Warn about deprecated/obsolete attribute usage. legacyAttributeCheck (name); } } else throw format (STRING_CMD_ADD_BAD_ATTRIBUTE, name); } // 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._category == Arg::cat_tag) { char type; std::string value; A3::extract_tag (arg._raw, type, value); if (type == '+') task.addTag (value); else task.removeTag (value); } // Substitutions. else if (arg._category == Arg::cat_subst) { std::string from; std::string to; bool global; A3::extract_subst (arg._raw, from, to, global); task.substitute (from, to, global); } // Anything else is essentially downgraded to 'word' and considered part of // the description. else { if (description.length ()) description += " "; description += arg._raw; } } } //////////////////////////////////////////////////////////////////////////////// // Disaster avoidance mechanism. void Command::safety () { if (! _read_only) { A3 write_filter = context.a3.extract_filter (); if (!write_filter.size ()) // Potential disaster. { // If user is willing to be asked, this can be avoided. if (context.config.getBoolean ("confirmation") && confirm (STRING_TASK_SAFETY_VALVE)) return; // No. throw std::string (STRING_TASK_SAFETY_FAIL); } } } //////////////////////////////////////////////////////////////////////////////// // Returns true or false indicating whether to proceed with a write command, on // a per-task basis, after (potentially) asking for permission. // // Factors: // filtered.size () // rc.bulk // rc.confirmation // this->_read_only bool Command::permission ( const Task& task, const std::string& question, unsigned int quantity) { // Read-only commands do not need to seek permission. Write commands are // granted permission automatically if the 'all' selection was made in an // earlier call. Or if the 'all' option has already been made. if (_read_only || _permission_all) return true; // If the 'quit' selection has already been made. if (_permission_quit) return false; // What remains are write commands that have not yet selected 'all' or 'quit'. // Describe the task. bool confirmation = context.config.getBoolean ("confirmation"); unsigned int bulk = context.config.getInteger ("bulk"); // Quantity 1 modifications have optional confirmation, and only (y/n). if (quantity == 1) { if (!_needs_confirm || !confirmation) return true; bool answer = confirm (question); return answer; } // 1 < Quantity < bulk modifications have optional confirmation, in the (y/n/a/q) // style. if (quantity < bulk && (!_needs_confirm || !confirmation)) return true; int answer = confirm4 (question); std::cout << "\n"; // #499 switch (answer) { case 1: return true; // yes case 2: _permission_all = true; return true; // all case 3: _permission_quit = true; return false; // quit } return false; // This line keeps the compiler happy. } //////////////////////////////////////////////////////////////////////////////// // Special processing for modifications. bool Command::next_mod_group (const A3& input, Arg& arg, unsigned int& pos) { if (pos < input.size ()) { arg = input[pos++]; // Date attributes aggregate durations and operators. if (arg._type == Arg::type_date && arg._category == Arg::cat_attr) { while (pos < input.size () && (input[pos]._type == Arg::type_duration || input[pos]._category == Arg::cat_op)) { arg._raw += " " + input[pos++]._raw; } } else if (arg._raw == "depends") { while (pos < input.size () && (input[pos]._category == Arg::cat_op || input[pos]._type == Arg::type_number)) { arg._raw += input[pos++]._raw; } } return true; } return false; } ////////////////////////////////////////////////////////////////////////////////