mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00

- Added 3 new color configuration variables to colorize today, days with due tasks and days with overdue tasks in the calendar: 'calendar.color.today', 'color.calendar.due' and 'calendar.calendar.overdue'
1668 lines
45 KiB
C++
1668 lines
45 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// 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 <iostream>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <pwd.h>
|
|
#include <time.h>
|
|
|
|
#include "Permission.h"
|
|
#include "text.h"
|
|
#include "util.h"
|
|
#include "main.h"
|
|
#include "../auto.h"
|
|
|
|
#ifdef HAVE_LIBNCURSES
|
|
#include <ncurses.h>
|
|
#endif
|
|
|
|
extern Context context;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleAdd (std::string &outs)
|
|
{
|
|
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", "");
|
|
}
|
|
else if (context.task.has ("wait"))
|
|
context.task.setStatus (Task::waiting);
|
|
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);
|
|
}
|
|
|
|
// Include tags.
|
|
foreach (tag, context.tagAdditions)
|
|
context.task.addTag (*tag);
|
|
|
|
// 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.");
|
|
|
|
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.get ("locking", true));
|
|
context.tdb.add (context.task);
|
|
|
|
#ifdef FEATURE_NEW_ID
|
|
// All this, just for an id number.
|
|
std::vector <Task> all;
|
|
Filter none;
|
|
context.tdb.loadPending (all, none);
|
|
out << "Created task " << context.tdb.nextId () << std::endl;
|
|
#endif
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleProjects (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
context.filter.push_back (Att ("status", "pending"));
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
int quantity = context.tdb.loadPending (tasks, context.filter);
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
// Scan all the tasks for their project name, building a map using project
|
|
// names as keys.
|
|
std::map <std::string, int> unique;
|
|
std::map <std::string, int> high;
|
|
std::map <std::string, int> medium;
|
|
std::map <std::string, int> low;
|
|
std::map <std::string, int> none;
|
|
std::string project;
|
|
std::string priority;
|
|
foreach (t, tasks)
|
|
{
|
|
project = t->get ("project");
|
|
priority = t->get ("priority");
|
|
|
|
unique[project] += 1;
|
|
|
|
if (priority == "H") high[project] += 1;
|
|
else if (priority == "M") medium[project] += 1;
|
|
else if (priority == "L") low[project] += 1;
|
|
else none[project] += 1;
|
|
}
|
|
|
|
if (unique.size ())
|
|
{
|
|
// Render a list of project names from the map.
|
|
Table table;
|
|
table.addColumn ("Project");
|
|
table.addColumn ("Tasks");
|
|
table.addColumn ("Pri:None");
|
|
table.addColumn ("Pri:L");
|
|
table.addColumn ("Pri:M");
|
|
table.addColumn ("Pri:H");
|
|
|
|
if (context.config.get ("color", true) ||
|
|
context.config.get (std::string ("_forcecolor"), false))
|
|
{
|
|
table.setColumnUnderline (0);
|
|
table.setColumnUnderline (1);
|
|
table.setColumnUnderline (2);
|
|
table.setColumnUnderline (3);
|
|
table.setColumnUnderline (4);
|
|
table.setColumnUnderline (5);
|
|
}
|
|
|
|
table.setColumnJustification (1, Table::right);
|
|
table.setColumnJustification (2, Table::right);
|
|
table.setColumnJustification (3, Table::right);
|
|
table.setColumnJustification (4, Table::right);
|
|
table.setColumnJustification (5, Table::right);
|
|
|
|
foreach (i, unique)
|
|
{
|
|
int row = table.addRow ();
|
|
table.addCell (row, 0, i->first);
|
|
table.addCell (row, 1, i->second);
|
|
table.addCell (row, 2, none[i->first]);
|
|
table.addCell (row, 3, low[i->first]);
|
|
table.addCell (row, 4, medium[i->first]);
|
|
table.addCell (row, 5, high[i->first]);
|
|
}
|
|
|
|
out << optionalBlankLine ()
|
|
<< table.render ()
|
|
<< optionalBlankLine ()
|
|
<< unique.size ()
|
|
<< (unique.size () == 1 ? " project" : " projects")
|
|
<< " (" << quantity << (quantity == 1 ? " task" : " tasks") << ")"
|
|
<< std::endl;
|
|
}
|
|
else {
|
|
out << "No projects."
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleCompletionProjects (std::string &outs)
|
|
{
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
|
|
Filter filter;
|
|
if (context.config.get (std::string ("complete.all.projects"), false))
|
|
context.tdb.load (tasks, filter);
|
|
else
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
// Scan all the tasks for their project name, building a map using project
|
|
// names as keys.
|
|
std::map <std::string, int> unique;
|
|
foreach (t, tasks)
|
|
unique[t->get ("project")] = 0;
|
|
|
|
std::stringstream out;
|
|
foreach (project, unique)
|
|
if (project->first.length ())
|
|
out << project->first << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleTags (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
context.filter.push_back (Att ("status", "pending"));
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
int quantity = context.tdb.loadPending (tasks, context.filter);
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
// Scan all the tasks for their project name, building a map using project
|
|
// names as keys.
|
|
std::map <std::string, int> unique;
|
|
foreach (t, tasks)
|
|
{
|
|
std::vector <std::string> tags;
|
|
t->getTags (tags);
|
|
|
|
foreach (tag, tags)
|
|
if (unique.find (*tag) != unique.end ())
|
|
unique[*tag]++;
|
|
else
|
|
unique[*tag] = 1;
|
|
}
|
|
|
|
if (unique.size ())
|
|
{
|
|
// Render a list of tags names from the map.
|
|
Table table;
|
|
table.addColumn ("Tag");
|
|
table.addColumn ("Count");
|
|
|
|
if (context.config.get ("color", true) ||
|
|
context.config.get (std::string ("_forcecolor"), false))
|
|
{
|
|
table.setColumnUnderline (0);
|
|
table.setColumnUnderline (1);
|
|
}
|
|
|
|
table.setColumnJustification (1, Table::right);
|
|
|
|
foreach (i, unique)
|
|
{
|
|
int row = table.addRow ();
|
|
table.addCell (row, 0, i->first);
|
|
table.addCell (row, 1, i->second);
|
|
}
|
|
|
|
out << optionalBlankLine ()
|
|
<< table.render ()
|
|
<< optionalBlankLine ()
|
|
<< unique.size ()
|
|
<< (unique.size () == 1 ? " tag" : " tags")
|
|
<< " (" << quantity << (quantity == 1 ? " task" : " tasks") << ")"
|
|
<< std::endl;
|
|
}
|
|
else {
|
|
out << "No tags."
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
|
|
outs = out.str ();
|
|
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleCompletionTags (std::string &outs)
|
|
{
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
|
|
Filter filter;
|
|
if (context.config.get (std::string ("complete.all.tags"), false))
|
|
context.tdb.load (tasks, filter);
|
|
else
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
// Scan all the tasks for their project name, building a map using project
|
|
// names as keys.
|
|
std::map <std::string, int> unique;
|
|
foreach (t, tasks)
|
|
{
|
|
std::vector <std::string> tags;
|
|
t->getTags (tags);
|
|
|
|
foreach (tag, tags)
|
|
unique[*tag] = 0;
|
|
}
|
|
|
|
std::stringstream out;
|
|
foreach (tag, unique)
|
|
out << tag->first << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleCompletionCommands (std::string &outs)
|
|
{
|
|
std::vector <std::string> commands;
|
|
context.cmd.allCommands (commands);
|
|
std::sort (commands.begin (), commands.end ());
|
|
|
|
std::stringstream out;
|
|
foreach (command, commands)
|
|
out << *command << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleCompletionConfig (std::string &outs)
|
|
{
|
|
std::vector <std::string> configs;
|
|
context.config.all (configs);
|
|
std::sort (configs.begin (), configs.end ());
|
|
|
|
std::stringstream out;
|
|
foreach (config, configs)
|
|
out << *config << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// A simple version display for use by completion scripts and the task-update
|
|
// script.
|
|
int handleCompletionVersion (std::string &outs)
|
|
{
|
|
outs = VERSION;
|
|
outs += "\n";
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleCompletionIDs (std::string &outs)
|
|
{
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
std::vector <int> ids;
|
|
foreach (task, tasks)
|
|
if (task->getStatus () != Task::deleted &&
|
|
task->getStatus () != Task::completed)
|
|
ids.push_back (task->id);
|
|
|
|
std::sort (ids.begin (), ids.end ());
|
|
|
|
std::stringstream out;
|
|
foreach (id, ids)
|
|
out << *id << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void handleUndo ()
|
|
{
|
|
context.disallowModification ();
|
|
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
context.tdb.undo ();
|
|
context.tdb.unlock ();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleVersion (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
// Create a table for the disclaimer.
|
|
int width = context.getWidth ();
|
|
Table disclaimer;
|
|
disclaimer.setTableWidth (width);
|
|
disclaimer.addColumn (" ");
|
|
disclaimer.setColumnWidth (0, Table::flexible);
|
|
disclaimer.setColumnJustification (0, Table::left);
|
|
disclaimer.addCell (disclaimer.addRow (), 0,
|
|
"Task may be copied only under the terms of the GNU General Public "
|
|
"License, which may be found in the task source kit.");
|
|
|
|
// Create a table for the URL.
|
|
Table link;
|
|
link.setTableWidth (width);
|
|
link.addColumn (" ");
|
|
link.setColumnWidth (0, Table::flexible);
|
|
link.setColumnJustification (0, Table::left);
|
|
link.addCell (link.addRow (), 0,
|
|
"Documentation for task can be found using 'man task', 'man taskrc', or "
|
|
"'man task-tutorial' at http://taskwarrior.org");
|
|
|
|
Color bold ("bold");
|
|
|
|
out << std::endl
|
|
<< ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false))
|
|
? bold.colorize (PACKAGE)
|
|
: PACKAGE)
|
|
<< " "
|
|
<< ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false))
|
|
? bold.colorize (VERSION)
|
|
: VERSION)
|
|
<< " built for "
|
|
|
|
#if defined (DARWIN)
|
|
<< "darwin"
|
|
#elif defined (SOLARIS)
|
|
<< "solaris"
|
|
#elif defined (CYGWIN)
|
|
<< "cygwin"
|
|
#elif defined (OPENBSD)
|
|
<< "openbsd"
|
|
#elif defined (HAIKU)
|
|
<< "haiku"
|
|
#elif defined (FREEBSD)
|
|
<< "freebsd"
|
|
#elif defined (LINUX)
|
|
<< "linux"
|
|
#else
|
|
<< "unknown"
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBNCURSES
|
|
<< "-ncurses"
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBREADLINE
|
|
<< "-readline"
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBLUA
|
|
<< "-lua"
|
|
#endif
|
|
|
|
<< std::endl
|
|
<< "Copyright (C) 2006 - 2010, P. Beckingham."
|
|
<< std::endl
|
|
<< disclaimer.render ()
|
|
<< link.render ()
|
|
<< std::endl;
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleConfig (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
int width = context.getWidth ();
|
|
|
|
std::vector <std::string> all;
|
|
context.config.all (all);
|
|
|
|
// Create a table for output.
|
|
Table table;
|
|
table.setTableWidth (width);
|
|
table.setDateFormat (context.config.get ("dateformat", "m/d/Y"));
|
|
table.addColumn ("Config variable");
|
|
table.addColumn ("Value");
|
|
|
|
if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false))
|
|
{
|
|
table.setColumnUnderline (0);
|
|
table.setColumnUnderline (1);
|
|
}
|
|
else
|
|
table.setTableDashedUnderline ();
|
|
|
|
table.setColumnWidth (0, Table::minimum);
|
|
table.setColumnWidth (1, Table::flexible);
|
|
table.setColumnJustification (0, Table::left);
|
|
table.setColumnJustification (1, Table::left);
|
|
table.sortOn (0, Table::ascendingCharacter);
|
|
|
|
foreach (i, all)
|
|
{
|
|
std::string value = context.config.get (*i);
|
|
if (value != "")
|
|
{
|
|
int row = table.addRow ();
|
|
table.addCell (row, 0, *i);
|
|
table.addCell (row, 1, value);
|
|
}
|
|
}
|
|
|
|
Color bold ("bold");
|
|
|
|
out << std::endl
|
|
<< table.render ()
|
|
<< std::endl;
|
|
|
|
// Complain about configuration variables that are not recognized.
|
|
// These are the regular configuration variables.
|
|
// Note that there is a leading and trailing space, to make searching easier.
|
|
std::string recognized =
|
|
" 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 "
|
|
"confirmation curses data.location dateformat 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 "
|
|
#endif
|
|
"import.synonym.status import.synonym.tags import.synonym.entry "
|
|
"import.synonym.start import.synonym.due import.synonym.recur "
|
|
"import.synonym.end import.synonym.project import.synonym.priority "
|
|
"import.synonym.fg import.synonym.bg import.synonym.description ";
|
|
|
|
// This configuration variable is supported, but not documented. It exists
|
|
// so that unit tests can force color to be on even when the output from task
|
|
// is redirected to a file, or stdout is not a tty.
|
|
recognized += "_forcecolor ";
|
|
|
|
std::vector <std::string> unrecognized;
|
|
foreach (i, all)
|
|
{
|
|
// Disallow partial matches by tacking a leading an trailing space on each
|
|
// variable name.
|
|
std::string pattern = " " + *i + " ";
|
|
if (recognized.find (pattern) == std::string::npos)
|
|
{
|
|
// These are special configuration variables, because their name is
|
|
// dynamic.
|
|
if (i->substr (0, 14) != "color.keyword." &&
|
|
i->substr (0, 14) != "color.project." &&
|
|
i->substr (0, 10) != "color.tag." &&
|
|
i->substr (0, 7) != "report." &&
|
|
i->substr (0, 6) != "alias.")
|
|
{
|
|
unrecognized.push_back (*i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unrecognized.size ())
|
|
{
|
|
out << "Your .taskrc file contains these unrecognized variables:"
|
|
<< std::endl;
|
|
|
|
foreach (i, unrecognized)
|
|
out << " " << *i << std::endl;
|
|
|
|
out << std::endl;
|
|
}
|
|
|
|
out << context.config.checkForDeprecatedColor ();
|
|
out << context.config.checkForDuplicates ();
|
|
// TODO Check for referenced but missing theme files.
|
|
// TODO Check for referenced but missing string files.
|
|
|
|
// Verify installation. This is mentioned in the documentation as the way to
|
|
// ensure everything is properly installed.
|
|
|
|
if (all.size () == 0) {
|
|
out << "Configuration error: .taskrc contains no entries"
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
else
|
|
{
|
|
if (context.config.get ("data.location") == "")
|
|
out << "Configuration error: data.location not specified in .taskrc "
|
|
"file."
|
|
<< std::endl;
|
|
|
|
if (access (expandPath (context.config.get ("data.location")).c_str (), X_OK))
|
|
out << "Configuration error: data.location contains a directory name"
|
|
" that doesn't exist, or is unreadable."
|
|
<< std::endl;
|
|
}
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleDelete (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
context.disallowModification ();
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
std::vector <Task> 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)
|
|
{
|
|
std::stringstream question;
|
|
question << "Permanently delete task "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "'?";
|
|
|
|
if (!context.config.get (std::string ("confirmation"), false) || 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.get ("echo.command", true))
|
|
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.get ("echo.command", true))
|
|
out << "Deleting task "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "'"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
else {
|
|
out << "Task not deleted." << std::endl;
|
|
rc = 1;
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleStart (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
context.disallowModification ();
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
bool nagged = false;
|
|
foreach (task, tasks)
|
|
{
|
|
if (! task->has ("start"))
|
|
{
|
|
char startTime[16];
|
|
sprintf (startTime, "%u", (unsigned int) time (NULL));
|
|
task->set ("start", startTime);
|
|
|
|
context.tdb.update (*task);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Started "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "'"
|
|
<< std::endl;
|
|
if (!nagged)
|
|
nagged = nag (*task);
|
|
}
|
|
else
|
|
{
|
|
out << "Task "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "' already started."
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleStop (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
context.disallowModification ();
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
if (task->has ("start"))
|
|
{
|
|
task->remove ("start");
|
|
context.tdb.update (*task);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Stopped "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "'"
|
|
<< std::endl;
|
|
}
|
|
else
|
|
{
|
|
out << "Task "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "' not started."
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleDone (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
int count = 0;
|
|
std::stringstream out;
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
std::vector <Task> all = tasks;
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
Permission permission;
|
|
if (context.sequence.size () > (size_t) context.config.get ("bulk", 2))
|
|
permission.bigSequence ();
|
|
|
|
bool nagged = false;
|
|
foreach (task, tasks)
|
|
{
|
|
if (task->getStatus () == Task::pending)
|
|
{
|
|
Task before (*task);
|
|
|
|
// Apply other deltas.
|
|
if (deltaDescription (*task))
|
|
permission.bigChange ();
|
|
|
|
deltaTags (*task);
|
|
deltaAttributes (*task);
|
|
deltaSubstitutions (*task);
|
|
|
|
// Add an end date.
|
|
char entryTime[16];
|
|
sprintf (entryTime, "%u", (unsigned int) time (NULL));
|
|
task->set ("end", entryTime);
|
|
|
|
// Change status.
|
|
task->setStatus (Task::completed);
|
|
|
|
if (taskDiff (before, *task))
|
|
{
|
|
if (permission.confirmed (before, taskDifferences (before, *task) + "Proceed with change?"))
|
|
{
|
|
context.tdb.update (*task);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Completed "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "'"
|
|
<< std::endl;
|
|
|
|
++count;
|
|
}
|
|
}
|
|
|
|
updateRecurrenceMask (all, *task);
|
|
if (!nagged)
|
|
nagged = nag (*task);
|
|
}
|
|
else
|
|
out << "Task "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "' is not pending"
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Marked "
|
|
<< count
|
|
<< " task"
|
|
<< (count == 1 ? "" : "s")
|
|
<< " as done"
|
|
<< std::endl;
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleExport (std::string &outs)
|
|
{
|
|
std::stringstream out;
|
|
|
|
// Deliberately no 'id'.
|
|
out << "'uuid',"
|
|
<< "'status',"
|
|
<< "'tags',"
|
|
<< "'entry',"
|
|
<< "'start',"
|
|
<< "'due',"
|
|
<< "'recur',"
|
|
<< "'end',"
|
|
<< "'project',"
|
|
<< "'priority',"
|
|
<< "'fg',"
|
|
<< "'bg',"
|
|
<< "'description'"
|
|
<< "\n";
|
|
|
|
int count = 0;
|
|
|
|
// Get all the tasks.
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
handleRecurrence ();
|
|
context.tdb.load (tasks, context.filter);
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
if (task->getStatus () != Task::recurring)
|
|
{
|
|
out << task->composeCSV ().c_str ();
|
|
++count;
|
|
}
|
|
}
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleModify (std::string &outs)
|
|
{
|
|
int count = 0;
|
|
std::stringstream out;
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
std::vector <Task> all = tasks;
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
Permission permission;
|
|
if (context.sequence.size () > (size_t) context.config.get ("bulk", 2))
|
|
permission.bigSequence ();
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
// Perform some logical consistency checks.
|
|
if (context.task.has ("recur") &&
|
|
!context.task.has ("due") &&
|
|
!task->has ("due"))
|
|
throw std::string ("You cannot specify a recurring task without a due date.");
|
|
|
|
if (context.task.has ("until") &&
|
|
!context.task.has ("recur") &&
|
|
!task->has ("recur"))
|
|
throw std::string ("You cannot specify an until date for a non-recurring task.");
|
|
|
|
if (task->has ("due") &&
|
|
!context.task.has ("due") &&
|
|
context.task.has ("recur"))
|
|
throw std::string ("You cannot remove the due date from a recurring task.");
|
|
|
|
if (task->has ("recur") &&
|
|
!context.task.has ("recur"))
|
|
throw std::string ("You cannot remove the recurrence from a recurring task.");
|
|
|
|
// Make all changes.
|
|
foreach (other, all)
|
|
{
|
|
if (other->id == task->id || // Self
|
|
(task->has ("parent") &&
|
|
task->get ("parent") == other->get ("parent")) || // Sibling
|
|
other->get ("uuid") == task->get ("parent")) // Parent
|
|
{
|
|
if (task->has ("parent"))
|
|
std::cout << "Task "
|
|
<< task->id
|
|
<< " is a recurring task, and all other instances of this"
|
|
<< " task may be modified."
|
|
<< std::endl;
|
|
|
|
Task before (*other);
|
|
|
|
// A non-zero value forces a file write.
|
|
int changes = 0;
|
|
|
|
// Apply other deltas.
|
|
if (deltaDescription (*other))
|
|
{
|
|
permission.bigChange ();
|
|
++changes;
|
|
}
|
|
|
|
changes += deltaTags (*other);
|
|
changes += deltaAttributes (*other);
|
|
changes += deltaSubstitutions (*other);
|
|
|
|
if (taskDiff (before, *other))
|
|
{
|
|
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Proceed with change?"))
|
|
{
|
|
context.tdb.update (*other);
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Modified " << count << " task" << (count == 1 ? "" : "s") << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleAppend (std::string &outs)
|
|
{
|
|
int count = 0;
|
|
std::stringstream out;
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
std::vector <Task> all = tasks;
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
Permission permission;
|
|
if (context.sequence.size () > (size_t) context.config.get ("bulk", 2))
|
|
permission.bigSequence ();
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
foreach (other, all)
|
|
{
|
|
if (other->id == task->id || // Self
|
|
(task->has ("parent") &&
|
|
task->get ("parent") == other->get ("parent")) || // Sibling
|
|
other->get ("uuid") == task->get ("parent")) // Parent
|
|
{
|
|
Task before (*other);
|
|
|
|
// A non-zero value forces a file write.
|
|
int changes = 0;
|
|
|
|
// Apply other deltas.
|
|
changes += deltaAppend (*other);
|
|
changes += deltaTags (*other);
|
|
changes += deltaAttributes (*other);
|
|
|
|
if (taskDiff (before, *other))
|
|
{
|
|
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Proceed with change?"))
|
|
{
|
|
context.tdb.update (*other);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Appended '"
|
|
<< context.task.get ("description")
|
|
<< "' to task "
|
|
<< other->id
|
|
<< std::endl;
|
|
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Appended " << count << " task" << (count == 1 ? "" : "s") << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handlePrepend (std::string &outs)
|
|
{
|
|
int count = 0;
|
|
std::stringstream out;
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
std::vector <Task> all = tasks;
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
Permission permission;
|
|
if (context.sequence.size () > (size_t) context.config.get ("bulk", 2))
|
|
permission.bigSequence ();
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
foreach (other, all)
|
|
{
|
|
if (other->id == task->id || // Self
|
|
(task->has ("parent") &&
|
|
task->get ("parent") == other->get ("parent")) || // Sibling
|
|
other->get ("uuid") == task->get ("parent")) // Parent
|
|
{
|
|
Task before (*other);
|
|
|
|
// A non-zero value forces a file write.
|
|
int changes = 0;
|
|
|
|
// Apply other deltas.
|
|
changes += deltaPrepend (*other);
|
|
changes += deltaTags (*other);
|
|
changes += deltaAttributes (*other);
|
|
|
|
if (taskDiff (before, *other))
|
|
{
|
|
if (changes && permission.confirmed (before, taskDifferences (before, *other) + "Are you sure?"))
|
|
{
|
|
context.tdb.update (*other);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Prepended '"
|
|
<< context.task.get ("description")
|
|
<< "' to task "
|
|
<< other->id
|
|
<< std::endl;
|
|
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Prepended " << count << " task" << (count == 1 ? "" : "s") << std::endl;
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleDuplicate (std::string &outs)
|
|
{
|
|
std::stringstream out;
|
|
int count = 0;
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
Task dup (*task);
|
|
dup.set ("uuid", uuid ()); // Needs a new UUID.
|
|
dup.setStatus (Task::pending);
|
|
dup.remove ("start"); // Does not inherit start date.
|
|
dup.remove ("end"); // Does not inherit end date.
|
|
|
|
// Recurring tasks are duplicated and downgraded to regular tasks.
|
|
if (task->getStatus () == Task::recurring)
|
|
{
|
|
dup.remove ("parent");
|
|
dup.remove ("recur");
|
|
dup.remove ("until");
|
|
dup.remove ("imak");
|
|
dup.remove ("imask");
|
|
|
|
out << "Note: task "
|
|
<< task->id
|
|
<< " was a recurring task. The new task is not."
|
|
<< std::endl;
|
|
}
|
|
|
|
// Apply deltas.
|
|
deltaDescription (dup);
|
|
deltaTags (dup);
|
|
deltaAttributes (dup);
|
|
deltaSubstitutions (dup);
|
|
|
|
// A New task needs a new entry time.
|
|
char entryTime[16];
|
|
sprintf (entryTime, "%u", (unsigned int) time (NULL));
|
|
dup.set ("entry", entryTime);
|
|
|
|
context.tdb.add (dup);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Duplicated "
|
|
<< task->id
|
|
<< " '"
|
|
<< task->get ("description")
|
|
<< "'"
|
|
<< std::endl;
|
|
++count;
|
|
}
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
{
|
|
out << "Duplicated " << count << " task" << (count == 1 ? "" : "s") << std::endl;
|
|
#ifdef FEATURE_NEW_ID
|
|
// All this, just for an id number.
|
|
std::vector <Task> all;
|
|
Filter none;
|
|
context.tdb.loadPending (all, none);
|
|
out << "Created task " << context.tdb.nextId () << std::endl;
|
|
#endif
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
#ifdef FEATURE_SHELL
|
|
void handleShell ()
|
|
{
|
|
// Display some kind of welcome message.
|
|
Color bold (Color::nocolor, Color::nocolor, false, true, false);
|
|
std::cout << ((context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false))
|
|
? bold.colorize (PACKAGE_STRING)
|
|
: PACKAGE_STRING)
|
|
<< " shell"
|
|
<< std::endl
|
|
<< std::endl
|
|
<< "Enter any task command (such as 'list'), or hit 'Enter'."
|
|
<< std::endl
|
|
<< "There is no need to include the 'task' command itself."
|
|
<< std::endl
|
|
<< "Enter 'quit' to end the session."
|
|
<< std::endl
|
|
<< std::endl;
|
|
|
|
// Make a copy because context.clear will delete them.
|
|
std::string permanentOverrides = " " + context.file_override
|
|
+ " " + context.var_overrides;
|
|
|
|
std::string quit = "quit"; // TODO i18n
|
|
std::string command;
|
|
bool keepGoing = true;
|
|
|
|
do
|
|
{
|
|
std::cout << context.config.get ("shell.prompt", "task>") << " ";
|
|
|
|
command = "";
|
|
std::getline (std::cin, command);
|
|
std::string decoratedCommand = trim (command + permanentOverrides);
|
|
|
|
// When looking for the 'quit' command, use 'command', not
|
|
// 'decoratedCommand'.
|
|
if (command.length () > 0 &&
|
|
command.length () <= quit.length () &&
|
|
lowerCase (command) == quit.substr (0, command.length ()))
|
|
{
|
|
keepGoing = false;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
context.clear ();
|
|
|
|
std::vector <std::string> args;
|
|
split (args, decoratedCommand, ' ');
|
|
foreach (arg, args) context.args.push_back (*arg);
|
|
|
|
context.initialize ();
|
|
context.run ();
|
|
}
|
|
|
|
catch (std::string& error)
|
|
{
|
|
std::cout << error << std::endl;
|
|
}
|
|
|
|
catch (...)
|
|
{
|
|
std::cerr << context.stringtable.get (100, "Unknown error.") << std::endl;
|
|
}
|
|
}
|
|
}
|
|
while (keepGoing && !std::cin.eof ());
|
|
|
|
// No need to repeat any overrides after the shell quits.
|
|
context.clearMessages ();
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleColor (std::string &outs)
|
|
{
|
|
int rc = 0;
|
|
std::stringstream out;
|
|
|
|
if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false))
|
|
{
|
|
// If there is something in the description, then assume that is a color,
|
|
// and display it as a sample.
|
|
std::string description = context.task.get ("description");
|
|
if (description != "")
|
|
{
|
|
Color one ("black on bright yellow");
|
|
Color two ("underline cyan on bright blue");
|
|
Color three ("color214 on color202");
|
|
Color four ("rgb150 on rgb020");
|
|
Color five ("underline grey10 on grey3");
|
|
Color six ("red on color173");
|
|
Color sample (description);
|
|
|
|
out << std::endl
|
|
<< "Use this command to see how colors are displayed by your terminal." << std::endl
|
|
<< std::endl
|
|
<< "16-color usage (supports underline, bold text, bright background):" << std::endl
|
|
<< " " << one.colorize ("task color black on bright yellow") << std::endl
|
|
<< " " << two.colorize ("task color underline cyan on bright blue") << std::endl
|
|
<< std::endl
|
|
<< "256-color usage (supports underline):" << std::endl
|
|
<< " " << three.colorize ("task color color214 on color202") << std::endl
|
|
<< " " << four.colorize ("task color rgb150 on rgb020") << std::endl
|
|
<< " " << five.colorize ("task color underline grey10 on grey3") << std::endl
|
|
<< " " << six.colorize ("task color red on color173") << std::endl
|
|
<< std::endl
|
|
<< "Your sample:" << std::endl
|
|
<< " " << sample.colorize ("task color " + description) << std::endl
|
|
<< std::endl;
|
|
}
|
|
|
|
// Show all supported colors. Possibly show some unsupported ones too.
|
|
else
|
|
{
|
|
out << std::endl
|
|
<< "Basic colors"
|
|
<< std::endl
|
|
<< " " << Color::colorize (" black ", "black")
|
|
<< " " << Color::colorize (" red ", "red")
|
|
<< " " << Color::colorize (" blue ", "blue")
|
|
<< " " << Color::colorize (" green ", "green")
|
|
<< " " << Color::colorize (" magenta ", "magenta")
|
|
<< " " << Color::colorize (" cyan ", "cyan")
|
|
<< " " << Color::colorize (" yellow ", "yellow")
|
|
<< " " << Color::colorize (" white ", "white")
|
|
<< std::endl
|
|
<< " " << Color::colorize (" black ", "white on black")
|
|
<< " " << Color::colorize (" red ", "white on red")
|
|
<< " " << Color::colorize (" blue ", "white on blue")
|
|
<< " " << Color::colorize (" green ", "black on green")
|
|
<< " " << Color::colorize (" magenta ", "black on magenta")
|
|
<< " " << Color::colorize (" cyan ", "black on cyan")
|
|
<< " " << Color::colorize (" yellow ", "black on yellow")
|
|
<< " " << Color::colorize (" white ", "black on white")
|
|
<< std::endl
|
|
<< std::endl;
|
|
|
|
out << "Effects"
|
|
<< std::endl
|
|
<< " " << Color::colorize (" red ", "red")
|
|
<< " " << Color::colorize (" bold red ", "bold red")
|
|
<< " " << Color::colorize (" underline on blue ", "underline on blue")
|
|
<< " " << Color::colorize (" on green ", "black on green")
|
|
<< " " << Color::colorize (" on bright green ", "black on bright green")
|
|
<< std::endl
|
|
<< std::endl;
|
|
|
|
// 16 system colors.
|
|
out << "color0 - color15" << std::endl;
|
|
for (int r = 0; r < 2; ++r)
|
|
{
|
|
out << " ";
|
|
for (int c = 0; c < 8; ++c)
|
|
{
|
|
std::stringstream s;
|
|
s << "on color" << (r*8 + c);
|
|
out << Color::colorize (" ", s.str ());
|
|
}
|
|
|
|
out << std::endl;
|
|
}
|
|
|
|
out << std::endl;
|
|
|
|
// Color cube.
|
|
out << "Color cube rgb000 - rgb555 (also color16 - color231)" << std::endl;
|
|
for (int g = 0; g < 6; ++g)
|
|
{
|
|
out << " ";
|
|
for (int r = 0; r < 6; ++r)
|
|
{
|
|
for (int b = 0; b < 6; ++b)
|
|
{
|
|
std::stringstream s;
|
|
s << "on rgb" << r << g << b;
|
|
out << Color::colorize (" ", s.str ());
|
|
}
|
|
|
|
out << " ";
|
|
}
|
|
|
|
out << std::endl;
|
|
}
|
|
|
|
out << std::endl;
|
|
|
|
// Grey ramp.
|
|
out << "Gray ramp gray0 - gray23 (also color232 - color255)" << std::endl << " ";
|
|
for (int g = 0; g < 24; ++g)
|
|
{
|
|
std::stringstream s;
|
|
s << "on gray" << g;
|
|
out << Color::colorize (" ", s.str ());
|
|
}
|
|
|
|
out << std::endl
|
|
<< std::endl
|
|
<< "Try running 'task color white on red'"
|
|
<< std::endl
|
|
<< std::endl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out << "Color is currently turned off in your .taskrc file. To enable "
|
|
"color, remove the line 'color=off', or change the 'off' to 'on'."
|
|
<< std::endl;
|
|
rc = 1;
|
|
}
|
|
|
|
outs = out.str ();
|
|
return rc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int handleAnnotate (std::string &outs)
|
|
{
|
|
if (!context.task.has ("description"))
|
|
throw std::string ("Cannot apply a blank annotation.");
|
|
|
|
std::stringstream out;
|
|
|
|
std::vector <Task> tasks;
|
|
context.tdb.lock (context.config.get ("locking", true));
|
|
Filter filter;
|
|
context.tdb.loadPending (tasks, filter);
|
|
|
|
// Filter sequence.
|
|
context.filter.applySequence (tasks, context.sequence);
|
|
|
|
Permission permission;
|
|
if (context.sequence.size () > (size_t) context.config.get ("bulk", 2))
|
|
permission.bigSequence ();
|
|
|
|
foreach (task, tasks)
|
|
{
|
|
Task before (*task);
|
|
task->addAnnotation (context.task.get ("description"));
|
|
|
|
if (taskDiff (before, *task))
|
|
{
|
|
if (permission.confirmed (before, taskDifferences (before, *task) + "Proceed with change?"))
|
|
{
|
|
context.tdb.update (*task);
|
|
|
|
if (context.config.get ("echo.command", true))
|
|
out << "Annotated "
|
|
<< task->id
|
|
<< " with '"
|
|
<< context.task.get ("description")
|
|
<< "'"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
context.tdb.commit ();
|
|
context.tdb.unlock ();
|
|
|
|
outs = out.str ();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int deltaAppend (Task& task)
|
|
{
|
|
if (context.task.has ("description"))
|
|
{
|
|
task.set ("description",
|
|
task.get ("description") + " " + context.task.get ("description"));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int deltaPrepend (Task& task)
|
|
{
|
|
if (context.task.has ("description"))
|
|
{
|
|
task.set ("description",
|
|
context.task.get ("description") + " " + task.get ("description"));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int deltaDescription (Task& task)
|
|
{
|
|
if (context.task.has ("description"))
|
|
{
|
|
task.set ("description", context.task.get ("description"));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int deltaTags (Task& task)
|
|
{
|
|
int changes = 0;
|
|
|
|
// Apply or remove tags, if any.
|
|
std::vector <std::string> tags;
|
|
context.task.getTags (tags);
|
|
foreach (tag, tags)
|
|
{
|
|
task.addTag (*tag);
|
|
++changes;
|
|
}
|
|
|
|
foreach (tag, context.tagRemovals)
|
|
{
|
|
task.removeTag (*tag);
|
|
++changes;
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int deltaAttributes (Task& task)
|
|
{
|
|
int changes = 0;
|
|
|
|
foreach (att, context.task)
|
|
{
|
|
if (att->second.name () != "uuid" &&
|
|
att->second.name () != "description" &&
|
|
att->second.name () != "tags")
|
|
{
|
|
// Modifying "wait" changes status.
|
|
if (att->second.name () == "wait")
|
|
{
|
|
if (att->second.value () == "")
|
|
task.setStatus (Task::pending);
|
|
else
|
|
task.setStatus (Task::waiting);
|
|
}
|
|
|
|
if (att->second.value () == "")
|
|
task.remove (att->second.name ());
|
|
else
|
|
// One of the few places where the compound attribute name is used.
|
|
task.set (att->first, att->second.value ());
|
|
|
|
++changes;
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int deltaSubstitutions (Task& task)
|
|
{
|
|
std::string description = task.get ("description");
|
|
std::vector <Att> annotations;
|
|
task.getAnnotations (annotations);
|
|
|
|
context.subst.apply (description, annotations);
|
|
|
|
task.set ("description", description);
|
|
task.setAnnotations (annotations);
|
|
|
|
return 1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|