Enhancement - #363 export.ical

- Added feature #363 supporting iCalendar export via the 'export.ical'
  command.
- Updated documentation.
- Removed unnecessary localization of canonical command names.
This commit is contained in:
Paul Beckingham
2010-05-31 16:05:51 -04:00
parent 3ef6aa9f8e
commit e368043fb8
15 changed files with 288 additions and 90 deletions

View File

@@ -105,12 +105,21 @@ void Cmd::load ()
{
if (commands.size () == 0)
{
// Commands whose names are not localized.
commands.push_back ("_projects");
commands.push_back ("_tags");
commands.push_back ("_commands");
commands.push_back ("_ids");
commands.push_back ("_config");
commands.push_back ("_version");
commands.push_back ("export.csv");
commands.push_back ("export.ical");
commands.push_back ("history.monthly");
commands.push_back ("history.annual");
commands.push_back ("ghistory.monthly");
commands.push_back ("ghistory.annual");
// Commands whose names are localized.
commands.push_back (context.stringtable.get (CMD_ADD, "add"));
commands.push_back (context.stringtable.get (CMD_APPEND, "append"));
commands.push_back (context.stringtable.get (CMD_ANNOTATE, "annotate"));
@@ -121,12 +130,7 @@ void Cmd::load ()
commands.push_back (context.stringtable.get (CMD_DONE, "done"));
commands.push_back (context.stringtable.get (CMD_DUPLICATE, "duplicate"));
commands.push_back (context.stringtable.get (CMD_EDIT, "edit"));
commands.push_back (context.stringtable.get (CMD_EXPORT, "export"));
commands.push_back (context.stringtable.get (CMD_HELP, "help"));
commands.push_back (context.stringtable.get (CMD_HISTORY_MONTHLY, "history.monthly"));
commands.push_back (context.stringtable.get (CMD_HISTORY_ANNUAL, "history.annual"));
commands.push_back (context.stringtable.get (CMD_GHISTORY_MONTHLY, "ghistory.monthly"));
commands.push_back (context.stringtable.get (CMD_GHISTORY_ANNUAL, "ghistory.annual"));
commands.push_back (context.stringtable.get (CMD_IMPORT, "import"));
commands.push_back (context.stringtable.get (CMD_INFO, "info"));
commands.push_back (context.stringtable.get (CMD_LOG, "log"));
@@ -198,15 +202,16 @@ bool Cmd::isReadOnlyCommand ()
command == "_ids" ||
command == "_config" ||
command == "_version" ||
command == "export.csv" ||
command == "export.ical" ||
command == "history.monthly" ||
command == "history.annual" ||
command == "ghistory.monthly" ||
command == "ghistory.annual" ||
command == context.stringtable.get (CMD_CALENDAR, "calendar") ||
command == context.stringtable.get (CMD_COLORS, "colors") ||
command == context.stringtable.get (CMD_CONFIG, "config") ||
command == context.stringtable.get (CMD_EXPORT, "export") ||
command == context.stringtable.get (CMD_HELP, "help") ||
command == context.stringtable.get (CMD_HISTORY_MONTHLY, "history.monthly") ||
command == context.stringtable.get (CMD_HISTORY_ANNUAL, "history.annual") ||
command == context.stringtable.get (CMD_GHISTORY_MONTHLY, "ghistory.monthly") ||
command == context.stringtable.get (CMD_GHISTORY_ANNUAL, "ghistory.annual") ||
command == context.stringtable.get (CMD_INFO, "info") ||
command == context.stringtable.get (CMD_PROJECTS, "projects") ||
command == context.stringtable.get (CMD_SHELL, "shell") ||

View File

@@ -159,10 +159,14 @@ std::string Config::defaults =
"#import.synonym.tags=?\n"
"#import.synonym.uuid=?\n"
"\n"
"# Export Controls\n"
"export.ical.class=PRIVATE # Could be PUBLIC, PRIVATE or CONFIDENTIAL\n"
"\n"
"# Aliases - alternate names for commands\n"
"alias.rm=delete # Alias for the delete command\n"
"alias.history=history.monthly # Prefer monthly history reports\n"
"alias.ghistory=ghistory.monthly # Prefer monthly graphical history reports\n"
"alias.history=history.monthly # Prefer monthly over annual history reports\n"
"alias.ghistory=ghistory.monthly # Prefer monthly graphical over annual history reports\n"
"alias.export=export.csv # Prefer CSV over iCal export\n"
"\n"
"# Fields: id,uuid,project,priority,priority_long,entry,entry_time,\n" // TODO
"# start,entry_time,due,recur,recurrence_indicator,age,\n" // TODO

View File

@@ -229,7 +229,8 @@ int Context::dispatch (std::string &out)
else if (cmd.command == "delete") { rc = handleDelete (out); }
else if (cmd.command == "start") { rc = handleStart (out); }
else if (cmd.command == "stop") { rc = handleStop (out); }
else if (cmd.command == "export") { rc = handleExport (out); }
else if (cmd.command == "export.csv") { rc = handleExportCSV (out); }
else if (cmd.command == "export.ical") { rc = handleExportiCal (out); }
else if (cmd.command == "import") { rc = handleImport (out); }
else if (cmd.command == "duplicate") { rc = handleDuplicate (out); }
else if (cmd.command == "edit") { rc = handleEdit (out); }

View File

@@ -5,12 +5,12 @@ task_SOURCES = API.cpp Att.cpp Cmd.cpp Color.cpp Config.cpp Context.cpp \
Grid.cpp Hooks.cpp Keymap.cpp Location.cpp Nibbler.cpp \
Path.cpp Permission.cpp Record.cpp Sequence.cpp \
StringTable.cpp Subst.cpp TDB.cpp Table.cpp Task.cpp Timer.cpp \
command.cpp custom.cpp edit.cpp import.cpp interactive.cpp \
main.cpp recur.cpp report.cpp rules.cpp text.cpp util.cpp \
API.h Att.h Cmd.h Color.h Config.h Context.h Date.h \
Directory.h Duration.h File.h Filter.h Grid.h Hooks.h Keymap.h \
Location.h Nibbler.h Path.h Permission.h Record.h Sequence.h \
StringTable.h Subst.h TDB.h Table.h Task.h Timer.h i18n.h \
main.h text.h util.h
command.cpp custom.cpp edit.cpp export.cpp import.cpp \
interactive.cpp main.cpp recur.cpp report.cpp rules.cpp \
text.cpp util.cpp API.h Att.h Cmd.h Color.h Config.h Context.h \
Date.h Directory.h Duration.h File.h Filter.h Grid.h Hooks.h \
Keymap.h Location.h Nibbler.h Path.h Permission.h Record.h \
Sequence.h StringTable.h Subst.h TDB.h Table.h Task.h Timer.h \
i18n.h main.h text.h util.h
task_CPPFLAGS=$(LUA_CFLAGS)
task_LDFLAGS=$(LUA_LFLAGS)

View File

@@ -1306,59 +1306,6 @@ int handleDone (std::string &outs)
return rc;
}
////////////////////////////////////////////////////////////////////////////////
int handleExport (std::string &outs)
{
int rc = 0;
if (context.hooks.trigger ("pre-export-command"))
{
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.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
foreach (task, tasks)
{
context.hooks.trigger ("pre-display", *task);
if (task->getStatus () != Task::recurring)
{
out << task->composeCSV ().c_str ();
++count;
}
}
outs = out.str ();
context.hooks.trigger ("post-export-command");
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
int handleModify (std::string &outs)
{

229
src/export.cpp Normal file
View File

@@ -0,0 +1,229 @@
////////////////////////////////////////////////////////////////////////////////
// task - a command line task list manager.
//
// Copyright 2006 - 2010, Paul Beckingham, Federico Hernandez.
// 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 <sstream>
#include "Att.h"
#include "text.h"
#include "util.h"
#include "main.h"
#include "../auto.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
int handleExportCSV (std::string &outs)
{
int rc = 0;
if (context.hooks.trigger ("pre-export-command"))
{
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.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
foreach (task, tasks)
{
context.hooks.trigger ("pre-display", *task);
if (task->getStatus () != Task::recurring)
{
out << task->composeCSV ().c_str ();
++count;
}
}
outs = out.str ();
context.hooks.trigger ("post-export-command");
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
// http://tools.ietf.org/html/rfc5545
//
// Note: Recurring tasks could be included in more detail.
int handleExportiCal (std::string &outs)
{
int rc = 0;
if (context.hooks.trigger ("pre-export-command"))
{
std::stringstream out;
out << "BEGIN:VCALENDAR\n"
<< "VERSION:2.0\n"
<< "PRODID:-//GBF//" << PACKAGE_STRING << "//EN\n";
int count = 0;
// Get all the tasks.
std::vector <Task> tasks;
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
foreach (task, tasks)
{
context.hooks.trigger ("pre-display", *task);
if (task->getStatus () != Task::recurring)
{
out << "BEGIN:VTODO\n";
// Required UID:20070313T123432Z-456553@example.com
out << "UID:" << task->get ("uuid") << "\n";
// Required DTSTAMP:20070313T123432Z
Date entry (atoi (task->get ("entry").c_str ()));
out << "DTSTAMP:" << entry.toISO () << "\n";
// Optional DTSTART:20070514T110000Z
if (task->has ("start"))
{
Date start (atoi (task->get ("start").c_str ()));
out << "DTSTART:" << start.toISO () << "\n";
}
// Optional DUE:20070709T130000Z
if (task->has ("due"))
{
Date due (atoi (task->get ("due").c_str ()));
out << "DUE:" << due.toISO () << "\n";
}
// Optional COMPLETED:20070707T100000Z
if (task->has ("end") && task->getStatus () == Task::completed)
{
Date end (atoi (task->get ("end").c_str ()));
out << "COMPLETED:" << end.toISO () << "\n";
}
out << "SUMMARY:" << task->get ("description") << "\n";
// Optional CLASS:PUBLIC/PRIVATE/CONFIDENTIAL
std::string classification = context.config.get ("export.ical.class");
if (classification == "")
classification = "PRIVATE";
out << "CLASS:" << classification << "\n";
// Optional multiple CATEGORIES:FAMILY,FINANCE
if (task->getTagCount () > 0)
{
std::vector <std::string> tags;
task->getTags (tags);
std::string all;
join (all, ",", tags);
out << "CATEGORIES:" << all << "\n";
}
// Optional PRIORITY:
// 1-4 H
// 5 M
// 6-9 L
if (task->has ("priority"))
{
out << "PRIORITY:";
std::string priority = task->get ("priority");
if (priority == "H") out << "1";
else if (priority == "M") out << "5";
else out << "9";
out << "\n";
}
// Optional STATUS:NEEDS-ACTION/IN-PROCESS/COMPLETED/CANCELLED
out << "STATUS:";
Task::status stat = task->getStatus ();
if (stat == Task::pending || stat == Task::waiting)
{
if (task->has ("start"))
out << "IN-PROCESS";
else
out << "NEEDS-ACTION";
}
else if (stat == Task::completed)
{
out << "COMPLETED";
}
else if (stat == Task::deleted)
{
out << "CANCELLED";
}
out << "\n";
// Optional COMMENT:annotation1
// Optional COMMENT:annotation2
std::vector <Att> annotations;
task->getAnnotations (annotations);
foreach (anno, annotations)
out << "COMMENT:" << anno->value () << "\n";
out << "END:VTODO\n";
++count;
}
}
out << "END:VCALENDAR\n";
outs = out.str ();
context.hooks.trigger ("post-export-command");
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -77,10 +77,9 @@
#define CMD_DONE 208
#define CMD_DUPLICATE 209
#define CMD_EDIT 210
#define CMD_EXPORT 211
#define CMD_HELP 212
#define CMD_HISTORY_MONTHLY 213
#define CMD_GHISTORY_MONTHLY 214
#define CMD_IMPORT 215
#define CMD_INFO 216
#define CMD_PREPEND 217
@@ -97,8 +96,6 @@
#define CMD_VERSION 228
#define CMD_SHELL 229
#define CMD_CONFIG 230
#define CMD_HISTORY_ANNUAL 231
#define CMD_GHISTORY_ANNUAL 232
// 3xx Attributes
#define ATT_PROJECT 300

View File

@@ -59,7 +59,6 @@ int handleAdd (std::string &);
int handleLog (std::string &);
int handleAppend (std::string &);
int handlePrepend (std::string &);
int handleExport (std::string &);
int handleDone (std::string &);
int handleModify (std::string &);
int handleProjects (std::string &);
@@ -127,6 +126,10 @@ std::string colorizeDebug (const std::string&);
// import.cpp
int handleImport (std::string&);
// export.cpp
int handleExportCSV (std::string &);
int handleExportiCal (std::string &);
// list template
///////////////////////////////////////////////////////////////////////////////
template <class T> bool listDiff (const T& left, const T& right)

View File

@@ -192,7 +192,11 @@ int shortUsage (std::string &outs)
row = table.addRow ();
table.addCell (row, 1, "task export");
table.addCell (row, 2, "Lists all tasks in CSV format.");
table.addCell (row, 2, "Lists all tasks in CSV format. Alias to export.csv");
row = table.addRow ();
table.addCell (row, 1, "task export.ical");
table.addCell (row, 2, "Lists all tasks in iCalendar format.");
row = table.addRow ();
table.addCell (row, 1, "task color [sample]");

View File

@@ -9,8 +9,9 @@ OBJECTS = ../t-TDB.o ../t-Task.o ../t-text.o ../t-Date.o ../t-Table.o \
../t-Nibbler.o ../t-Location.o ../t-Filter.o ../t-Context.o \
../t-Keymap.o ../t-command.o ../t-interactive.o ../t-report.o \
../t-Grid.o ../t-Color.o ../t-rules.o ../t-recur.o ../t-custom.o \
../t-import.o ../t-edit.o ../t-Timer.o ../t-Permission.o ../t-Path.o \
../t-File.o ../t-Directory.o ../t-Hooks.o ../t-API.o
../t-export.o ../t-import.o ../t-edit.o ../t-Timer.o \
../t-Permission.o ../t-Path.o ../t-File.o ../t-Directory.o \
../t-Hooks.o ../t-API.o
all: $(PROJECT)