Files
taskwarrior-2.x/src/task.cpp
Paul Beckingham 0449f9e0a2 Enhancements - version command
- Implemented version command.
- Corrected config handling in version command.
2009-06-09 23:58:05 -04:00

502 lines
19 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
// Copyright 2006 - 2009, 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 <fstream>
#include <sstream>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#include <time.h>
#include "Context.h"
#include "Date.h"
#include "Duration.h"
#include "Table.h"
#include "TDB.h"
#include "T.h"
#include "text.h"
#include "util.h"
#include "task.h"
#ifdef HAVE_LIBNCURSES
#include <ncurses.h>
#endif
// Global context for use by all.
Context context;
////////////////////////////////////////////////////////////////////////////////
static std::string shortUsage ()
{
std::stringstream out;
Table table;
int width = context.config.get ("defaultwidth", (int) 80);
#ifdef HAVE_LIBNCURSES
if (context.config.get ("curses", true))
{
WINDOW* w = initscr ();
width = w->_maxx + 1;
endwin ();
}
#endif
table.addColumn (" ");
table.addColumn (" ");
table.addColumn (" ");
table.setColumnJustification (0, Table::left);
table.setColumnJustification (1, Table::left);
table.setColumnJustification (2, Table::left);
table.setColumnWidth (0, Table::minimum);
table.setColumnWidth (1, Table::minimum);
table.setColumnWidth (2, Table::flexible);
table.setTableWidth (width);
table.setDateFormat (context.config.get ("dateformat", "m/d/Y"));
int row = table.addRow ();
table.addCell (row, 0, "Usage:");
table.addCell (row, 1, "task");
row = table.addRow ();
table.addCell (row, 1, "task add [tags] [attrs] desc...");
table.addCell (row, 2, "Adds a new task");
row = table.addRow ();
table.addCell (row, 1, "task append [tags] [attrs] desc...");
table.addCell (row, 2, "Appends more description to an existing task");
row = table.addRow ();
table.addCell (row, 1, "task annotate ID desc...");
table.addCell (row, 2, "Adds an annotation to an existing task");
row = table.addRow ();
table.addCell (row, 1, "task completed [tags] [attrs] desc...");
table.addCell (row, 2, "Chronological listing of all completed tasks matching the specified criteria");
row = table.addRow ();
table.addCell (row, 1, "task ID [tags] [attrs] [desc...]");
table.addCell (row, 2, "Modifies the existing task with provided arguments");
row = table.addRow ();
table.addCell (row, 1, "task ID /from/to/");
table.addCell (row, 2, "Performs one substitution on the task description, for fixing mistakes");
row = table.addRow ();
table.addCell (row, 1, "task ID /from/to/g");
table.addCell (row, 2, "Performs all substitutions on the task description, for fixing mistakes");
row = table.addRow ();
table.addCell (row, 1, "task edit ID");
table.addCell (row, 2, "Launches an editor to let you modify all aspects of a task directly, therefore it is to be used carefully");
row = table.addRow ();
table.addCell (row, 1, "task duplicate ID [tags] [attrs] [desc...]");
table.addCell (row, 2, "Duplicates the specified task, and allows modifications");
row = table.addRow ();
table.addCell (row, 1, "task delete ID");
table.addCell (row, 2, "Deletes the specified task");
row = table.addRow ();
table.addCell (row, 1, "task undelete ID");
table.addCell (row, 2, "Undeletes the specified task, provided a report has not yet been run");
row = table.addRow ();
table.addCell (row, 1, "task info ID");
table.addCell (row, 2, "Shows all data, metadata for specified task");
row = table.addRow ();
table.addCell (row, 1, "task start ID");
table.addCell (row, 2, "Marks specified task as started");
row = table.addRow ();
table.addCell (row, 1, "task stop ID");
table.addCell (row, 2, "Removes the 'start' time from a task");
row = table.addRow ();
table.addCell (row, 1, "task done ID [tags] [attrs] [desc...]");
table.addCell (row, 2, "Marks the specified task as completed");
row = table.addRow ();
table.addCell (row, 1, "task undo ID");
table.addCell (row, 2, "Marks the specified done task as pending, provided a report has not yet been run");
row = table.addRow ();
table.addCell (row, 1, "task projects");
table.addCell (row, 2, "Shows a list of all project names used, and how many tasks are in each");
row = table.addRow ();
table.addCell (row, 1, "task tags");
table.addCell (row, 2, "Shows a list of all tags used");
row = table.addRow ();
table.addCell (row, 1, "task summary");
table.addCell (row, 2, "Shows a report of task status by project");
row = table.addRow ();
table.addCell (row, 1, "task timesheet [weeks]");
table.addCell (row, 2, "Shows a weekly report of tasks completed and started");
row = table.addRow ();
table.addCell (row, 1, "task history");
table.addCell (row, 2, "Shows a report of task history, by month");
row = table.addRow ();
table.addCell (row, 1, "task ghistory");
table.addCell (row, 2, "Shows a graphical report of task history, by month");
row = table.addRow ();
table.addCell (row, 1, "task next");
table.addCell (row, 2, "Shows the most important tasks for each project");
row = table.addRow ();
table.addCell (row, 1, "task calendar");
table.addCell (row, 2, "Shows a monthly calendar, with due tasks marked");
row = table.addRow ();
table.addCell (row, 1, "task active");
table.addCell (row, 2, "Shows all task that are started, but not completed");
row = table.addRow ();
table.addCell (row, 1, "task overdue");
table.addCell (row, 2, "Shows all incomplete tasks that are beyond their due date");
row = table.addRow ();
table.addCell (row, 1, "task stats");
table.addCell (row, 2, "Shows task database statistics");
row = table.addRow ();
table.addCell (row, 1, "task import");
table.addCell (row, 2, "Imports tasks from a variety of formats");
row = table.addRow ();
table.addCell (row, 1, "task export");
table.addCell (row, 2, "Exports all tasks as a CSV file");
row = table.addRow ();
table.addCell (row, 1, "task color");
table.addCell (row, 2, "Displays all possible colors");
row = table.addRow ();
table.addCell (row, 1, "task version");
table.addCell (row, 2, "Shows the task version number");
row = table.addRow ();
table.addCell (row, 1, "task help");
table.addCell (row, 2, "Shows the long usage text");
// Add custom reports here...
std::vector <std::string> all;
allCustomReports (all);
foreach (report, all)
{
std::string command = std::string ("task ") + *report + std::string (" [tags] [attrs] desc...");
std::string description = context.config.get (
std::string ("report.") + *report + ".description", std::string ("(missing description)"));
row = table.addRow ();
table.addCell (row, 1, command);
table.addCell (row, 2, description);
}
out << table.render ()
<< std::endl
<< "See http://taskwarrior.org/wiki/taskwarrior/Download for the latest "
<< "releases and a full tutorial. New releases containing fixes and "
<< "enhancements are made frequently. Join in the discussion of task, "
<< "present and future, at http://taskwarrior.org"
<< std::endl
<< std::endl;
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
static std::string longUsage ()
{
std::stringstream out;
out << shortUsage ()
<< "ID is the numeric identifier displayed by the 'task list' command. "
<< "You can specify multiple IDs for task commands, and multiple tasks "
<< "will be affected. To specify multiple IDs make sure you use one "
<< "of these forms:" << "\n"
<< " task delete 1,2,3" << "\n"
<< " task info 1-3" << "\n"
<< " task pri:H 1,2-5,19" << "\n"
<< "\n"
<< "Tags are arbitrary words, any quantity:" << "\n"
<< " +tag The + means add the tag" << "\n"
<< " -tag The - means remove the tag" << "\n"
<< "\n"
<< "Attributes are:" << "\n"
<< " project: Project name" << "\n"
<< " priority: Priority" << "\n"
<< " due: Due date" << "\n"
<< " recur: Recurrence frequency" << "\n"
<< " until: Recurrence end date" << "\n"
<< " fg: Foreground color" << "\n"
<< " bg: Background color" << "\n"
<< "\n"
<< "The default .taskrc file can be overridden with:" << "\n"
<< " task rc:<alternate file> ..." << "\n"
<< "\n"
<< "The values in .taskrc (or alternate) can be overridden with:" << "\n"
<< " task ... rc.<name>:<value>" << "\n"
<< "\n"
<< "Any command or attribute name may be abbreviated if still unique:" << "\n"
<< " task list project:Home" << "\n"
<< " task li pro:Home" << "\n"
<< "\n"
<< "Some task descriptions need to be escaped because of the shell:" << "\n"
<< " task add \"quoted ' quote\"" << "\n"
<< " task add escaped \\' quote" << "\n"
<< "\n"
<< "The argument -- tells task to treat all other args as description." << "\n"
<< "\n"
<< "Many characters have special meaning to the shell, including:" << "\n"
<< " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~" << "\n"
<< std::endl;
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
// Set up randomness.
#ifdef HAVE_SRANDOM
srandom (time (NULL));
#else
srand (time (NULL));
#endif
int status = 0;
try
{
context.initialize (argc, argv);
if (context.program.find ("itask") != std::string::npos)
status = context.interactive ();
else
status = context.run ();
// start OBSOLETE
/*
TDB tdb;
std::string dataLocation = expandPath (context.config.get ("data.location"));
tdb.dataDirectory (dataLocation);
// Allow user override of file locking. Solaris/NFS machines may want this.
if (! context.config.get ("locking", true))
tdb.noLock ();
// Check for silly shadow file settings.
std::string shadowFile = expandPath (context.config.get ("shadow.file"));
if (shadowFile != "")
{
if (shadowFile == dataLocation + "/pending.data")
throw std::string ("Configuration variable 'shadow.file' is set to "
"overwrite your pending tasks. Please change it.");
if (shadowFile == dataLocation + "/completed.data")
throw std::string ("Configuration variable 'shadow.file' is set to "
"overwrite your completed tasks. Please change it.");
}
std::cout << runTaskCommand (context.args, tdb);
*/
// end OBSOLETE
}
catch (std::string& error)
{
std::cout << error << std::endl;
return -1;
}
catch (...)
{
std::cerr << context.stringtable.get (100, "Unknown error.") << std::endl;
return -2;
}
return status;
}
////////////////////////////////////////////////////////////////////////////////
void updateShadowFile (TDB& tdb)
{
try
{
// Determine if shadow file is enabled.
std::string shadowFile = expandPath (context.config.get ("shadow.file"));
if (shadowFile != "")
{
std::string oldCurses = context.config.get ("curses");
std::string oldColor = context.config.get ("color");
context.config.set ("curses", "off");
context.config.set ("color", "off");
// Run report. Use shadow.command, using default.command as a fallback
// with "list" as a default.
std::string command = context.config.get ("shadow.command",
context.config.get ("default.command", "list"));
std::vector <std::string> args;
split (args, command, ' ');
std::string result = runTaskCommand (args, tdb);
std::ofstream out (shadowFile.c_str ());
if (out.good ())
{
out << result;
out.close ();
}
else
throw std::string ("Could not write file '") + shadowFile + "'";
context.config.set ("curses", oldCurses);
context.config.set ("color", oldColor);
}
// Optionally display a notification that the shadow file was updated.
if (context.config.get (std::string ("shadow.notify"), false))
std::cout << "[Shadow file '" << shadowFile << "' updated]" << std::endl;
}
catch (std::string& error)
{
std::cerr << error << std::endl;
}
catch (...)
{
std::cerr << "Unknown error." << std::endl;
}
}
////////////////////////////////////////////////////////////////////////////////
// TODO Obsolete
std::string runTaskCommand (
int argc,
char** argv,
TDB& tdb,
bool gc /* = true */,
bool shadow /* = true */)
{
std::vector <std::string> args;
for (int i = 1; i < argc; ++i)
if (strncmp (argv[i], "rc:", 3) &&
strncmp (argv[i], "rc.", 3))
{
std::cout << "arg=" << argv[i] << std::endl;
args.push_back (argv[i]);
}
return runTaskCommand (args, tdb, gc, shadow);
}
////////////////////////////////////////////////////////////////////////////////
// TODO Obsolete
std::string runTaskCommand (
std::vector <std::string>& args,
TDB& tdb,
bool gc /* = false */,
bool shadow /* = false */)
{
// If argc == 1 and there is a default.command, use it. Otherwise use
// argc/argv.
std::string defaultCommand = context.config.get ("default.command");
if (args.size () == 0 || defaultCommand != "")
{
// Stuff the command line.
args.clear ();
split (args, defaultCommand, ' ');
std::cout << "[task " << defaultCommand << "]" << std::endl;
}
loadCustomReports ();
std::string command;
T task;
parse (args, command, task);
bool gcMod = false; // Change occurred by way of gc.
bool cmdMod = false; // Change occurred by way of command type.
std::string out;
/*
// Read-only commands with no side effects.
if (command == "export") { out = handleExport (tdb, task); }
else if (command == "info") { out = handleInfo (tdb, task); }
else if (command == "stats") { out = handleReportStats (tdb, task); }
else if (command == "history") { out = handleReportHistory (tdb, task); }
else if (command == "ghistory") { out = handleReportGHistory (tdb, task); }
else if (command == "calendar") { out = handleReportCalendar (tdb, task); }
else if (command == "summary") { out = handleReportSummary (tdb, task); }
else if (command == "timesheet") { out = handleReportTimesheet (tdb, task); }
else if (command == "help") { out = longUsage ( ); }
// Commands that cause updates.
else if (command == "" && task.getId ()) { cmdMod = true; out = handleModify (tdb, task); }
else if (command == "add") { cmdMod = true; out = handleAdd (tdb, task); }
else if (command == "append") { cmdMod = true; out = handleAppend (tdb, task); }
else if (command == "annotate") { cmdMod = true; out = handleAnnotate (tdb, task); }
else if (command == "done") { cmdMod = true; out = handleDone (tdb, task); }
else if (command == "undelete") { cmdMod = true; out = handleUndelete (tdb, task); }
else if (command == "delete") { cmdMod = true; out = handleDelete (tdb, task); }
else if (command == "start") { cmdMod = true; out = handleStart (tdb, task); }
else if (command == "stop") { cmdMod = true; out = handleStop (tdb, task); }
else if (command == "undo") { cmdMod = true; out = handleUndo (tdb, task); }
else if (command == "import") { cmdMod = true; out = handleImport (tdb, task); }
else if (command == "duplicate") { cmdMod = true; out = handleDuplicate (tdb, task); }
else if (command == "edit") { cmdMod = true; out = handleEdit (tdb, task); }
// Command that display IDs and therefore need TDB::gc first.
else if (command == "completed") { if (gc) gcMod = tdb.gc (); out = handleCompleted (tdb, task); }
else if (command == "next") { if (gc) gcMod = tdb.gc (); out = handleReportNext (tdb, task); }
else if (command == "active") { if (gc) gcMod = tdb.gc (); out = handleReportActive (tdb, task); }
else if (command == "overdue") { if (gc) gcMod = tdb.gc (); out = handleReportOverdue (tdb, task); }
else if (isCustomReport (command)) { if (gc) gcMod = tdb.gc (); out = handleCustomReport (tdb, task, command); }
// If the command is not recognized, display usage.
else { out = shortUsage (); }
// Only update the shadow file if such an update was not suppressed (shadow),
// and if an actual change occurred (gcMod || cmdMod).
if (shadow && (gcMod || cmdMod))
updateShadowFile (tdb);
*/
return out;
}
////////////////////////////////////////////////////////////////////////////////