Merge branch '1.8.0'

Conflicts:
	.gitignore
	AUTHORS
	ChangeLog
	DEVELOPERS
	Makefile.am
	NEWS
	README
	configure.ac
	doc/man/task.1
	doc/man/taskrc.5
	src/T.cpp
	src/T.h
	src/TDB.cpp
	src/TDB.h
	src/command.cpp
	src/edit.cpp
	src/import.cpp
	src/parse.cpp
	src/report.cpp
	src/rules.cpp
	src/task.cpp
	src/task.h
	task_completion.sh
This commit is contained in:
Paul Beckingham
2009-07-21 19:15:54 -04:00
231 changed files with 16844 additions and 11820 deletions

761
src/Att.cpp Normal file
View File

@@ -0,0 +1,761 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include <stdlib.h>
#include <string.h>
#include "text.h"
#include "color.h"
#include "util.h"
#include "Date.h"
#include "Duration.h"
#include "Context.h"
#include "Att.h"
extern Context context;
static const char* internalNames[] =
{
"entry",
"start",
"end",
"parent",
"uuid",
"mask",
"imask",
"limit",
"status",
"description",
};
static const char* modifiableNames[] =
{
"project",
"priority",
"fg",
"bg",
"due",
"recur",
"until",
"wait",
};
// Synonyms on the same line.
static const char* modifierNames[] =
{
"before", "under", "below",
"after", "over", "above",
"none",
"any",
"is", "equals",
"isnt", "not",
"has", "contains",
"hasnt",
"startswith", "left",
"endswith", "right",
};
#define NUM_INTERNAL_NAMES (sizeof (internalNames) / sizeof (internalNames[0]))
#define NUM_MODIFIABLE_NAMES (sizeof (modifiableNames) / sizeof (modifiableNames[0]))
#define NUM_MODIFIER_NAMES (sizeof (modifierNames) / sizeof (modifierNames[0]))
////////////////////////////////////////////////////////////////////////////////
Att::Att ()
: mName ("")
, mValue ("")
, mMod ("")
{
}
////////////////////////////////////////////////////////////////////////////////
Att::Att (const std::string& name, const std::string& mod, const std::string& value)
{
mName = name;
mValue = value;
mMod = mod;
}
////////////////////////////////////////////////////////////////////////////////
Att::Att (const std::string& name, const std::string& mod, int value)
{
mName = name;
std::stringstream s;
s << value;
mValue = s.str ();
mMod = mod;
}
////////////////////////////////////////////////////////////////////////////////
Att::Att (const std::string& name, const std::string& value)
{
mName = name;
mValue = value;
mMod = "";
}
////////////////////////////////////////////////////////////////////////////////
Att::Att (const std::string& name, int value)
{
mName = name;
std::stringstream s;
s << value;
mValue = s.str ();
mMod = "";
}
////////////////////////////////////////////////////////////////////////////////
Att::Att (const Att& other)
{
mName = other.mName;
mValue = other.mValue;
mMod = other.mMod;
}
////////////////////////////////////////////////////////////////////////////////
Att& Att::operator= (const Att& other)
{
if (this != &other)
{
mName = other.mName;
mValue = other.mValue;
mMod = other.mMod;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Att::~Att ()
{
}
////////////////////////////////////////////////////////////////////////////////
// For parsing.
bool Att::valid (const std::string& input) const
{
Nibbler n (input);
std::string ignored;
if (n.getUntilOneOf (".:", ignored))
{
if (ignored.length () == 0)
return false;
while (n.skip ('.'))
if (!n.getUntilOneOf (".:", ignored))
return false;
if (n.skip (':') &&
(n.getQuoted ('"', ignored) ||
n.getUntil (' ', ignored) ||
n.getUntilEOS (ignored) ||
n.depleted ()))
return true;
return false;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Att::validInternalName (const std::string& name)
{
for (unsigned int i = 0; i < NUM_INTERNAL_NAMES; ++i)
if (name == internalNames[i])
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Att::validModifiableName (const std::string& name)
{
for (unsigned int i = 0; i < NUM_MODIFIABLE_NAMES; ++i)
if (name == modifiableNames[i])
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Att::validNameValue (
const std::string& name,
const std::string& mod,
const std::string& value)
{
std::string writableName = name;
std::string writableMod = mod;
std::string writableValue = value;
return Att::validNameValue (writableName, writableMod, writableValue);
}
////////////////////////////////////////////////////////////////////////////////
bool Att::validNameValue (
std::string& name,
std::string& mod,
std::string& value)
{
// First, guess at the full attribute name.
std::vector <std::string> candidates;
for (unsigned i = 0; i < NUM_INTERNAL_NAMES; ++i)
candidates.push_back (internalNames[i]);
for (unsigned i = 0; i < NUM_MODIFIABLE_NAMES; ++i)
candidates.push_back (modifiableNames[i]);
std::vector <std::string> matches;
autoComplete (name, candidates, matches);
if (matches.size () == 0)
return false;
else if (matches.size () != 1)
{
std::string error = "Ambiguous attribute '" + name + "' - could be either of "; // TODO i18n
std::string combined;
join (combined, ", ", matches);
throw error + combined;
}
name = matches[0];
// Second, guess at the modifier name.
if (mod != "")
{
candidates.clear ();
for (unsigned i = 0; i < NUM_MODIFIER_NAMES; ++i)
candidates.push_back (modifierNames[i]);
matches.clear ();
autoComplete (mod, candidates, matches);
if (matches.size () == 0)
throw std::string ("Unrecognized modifier '") + mod + "'";
else if (matches.size () != 1)
{
std::string error = "Ambiguous modifier '" + mod + "' - could be either of "; // TODO i18n
std::string combined;
join (combined, ", ", matches);
error += combined;
throw error + combined;
}
mod = matches[0];
}
// Some attributes are intended to be private, unless the command is read-
// only, in which cased these are perfectly valid elements of a filter.
if (context.cmd.isWriteCommand () &&
!validModifiableName (name))
throw std::string ("\"") +
name +
"\" is not an attribute you may modify directly.";
// Thirdly, make sure the value has the expected form or values.
if (name == "project")
{
if (!noSpaces (value))
throw std::string ("The '") + name + "' attribute may not contain spaces.";
}
else if (name == "priority")
{
if (value != "")
{
value = upperCase (value);
if (value != "H" &&
value != "M" &&
value != "L")
throw std::string ("\"") +
value +
"\" is not a valid priority. Use H, M, L or leave blank.";
}
}
else if (name == "description")
{
if (context.cmd.isWriteCommand ())
{
if (value == "")
throw std::string ("The '") + name + "' attribute must not be blank.";
if (!noVerticalSpace (value))
throw std::string ("The '") + name + "' attribute must not contain vertical white space.";
}
}
else if (name == "fg" || name == "bg")
{
if (value != "")
Text::guessColor (value);
}
else if (name == "due" ||
name == "until" ||
name == "wait")
{
// Validate and convert to epoch.
if (value != "")
value = Date (value, context.config.get ("dateformat", "m/d/Y")).toEpochString ();
}
else if (name == "recur")
{
// Just validate, don't convert to days.
Duration d;
if (value != "")
d.parse (value);
}
else if (name == "limit")
{
if (value == "" || !digitsOnly (value))
throw std::string ("The '") + name + "' attribute must be an integer.";
}
else if (name == "status")
{
value = lowerCase (value);
std::vector <std::string> matches;
std::vector <std::string> candidates;
candidates.push_back ("pending");
candidates.push_back ("completed");
candidates.push_back ("deleted");
candidates.push_back ("recurring");
candidates.push_back ("waiting");
autoComplete (value, candidates, matches);
if (matches.size () == 1)
value = matches[0];
else
throw std::string ("\"") +
value +
"\" is not a valid status. Use 'pending', 'completed', 'deleted', 'recurring' or 'waiting'.";
}
else if (! validInternalName (name) &&
! validModifiableName (name))
throw std::string ("'") + name + "' is not a recognized attribute.";
return true;
}
////////////////////////////////////////////////////////////////////////////////
// TODO Obsolete
bool Att::validMod (const std::string& mod)
{
for (unsigned int i = 0; i < NUM_MODIFIER_NAMES; ++i)
if (modifierNames[i] == mod)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
// The type of an attribute is useful for modifier evaluation.
std::string Att::type (const std::string& name) const
{
if (name == "due" ||
name == "until" ||
name == "start" ||
name == "entry" ||
name == "end" ||
name == "wait")
return "date";
else if (name == "recur")
return "duration";
else if (name == "limit")
return "number";
else
return "text";
}
////////////////////////////////////////////////////////////////////////////////
//
// start --> name --> . --> mod --> : --> " --> value --> " --> end
// | ^
// |_____________________|
//
void Att::parse (const std::string& input)
{
Nibbler n (input);
parse (n);
}
void Att::parse (Nibbler& n)
{
// Ensure a clean object first.
mName = "";
mValue = "";
mMod = "";
if (n.getUntilOneOf (".:", mName))
{
if (mName.length () == 0)
throw std::string ("Missing attribute name"); // TODO i18n
if (n.skip ('.'))
{
std::string mod;
if (n.getUntil (":", mod))
{
if (validMod (mod))
mMod = mod;
else
throw std::string ("The name '") + mod + "' is not a valid modifier"; // TODO i18n
}
else
throw std::string ("Missing . or : after modifier"); // TODO i18n
}
if (n.skip (':'))
{
// Both quoted and unquoted Att's are accepted.
// Consider removing this for a stricter parse.
if (n.getQuoted ('"', mValue) ||
n.getUntil (' ', mValue))
{
decode (mValue);
}
}
else
throw std::string ("Missing : after attribute name"); // TODO i18n
}
else
throw std::string ("Missing : after attribute name"); // TODO i18n
/* TODO This might be too slow to include. Test.
validNameValue (mName, mMod, mValue);
*/
}
////////////////////////////////////////////////////////////////////////////////
// "this" is the attribute that has modifiers. "other" is the attribute from a
// Record that does not have modifiers, but may have a value.
bool Att::match (const Att& other) const
{
// All matches are assumed to pass, any short-circuit on non-match.
// If there are no mods, just perform a straight compare on value.
if (mMod == "")
{
if (mValue != other.mValue)
return false;
}
// has = contains as a substring.
else if (mMod == "has" || mMod == "contains") // TODO i18n
{
if (other.mValue.find (mValue) == std::string::npos)
return false;
}
// is = equal. Nop.
else if (mMod == "is" || mMod == "equals") // TODO i18n
{
if (mValue != other.mValue)
return false;
}
// isnt = not equal.
else if (mMod == "isnt" || mMod == "not") // TODO i18n
{
if (mValue == other.mValue)
return false;
}
// any = any value, but not empty value.
else if (mMod == "any") // TODO i18n
{
if (other.mValue == "")
return false;
}
// none = must have empty value.
else if (mMod == "none") // TODO i18n
{
if (other.mValue != "")
return false;
}
// startswith = first characters must match.
else if (mMod == "startswith" || mMod == "left") // TODO i18n
{
if (other.mValue.length () < mValue.length ())
return false;
if (mValue != other.mValue.substr (0, mValue.length ()))
return false;
}
// endswith = last characters must match.
else if (mMod == "endswith" || mMod == "right") // TODO i18n
{
if (other.mValue.length () < mValue.length ())
return false;
if (mValue != other.mValue.substr (
other.mValue.length () - mValue.length (),
std::string::npos))
return false;
}
// hasnt = does not contain as a substring.
else if (mMod == "hasnt") // TODO i18n
{
if (other.mValue.find (mValue) != std::string::npos)
return false;
}
// before = under = below = <
else if (mMod == "before" || mMod == "under" || mMod == "below")
{
std::string which = type (mName);
if (which == "duration")
{
Duration literal (mValue);
Duration variable ((time_t)::atoi (other.mValue.c_str ()));
if (!(variable < literal))
return false;
}
else if (which == "date")
{
Date literal (mValue.c_str (), context.config.get ("dateformat", "m/d/Y"));
Date variable ((time_t)::atoi (other.mValue.c_str ()));
if (other.mValue == "" || ! (variable < literal))
return false;
}
else if (which == "number")
{
if (::atoi (mValue.c_str ()) >= ::atoi (other.mValue.c_str ()))
return false;
}
else if (which == "text")
{
if (mValue <= other.mValue)
return false;
}
}
// after = over = above = >
else if (mMod == "after" || mMod == "over" || mMod == "above")
{
std::string which = type (mName);
if (which == "duration")
{
Duration literal (mValue);
Duration variable ((time_t)::atoi (other.mValue.c_str ()));
if (! (variable > literal))
return false;
}
else if (which == "date")
{
Date literal (mValue.c_str (), context.config.get ("dateformat", "m/d/Y"));
Date variable ((time_t)::atoi (other.mValue.c_str ()));
if (! (variable > literal))
return false;
}
else if (which == "number")
{
if (::atoi (mValue.c_str ()) <= ::atoi (other.mValue.c_str ()))
return false;
}
else if (which == "text")
{
if (mValue >= other.mValue)
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// name : " value "
std::string Att::composeF4 () const
{
std::string output = "";
if (mName != "" && mValue != "")
{
std::string value = mValue;
encode (value);
enquote (value);
output += mName + ":" + value;
}
return output;
}
////////////////////////////////////////////////////////////////////////////////
void Att::mod (const std::string& input)
{
if (input != "" && !validMod (input))
throw std::string ("The name '") + input + "' is not a valid modifier"; // TODO i18n
mMod = input;
}
////////////////////////////////////////////////////////////////////////////////
std::string Att::mod () const
{
return mMod;
}
////////////////////////////////////////////////////////////////////////////////
std::string Att::name () const
{
return mName;
}
////////////////////////////////////////////////////////////////////////////////
void Att::name (const std::string& name)
{
mName = name;
}
////////////////////////////////////////////////////////////////////////////////
std::string Att::value () const
{
return mValue;
}
////////////////////////////////////////////////////////////////////////////////
void Att::value (const std::string& value)
{
mValue = value;
}
////////////////////////////////////////////////////////////////////////////////
int Att::value_int () const
{
return ::atoi (mValue.c_str ());
}
////////////////////////////////////////////////////////////////////////////////
void Att::value_int (int value)
{
std::stringstream s;
s << value;
mValue = s.str ();
}
////////////////////////////////////////////////////////////////////////////////
// Add quotes.
void Att::enquote (std::string& value) const
{
value = '"' + value + '"';
}
////////////////////////////////////////////////////////////////////////////////
// Remove quotes. Instead of being picky, just remove them all. There should
// be none within the value, and this will correct for one possible corruption
// that hand-editing the pending.data file could cause.
void Att::dequote (std::string& value) const
{
std::string::size_type quote;
while ((quote = value.find ('"')) != std::string::npos)
value.replace (quote, 1, "");
}
////////////////////////////////////////////////////////////////////////////////
// Encode values prior to serialization.
// \t -> &tab;
// " -> &quot;
// , -> &comma;
// [ -> &open;
// ] -> &close;
// : -> &colon;
void Att::encode (std::string& value) const
{
std::string::size_type i;
while ((i = value.find ('\t')) != std::string::npos)
value.replace (i, 1, "&tab;"); // no i18n
while ((i = value.find ('"')) != std::string::npos)
value.replace (i, 1, "&quot;"); // no i18n
while ((i = value.find (',')) != std::string::npos)
value.replace (i, 1, "&comma;"); // no i18n
while ((i = value.find ('[')) != std::string::npos)
value.replace (i, 1, "&open;"); // no i18n
while ((i = value.find (']')) != std::string::npos)
value.replace (i, 1, "&close;"); // no i18n
while ((i = value.find (':')) != std::string::npos)
value.replace (i, 1, "&colon;"); // no i18n
}
////////////////////////////////////////////////////////////////////////////////
// Decode values after parse.
// \t <- &tab;
// " <- &quot;
// , <- &comma;
// [ <- &open;
// ] <- &close;
// : <- &colon;
void Att::decode (std::string& value) const
{
std::string::size_type i;
while ((i = value.find ("&tab;")) != std::string::npos) // no i18n
value.replace (i, 5, "\t");
while ((i = value.find ("&quot;")) != std::string::npos) // no i18n
value.replace (i, 6, "\"");
while ((i = value.find ("&comma;")) != std::string::npos) // no i18n
value.replace (i, 7, ",");
while ((i = value.find ("&open;")) != std::string::npos) // no i18n
value.replace (i, 6, "[");
while ((i = value.find ("&close;")) != std::string::npos) // no i18n
value.replace (i, 7, "]");
while ((i = value.find ("&colon;")) != std::string::npos) // no i18n
value.replace (i, 7, ":");
}
////////////////////////////////////////////////////////////////////////////////

84
src/Att.h Normal file
View File

@@ -0,0 +1,84 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_ATT
#define INCLUDED_ATT
#include <string>
#include <vector>
#include "Nibbler.h"
class Att
{
public:
Att ();
Att (const std::string&, const std::string&, const std::string&);
Att (const std::string&, const std::string&, int);
Att (const std::string&, const std::string&);
Att (const std::string&, int);
Att (const Att&);
Att& operator= (const Att&);
~Att ();
bool valid (const std::string&) const;
static bool validInternalName (const std::string&);
static bool validModifiableName (const std::string&);
static bool validNameValue (const std::string&, const std::string&, const std::string&);
static bool validNameValue (std::string&, std::string&, std::string&);
static bool validMod (const std::string&);
std::string type (const std::string&) const;
void parse (const std::string&);
void parse (Nibbler&);
bool match (const Att&) const;
std::string composeF4 () const;
void mod (const std::string&);
std::string mod () const;
std::string name () const;
void name (const std::string&);
std::string value () const;
void value (const std::string&);
int value_int () const;
void value_int (int);
private:
void enquote (std::string&) const;
void dequote (std::string&) const;
void encode (std::string&) const;
void decode (std::string&) const;
private:
std::string mName;
std::string mValue;
std::string mMod;
};
#endif
////////////////////////////////////////////////////////////////////////////////

234
src/Cmd.cpp Normal file
View File

@@ -0,0 +1,234 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <algorithm>
#include "Cmd.h"
#include "Context.h"
#include "util.h"
#include "text.h"
#include "i18n.h"
#include "main.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
Cmd::Cmd ()
: command ("")
{
}
////////////////////////////////////////////////////////////////////////////////
Cmd::Cmd (const std::string& input)
{
parse (input);
}
////////////////////////////////////////////////////////////////////////////////
Cmd::~Cmd ()
{
}
////////////////////////////////////////////////////////////////////////////////
// Determines whether the string represents a unique command name or custom
// report name.
bool Cmd::valid (const std::string& input)
{
load ();
std::vector <std::string> matches;
autoComplete (lowerCase (context.canonicalize (input)), commands, matches);
return matches.size () == 1 ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
// Determines whether the string represents a valid custom report name.
bool Cmd::validCustom (const std::string& input)
{
load ();
std::vector <std::string> matches;
autoComplete (lowerCase (context.canonicalize (input)), customReports, matches);
return matches.size () == 1 ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::parse (const std::string& input)
{
load ();
std::string candidate = lowerCase (context.canonicalize (input));
std::vector <std::string> matches;
autoComplete (candidate, commands, matches);
if (1 == matches.size ())
command = matches[0];
else if (0 == matches.size ())
command = "";
else
{
std::string error = "Ambiguous command '" + candidate + "' - could be either of "; // TODO i18n
std::string combined;
join (combined, ", ", matches);
throw error + combined;
}
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::load ()
{
if (commands.size () == 0)
{
commands.push_back ("_projects");
commands.push_back ("_tags");
commands.push_back ("_commands");
commands.push_back ("_ids");
commands.push_back ("_config");
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"));
commands.push_back (context.stringtable.get (CMD_CALENDAR, "calendar"));
commands.push_back (context.stringtable.get (CMD_COLORS, "colors"));
commands.push_back (context.stringtable.get (CMD_DELETE, "delete"));
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, "history"));
commands.push_back (context.stringtable.get (CMD_GHISTORY, "ghistory"));
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_PROJECTS, "projects"));
#ifdef FEATURE_SHELL
commands.push_back (context.stringtable.get (CMD_SHELL, "shell"));
#endif
commands.push_back (context.stringtable.get (CMD_START, "start"));
commands.push_back (context.stringtable.get (CMD_STATS, "stats"));
commands.push_back (context.stringtable.get (CMD_STOP, "stop"));
commands.push_back (context.stringtable.get (CMD_SUMMARY, "summary"));
commands.push_back (context.stringtable.get (CMD_TAGS, "tags"));
commands.push_back (context.stringtable.get (CMD_TIMESHEET, "timesheet"));
commands.push_back (context.stringtable.get (CMD_UNDO, "undo"));
commands.push_back (context.stringtable.get (CMD_VERSION, "version"));
// Now load the custom reports.
std::vector <std::string> all;
context.config.all (all);
foreach (i, all)
{
if (i->substr (0, 7) == "report.")
{
std::string report = i->substr (7, std::string::npos);
std::string::size_type columns = report.find (".columns");
if (columns != std::string::npos)
{
report = report.substr (0, columns);
// Make sure a custom report does not clash with a built-in
// command.
if (std::find (commands.begin (), commands.end (), report) != commands.end ())
throw std::string ("Custom report '") + report +
"' conflicts with built-in task command.";
// A custom report is also a command.
customReports.push_back (report);
commands.push_back (report);
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::allCustomReports (std::vector <std::string>& all) const
{
all = customReports;
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::allCommands (std::vector <std::string>& all) const
{
all.clear ();
foreach (command, commands)
if (command->substr (0, 1) != "_")
all.push_back (*command);
}
////////////////////////////////////////////////////////////////////////////////
// Commands that do not directly modify the data files.
bool Cmd::isReadOnlyCommand ()
{
if (command == "_projects" ||
command == "_tags" ||
command == "_commands" ||
command == "_ids" ||
command == "_config" ||
command == context.stringtable.get (CMD_CALENDAR, "calendar") ||
command == context.stringtable.get (CMD_COLORS, "colors") ||
command == context.stringtable.get (CMD_EXPORT, "export") ||
command == context.stringtable.get (CMD_HELP, "help") ||
command == context.stringtable.get (CMD_HISTORY, "history") ||
command == context.stringtable.get (CMD_GHISTORY, "ghistory") ||
command == context.stringtable.get (CMD_INFO, "info") ||
command == context.stringtable.get (CMD_PROJECTS, "projects") ||
command == context.stringtable.get (CMD_SHELL, "shell") ||
command == context.stringtable.get (CMD_STATS, "stats") ||
command == context.stringtable.get (CMD_SUMMARY, "summary") ||
command == context.stringtable.get (CMD_TAGS, "tags") ||
command == context.stringtable.get (CMD_TIMESHEET, "timesheet") ||
command == context.stringtable.get (CMD_VERSION, "version") ||
validCustom (command))
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
// Commands that directly modify the data files.
bool Cmd::isWriteCommand ()
{
if (command == context.stringtable.get (CMD_ADD, "add") ||
command == context.stringtable.get (CMD_APPEND, "append") ||
command == context.stringtable.get (CMD_ANNOTATE, "annotate") ||
command == context.stringtable.get (CMD_DELETE, "delete") ||
command == context.stringtable.get (CMD_DONE, "done") ||
command == context.stringtable.get (CMD_DUPLICATE, "duplicate") ||
command == context.stringtable.get (CMD_EDIT, "edit") ||
command == context.stringtable.get (CMD_IMPORT, "import") ||
command == context.stringtable.get (CMD_START, "start") ||
command == context.stringtable.get (CMD_STOP, "stop") ||
command == context.stringtable.get (CMD_UNDO, "undo"))
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////

64
src/Cmd.h Normal file
View File

@@ -0,0 +1,64 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_CMD
#define INCLUDED_CMD
#include <vector>
#include <string>
class Cmd
{
public:
Cmd (); // Default constructor
Cmd (const std::string&); // Default constructor
~Cmd (); // Destructor
Cmd (const Cmd&);
Cmd& operator= (const Cmd&);
bool valid (const std::string&);
bool validCustom (const std::string&);
void parse (const std::string&);
void allCustomReports (std::vector <std::string>&) const;
void allCommands (std::vector <std::string>&) const;
bool isReadOnlyCommand ();
bool isWriteCommand ();
public:
std::string command;
private:
void load ();
private:
std::vector <std::string> commands;
std::vector <std::string> customReports;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -32,8 +32,9 @@
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#include "task.h"
#include "Config.h"
#include "text.h"
#include "util.h"
////////////////////////////////////////////////////////////////////////////////
// These are default (but overridable) reports. These entries are necessary
@@ -44,32 +45,7 @@
// upgrade program to make the change, or c) this.
Config::Config ()
{
(*this)["report.long.description"] = "Lists all task, all data, matching the specified criteria";
(*this)["report.long.columns"] = "id,project,priority,entry,start,due,recur,age,tags,description";
(*this)["report.long.labels"] = "ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description";
(*this)["report.long.sort"] = "due+,priority-,project+";
(*this)["report.list.description"] = "Lists all tasks matching the specified criteria";
(*this)["report.list.columns"] = "id,project,priority,due,active,age,description";
(*this)["report.list.labels"] = "ID,Project,Pri,Due,Active,Age,Description";
(*this)["report.list.sort"] = "due+,priority-,project+";
(*this)["report.ls.description"] = "Minimal listing of all tasks matching the specified criteria";
(*this)["report.ls.columns"] = "id,project,priority,description";
(*this)["report.ls.labels"] = "ID,Project,Pri,Description";
(*this)["report.ls.sort"] = "priority-,project+";
(*this)["report.newest.description"] = "Shows the newest tasks";
(*this)["report.newest.columns"] = "id,project,priority,due,active,age,description";
(*this)["report.newest.labels"] = "ID,Project,Pri,Due,Active,Age,Description";
(*this)["report.newest.sort"] = "id-";
(*this)["report.newest.limit"] = "10";
(*this)["report.oldest.description"] = "Shows the oldest tasks";
(*this)["report.oldest.columns"] = "id,project,priority,due,active,age,description";
(*this)["report.oldest.labels"] = "ID,Project,Pri,Due,Active,Age,Description";
(*this)["report.oldest.sort"] = "id+";
(*this)["report.oldest.limit"] = "10";
setDefaults ();
}
////////////////////////////////////////////////////////////////////////////////
@@ -92,20 +68,20 @@ bool Config::load (const std::string& file)
while (getline (in, line))
{
// Remove comments.
size_type pound = line.find ("#");
std::string::size_type pound = line.find ("#"); // no i18n
if (pound != std::string::npos)
line = line.substr (0, pound);
line = trim (line, " \t");
line = trim (line, " \t"); // no i18n
// Skip empty lines.
if (line.length () > 0)
{
size_type equal = line.find ("=");
std::string::size_type equal = line.find ("="); // no i18n
if (equal != std::string::npos)
{
std::string key = trim (line.substr (0, equal), " \t");
std::string value = trim (line.substr (equal+1, line.length () - equal), " \t");
std::string key = trim (line.substr (0, equal), " \t"); // no i18n
std::string value = trim (line.substr (equal+1, line.length () - equal), " \t"); // no i18n
(*this)[key] = value;
}
}
@@ -119,112 +95,234 @@ bool Config::load (const std::string& file)
}
////////////////////////////////////////////////////////////////////////////////
void Config::createDefault (const std::string& home)
void Config::createDefaultRC (const std::string& rc, const std::string& data)
{
// Strip trailing slash off home directory, if necessary.
std::string terminatedHome = home;
if (home[home.length () - 1] == '/')
terminatedHome = home.substr (0, home.length () - 1);
// Create a sample .taskrc file.
std::stringstream contents;
contents << "# Task program configuration file.\n"
<< "# For more documentation, see http://taskwarrior.org\n"
<< "\n"
<< "# Files\n"
<< "data.location=" << data << "\n"
<< "locking=on # Use file-level locking\n"
<< "\n"
<< "# Terminal\n"
<< "curses=on # Use ncurses library to determine terminal width\n"
<< "#defaultwidth=80 # Without ncurses, assumed width\n"
<< "#editor=vi # Preferred text editor\n"
<< "\n"
<< "# Miscellaneous\n"
<< "confirmation=yes # Confirmation on delete, big changes\n"
<< "echo.command=yes # Details on command just run\n"
<< "next=2 # How many tasks per project in next report\n"
<< "bulk=2 # > 2 tasks considered 'a lot', for confirmation\n"
<< "nag=You have higher priority tasks. # Nag message to keep you honest\n"
<< "\n"
<< "# Dates\n"
<< "dateformat=m/d/Y # Preferred input and display date format\n"
<< "weekstart=Sunday # Sunday or Monday only\n"
<< "displayweeknumber=yes # Show week numbers on calendar\n"
<< "due=7 # Task is considered due in 7 days\n"
<< "#monthsperline=2 # Number of calendar months on a line\n"
<< "\n"
<< "# Color controls.\n"
<< "color=on # Use color\n"
<< "color.overdue=bold_red # Color of overdue tasks\n"
<< "color.due=bold_yellow # Color of due tasks\n"
<< "color.pri.H=bold # Color of priority:H tasks\n"
<< "#color.pri.M=on_yellow # Color of priority:M tasks\n"
<< "#color.pri.L=on_green # Color of priority:L tasks\n"
<< "#color.pri.none=white on_blue # Color of priority: tasks\n"
<< "color.active=bold_cyan # Color of active tasks\n"
<< "color.tagged=yellow # Color of tagged tasks\n"
<< "#color.tag.bug=yellow # Color of +bug tasks\n"
<< "#color.project.garden=on_green # Color of project:garden tasks\n"
<< "#color.keyword.car=on_blue # Color of description.contains:car tasks\n"
<< "#color.recurring=on_red # Color of recur.any: tasks\n"
<< "#color.header=bold_green # Color of header messages\n"
<< "#color.footnote=bold_green # Color of footnote messages\n"
<< "\n"
<< "#shadow.file=/tmp/shadow.txt # Location of shadow file\n"
<< "#shadow.command=list # Task command for shadow file\n"
<< "#shadow.notify=on # Footnote when updated\n"
<< "\n"
<< "#default.project=foo # Unless otherwise specified\n"
<< "#default.priority=M # Unless otherwise specified\n"
<< "default.command=list # Unless otherwise specified\n"
<< "\n"
<< "# Fields: id,uuid,project,priority,entry,start,due,recur,recur_ind,age,\n"
<< "# age_compact,active,tags,description,description_only\n"
<< "# Description: This report is ...\n"
<< "# Sort: due+,priority-,project+\n"
<< "# Filter: pro:x pri:H +bug limit:10\n"
<< "\n"
<< "# task long\n"
<< "report.long.description=Lists all task, all data, matching the specified criteria\n"
<< "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n"
<< "report.long.labels=ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description\n"
<< "report.long.sort=due+,priority-,project+\n"
<< "report.long.filter=status:pending\n"
<< "\n"
<< "# task list\n"
<< "report.list.description=Lists all tasks matching the specified criteria\n"
<< "report.list.columns=id,project,priority,due,active,age,description\n"
<< "report.list.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.list.sort=due+,priority-,project+\n"
<< "report.list.filter=status:pending\n"
<< "\n"
<< "# task ls\n"
<< "report.ls.description=Minimal listing of all tasks matching the specified criteria\n"
<< "report.ls.columns=id,project,priority,description\n"
<< "report.ls.labels=ID,Project,Pri,Description\n"
<< "report.ls.sort=priority-,project+\n"
<< "report.ls.filter=status:pending\n"
<< "\n"
<< "# task newest\n"
<< "report.newest.description=Shows the newest tasks\n"
<< "report.newest.columns=id,project,priority,due,active,age,description\n"
<< "report.newest.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.newest.sort=id-\n"
<< "report.newest.filter=status:pending limit:10\n"
<< "\n"
<< "# task oldest\n"
<< "report.oldest.description=Shows the oldest tasks\n"
<< "report.oldest.columns=id,project,priority,due,active,age,description\n"
<< "report.oldest.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.oldest.sort=id+\n"
<< "report.oldest.filter=status:pending limit:10\n"
<< "\n"
<< "# task overdue\n"
<< "report.overdue.description=Lists overdue tasks matching the specified criteria\n"
<< "report.overdue.columns=id,project,priority,due,active,age,description\n"
<< "report.overdue.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.overdue.sort=due+,priority-,project+\n"
<< "report.overdue.filter=status:pending due.before:today\n"
<< "\n"
<< "# task active\n"
<< "report.active.description=Lists active tasks matching the specified criteria\n"
<< "report.active.columns=id,project,priority,due,active,age,description\n"
<< "report.active.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.active.sort=due+,priority-,project+\n"
<< "report.active.filter=status:pending start.any:\n"
<< "\n"
<< "# task completed\n"
<< "report.completed.description=Lists completed tasks matching the specified criteria\n"
<< "report.completed.columns=end,project,priority,age,description\n"
<< "report.completed.labels=Complete,Project,Pri,Age,Description\n"
<< "report.completed.sort=end+,priority-,project+\n"
<< "report.completed.filter=status:completed\n"
<< "\n"
<< "# task recurring\n"
<< "report.recurring.description=Lists recurring tasks matching the specified criteria\n"
<< "report.recurring.columns=id,project,priority,due,recur,active,age,description\n"
<< "report.recurring.labels=ID,Project,Pri,Due,Recur,Active,Age,Description\n"
<< "report.recurring.sort=due+,priority-,project+\n"
<< "report.recurring.filter=status:pending parent.any:\n"
<< "\n"
<< "# task waiting\n"
<< "report.waiting.description=Lists all waiting tasks matching the specified criteria\n"
<< "report.waiting.columns=id,project,priority,wait,age,description\n"
<< "report.waiting.labels=ID,Project,Pri,Wait,Age,Description\n"
<< "report.waiting.sort=wait+,priority-,project+\n"
<< "report.waiting.filter=status:waiting\n"
<< "\n"
<< "# task all\n"
<< "report.all.description=Lists all tasks matching the specified criteria\n"
<< "report.all.columns=id,project,priority,due,active,age,description\n"
<< "report.all.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.all.sort=due+,priority-,project+\n"
<< "\n"
<< "# task next\n"
<< "report.next.description=Lists the most urgent tasks\n"
<< "report.next.columns=id,project,priority,due,active,age,description\n"
<< "report.next.labels=ID,Project,Pri,Due,Active,Age,Description\n"
<< "report.next.sort=due+,priority-,project+\n"
<< "report.next.filter=status:pending\n"
<< "\n";
// Determine default names of init file and task directory.
std::string rcFile = terminatedHome + "/.taskrc";
std::string dataDir = terminatedHome + "/.task";;
spit (rc, contents.str ());
}
// If rcFile is not found, offer to create one.
if (-1 == access (rcFile.c_str (), F_OK))
{
if (confirm (
"A configuration file could not be found in "
+ rcFile
+ "\n\n"
+ "Would you like a sample .taskrc created, so task can proceed?"))
{
// Create a sample .taskrc file.
FILE* out;
if ((out = fopen (rcFile.c_str (), "w")))
{
fprintf (out, "data.location=%s\n", dataDir.c_str ());
fprintf (out, "confirmation=yes\n");
fprintf (out, "echo.command=yes\n");
fprintf (out, "next=2\n");
fprintf (out, "dateformat=m/d/Y\n");
fprintf (out, "#monthsperline=2\n");
fprintf (out, "#defaultwidth=80\n");
fprintf (out, "curses=on\n");
fprintf (out, "color=on\n");
fprintf (out, "due=7\n");
fprintf (out, "nag=You have higher priority tasks.\n");
fprintf (out, "locking=on\n");
fprintf (out, "#editor=vi\n");
////////////////////////////////////////////////////////////////////////////////
void Config::createDefaultData (const std::string& data)
{
if (access (data.c_str (), F_OK))
mkdir (data.c_str (), S_IRWXU);
}
fprintf (out, "color.overdue=bold_red\n");
fprintf (out, "color.due=bold_yellow\n");
fprintf (out, "color.pri.H=bold\n");
fprintf (out, "#color.pri.M=on_yellow\n");
fprintf (out, "#color.pri.L=on_green\n");
fprintf (out, "#color.pri.none=white on_blue\n");
fprintf (out, "color.active=bold_cyan\n");
fprintf (out, "color.tagged=yellow\n");
fprintf (out, "#color.tag.bug=yellow\n");
fprintf (out, "#color.project.garden=on_green\n");
fprintf (out, "#color.keyword.car=on_blue\n");
fprintf (out, "#color.recurring=on_red\n");
fprintf (out, "#shadow.file=%s/shadow.txt\n", dataDir.c_str ());
fprintf (out, "#shadow.command=list\n");
fprintf (out, "#shadow.notify=on\n");
fprintf (out, "#default.project=foo\n");
fprintf (out, "#default.priority=M\n");
fprintf (out, "default.command=list\n");
////////////////////////////////////////////////////////////////////////////////
void Config::setDefaults ()
{
set ("report.long.description", "Lists all task, all data, matching the specified criteria"); // TODO i18n
set ("report.long.columns", "id,project,priority,entry,start,due,recur,age,tags,description"); // TODO i18n
set ("report.long.labels", "ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description"); // TODO i18n
set ("report.long.sort", "due+,priority-,project+"); // TODO i18n
set ("report.long.filter", "status:pending"); // TODO i18n
// Custom reports.
fprintf (out, "# Fields: id,uuid,project,priority,entry,start,due,recur,age,active,tags,description\n");
fprintf (out, "# description_only\n");
fprintf (out, "# Description: This report is ...\n");
fprintf (out, "# Sort: due+,priority-,project+\n");
fprintf (out, "# Filter: pro:x pri:H +bug\n");
fprintf (out, "# Limit: 10\n");
set ("report.list.description", "Lists all tasks matching the specified criteria"); // TODO i18n
set ("report.list.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.list.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.list.sort", "due+,priority-,project+"); // TODO i18n
set ("report.list.filter", "status:pending"); // TODO i18n
fprintf (out, "report.long.description=Lists all task, all data, matching the specified criteria\n");
fprintf (out, "report.long.labels=ID,Project,Pri,Added,Started,Due,Recur,Age,Tags,Description\n");
fprintf (out, "report.long.columns=id,project,priority,entry,start,due,recur,age,tags,description\n");
fprintf (out, "report.long.sort=due+,priority-,project+\n");
set ("report.ls.description", "Minimal listing of all tasks matching the specified criteria"); // TODO i18n
set ("report.ls.columns", "id,project,priority,description"); // TODO i18n
set ("report.ls.labels", "ID,Project,Pri,Description"); // TODO i18n
set ("report.ls.sort", "priority-,project+"); // TODO i18n
set ("report.ls.filter", "status:pending"); // TODO i18n
fprintf (out, "report.list.description=Lists all tasks matching the specified criteria\n");
fprintf (out, "report.list.labels=ID,Project,Pri,Due,Active,Age,Description\n");
fprintf (out, "report.list.columns=id,project,priority,due,active,age,description\n");
fprintf (out, "report.list.sort=due+,priority-,project+\n");
set ("report.newest.description", "Shows the newest tasks"); // TODO i18n
set ("report.newest.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.newest.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.newest.sort", "id-"); // TODO i18n
set ("report.newest.filter", "status:pending limit:10"); // TODO i18n
fprintf (out, "report.ls.description=Minimal listing of all tasks matching the specified criteria\n");
fprintf (out, "report.ls.labels=ID,Project,Pri,Description\n");
fprintf (out, "report.ls.columns=id,project,priority,description\n");
fprintf (out, "report.ls.sort=priority-,project+\n");
set ("report.oldest.description", "Shows the oldest tasks"); // TODO i18n
set ("report.oldest.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.oldest.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.oldest.sort", "id+"); // TODO i18n
set ("report.oldest.filter", "status:pending limit:10"); // TODO i18n
fprintf (out, "report.newest.description=Shows the newest tasks\n");
fprintf (out, "report.newest.labels=ID,Project,Pri,Due,Active,Age,Description\n");
fprintf (out, "report.newest.columns=id,project,priority,due,active,age,description\n");
fprintf (out, "report.newest.sort=id-\n");
fprintf (out, "report.newest.limit=10\n");
set ("report.overdue.description", "Lists overdue tasks matching the specified criteria"); // TODO i18n
set ("report.overdue.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.overdue.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.overdue.sort", "due+,priority-,project+"); // TODO i18n
set ("report.overdue.filter", "status:pending due.before:today"); // TODO i18n
fprintf (out, "report.oldest.description=Shows the oldest tasks\n");
fprintf (out, "report.oldest.labels=ID,Project,Pri,Due,Active,Age,Description\n");
fprintf (out, "report.oldest.columns=id,project,priority,due,active,age,description\n");
fprintf (out, "report.oldest.sort=id+\n");
fprintf (out, "report.oldest.limit=10\n");
set ("report.active.description", "Lists active tasks matching the specified criteria"); // TODO i18n
set ("report.active.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.active.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.active.sort", "due+,priority-,project+"); // TODO i18n
set ("report.active.filter", "status:pending start.any:"); // TODO i18n
fclose (out);
set ("report.completed.description", "Lists completed tasks matching the specified criteria"); // TODO i18n
set ("report.completed.columns", "end,project,priority,age,description"); // TODO i18n
set ("report.completed.labels", "Complete,Project,Pri,Age,Description"); // TODO i18n
set ("report.completed.sort", "end+,priority-,project+"); // TODO i18n
set ("report.completed.filter", "status:completed"); // TODO i18n
std::cout << "Done." << std::endl;
}
}
}
set ("report.recurring.description", "Lists recurring tasks matching the specified criteria"); // TODO i18n
set ("report.recurring.columns", "id,project,priority,due,recur,active,age,description"); // TODO i18n
set ("report.recurring.labels", "ID,Project,Pri,Due,Recur,Active,Age,Description"); // TODO i18n
set ("report.recurring.sort", "due+,priority-,project+"); // TODO i18n
set ("report.recurring.filter", "status:pending parent.any:"); // TODO i18n
this->load (rcFile);
set ("report.waiting.description", "Lists all waiting tasks matching the specified criteria"); // TODO i18n
set ("report.waiting.columns", "id,project,priority,wait,age,description"); // TODO i18n
set ("report.waiting.labels", "ID,Project,Pri,Wait,Age,Description"); // TODO i18n
set ("report.waiting.sort", "wait+,priority-,project+"); // TODO i18n
set ("report.waiting.filter", "status:waiting"); // TODO i18n
// Get the data.location value from the (potentially newly created) .taskrc
// file.
dataDir = this->get ("data.location", dataDir);
if (-1 == access (dataDir.c_str (), F_OK))
mkdir (dataDir.c_str (), S_IRWXU);
set ("report.all.description", "Lists all tasks matching the specified criteria"); // TODO i18n
set ("report.all.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.all.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.all.sort", "due+,priority-,project+"); // TODO i18n
set ("report.next.description", "Lists the most urgent tasks"); // TODO i18n
set ("report.next.columns", "id,project,priority,due,active,age,description"); // TODO i18n
set ("report.next.labels", "ID,Project,Pri,Due,Active,Age,Description"); // TODO i18n
set ("report.next.sort", "due+,priority-,project+"); // TODO i18n
set ("report.next.filter", "status:pending"); // TODO i18n
}
////////////////////////////////////////////////////////////////////////////////
@@ -265,19 +363,19 @@ const std::string Config::get (
}
////////////////////////////////////////////////////////////////////////////////
bool Config::get (const std::string& key, bool default_value)
bool Config::get (const std::string& key, const bool default_value)
{
if ((*this).find (key) != (*this).end ())
{
std::string value = lowerCase ((*this)[key]);
if (value == "t" ||
value == "true" ||
value == "1" ||
value == "yes" ||
value == "on" ||
value == "enable" ||
value == "enabled")
if (value == "t" || // TODO i18n
value == "true" || // TODO i18n
value == "1" || // no i18n
value == "yes" || // TODO i18n
value == "on" || // TODO i18n
value == "enable" || // TODO i18n
value == "enabled") // TODO i18n
return true;
return false;

View File

@@ -37,14 +37,19 @@ public:
Config ();
Config (const std::string&);
Config (const Config&);
Config& operator= (const Config&);
bool load (const std::string&);
void createDefault (const std::string&);
void createDefaultRC (const std::string&, const std::string&);
void createDefaultData (const std::string&);
void setDefaults ();
const std::string get (const char*);
const std::string get (const char*, const char*);
const std::string get (const std::string&);
const std::string get (const std::string&, const std::string&);
bool get (const std::string&, bool);
bool get (const std::string&, const bool);
int get (const std::string&, const int);
double get (const std::string&, const double);
void set (const std::string&, const int);

757
src/Context.cpp Normal file
View File

@@ -0,0 +1,757 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <fstream>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include "Context.h"
#include "Timer.h"
#include "text.h"
#include "util.h"
#include "main.h"
#include "i18n.h"
#include "../auto.h"
////////////////////////////////////////////////////////////////////////////////
Context::Context ()
: config ()
, filter ()
, keymap ()
, sequence ()
, subst ()
, task ()
, tdb ()
, stringtable ()
, program ("")
, cmd ()
, inShadow (false)
{
}
////////////////////////////////////////////////////////////////////////////////
Context::~Context ()
{
}
////////////////////////////////////////////////////////////////////////////////
void Context::initialize (int argc, char** argv)
{
// Capture the args.
for (int i = 0; i < argc; ++i)
if (i == 0)
{
program = argv[i];
std::string::size_type cal = program.find ("/cal");
if (program == "cal" ||
(cal != std::string::npos && program.length () == cal + 4))
args.push_back ("calendar");
}
else
args.push_back (argv[i]);
initialize ();
}
////////////////////////////////////////////////////////////////////////////////
void Context::initialize ()
{
Timer t ("Context::initialize");
// Load the configuration file from the home directory. If the file cannot
// be found, offer to create a sample one.
loadCorrectConfigFile ();
loadAliases ();
// When redirecting output to a file, do not use color, curses.
if (!isatty (fileno (stdout)))
{
config.set ("curses", "off");
if (! config.get (std::string ("_forcecolor"), false))
config.set ("color", "off");
}
if (config.get ("color", true))
initializeColorRules ();
// Load appropriate stringtable as soon after the config file as possible, to
// allow all subsequent messages to be localizable.
std::string location = expandPath (config.get ("data.location"));
std::string locale = config.get ("locale");
// If there is a locale variant (en-US.<variant>), then strip it.
std::string::size_type period = locale.find ('.');
if (period != std::string::npos)
locale = locale.substr (0, period);
if (locale != "")
stringtable.load (location + "/strings." + locale);
// TODO Handle "--version, -v" right here?
// init TDB.
tdb.clear ();
std::vector <std::string> all;
split (all, location, ',');
foreach (path, all)
tdb.location (expandPath (*path));
}
////////////////////////////////////////////////////////////////////////////////
int Context::run ()
{
Timer t ("Context::run");
std::string output;
try
{
parse (); // Parse command line.
output = dispatch (); // Dispatch to command handlers.
}
catch (const std::string& error)
{
footnote (error);
}
catch (...)
{
footnote (stringtable.get (100, "Unknown error."));
}
// Dump all debug messages.
if (config.get (std::string ("debug"), false))
foreach (d, debugMessages)
std::cout << colorizeDebug (*d) << std::endl;
// Dump all headers.
foreach (h, headers)
std::cout << colorizeHeader (*h) << std::endl;
// Dump the report output.
std::cout << output;
// Dump all footnotes.
foreach (f, footnotes)
std::cout << colorizeFootnote (*f) << std::endl;
return 0;
}
////////////////////////////////////////////////////////////////////////////////
std::string Context::dispatch ()
{
Timer t ("Context::dispatch");
// TODO Just look at this thing. It cries out for a dispatch table.
std::string out;
if (cmd.command == "projects") { out = handleProjects (); }
else if (cmd.command == "tags") { out = handleTags (); }
else if (cmd.command == "colors") { out = handleColor (); }
else if (cmd.command == "version") { out = handleVersion (); }
else if (cmd.command == "help") { out = longUsage (); }
else if (cmd.command == "stats") { out = handleReportStats (); }
else if (cmd.command == "info") { out = handleInfo (); }
else if (cmd.command == "history") { out = handleReportHistory (); }
else if (cmd.command == "ghistory") { out = handleReportGHistory (); }
else if (cmd.command == "summary") { out = handleReportSummary (); }
else if (cmd.command == "calendar") { out = handleReportCalendar (); }
else if (cmd.command == "timesheet") { out = handleReportTimesheet (); }
else if (cmd.command == "add") { out = handleAdd (); }
else if (cmd.command == "append") { out = handleAppend (); }
else if (cmd.command == "annotate") { out = handleAnnotate (); }
else if (cmd.command == "done") { out = handleDone (); }
else if (cmd.command == "delete") { out = handleDelete (); }
else if (cmd.command == "start") { out = handleStart (); }
else if (cmd.command == "stop") { out = handleStop (); }
else if (cmd.command == "export") { out = handleExport (); }
else if (cmd.command == "import") { out = handleImport (); }
else if (cmd.command == "duplicate") { out = handleDuplicate (); }
else if (cmd.command == "edit") { out = handleEdit (); }
#ifdef FEATURE_SHELL
else if (cmd.command == "shell") { handleShell (); }
#endif
else if (cmd.command == "undo") { handleUndo (); }
else if (cmd.command == "_projects") { out = handleCompletionProjects (); }
else if (cmd.command == "_tags") { out = handleCompletionTags (); }
else if (cmd.command == "_commands") { out = handleCompletionCommands (); }
else if (cmd.command == "_ids") { out = handleCompletionIDs (); }
else if (cmd.command == "_config") { out = handleCompletionConfig (); }
else if (cmd.command == "" &&
sequence.size ()) { out = handleModify (); }
// Command that display IDs and therefore need TDB::gc first.
else if (cmd.command == "next") { if (!inShadow) tdb.gc (); out = handleReportNext (); }
else if (cmd.validCustom (cmd.command)) { if (!inShadow) tdb.gc (); out = handleCustomReport (cmd.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),
if (cmd.isWriteCommand () && !inShadow)
shadow ();
return out;
}
////////////////////////////////////////////////////////////////////////////////
void Context::shadow ()
{
// Determine if shadow file is enabled.
std::string shadowFile = expandPath (config.get ("shadow.file"));
if (shadowFile != "")
{
inShadow = true; // Prevents recursion in case shadow command writes.
// TODO Reinstate these checks.
/*
// Check for silly shadow file settings.
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::string oldCurses = config.get ("curses");
std::string oldColor = config.get ("color");
config.set ("curses", "off");
config.set ("color", "off");
clear ();
// Run report. Use shadow.command, using default.command as a fallback
// with "list" as a default.
std::string command = config.get ("shadow.command",
config.get ("default.command", "list"));
split (args, command, ' ');
initialize ();
parse ();
std::string result = dispatch ();
std::ofstream out (shadowFile.c_str ());
if (out.good ())
{
out << result;
out.close ();
}
else
throw std::string ("Could not write file '") + shadowFile + "'";
config.set ("curses", oldCurses);
config.set ("color", oldColor);
// Optionally display a notification that the shadow file was updated.
if (config.get (std::string ("shadow.notify"), false))
footnote (std::string ("[Shadow file '") + shadowFile + "' updated]");
inShadow = false;
}
}
////////////////////////////////////////////////////////////////////////////////
// Only allows aliases 10 deep.
std::string Context::canonicalize (const std::string& input) const
{
std::string canonical = input;
// First try to autocomplete the alias.
std::vector <std::string> options;
std::vector <std::string> matches;
foreach (name, aliases)
options.push_back (name->first);
autoComplete (input, options, matches);
if (matches.size () == 1)
{
canonical = matches[0];
// Follow the chain.
int i = 10; // Safety valve.
std::map <std::string, std::string>::const_iterator found;
while ((found = aliases.find (canonical)) != aliases.end () && i-- > 0)
canonical = found->second;
if (i < 1)
return input;
}
return canonical;
}
////////////////////////////////////////////////////////////////////////////////
void Context::loadCorrectConfigFile ()
{
// Set up default locations.
struct passwd* pw = getpwuid (getuid ());
if (!pw)
throw std::string (
stringtable.get (
SHELL_READ_PASSWD,
"Could not read home directory from the passwd file."));
std::string home = pw->pw_dir;
std::string rc = home + "/.taskrc";
std::string data = home + "/.task";
// Is there an override for rc?
foreach (arg, args)
{
if (*arg == "--")
break;
else if (arg->substr (0, 3) == "rc:")
{
rc = arg->substr (3, std::string::npos);
args.erase (arg);
header ("Using alternate .taskrc file " + rc); // TODO i18n
break;
}
}
// Load rc file.
config.clear (); // Dump current values.
config.setDefaults (); // Add in the custom reports.
config.load (rc); // Load new file.
if (config.get ("data.location") != "")
data = config.get ("data.location");
// Is there an override for data?
foreach (arg, args)
{
if (*arg == "--")
break;
else if (arg->substr (0, 17) == "rc.data.location:")
{
data = arg->substr (17, std::string::npos);
header ("Using alternate data.location " + data); // TODO i18n
break;
}
}
// Do we need to create a default rc?
if (access (rc.c_str (), F_OK) &&
confirm ("A configuration file could not be found in " // TODO i18n
+ home
+ "\n\n"
+ "Would you like a sample .taskrc created, so task can proceed?"))
{
config.createDefaultRC (rc, data);
}
// Create data location, if necessary.
config.createDefaultData (data);
// Load rc file.
config.clear (); // Dump current values.
config.setDefaults (); // Add in the custom reports.
config.load (rc); // Load new file.
// Apply overrides of type: "rc.name:value"
std::vector <std::string> filtered;
bool foundTerminator = false;
foreach (arg, args)
{
if (*arg == "--")
{
foundTerminator = true;
filtered.push_back (*arg);
}
else if (!foundTerminator &&
arg->substr (0, 3) == "rc.")
{
std::string name;
std::string value;
Nibbler n (*arg);
if (n.getUntil ('.', name) &&
n.skip ('.') &&
n.getUntil (':', name) &&
n.skip (':') &&
n.getUntilEOS (value))
{
config.set (name, value);
footnote (std::string ("Configuration override ") + // TODO i18n
arg->substr (3, std::string::npos));
}
}
else
filtered.push_back (*arg);
}
args = filtered;
}
////////////////////////////////////////////////////////////////////////////////
void Context::loadAliases ()
{
aliases.clear ();
std::vector <std::string> vars;
config.all (vars);
foreach (var, vars)
{
if (var->substr (0, 6) == "alias.")
{
std::string alias = var->substr (6, std::string::npos);
std::string canonical = config.get (*var);
aliases[alias] = canonical;
debug (std::string ("Alias ") + alias + " -> " + canonical);
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Context::parse ()
{
parse (args, cmd, task, sequence, subst, filter);
}
////////////////////////////////////////////////////////////////////////////////
void Context::parse (
std::vector <std::string>& parseArgs,
Cmd& parseCmd,
Task& parseTask,
Sequence& parseSequence,
Subst& parseSubst,
Filter& parseFilter)
{
Timer t ("Context::parse");
Att attribute;
tagAdditions.clear ();
tagRemovals.clear ();
std::string descCandidate = "";
bool terminated = false;
bool foundSequence = false;
bool foundSomethingAfterSequence = false;
foreach (arg, parseArgs)
{
if (!terminated)
{
// The '--' argument shuts off all parsing - everything is an argument.
if (*arg == "--")
{
debug ("parse terminator '" + *arg + "'");
terminated = true;
}
// Sequence
// Note: "add" doesn't require an ID
else if (parseCmd.command != "add" &&
! foundSomethingAfterSequence &&
parseSequence.valid (*arg))
{
debug ("parse sequence '" + *arg + "'");
parseSequence.parse (*arg);
foundSequence = true;
}
// Tags to include begin with '+'.
else if (arg->length () > 1 &&
(*arg)[0] == '+' &&
noSpaces (*arg))
{
debug ("parse tag addition '" + *arg + "'");
if (foundSequence)
foundSomethingAfterSequence = true;
if (arg->find (',') != std::string::npos)
throw stringtable.get (TAGS_NO_COMMA,
"Tags are not permitted to contain commas.");
tagAdditions.push_back (arg->substr (1, std::string::npos));
parseTask.addTag (arg->substr (1, std::string::npos));
}
// Tags to remove begin with '-'.
else if (arg->length () > 1 &&
(*arg)[0] == '-' &&
noSpaces (*arg))
{
debug ("parse tag removal '" + *arg + "'");
if (foundSequence)
foundSomethingAfterSequence = true;
if (arg->find (',') != std::string::npos)
throw stringtable.get (TAGS_NO_COMMA,
"Tags are not permitted to contain commas.");
tagRemovals.push_back (arg->substr (1, std::string::npos));
}
// Atributes - name[.mod]:[value]
else if (attribute.valid (*arg))
{
debug ("parse attribute '" + *arg + "'");
if (foundSequence)
foundSomethingAfterSequence = true;
attribute.parse (*arg);
// There has to be a better way. And it starts with a fresh coffee.
std::string name = attribute.name ();
std::string mod = attribute.mod ();
std::string value = attribute.value ();
if (attribute.validNameValue (name, mod, value))
{
attribute.name (name);
attribute.mod (mod);
attribute.value (value);
parseTask[attribute.name ()] = attribute;
}
// *arg has the appearance of an attribute (foo:bar), but isn't
// recognized, so downgrade it to part of the description.
else
{
if (foundSequence)
foundSomethingAfterSequence = true;
if (descCandidate.length ())
descCandidate += " ";
descCandidate += *arg;
}
}
// Substitution of description and/or annotation text.
else if (parseSubst.valid (*arg))
{
if (foundSequence)
foundSomethingAfterSequence = true;
debug ("parse subst '" + *arg + "'");
parseSubst.parse (*arg);
}
// It might be a command if one has not already been found.
else if (parseCmd.command == "" &&
parseCmd.valid (*arg))
{
debug ("parse cmd '" + *arg + "'");
parseCmd.parse (*arg);
if (foundSequence)
foundSomethingAfterSequence = true;
}
// Anything else is just considered description.
else
{
if (foundSequence)
foundSomethingAfterSequence = true;
if (descCandidate.length ())
descCandidate += " ";
descCandidate += *arg;
}
}
// Command is terminated, therefore everything subsequently is a description.
else
{
debug ("parse post-termination description '" + *arg + "'");
if (foundSequence)
foundSomethingAfterSequence = true;
if (descCandidate.length ())
descCandidate += " ";
descCandidate += *arg;
}
}
if (descCandidate != "" && noVerticalSpace (descCandidate))
{
debug ("parse description '" + descCandidate + "'");
parseTask.set ("description", descCandidate);
}
// At this point, either a sequence or a command should have been found.
if (parseSequence.size () == 0 && parseCmd.command == "")
parseCmd.parse (descCandidate);
// Read-only command (reports, status, info ...) use filters. Write commands
// (add, done ...) do not.
if (parseCmd.isReadOnlyCommand ())
autoFilter (parseTask, parseFilter);
// If no command was specified, and there were no command line arguments
// then invoke the default command.
if (parseCmd.command == "" && parseArgs.size () == 0)
{
std::string defaultCommand = config.get ("default.command");
if (defaultCommand != "")
{
// Stuff the command line.
parseArgs.clear ();
split (parseArgs, defaultCommand, ' ');
header ("[task " + defaultCommand + "]");
// Reinitialize the context and recurse.
initialize ();
parse (args, cmd, task, sequence, subst, filter);
}
else
throw stringtable.get (
CMD_MISSING,
"You must specify a command, or a task ID to modify");
}
}
////////////////////////////////////////////////////////////////////////////////
void Context::clear ()
{
// Config config;
filter.clear ();
// Keymap keymap;
sequence.clear ();
subst.clear ();
// task.clear ();
task = Task ();
tdb.clear ();
// stringtable.clear ();
program = "";
args.clear ();
cmd.command = "";
tagAdditions.clear ();
tagRemovals.clear ();
clearMessages ();
inShadow = false;
}
////////////////////////////////////////////////////////////////////////////////
// Add all the attributes in the task to the filter. All except uuid.
void Context::autoFilter (Task& t, Filter& f)
{
foreach (att, t)
{
// Words are found in the description using the .has modifier.
if (att->first == "description" && att->second.mod () == "")
{
std::vector <std::string> words;
split (words, att->second.value (), ' ');
foreach (word, words)
{
f.push_back (Att ("description", "has", *word));
debug ("auto filter: " + att->first + ".has:" + *word);
}
}
// Projects are matched left-most.
else if (att->first == "project" && att->second.mod () == "")
{
if (att->second.value () != "")
{
f.push_back (Att ("project", "startswith", att->second.value ()));
debug ("auto filter: " + att->first + ".startswith:" + att->second.value ());
}
else
{
f.push_back (Att ("project", "is", att->second.value ()));
debug ("auto filter: " + att->first + ".is:" + att->second.value ());
}
}
// The limit attribute does not participate in filtering, and needs to be
// specifically handled in handleCustomReport.
else if (att->first == "limit")
{
}
// Every task has a unique uuid by default, and it shouldn't be included,
// because it is guaranteed to not match.
else if (att->first == "uuid")
{
}
// The mechanism for filtering on tags is +/-<tag>.
else if (att->first == "tags")
{
}
// Generic attribute matching.
else
{
f.push_back (att->second);
debug ("auto filter: " +
att->first +
(att->second.mod () != "" ?
("." + att->second.mod () + ":") :
":") +
att->second.value ());
}
}
// Include tagAdditions.
foreach (tag, tagAdditions)
{
f.push_back (Att ("tags", "has", *tag));
debug ("auto filter: +" + *tag);
}
// Include tagRemovals.
foreach (tag, tagRemovals)
{
f.push_back (Att ("tags", "hasnt", *tag));
debug ("auto filter: -" + *tag);
}
}
////////////////////////////////////////////////////////////////////////////////
void Context::header (const std::string& input)
{
headers.push_back (input);
}
////////////////////////////////////////////////////////////////////////////////
void Context::footnote (const std::string& input)
{
footnotes.push_back (input);
}
////////////////////////////////////////////////////////////////////////////////
void Context::debug (const std::string& input)
{
debugMessages.push_back (input);
}
////////////////////////////////////////////////////////////////////////////////
void Context::clearMessages ()
{
headers.clear ();
footnotes.clear ();
debugMessages.clear ();
}
////////////////////////////////////////////////////////////////////////////////

98
src/Context.h Normal file
View File

@@ -0,0 +1,98 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_CONTEXT
#define INCLUDED_CONTEXT
#include "Filter.h"
#include "Keymap.h"
#include "Config.h"
#include "Sequence.h"
#include "Subst.h"
#include "Cmd.h"
#include "Task.h"
#include "TDB.h"
#include "StringTable.h"
class Context
{
public:
Context (); // Default constructor
~Context (); // Destructor
Context (const Context&);
Context& operator= (const Context&);
void initialize (int, char**); // all startup
void initialize (); // for reinitializing
int run (); // task classic
int interactive (); // task interactive (not implemented)
std::string dispatch (); // command handler dispatch
void shadow (); // shadow file update
int getWidth (); // determine terminal width
void header (const std::string&); // Header message sink
void footnote (const std::string&); // Footnote message sink
void debug (const std::string&); // Debug message sink
void clearMessages ();
void parse ();
void parse (std::vector <std::string>&, Cmd&, Task&, Sequence&, Subst&, Filter&);
void clear ();
std::string canonicalize (const std::string&) const;
private:
void loadCorrectConfigFile ();
void loadAliases ();
void autoFilter (Task&, Filter&);
public:
Config config;
Filter filter;
Keymap keymap;
Sequence sequence;
Subst subst;
Task task;
TDB tdb;
StringTable stringtable;
std::string program;
std::vector <std::string> args;
Cmd cmd;
std::map <std::string, std::string> aliases;
std::vector <std::string> tagAdditions;
std::vector <std::string> tagRemovals;
private:
std::vector <std::string> headers;
std::vector <std::string> footnotes;
std::vector <std::string> debugMessages;
bool inShadow;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -25,11 +25,13 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <sstream>
#include <time.h>
#include <assert.h>
#include <stdlib.h>
#include "task.h"
#include "Date.h"
#include "text.h"
#include "util.h"
////////////////////////////////////////////////////////////////////////////////
// Defaults to "now".
@@ -63,6 +65,10 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
int day = 0;
int year = 0;
// Perhaps it is an epoch date, in string form?
if (isEpoch (mdy))
return;
// Before parsing according to "format", perhaps this is a relative date?
if (isRelativeDate (mdy))
return;
@@ -81,8 +87,8 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
throw std::string ("\"") + mdy + "\" is not a valid date.";
}
if (i + 1 < mdy.length () &&
(mdy[i + 0] == '0' || mdy[i + 0] == '1') &&
if (i + 1 < mdy.length () &&
(mdy[i + 0] == '0' || mdy[i + 0] == '1') &&
::isdigit (mdy[i + 1]))
{
month = ::atoi (mdy.substr (i, 2).c_str ());
@@ -118,7 +124,7 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
// Double digit.
case 'y':
if (i + 1 >= mdy.length () ||
if (i + 1 >= mdy.length () ||
! ::isdigit (mdy[i + 0]) ||
! ::isdigit (mdy[i + 1]))
{
@@ -179,6 +185,9 @@ Date::Date (const std::string& mdy, const std::string& format /* = "m/d/Y" */)
}
}
if (i < mdy.length ())
throw std::string ("\"") + mdy + "\" is not a valid date in " + format + " format.";
if (!valid (month, day, year))
throw std::string ("\"") + mdy + "\" is not a valid date.";
@@ -208,6 +217,14 @@ time_t Date::toEpoch ()
return mT;
}
////////////////////////////////////////////////////////////////////////////////
std::string Date::toEpochString ()
{
std::stringstream epoch;
epoch << mT;
return epoch.str ();
}
////////////////////////////////////////////////////////////////////////////////
void Date::toEpoch (time_t& epoch)
{
@@ -253,6 +270,22 @@ const std::string Date::toString (const std::string& format /*= "m/d/Y" */) cons
return formatted;
}
////////////////////////////////////////////////////////////////////////////////
bool Date::valid (const std::string& input, const std::string& format)
{
try
{
Date test (input, format);
}
catch (...)
{
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool Date::valid (const int m, const int d, const int y)
{
@@ -277,9 +310,11 @@ bool Date::leapYear (int year)
{
bool ly = false;
if (!(year % 4)) ly = true;
else if (!(year % 400)) ly = true;
else if (!(year % 100)) ly = false;
// (year % 4 == 0) && (year % 100 !=0) OR
// (year % 400 == 0)
// are leapyears
if (((!(year % 4)) && (year % 100)) || (!(year % 400))) ly = true;
return ly;
}
@@ -354,6 +389,28 @@ std::string Date::dayName (int dow)
return days[dow];
}
////////////////////////////////////////////////////////////////////////////////
int Date::weekOfYear (int weekStart) const
{
struct tm* t = localtime (&mT);
char weekStr[3];
if (weekStart == 0)
strftime(weekStr, sizeof(weekStr), "%U", t);
else if (weekStart == 1)
strftime(weekStr, sizeof(weekStr), "%V", t);
else
throw std::string ("The 'weekstart' configuration variable may "
"only contain 'Sunday' or 'Monday'.");
int weekNumber = ::atoi (weekStr);
if (weekStart == 0)
weekNumber += 1;
return weekNumber;
}
////////////////////////////////////////////////////////////////////////////////
int Date::dayOfWeek () const
{
@@ -490,6 +547,19 @@ time_t Date::operator- (const Date& rhs)
return mT - rhs.mT;
}
////////////////////////////////////////////////////////////////////////////////
bool Date::isEpoch (const std::string& input)
{
if (digitsOnly (input) &&
input.length () > 8)
{
mT = (time_t) ::atoi (input.c_str ());
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// If the input string looks like a relative date, determine that date, set mT
// and return true.

View File

@@ -44,8 +44,10 @@ public:
void toEpoch (time_t&);
time_t toEpoch ();
std::string toEpochString ();
void toMDY (int&, int&, int&);
const std::string toString (const std::string& format = "m/d/Y") const;
static bool valid (const std::string&, const std::string& format = "m/d/Y");
static bool valid (const int, const int, const int);
static bool leapYear (int);
@@ -53,11 +55,13 @@ public:
static std::string monthName (int);
static void dayName (int, std::string&);
static std::string dayName (int);
static int weekOfYear (const std::string&);
static int dayOfWeek (const std::string&);
int month () const;
int day () const;
int year () const;
int weekOfYear (int) const;
int dayOfWeek () const;
bool operator== (const Date&);
@@ -77,6 +81,7 @@ public:
time_t operator- (const Date&);
private:
bool isEpoch (const std::string&);
bool isRelativeDate (const std::string&);
protected:

205
src/Duration.cpp Normal file
View File

@@ -0,0 +1,205 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include <string>
#include <vector>
#include <stdlib.h>
#include "text.h"
#include "util.h"
#include "Duration.h"
////////////////////////////////////////////////////////////////////////////////
Duration::Duration ()
: mDays (0)
{
}
////////////////////////////////////////////////////////////////////////////////
Duration::Duration (time_t input)
{
mDays = input;
}
////////////////////////////////////////////////////////////////////////////////
Duration::Duration (const std::string& input)
{
parse (input);
}
////////////////////////////////////////////////////////////////////////////////
Duration::operator int ()
{
return (int) mDays;
}
////////////////////////////////////////////////////////////////////////////////
Duration::operator time_t ()
{
return mDays;
}
////////////////////////////////////////////////////////////////////////////////
Duration::operator std::string ()
{
std::stringstream s;
s << mDays;
return s.str ();
}
////////////////////////////////////////////////////////////////////////////////
bool Duration::operator< (const Duration& other)
{
return mDays < other.mDays;
}
////////////////////////////////////////////////////////////////////////////////
bool Duration::operator> (const Duration& other)
{
return mDays > other.mDays;
}
////////////////////////////////////////////////////////////////////////////////
Duration::~Duration ()
{
}
////////////////////////////////////////////////////////////////////////////////
bool Duration::valid (const std::string& input) const
{
std::string lower_input = lowerCase (input);
std::vector <std::string> supported;
supported.push_back ("daily"); // TODO i18n
supported.push_back ("day"); // TODO i18n
supported.push_back ("weekly"); // TODO i18n
supported.push_back ("weekdays"); // TODO i18n
supported.push_back ("sennight"); // TODO i18n
supported.push_back ("biweekly"); // TODO i18n
supported.push_back ("fortnight"); // TODO i18n
supported.push_back ("monthly"); // TODO i18n
supported.push_back ("bimonthly"); // TODO i18n
supported.push_back ("quarterly"); // TODO i18n
supported.push_back ("biannual"); // TODO i18n
supported.push_back ("biyearly"); // TODO i18n
supported.push_back ("annual"); // TODO i18n
supported.push_back ("semiannual"); // TODO i18n
supported.push_back ("yearly"); // TODO i18n
std::vector <std::string> matches;
if (autoComplete (lower_input, supported, matches) == 1)
return true;
// Support \d+ d|w|m|q|y
// Verify all digits followed by d, w, m, q, or y.
unsigned int length = lower_input.length ();
for (unsigned int i = 0; i < length; ++i)
{
if (! isdigit (lower_input[i]) &&
i == length - 1)
{
std::string type = lower_input.substr (length - 1, std::string::npos);
if (type == "d" || // TODO i18n
type == "w" || // TODO i18n
type == "m" || // TODO i18n
type == "q" || // TODO i18n
type == "y") // TODO i18n
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
void Duration::parse (const std::string& input)
{
std::string lower_input = lowerCase (input);
std::vector <std::string> supported;
supported.push_back ("daily"); // TODO i18n
supported.push_back ("day"); // TODO i18n
supported.push_back ("weekly"); // TODO i18n
supported.push_back ("weekdays"); // TODO i18n
supported.push_back ("sennight"); // TODO i18n
supported.push_back ("biweekly"); // TODO i18n
supported.push_back ("fortnight"); // TODO i18n
supported.push_back ("monthly"); // TODO i18n
supported.push_back ("bimonthly"); // TODO i18n
supported.push_back ("quarterly"); // TODO i18n
supported.push_back ("biannual"); // TODO i18n
supported.push_back ("biyearly"); // TODO i18n
supported.push_back ("annual"); // TODO i18n
supported.push_back ("semiannual"); // TODO i18n
supported.push_back ("yearly"); // TODO i18n
std::vector <std::string> matches;
if (autoComplete (lower_input, supported, matches) == 1)
{
std::string found = matches[0];
if (found == "daily" || found == "day") mDays = 1; // TODO i18n
else if (found == "weekdays") mDays = 1; // TODO i18n
else if (found == "weekly" || found == "sennight") mDays = 7; // TODO i18n
else if (found == "biweekly" || found == "fortnight") mDays = 14; // TODO i18n
else if (found == "monthly") mDays = 30; // TODO i18n
else if (found == "bimonthly") mDays = 61; // TODO i18n
else if (found == "quarterly") mDays = 91; // TODO i18n
else if (found == "semiannual") mDays = 183; // TODO i18n
else if (found == "yearly" || found == "annual") mDays = 365; // TODO i18n
else if (found == "biannual" || found == "biyearly") mDays = 730; // TODO i18n
}
// Support \d+ d|w|m|q|y
else
{
// Verify all digits followed by d, w, m, q, or y.
unsigned int length = lower_input.length ();
for (unsigned int i = 0; i < length; ++i)
{
if (! isdigit (lower_input[i]) &&
i == length - 1)
{
int number = ::atoi (lower_input.substr (0, i).c_str ());
switch (lower_input[length - 1])
{
case 'd': mDays = number * 1; break; // TODO i18n
case 'w': mDays = number * 7; break; // TODO i18n
case 'm': mDays = number * 30; break; // TODO i18n
case 'q': mDays = number * 91; break; // TODO i18n
case 'y': mDays = number * 365; break; // TODO i18n
}
}
}
}
if (mDays == 0)
throw std::string ("The duration '") + input + "' was not recognized."; // TODO i18n
}
////////////////////////////////////////////////////////////////////////////////

55
src/Duration.h Normal file
View File

@@ -0,0 +1,55 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_DURATION
#define INCLUDED_DURATION
#include <string>
#include <time.h>
class Duration
{
public:
Duration (); // Default constructor
Duration (time_t); // Default constructor
Duration (const std::string&); // Parse
bool operator< (const Duration&);
bool operator> (const Duration&);
~Duration (); // Destructor
operator int ();
operator time_t ();
operator std::string ();
bool valid (const std::string&) const;
void parse (const std::string&);
private:
time_t mDays;
};
#endif
////////////////////////////////////////////////////////////////////////////////

134
src/Filter.cpp Normal file
View File

@@ -0,0 +1,134 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include "Filter.h"
#include "util.h"
#include "main.h"
////////////////////////////////////////////////////////////////////////////////
// For every Att in the filter, lookup the equivalent in Record, and perform a
// match. Aren't filters easy now that everything is an attribute?
bool Filter::pass (const Record& record) const
{
Record::const_iterator r;
// First do description/annotation matches.
foreach (att, (*this))
{
// Descriptions have special handling.
if (att->name () == "description")
{
if ((r = record.find (att->name ())) != record.end ())
{
// A description match failure can be salvaged by an annotation match.
if (! att->match (r->second))
{
bool annoMatch = false;
foreach (ra, record)
{
if (ra->first.length () > 11 &&
ra->first.substr (0, 11) == "annotation_")
{
if (att->match (ra->second))
{
annoMatch = true;
break;
}
}
}
if (!annoMatch)
return false;
}
}
else if (! att->match (Att ()))
return false;
}
// Annotations are skipped.
else if (att->name ().length () > 11 &&
att->name ().substr (0, 11) == "annotation_")
{
}
else
{
// An individual attribute match failure is enough to fail the filter.
if ((r = record.find (att->name ())) != record.end ())
{
if (! att->match (r->second))
return false;
}
else if (! att->match (Att ()))
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
void Filter::applySequence (std::vector<Task>& all, Sequence& sequence)
{
std::vector <Task> filtered;
foreach (task, all)
foreach (i, sequence)
if (task->id == *i)
filtered.push_back (*task);
if (sequence.size () != filtered.size ())
{
std::vector <int> filteredSequence;
foreach (task, filtered)
filteredSequence.push_back (task->id);
std::vector <int> left;
std::vector <int> right;
listDiff (filteredSequence, (std::vector <int>&)sequence, left, right);
if (left.size ())
throw std::string ("Sequence filtering error - please report this error");
if (right.size ())
{
std::stringstream out;
out << "Task";
if (right.size () > 1) out << "s";
foreach (r, right)
out << " " << *r;
out << " not found";
throw out.str ();
}
}
all = filtered;
}
////////////////////////////////////////////////////////////////////////////////

43
src/Filter.h Normal file
View File

@@ -0,0 +1,43 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_FILTER
#define INCLUDED_FILTER
#include <vector>
#include "Att.h"
#include "Task.h"
#include "Record.h"
class Filter : public std::vector <Att>
{
public:
bool pass (const Record&) const;
void applySequence (std::vector<Task>&, Sequence&);
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -306,7 +306,7 @@ Grid::Cell::operator char () const
{
switch (mType)
{
case CELL_BOOL: return mBool ? 'Y' : 'N';
case CELL_BOOL: return mBool ? 'Y' : 'N'; // TODO i18n
case CELL_CHAR: return mChar;
case CELL_INT: return (char) mInt;
case CELL_FLOAT: return (char) (int) mFloat;
@@ -368,7 +368,7 @@ Grid::Cell::operator std::string () const
switch (mType)
{
case CELL_BOOL: return mBool ? "true" : "false";
case CELL_BOOL: return mBool ? "true" : "false"; // TODO i18n
case CELL_CHAR: sprintf (s, "%c", mChar);
return std::string (s);
case CELL_INT: sprintf (s, "%d", mInt);

View File

@@ -45,6 +45,9 @@ public:
Cell (const double);
Cell (const std::string&);
Cell (const Cell&);
Cell& operator= (const Cell&);
operator bool () const;
operator char () const;
operator int () const;
@@ -72,6 +75,9 @@ public:
Grid ();
~Grid ();
Grid (const Grid&);
Grid& operator= (const Grid&);
void add (const unsigned int, const unsigned int, const bool);
void add (const unsigned int, const unsigned int, const char);
void add (const unsigned int, const unsigned int, const int);

66
src/Keymap.cpp Normal file
View File

@@ -0,0 +1,66 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <string>
#include "Keymap.h"
////////////////////////////////////////////////////////////////////////////////
Keymap::Keymap ()
{
}
////////////////////////////////////////////////////////////////////////////////
Keymap::Keymap (const Keymap& other)
{
throw std::string ("unimplemented Keymap::Keymap");
// mOne = other.mOne;
}
////////////////////////////////////////////////////////////////////////////////
Keymap& Keymap::operator= (const Keymap& other)
{
throw std::string ("unimplemented Keymap::operator=");
if (this != &other)
{
// mOne = other.mOne;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Keymap::~Keymap ()
{
}
////////////////////////////////////////////////////////////////////////////////
void Keymap::load (const std::string& file)
{
throw std::string ("unimplemented Keymap::load");
}
////////////////////////////////////////////////////////////////////////////////

51
src/Keymap.h Normal file
View File

@@ -0,0 +1,51 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_KEYMAP
#define INCLUDED_KEYMAP
#include <string>
class Keymap
{
public:
Keymap (); // Default constructor
Keymap (const Keymap&); // Copy constructor
Keymap& operator= (const Keymap&); // Assignment operator
~Keymap (); // Destructor
void load (const std::string&); // Load the map file
/*
real (); // Convert soft to real
soft (); // Convert real to soft
*/
private:
// TODO Structure for mapping strings to keys.
};
#endif
////////////////////////////////////////////////////////////////////////////////

73
src/Location.cpp Normal file
View File

@@ -0,0 +1,73 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "Location.h"
////////////////////////////////////////////////////////////////////////////////
Location::Location ()
: path ("")
, pending (NULL)
, completed (NULL)
, undo (NULL)
{
}
////////////////////////////////////////////////////////////////////////////////
Location::Location (const std::string& p)
: path (p)
{
}
////////////////////////////////////////////////////////////////////////////////
Location::Location (const Location& other)
{
path = other.path;
pending = other.pending;
completed = other.completed;
undo = other.undo;
}
////////////////////////////////////////////////////////////////////////////////
Location& Location::operator= (const Location& other)
{
if (this != &other)
{
path = other.path;
pending = other.pending;
completed = other.completed;
undo = other.undo;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Location::~Location ()
{
}
////////////////////////////////////////////////////////////////////////////////

50
src/Location.h Normal file
View File

@@ -0,0 +1,50 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_LOCATION
#define INCLUDED_LOCATION
#include <string>
#include <stdio.h>
class Location
{
public:
Location (); // Default constructor
Location (const std::string&); // Default constructor
Location (const Location&); // Copy constructor
Location& operator= (const Location&); // Assignment operator
~Location (); // Destructor
public:
std::string path;
FILE* pending;
FILE* completed;
FILE* undo;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,2 +1,11 @@
bin_PROGRAMS = task
task_SOURCES = Config.cpp Date.cpp T.cpp TDB.cpp Table.cpp Grid.cpp Timer.cpp color.cpp parse.cpp task.cpp command.cpp edit.cpp report.cpp util.cpp text.cpp rules.cpp import.cpp Config.h Date.h T.h TDB.h Table.h Grid.h Timer.h color.h task.h
task_SOURCES = Att.cpp Cmd.cpp Config.cpp Context.cpp Date.cpp Duration.cpp \
Filter.cpp Grid.cpp Keymap.cpp Location.cpp Nibbler.cpp \
Record.cpp Sequence.cpp StringTable.cpp Subst.cpp Task.cpp \
TDB.cpp Table.cpp Timer.cpp Permission.cpp color.cpp edit.cpp \
command.cpp import.cpp interactive.cpp recur.cpp report.cpp \
custom.cpp rules.cpp main.cpp text.cpp util.cpp \
Att.h Cmd.h Config.h Context.h Date.h Duration.h Filter.h \
Grid.h Keymap.h Location.h Nibbler.h Record.h Sequence.h \
StringTable.h Subst.h Task.h TDB.h Table.h Timer.h \
Permission.h color.h i18n.h main.h text.h util.h

304
src/Nibbler.cpp Normal file
View File

@@ -0,0 +1,304 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <stdlib.h>
#include <ctype.h>
#include "Nibbler.h"
////////////////////////////////////////////////////////////////////////////////
Nibbler::Nibbler ()
: mInput ("")
, mCursor (0)
{
}
////////////////////////////////////////////////////////////////////////////////
Nibbler::Nibbler (const char* input)
: mInput (input)
, mCursor (0)
{
}
////////////////////////////////////////////////////////////////////////////////
Nibbler::Nibbler (const std::string& input)
: mInput (input)
, mCursor (0)
{
}
////////////////////////////////////////////////////////////////////////////////
Nibbler::Nibbler (const Nibbler& other)
{
mInput = other.mInput;
mCursor = other.mCursor;
}
////////////////////////////////////////////////////////////////////////////////
Nibbler& Nibbler::operator= (const Nibbler& other)
{
if (this != &other)
{
mInput = other.mInput;
mCursor = other.mCursor;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Nibbler::~Nibbler ()
{
}
////////////////////////////////////////////////////////////////////////////////
// Extract up until the next c, or EOS.
bool Nibbler::getUntil (char c, std::string& result)
{
if (mCursor < mInput.length ())
{
std::string::size_type i = mInput.find (c, mCursor);
if (i != std::string::npos)
{
result = mInput.substr (mCursor, i - mCursor);
mCursor = i;
}
else
{
result = mInput.substr (mCursor, std::string::npos);
mCursor = mInput.length ();
}
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getUntil (const std::string& terminator, std::string& result)
{
if (mCursor < mInput.length ())
{
std::string::size_type i = mInput.find (terminator, mCursor);
if (i != std::string::npos)
{
result = mInput.substr (mCursor, i - mCursor);
mCursor = i;
}
else
{
result = mInput.substr (mCursor, std::string::npos);
mCursor = mInput.length ();
}
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getUntilOneOf (const std::string& chars, std::string& result)
{
if (mCursor < mInput.length ())
{
std::string::size_type i = mInput.find_first_of (chars, mCursor);
if (i != std::string::npos)
{
result = mInput.substr (mCursor, i - mCursor);
mCursor = i;
}
else
{
result = mInput.substr (mCursor, std::string::npos);
mCursor = mInput.length ();
}
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::skipN (const int quantity /* = 1 */)
{
if (mCursor >= mInput.length ())
return false;
if (mCursor <= mInput.length () - quantity)
{
mCursor += quantity;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::skip (char c)
{
if (mCursor < mInput.length () &&
mInput[mCursor] == c)
{
++mCursor;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::skipAll (char c)
{
std::string::size_type i = mCursor;
while (i < mInput.length () && mInput[i] == c)
++i;
if (i != mCursor)
{
mCursor = i;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::skipAllOneOf (const std::string& chars)
{
if (mCursor < mInput.length ())
{
std::string::size_type i = mInput.find_first_not_of (chars, mCursor);
if (i == mCursor)
return false;
if (i == std::string::npos)
mCursor = mInput.length (); // Yes, off the end.
else
mCursor = i;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getQuoted (char c, std::string& result)
{
std::string::size_type start = mCursor;
if (start < mInput.length () && mInput[start] == c)
{
++start;
if (start < mInput.length ())
{
std::string::size_type end = mInput.find (c, start);
if (end != std::string::npos)
{
result = mInput.substr (start, end - start);
mCursor = end + 1;
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getInt (int& result)
{
std::string::size_type i = mCursor;
if (i < mInput.length ())
{
if (mInput[i] == '-')
++i;
else if (mInput[i] == '+')
++i;
}
while (i < mInput.length () && ::isdigit (mInput[i]))
++i;
if (i > mCursor)
{
result = ::atoi (mInput.substr (mCursor, i - mCursor).c_str ());
mCursor = i;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getUnsignedInt (int& result)
{
std::string::size_type i = mCursor;
while (i < mInput.length () && ::isdigit (mInput[i]))
++i;
if (i > mCursor)
{
result = ::atoi (mInput.substr (mCursor, i - mCursor).c_str ());
mCursor = i;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getUntilEOL (std::string& result)
{
return getUntil ('\n', result);
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::getUntilEOS (std::string& result)
{
if (mCursor < mInput.length ())
{
result = mInput.substr (mCursor, std::string::npos);
mCursor = mInput.length ();
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool Nibbler::depleted ()
{
if (mCursor >= mInput.length ())
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////

62
src/Nibbler.h Normal file
View File

@@ -0,0 +1,62 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_NIBBLER
#define INCLUDED_NIBBLER
#include <string>
class Nibbler
{
public:
Nibbler (); // Default constructor
Nibbler (const char*); // Constructor
Nibbler (const std::string&); // Constructor
Nibbler (const Nibbler&); // Copy constructor
Nibbler& operator= (const Nibbler&); // Assignment operator
~Nibbler (); // Destructor
bool getUntil (char, std::string&);
bool getUntil (const std::string&, std::string&);
bool getUntilOneOf (const std::string&, std::string&);
bool skipN (const int quantity = 1);
bool skip (char);
bool skipAll (char);
bool skipAllOneOf (const std::string&);
bool getQuoted (char, std::string&);
bool getInt (int&);
bool getUnsignedInt (int&i);
bool getUntilEOL (std::string&);
bool getUntilEOS (std::string&);
bool depleted ();
private:
std::string mInput;
std::string::size_type mCursor;
};
#endif
////////////////////////////////////////////////////////////////////////////////

74
src/Permission.cpp Normal file
View File

@@ -0,0 +1,74 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "Permission.h"
#include "Context.h"
#include "util.h"
#include "i18n.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
Permission::Permission ()
: needConfirmation (false)
, allConfirmed (false)
{
// Turning confirmations off is the same as entering "all".
if (context.config.get ("confirmation", true) == false)
allConfirmed = true;
}
////////////////////////////////////////////////////////////////////////////////
bool Permission::confirmed (const Task& task, const std::string& question)
{
if (!needConfirmation)
return true;
if (allConfirmed)
return true;
std::cout << std::endl
<< "Task "
<< task.id
<< " \""
<< task.get ("description")
<< "\""
<< std::endl;
int answer = confirm3 (question);
if (answer == 2)
allConfirmed = true;
if (answer > 0)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////

50
src/Permission.h Normal file
View File

@@ -0,0 +1,50 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_PERMISSION
#define INCLUDED_PERMISSION
#include <string>
#include "Task.h"
class Permission
{
public:
Permission ();
Permission (const Permission&);
Permission& operator= (const Permission&);
void bigChange () { needConfirmation = true; }
void bigSequence () { needConfirmation = true; }
bool confirmed (const Task&, const std::string&);
private:
bool needConfirmation;
bool allConfirmed;
};
#endif
////////////////////////////////////////////////////////////////////////////////

183
src/Record.cpp Normal file
View File

@@ -0,0 +1,183 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include <stdlib.h>
#include "util.h"
#include "Nibbler.h"
#include "Context.h"
#include "i18n.h"
#include "Record.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
Record::Record ()
{
}
////////////////////////////////////////////////////////////////////////////////
Record::Record (const std::string& input)
{
parse (input);
}
////////////////////////////////////////////////////////////////////////////////
Record::~Record ()
{
}
////////////////////////////////////////////////////////////////////////////////
// The format is:
//
// [ Att::composeF4 ... ] \n
//
std::string Record::composeF4 () const
{
std::string ff4 = "[";
bool first = true;
std::map <std::string, Att>::const_iterator it;
for (it = this->begin (); it != this->end (); ++it)
{
if (it->second.value () != "")
{
ff4 += (first ? "" : " ") + it->second.composeF4 ();
first = false;
}
}
ff4 += "]\n";
return ff4;
}
////////////////////////////////////////////////////////////////////////////////
//
// start --> [ --> Att --> ] --> end
// ^ |
// +-------+
//
void Record::parse (const std::string& input)
{
clear ();
Nibbler n (input);
std::string line;
if (n.skip ('[') &&
n.getUntil (']', line) &&
n.skip (']') &&
n.depleted ())
{
if (line.length () == 0)
throw context.stringtable.get (RECORD_EMPTY,
"Empty record in input");
Nibbler nl (line);
Att a;
while (!nl.depleted ())
{
a.parse (nl);
(*this)[a.name ()] = a;
nl.skip (' ');
}
std::string remainder;
nl.getUntilEOS (remainder);
if (remainder.length ())
throw context.stringtable.get (RECORD_EXTRA,
"Unrecognized characters at end of line");
}
else
throw context.stringtable.get (RECORD_NOT_FF4,
"Record not recognized as format 4");
}
////////////////////////////////////////////////////////////////////////////////
bool Record::has (const std::string& name) const
{
Record::const_iterator i = this->find (name);
if (i != this->end ())
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
std::vector <Att> Record::all ()
{
std::vector <Att> all;
foreach (a, (*this))
all.push_back (a->second);
return all;
}
////////////////////////////////////////////////////////////////////////////////
const std::string Record::get (const std::string& name) const
{
Record::const_iterator i = this->find (name);
if (i != this->end ())
return i->second.value ();
return "";
}
////////////////////////////////////////////////////////////////////////////////
int Record::get_int (const std::string& name) const
{
Record::const_iterator i = this->find (name);
if (i != this->end ())
return ::atoi (i->second.value ().c_str ());
return 0;
}
////////////////////////////////////////////////////////////////////////////////
void Record::set (const std::string& name, const std::string& value)
{
(*this)[name] = Att (name, value);
}
////////////////////////////////////////////////////////////////////////////////
void Record::set (const std::string& name, int value)
{
std::stringstream svalue;
svalue << value;
(*this)[name] = Att (name, svalue.str ());
}
////////////////////////////////////////////////////////////////////////////////
void Record::remove (const std::string& name)
{
Record::iterator it;
if ((it = this->find (name)) != this->end ())
this->erase (it);
}
////////////////////////////////////////////////////////////////////////////////

56
src/Record.h Normal file
View File

@@ -0,0 +1,56 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_RECORD
#define INCLUDED_RECORD
#include <vector>
#include <map>
#include <string>
#include "Att.h"
class Record : public std::map <std::string, Att>
{
public:
Record (); // Default constructor
Record (const std::string&); // Copy constructor
virtual ~Record (); // Destructor
std::string composeF4 () const;
std::string composeCSV () const;
void parse (const std::string&);
bool has (const std::string&) const;
std::vector <Att> all ();
const std::string get (const std::string&) const;
int get_int (const std::string&) const;
void set (const std::string&, const std::string&);
void set (const std::string&, int);
void remove (const std::string&);
};
#endif
////////////////////////////////////////////////////////////////////////////////

162
src/Sequence.cpp Normal file
View File

@@ -0,0 +1,162 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <map>
#include <string>
#include <algorithm>
#include <ctype.h>
#include "util.h"
#include "text.h"
#include "i18n.h"
#include "Context.h"
#include "Sequence.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
Sequence::Sequence ()
{
}
////////////////////////////////////////////////////////////////////////////////
Sequence::Sequence (const std::string& input)
{
parse (input);
}
////////////////////////////////////////////////////////////////////////////////
Sequence::~Sequence ()
{
}
////////////////////////////////////////////////////////////////////////////////
bool Sequence::valid (const std::string& input) const
{
std::vector <std::string> ranges;
split (ranges, input, ',');
std::vector <std::string>::iterator it;
for (it = ranges.begin (); it != ranges.end (); ++it)
{
std::vector <std::string> range;
split (range, *it, '-');
if (range.size () < 1 ||
range.size () > 2)
return false;
if (range.size () <= 2 && !validId (range[0]))
return false;
if (range.size () == 2 && !validId (range[1]))
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
void Sequence::parse (const std::string& input)
{
std::vector <std::string> ranges;
split (ranges, input, ',');
std::vector <std::string>::iterator it;
for (it = ranges.begin (); it != ranges.end (); ++it)
{
std::vector <std::string> range;
split (range, *it, '-');
switch (range.size ())
{
case 1:
{
if (! validId (range[0]))
throw context.stringtable.get (SEQUENCE_BAD_SEQ, "Invalid ID in sequence");
int id = ::atoi (range[0].c_str ());
this->push_back (id);
}
break;
case 2:
{
if (! validId (range[0]) ||
! validId (range[1]))
throw context.stringtable.get (SEQUENCE_BAD_SEQ, "Invalid ID in range");
int low = ::atoi (range[0].c_str ());
int high = ::atoi (range[1].c_str ());
if (low > high)
throw context.stringtable.get (SEQUENCE_INVERTED, "Inverted sequence range high-low");
if (high - low >= SEQUENCE_MAX)
throw context.stringtable.get (SEQUENCE_RANGE_MAX, "ID Range too large");
for (int i = low; i <= high; ++i)
this->push_back (i);
}
break;
default:
throw context.stringtable.get (SEQUENCE_NOT_A_SEQUENCE, "Not a sequence.");
break;
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Sequence::combine (const Sequence& other)
{
// Create a map using the sequence elements as keys. This will create a
// unique list, with no duplicates.
std::map <int, int> both;
foreach (i, *this) both[*i] = 0;
foreach (i, other) both[*i] = 0;
// Now make a sequence out of the keys of the map.
this->clear ();
foreach (i, both)
this->push_back (i->first);
std::sort (this->begin (), this->end ());
}
////////////////////////////////////////////////////////////////////////////////
bool Sequence::validId (const std::string& input) const
{
if (input.length () == 0)
return false;
for (size_t i = 0; i < input.length (); ++i)
if (!::isdigit (input[i]))
return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////

51
src/Sequence.h Normal file
View File

@@ -0,0 +1,51 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_SEQUENCE
#define INCLUDED_SEQUENCE
#include <vector>
#include <string>
#define SEQUENCE_MAX 1000
class Sequence : public std::vector <int>
{
public:
Sequence (); // Default constructor
Sequence (const std::string&); // Parse
~Sequence (); // Destructor
bool valid (const std::string&) const;
void parse (const std::string&);
void combine (const Sequence&);
private:
bool validId (const std::string&) const;
};
#endif
////////////////////////////////////////////////////////////////////////////////

98
src/StringTable.cpp Normal file
View File

@@ -0,0 +1,98 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <fstream>
#include <sstream>
#include <text.h>
#include <util.h>
#include <stdlib.h>
#include "StringTable.h"
////////////////////////////////////////////////////////////////////////////////
StringTable::StringTable ()
{
}
////////////////////////////////////////////////////////////////////////////////
StringTable::~StringTable ()
{
}
////////////////////////////////////////////////////////////////////////////////
// UTF-8 encoding
//
// 123 This is the string
// 124 This is another string
// ...
void StringTable::load (const std::string& file)
{
this->clear (); // Allows dynamic reload.
std::ifstream in;
in.open (file.c_str (), std::ifstream::in);
if (in.good ())
{
std::string line;
while (getline (in, line))
{
// Remove comments.
std::string::size_type pound = line.find ("#"); // no i18n
if (pound != std::string::npos)
line = line.substr (0, pound);
line = trim (line, " \t"); // no i18n
// Skip empty lines.
if (line.length () > 0)
{
std::string::size_type equal = line.find (" "); // no i18n
if (equal != std::string::npos)
{
int key = ::atoi (trim (line.substr (0, equal), " \t").c_str ()); // no i18n
std::string value = trim (line.substr (equal+1, line.length () - equal), " \t"); // no i18n
(*this)[key] = value;
}
}
}
in.close ();
}
else
throw std::string ("Could not read string file '") + file + "'"; // TODO i18n
}
////////////////////////////////////////////////////////////////////////////////
std::string StringTable::get (int id, const std::string& alternate)
{
// Return the right string.
if (this->find (id) != this->end ())
return (*this)[id];
return alternate;
}
////////////////////////////////////////////////////////////////////////////////

47
src/StringTable.h Normal file
View File

@@ -0,0 +1,47 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_STRINGTABLE
#define INCLUDED_STRINGTABLE
#include <map>
#include <string>
class StringTable : public std::map <int, std::string>
{
public:
StringTable (); // Default constructor
~StringTable (); // Destructor
StringTable (const StringTable&);
StringTable& operator= (const StringTable&);
void load (const std::string&);
std::string get (int, const std::string&);
};
#endif
////////////////////////////////////////////////////////////////////////////////

178
src/Subst.cpp Normal file
View File

@@ -0,0 +1,178 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "Subst.h"
#include "Nibbler.h"
#include "Context.h"
#include "i18n.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
Subst::Subst ()
: mFrom ("")
, mTo ("")
, mGlobal (false)
{
}
////////////////////////////////////////////////////////////////////////////////
Subst::Subst (const std::string& input)
{
parse (input);
}
////////////////////////////////////////////////////////////////////////////////
Subst::Subst (const Subst& other)
{
mFrom = other.mFrom;
mTo = other.mTo;
mGlobal = other.mGlobal;
}
////////////////////////////////////////////////////////////////////////////////
Subst& Subst::operator= (const Subst& other)
{
if (this != &other)
{
mFrom = other.mFrom;
mTo = other.mTo;
mGlobal = other.mGlobal;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Subst::~Subst ()
{
}
////////////////////////////////////////////////////////////////////////////////
bool Subst::valid (const std::string& input) const
{
std::string ignored;
Nibbler n (input);
if (n.skip ('/') &&
n.getUntil ('/', ignored) &&
n.skip ('/') &&
n.getUntil ('/', ignored) &&
n.skip ('/'))
{
n.skip ('g');
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
void Subst::parse (const std::string& input)
{
Nibbler n (input);
if (n.skip ('/') &&
n.getUntil ('/', mFrom) &&
n.skip ('/') &&
n.getUntil ('/', mTo) &&
n.skip ('/'))
{
mGlobal = n.skip ('g');
if (mFrom == "")
throw context.stringtable.get (SUBST_EMPTY,
"Cannot substitute an empty string");
if (!n.depleted ())
throw context.stringtable.get (SUBST_BAD_CHARS,
"Unrecognized character(s) at end of substitution");
}
else
throw context.stringtable.get (SUBST_MALFORMED,
"Malformed substitution");
}
////////////////////////////////////////////////////////////////////////////////
void Subst::apply (
std::string& description,
std::vector <Att>& annotations) const
{
std::string::size_type pattern;
if (mFrom != "")
{
if (mGlobal)
{
// Perform all subs on description.
while ((pattern = description.find (mFrom)) != std::string::npos)
description.replace (pattern, mFrom.length (), mTo);
// Perform all subs on annotations.
std::vector <Att>::iterator i;
for (i = annotations.begin (); i != annotations.end (); ++i)
{
std::string description = i->value ();
while ((pattern = description.find (mFrom)) != std::string::npos)
{
description.replace (pattern, mFrom.length (), mTo);
i->value (description);
}
}
}
else
{
// Perform first description substitution.
if ((pattern = description.find (mFrom)) != std::string::npos)
description.replace (pattern, mFrom.length (), mTo);
// Failing that, perform the first annotation substitution.
else
{
std::vector <Att>::iterator i;
for (i = annotations.begin (); i != annotations.end (); ++i)
{
std::string description = i->value ();
if ((pattern = description.find (mFrom)) != std::string::npos)
{
description.replace (pattern, mFrom.length (), mTo);
i->value (description);
break;
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Subst::clear ()
{
mFrom = "";
mTo = "";
mGlobal = false;
}
////////////////////////////////////////////////////////////////////////////////

54
src/Subst.h Normal file
View File

@@ -0,0 +1,54 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_SUBST
#define INCLUDED_SUBST
#include <string>
#include "Att.h"
class Subst
{
public:
Subst (); // Default constructor
Subst (const std::string&); // Default constructor
Subst (const Subst&); // Copy constructor
Subst& operator= (const Subst&); // Assignment operator
~Subst (); // Destructor
bool valid (const std::string&) const;
void parse (const std::string&);
void apply (std::string&, std::vector <Att>&) const;
void clear ();
public:
std::string mFrom;
std::string mTo;
bool mGlobal;
};
#endif
////////////////////////////////////////////////////////////////////////////////

731
src/T.cpp
View File

@@ -1,731 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include <algorithm>
#include "task.h"
#include "T.h"
////////////////////////////////////////////////////////////////////////////////
// Default
Tt::Tt ()
{
mUUID = uuid ();
mStatus = pending;
mId = 0;
mSequence.clear ();
mTags.clear ();
mAttributes.clear ();
mDescription = "";
mFrom = "";
mTo = "";
mGlobal = false;
mAnnotations.clear ();
}
////////////////////////////////////////////////////////////////////////////////
// Initialize by parsing storage format
Tt::Tt (const std::string& line)
{
parse (line);
}
////////////////////////////////////////////////////////////////////////////////
Tt::Tt (const Tt& other)
{
mStatus = other.mStatus;
mUUID = other.mUUID;
mId = other.mId;
mSequence = other.mSequence;
mDescription = other.mDescription;
mTags = other.mTags;
mRemoveTags = other.mRemoveTags;
mAttributes = other.mAttributes;
mAnnotations = other.mAnnotations;
}
////////////////////////////////////////////////////////////////////////////////
Tt& Tt::operator= (const Tt& other)
{
if (this != &other)
{
mStatus = other.mStatus;
mUUID = other.mUUID;
mId = other.mId;
mSequence = other.mSequence;
mDescription = other.mDescription;
mTags = other.mTags;
mRemoveTags = other.mRemoveTags;
mAttributes = other.mAttributes;
mAnnotations = other.mAnnotations;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Tt::~Tt ()
{
}
////////////////////////////////////////////////////////////////////////////////
bool Tt::hasTag (const std::string& tag) const
{
std::vector <std::string>::const_iterator it = find (mTags.begin (), mTags.end (), tag);
if (it != mTags.end ())
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
// SPECIAL METHOD - DO NOT REMOVE
void Tt::getRemoveTags (std::vector<std::string>& all)
{
all = mRemoveTags;
}
////////////////////////////////////////////////////////////////////////////////
// SPECIAL METHOD - DO NOT REMOVE
void Tt::addRemoveTag (const std::string& tag)
{
if (tag.find (' ') != std::string::npos)
throw std::string ("Tt::addRemoveTag - tags may not contain spaces");
mRemoveTags.push_back (tag);
}
////////////////////////////////////////////////////////////////////////////////
int Tt::getTagCount () const
{
return mTags.size ();
}
////////////////////////////////////////////////////////////////////////////////
void Tt::getTags (std::vector<std::string>& all) const
{
all = mTags;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::addTag (const std::string& tag)
{
if (tag.find (' ') != std::string::npos)
throw std::string ("Tt::addTag - tags may not contain spaces");
if (tag[0] == '+')
{
if (! hasTag (tag.substr (1, std::string::npos)))
mTags.push_back (tag.substr (1, std::string::npos));
}
else
{
if (! hasTag (tag))
mTags.push_back (tag);
}
}
////////////////////////////////////////////////////////////////////////////////
void Tt::addTags (const std::vector <std::string>& tags)
{
for (size_t i = 0; i < tags.size (); ++i)
{
if (tags[i].find (' ') != std::string::npos)
throw std::string ("Tt::addTags - tags may not contain spaces");
if (tags[i][0] == '+')
{
if (! hasTag (tags[i].substr (1, std::string::npos)))
mTags.push_back (tags[i].substr (1, std::string::npos));
}
else
{
if (! hasTag (tags[i]))
mTags.push_back (tags[i]);
}
}
}
////////////////////////////////////////////////////////////////////////////////
void Tt::removeTag (const std::string& tag)
{
std::vector <std::string> copy;
for (size_t i = 0; i < mTags.size (); ++i)
if (mTags[i] != tag)
copy.push_back (mTags[i]);
mTags = copy;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::removeTags ()
{
mTags.clear ();
}
////////////////////////////////////////////////////////////////////////////////
void Tt::getAttributes (std::map<std::string, std::string>& all)
{
all = mAttributes;
}
////////////////////////////////////////////////////////////////////////////////
const std::string Tt::getAttribute (const std::string& name)
{
if (mAttributes.find (name) != mAttributes.end ())
return mAttributes[name];
return "";
}
////////////////////////////////////////////////////////////////////////////////
void Tt::setAttribute (const std::string& name, const std::string& value)
{
if (name.find (' ') != std::string::npos)
throw std::string ("An attribute name may not contain spaces");
if (value.find (' ') != std::string::npos)
throw std::string ("An attribute value may not contain spaces");
mAttributes[name] = value;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::setAttributes (const std::map <std::string, std::string>& attributes)
{
foreach (i, attributes)
{
if (i->first.find (' ') != std::string::npos)
throw std::string ("An attribute name may not contain spaces");
if (i->second.find (' ') != std::string::npos)
throw std::string ("An attribute value may not contain spaces");
mAttributes[i->first] = i->second;
}
}
////////////////////////////////////////////////////////////////////////////////
void Tt::removeAttributes ()
{
mAttributes.clear ();
}
////////////////////////////////////////////////////////////////////////////////
void Tt::removeAttribute (const std::string& name)
{
std::map <std::string, std::string> copy = mAttributes;
mAttributes.clear ();
foreach (i, copy)
if (i->first != name)
mAttributes[i->first] = i->second;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::getSubstitution (
std::string& from,
std::string& to,
bool& global) const
{
from = mFrom;
to = mTo;
global = mGlobal;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::setSubstitution (
const std::string& from,
const std::string& to,
bool global)
{
mFrom = from;
mTo = to;
mGlobal = global;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::getAnnotations (std::map <time_t, std::string>& all) const
{
all = mAnnotations;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::setAnnotations (const std::map <time_t, std::string>& all)
{
mAnnotations = all;
}
////////////////////////////////////////////////////////////////////////////////
void Tt::addAnnotation (const std::string& description)
{
std::string sanitized = description;
std::replace (sanitized.begin (), sanitized.end (), '"', '\'');
std::replace (sanitized.begin (), sanitized.end (), '[', '(');
std::replace (sanitized.begin (), sanitized.end (), ']', ')');
mAnnotations[time (NULL)] = sanitized;
}
////////////////////////////////////////////////////////////////////////////////
bool Tt::sequenceContains (int id) const
{
foreach (seq, mSequence)
if (*seq == id)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
// uuid status [tags] [attributes] [annotations] description
//
// uuid \x{8}-\x{4}-\x{4}-\x{4}-\x{12}
// status - + X r
// tags \w+ \s ...
// attributes \w+:\w+ \s ...
// description .+
//
const std::string Tt::compose () const
{
// UUID
std::string line = mUUID + ' ';
// Status
if (mStatus == pending) line += "- [";
else if (mStatus == completed) line += "+ [";
else if (mStatus == deleted) line += "X [";
else if (mStatus == recurring) line += "r [";
// Tags
for (size_t i = 0; i < mTags.size (); ++i)
{
line += (i > 0 ? " " : "");
line += mTags[i];
}
line += "] [";
// Attributes
int count = 0;
foreach (i, mAttributes)
{
line += (count > 0 ? " " : "");
line += i->first + ":" + i->second;
++count;
}
line += "] [";
// Annotations
std::stringstream annotation;
bool first = true;
foreach (note, mAnnotations)
{
if (first)
first = false;
else
annotation << " ";
annotation << note->first << ":\"" << note->second << "\"";
}
line += annotation.str () + "] ";
// Description
line += mDescription;
// EOL
line += "\n";
if (line.length () > T_LINE_MAX)
throw std::string ("Line too long");
return line;
}
////////////////////////////////////////////////////////////////////////////////
const std::string Tt::composeCSV ()
{
// UUID
std::string line = "'" + mUUID + "',";
// Status
if (mStatus == pending) line += "'pending',";
else if (mStatus == completed) line += "'completed',";
else if (mStatus == deleted) line += "'deleted',";
else if (mStatus == recurring) line += "'recurring',";
// Tags
line += "'";
for (size_t i = 0; i < mTags.size (); ++i)
{
line += (i > 0 ? " " : "");
line += mTags[i];
}
line += "',";
std::string value = mAttributes["entry"];
line += value + ",";
value = mAttributes["start"];
if (value != "")
line += value;
line += ",";
value = mAttributes["due"];
if (value != "")
line += value;
line += ",";
value = mAttributes["recur"];
if (value != "")
line += value;
line += ",";
value = mAttributes["end"];
if (value != "")
line += value;
line += ",";
value = mAttributes["project"];
if (value != "")
line += "'" + value + "'";
line += ",";
value = mAttributes["priority"];
if (value != "")
line += "'" + value + "'";
line += ",";
value = mAttributes["fg"];
if (value != "")
line += "'" + value + "'";
line += ",";
value = mAttributes["bg"];
if (value != "")
line += "'" + value + "'";
line += ",";
// Convert single quotes to double quotes, because single quotes are used to
// delimit the values that need it.
std::string clean = mDescription;
std::replace (clean.begin (), clean.end (), '\'', '"');
line += "'" + clean + "'\n";
return line;
}
////////////////////////////////////////////////////////////////////////////////
// Read all file formats, write only the latest.
void Tt::parse (const std::string& line)
{
switch (determineVersion (line))
{
// File format version 1, from 2006.11.27 - 2007.12.31
case 1:
{
// Generate a UUID for forward support.
mUUID = uuid ();
if (line.length () > 6) // ^\[\]\s\[\]\n
{
if (line[0] == 'X')
setStatus (deleted);
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
closeTagBracket != std::string::npos)
{
size_t openAttrBracket = line.find ("[", closeTagBracket);
size_t closeAttrBracket = line.find ("]", openAttrBracket);
if (openAttrBracket != std::string::npos &&
closeAttrBracket != std::string::npos)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> rawTags;
split (mTags, tags, ' ');
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> pairs;
split (pairs, attributes, ' ');
for (size_t i = 0; i < pairs.size (); ++i)
{
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair[1] != "")
mAttributes[pair[0]] = pair[1];
}
mDescription = line.substr (closeAttrBracket + 2, std::string::npos);
}
else
throw std::string ("Missing attribute brackets");
}
else
throw std::string ("Missing tag brackets");
}
else
throw std::string ("Line too short");
mAnnotations.clear ();
}
break;
// File format version 2, from 2008.1.1 - 2009.3.23
case 2:
{
if (line.length () > 46) // ^.{36} . \[\] \[\] \n
{
mUUID = line.substr (0, 36);
mStatus = line[37] == '+' ? completed
: line[37] == 'X' ? deleted
: line[37] == 'r' ? recurring
: pending;
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
closeTagBracket != std::string::npos)
{
size_t openAttrBracket = line.find ("[", closeTagBracket);
size_t closeAttrBracket = line.find ("]", openAttrBracket);
if (openAttrBracket != std::string::npos &&
closeAttrBracket != std::string::npos)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> rawTags;
split (mTags, tags, ' ');
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> pairs;
split (pairs, attributes, ' ');
for (size_t i = 0; i < pairs.size (); ++i)
{
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair.size () == 2)
mAttributes[pair[0]] = pair[1];
}
mDescription = line.substr (closeAttrBracket + 2, std::string::npos);
}
else
throw std::string ("Missing attribute brackets");
}
else
throw std::string ("Missing tag brackets");
}
else
throw std::string ("Line too short");
mAnnotations.clear ();
}
break;
// File format version 3, from 2009.3.23
case 3:
{
if (line.length () > 49) // ^.{36} . \[\] \[\] \[\] \n
{
mUUID = line.substr (0, 36);
mStatus = line[37] == '+' ? completed
: line[37] == 'X' ? deleted
: line[37] == 'r' ? recurring
: pending;
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
closeTagBracket != std::string::npos)
{
size_t openAttrBracket = line.find ("[", closeTagBracket);
size_t closeAttrBracket = line.find ("]", openAttrBracket);
if (openAttrBracket != std::string::npos &&
closeAttrBracket != std::string::npos)
{
size_t openAnnoBracket = line.find ("[", closeAttrBracket);
size_t closeAnnoBracket = line.find ("]", openAnnoBracket);
if (openAnnoBracket != std::string::npos &&
closeAnnoBracket != std::string::npos)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> rawTags;
split (mTags, tags, ' ');
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> pairs;
split (pairs, attributes, ' ');
for (size_t i = 0; i < pairs.size (); ++i)
{
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair.size () == 2)
mAttributes[pair[0]] = pair[1];
}
// Extract and split the annotations, which are of the form:
// 1234:"..." 5678:"..."
std::string annotations = line.substr (
openAnnoBracket + 1, closeAnnoBracket - openAnnoBracket - 1);
pairs.clear ();
std::string::size_type start = 0;
std::string::size_type end = 0;
do
{
end = annotations.find ('"', start);
if (end != std::string::npos)
{
end = annotations.find ('"', end + 1);
if (start != std::string::npos &&
end != std::string::npos)
{
pairs.push_back (annotations.substr (start, end - start + 1));
start = end + 2;
}
}
}
while (start != std::string::npos &&
end != std::string::npos);
for (size_t i = 0; i < pairs.size (); ++i)
{
std::string pair = pairs[i];
std::string::size_type colon = pair.find (":");
if (colon != std::string::npos)
{
std::string name = pair.substr (0, colon);
std::string value = pair.substr (colon + 2, pair.length () - colon - 3);
mAnnotations[::atoi (name.c_str ())] = value;
}
}
mDescription = line.substr (closeAnnoBracket + 2, std::string::npos);
}
else
throw std::string ("Missing annotation brackets.");
}
else
throw std::string ("Missing attribute brackets.");
}
else
throw std::string ("Missing tag brackets.");
}
else
throw std::string ("Line too short.");
}
break;
default:
throw std::string ("Unrecognized task file format.");
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// If this code is inaccurate, data corruption ensues.
int Tt::determineVersion (const std::string& line)
{
// Version 1 looks like:
//
// [tags] [attributes] description\n
// X [tags] [attributes] description\n
//
// Scan for the first character being either the bracket or X.
if (line[0] == '[' ||
line[0] == 'X')
return 1;
// Version 2 looks like:
//
// uuid status [tags] [attributes] description\n
//
// Where uuid looks like:
//
// 27755d92-c5e9-4c21-bd8e-c3dd9e6d3cf7
//
// Scan for the hyphens in the uuid, the following space, and a valid status
// character.
if (line[8] == '-' &&
line[13] == '-' &&
line[18] == '-' &&
line[23] == '-' &&
line[36] == ' ' &&
(line[37] == '-' || line[37] == '+' || line[37] == 'X' || line[37] == 'r'))
{
// Version 3 looks like:
//
// uuid status [tags] [attributes] [annotations] description\n
//
// Scan for the number of [] pairs.
std::string::size_type tagAtts = line.find ("] [", 0);
std::string::size_type attsAnno = line.find ("] [", tagAtts + 1);
std::string::size_type annoDesc = line.find ("] ", attsAnno + 1);
if (tagAtts != std::string::npos &&
attsAnno != std::string::npos &&
annoDesc != std::string::npos)
return 3;
else
return 2;
}
// Version 4?
//
// Fortunately, with the hindsight that will come with version 4, the
// identifying characteristics of 1, 2 and 3 may be modified such that if 4
// has a UUID followed by a status, then there is still a way to differentiate
// between 2, 3 and 4.
//
// The danger is that a version 3 binary reads and misinterprets a version 4
// file. This is why it is a good idea to rely on an explicit version
// declaration rather than chance positioning.
// Zero means 'no idea'.
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// TODO Expand this method into a full-blown task validation check.
bool Tt::validate () const
{
// TODO Verify until > due
// TODO Verify entry < until, due, start, end
return true;
}
////////////////////////////////////////////////////////////////////////////////

113
src/T.h
View File

@@ -1,113 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_Tt
#define INCLUDED_Tt
#include <string>
#include <vector>
#include <map>
// Length of longest line.
#define T_LINE_MAX 32768
class Tt
{
public:
enum status {pending, completed, deleted, recurring};
Tt (); // Default constructor
Tt (const std::string&); // Initialize by parsing storage format
Tt (const Tt&); // Copy constructor
Tt& operator= (const Tt&); // Assignment operator
~Tt (); // Destructor
std::string getUUID () const { return mUUID; }
void setUUID (const std::string& uuid) { mUUID = uuid; }
int getId () const { return mId; }
void setId (int id) { mId = id; mSequence.push_back (id); }
std::vector <int> getAllIds () const { return mSequence; }
void addId (int id) { if (mId == 0) mId = id; mSequence.push_back (id); }
status getStatus () const { return mStatus; }
void setStatus (status s) { mStatus = s; }
const std::string getDescription () const { return mDescription; }
void setDescription (const std::string& description) { mDescription = description; }
int getAnnotationCount () const { return mAnnotations.size (); }
void getSubstitution (std::string&, std::string&, bool&) const;
void setSubstitution (const std::string&, const std::string&, bool);
bool hasTag (const std::string&) const;
void getRemoveTags (std::vector<std::string>&); // SPECIAL
void addRemoveTag (const std::string&); // SPECIAL
int getTagCount () const;
void getTags (std::vector<std::string>&) const;
void addTag (const std::string&);
void addTags (const std::vector <std::string>&);
void removeTag (const std::string&);
void removeTags ();
void getAttributes (std::map<std::string, std::string>&);
const std::string getAttribute (const std::string&);
void setAttribute (const std::string&, const std::string&);
void setAttributes (const std::map <std::string, std::string>&);
void removeAttribute (const std::string&);
void removeAttributes ();
void getAnnotations (std::map <time_t, std::string>&) const;
void setAnnotations (const std::map <time_t, std::string>&);
void addAnnotation (const std::string&);
bool sequenceContains (int) const;
const std::string compose () const;
const std::string composeCSV ();
void parse (const std::string&);
bool validate () const;
private:
int determineVersion (const std::string&);
private:
status mStatus;
std::string mUUID;
int mId;
std::vector <int> mSequence;
std::string mDescription;
std::vector<std::string> mTags;
std::vector<std::string> mRemoveTags;
std::map<std::string, std::string> mAttributes;
std::string mFrom;
std::string mTo;
bool mGlobal;
std::map <time_t, std::string> mAnnotations;
};
#endif
////////////////////////////////////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -27,41 +27,59 @@
#ifndef INCLUDED_TDB
#define INCLUDED_TDB
#include <map>
#include <vector>
#include <string>
#include "T.h"
#include "Location.h"
#include "Filter.h"
#include "Task.h"
// Length of longest line.
#define T_LINE_MAX 32768
class TDB
{
public:
TDB ();
~TDB ();
TDB (); // Default constructor
~TDB (); // Destructor
void dataDirectory (const std::string&);
bool allT (std::vector <Tt>&);
bool pendingT (std::vector <Tt>&);
bool allPendingT (std::vector <Tt>&);
bool completedT (std::vector <Tt>&) const;
bool allCompletedT (std::vector <Tt>&) const;
bool addT (const Tt&);
bool modifyT (const Tt&);
int gc ();
int nextId ();
TDB (const TDB&);
TDB& operator= (const TDB&);
void noLock ();
void clear ();
void location (const std::string&);
void lock (bool lockFile = true);
void unlock ();
int load (std::vector <Task>&, Filter&);
int loadPending (std::vector <Task>&, Filter&);
int loadCompleted (std::vector <Task>&, Filter&);
void add (const Task&); // Single task add to pending
void update (const Task&); // Single task update to pending
int commit (); // Write out all tasks
int gc (); // Clean up pending
int nextId ();
void undo ();
private:
bool lock (FILE*) const;
bool overwritePending (std::vector <Tt>&);
bool writePending (const Tt&);
bool writeCompleted (const Tt&);
bool readLockedFile (const std::string&, std::vector <std::string>&) const;
FILE* openAndLock (const std::string&);
void writeUndo (const Task&, FILE*);
void writeUndo (const Task&, const Task&, FILE*);
private:
std::string mPendingFile;
std::string mCompletedFile;
std::vector <Location> mLocations;
bool mLock;
bool mAllOpenAndLocked;
int mId;
bool mNoLock;
std::vector <Task> mPending; // Contents of pending.data
std::vector <Task> mNew; // Uncommitted new tasks
std::vector <Task> mModified; // Uncommitted modified tasks
// TODO Need cache of raw file contents to preserve comments.
};
#endif

View File

@@ -46,9 +46,12 @@
#include <iostream>
#include <string.h>
#include <assert.h>
#include <Table.h>
#include <Date.h>
#include <task.h>
#include "Table.h"
#include "Date.h"
#include "Duration.h"
#include "Timer.h"
#include "text.h"
#include "util.h"
////////////////////////////////////////////////////////////////////////////////
Table::Table ()
@@ -114,11 +117,11 @@ void Table::setTableDashedUnderline ()
int Table::addColumn (const std::string& col)
{
mSpecifiedWidth.push_back (minimum);
mMaxDataWidth.push_back (col.length ());
mMaxDataWidth.push_back (col == "" ? 1 : col.length ());
mCalculatedWidth.push_back (0);
mColumnPadding.push_back (0);
mColumns.push_back (col);
mColumns.push_back (col == "" ? " " : col);
return mColumns.size () - 1;
}
@@ -989,7 +992,7 @@ void Table::sort (std::vector <int>& order)
break;
else if ((std::string)*left != "" && (std::string)*right == "")
SWAP
else if (convertDuration ((std::string)*left) > convertDuration ((std::string)*right))
else if (Duration ((std::string)*left) > Duration ((std::string)*right))
SWAP
break;
@@ -998,7 +1001,7 @@ void Table::sort (std::vector <int>& order)
break;
else if ((std::string)*left == "" && (std::string)*right != "")
SWAP
else if (convertDuration ((std::string)*left) < convertDuration ((std::string)*right))
else if (Duration ((std::string)*left) < Duration ((std::string)*right))
SWAP
break;
}
@@ -1038,6 +1041,8 @@ void Table::clean (std::string& value)
////////////////////////////////////////////////////////////////////////////////
const std::string Table::render (int maximum /* = 0 */)
{
Timer t ("Table::render");
calculateColumnWidths ();
// Print column headers in column order.

View File

@@ -52,6 +52,9 @@ public:
Table ();
virtual ~Table ();
Table (const Table&);
Table& operator= (const Table&);
void setTableColor (Text::color, Text::color);
void setTableFg (Text::color);
void setTableBg (Text::color);

642
src/Task.cpp Normal file
View File

@@ -0,0 +1,642 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include <algorithm>
#include "Nibbler.h"
#include "Date.h"
#include "Duration.h"
#include "Task.h"
#include "text.h"
#include "util.h"
////////////////////////////////////////////////////////////////////////////////
Task::Task ()
: id (0)
{
}
////////////////////////////////////////////////////////////////////////////////
Task::Task (const Task& other)
: Record (other)
, id (other.id)
{
}
////////////////////////////////////////////////////////////////////////////////
Task& Task::operator= (const Task& other)
{
if (this != &other)
{
Record::operator= (other);
id = other.id;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
// The uuid and id attributes must be exempt for comparison.
bool Task::operator== (const Task& other)
{
if (size () != other.size ())
return false;
foreach (att, *this)
if (att->first != "uuid")
if (att->second.value () != other.get (att->first))
return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Attempt an FF4 parse first, using Record::parse, and in the event of an error
// try a legacy parse (F3, FF2). Note that FF1 is no longer supported.
Task::Task (const std::string& input)
{
std::string copy;
if (input[input.length () - 1] == '\n')
copy = input.substr (0, input.length () - 1);
else
copy = input;
try
{
Record::parse (copy);
}
catch (std::string& e)
{
legacyParse (copy);
}
}
////////////////////////////////////////////////////////////////////////////////
Task::~Task ()
{
}
////////////////////////////////////////////////////////////////////////////////
Task::status Task::textToStatus (const std::string& input)
{
if (input == "pending") return Task::pending; // TODO i18n
else if (input == "completed") return Task::completed; // TODO i18n
else if (input == "deleted") return Task::deleted; // TODO i18n
else if (input == "recurring") return Task::recurring; // TODO i18n
else if (input == "waiting") return Task::waiting; // TODO i18n
return Task::pending;
}
////////////////////////////////////////////////////////////////////////////////
std::string Task::statusToText (Task::status s)
{
if (s == Task::pending) return "pending"; // TODO i18n
else if (s == Task::completed) return "completed"; // TODO i18n
else if (s == Task::deleted) return "deleted"; // TODO i18n
else if (s == Task::recurring) return "recurring"; // TODO i18n
else if (s == Task::waiting) return "waiting"; // TODO i18n
return "pending";
}
////////////////////////////////////////////////////////////////////////////////
void Task::setEntry ()
{
char entryTime[16];
sprintf (entryTime, "%u", (unsigned int) time (NULL));
set ("entry", entryTime); // No i18n
}
////////////////////////////////////////////////////////////////////////////////
Task::status Task::getStatus ()
{
return textToStatus (get ("status")); // No i18n
}
////////////////////////////////////////////////////////////////////////////////
void Task::setStatus (Task::status status)
{
set ("status", statusToText (status)); // No i18n
}
////////////////////////////////////////////////////////////////////////////////
void Task::parse (const std::string& line)
{
std::string copy;
if (line[line.length () - 1] == '\n')
copy = line.substr (0, line.length () - 1);
else
copy = line;
try
{
Record::parse (copy);
}
catch (std::string& e)
{
legacyParse (copy);
}
}
////////////////////////////////////////////////////////////////////////////////
// Support FF2, FF3.
// Thankfully FF1 is no longer supported.
void Task::legacyParse (const std::string& line)
{
switch (determineVersion (line))
{
// File format version 1, from 2006.11.27 - 2007.12.31
case 1:
throw std::string ("Task no longer supports file format 1, originally used "
"between 27 November 2006 and 31 December 2007."); // TODO i18n
break;
// File format version 2, from 2008.1.1 - 2009.3.23
case 2:
{
if (line.length () > 46) // ^.{36} . \[\] \[\] \n
{
set ("uuid", line.substr (0, 36));
Task::status status = line[37] == '+' ? completed
: line[37] == 'X' ? deleted
: line[37] == 'r' ? recurring
: pending;
set ("status", statusToText (status)); // No i18n
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
closeTagBracket != std::string::npos)
{
size_t openAttrBracket = line.find ("[", closeTagBracket);
size_t closeAttrBracket = line.find ("]", openAttrBracket);
if (openAttrBracket != std::string::npos &&
closeAttrBracket != std::string::npos)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> tagSet;
split (tagSet, tags, ' ');
addTags (tagSet);
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> pairs;
split (pairs, attributes, ' ');
for (size_t i = 0; i < pairs.size (); ++i)
{
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair.size () == 2)
set (pair[0], pair[1]);
}
set ("description", line.substr (closeAttrBracket + 2, std::string::npos)); // No i18n
}
else
throw std::string ("Missing attribute brackets"); // TODO i18n
}
else
throw std::string ("Missing tag brackets"); // TODO i18n
}
else
throw std::string ("Line too short"); // TODO i18n
removeAnnotations ();
}
break;
// File format version 3, from 2009.3.23
case 3:
{
if (line.length () > 49) // ^.{36} . \[\] \[\] \[\] \n
{
set ("uuid", line.substr (0, 36));
Task::status status = line[37] == '+' ? completed
: line[37] == 'X' ? deleted
: line[37] == 'r' ? recurring
: pending;
set ("status", statusToText (status)); // No i18n
size_t openTagBracket = line.find ("[");
size_t closeTagBracket = line.find ("]", openTagBracket);
if (openTagBracket != std::string::npos &&
closeTagBracket != std::string::npos)
{
size_t openAttrBracket = line.find ("[", closeTagBracket);
size_t closeAttrBracket = line.find ("]", openAttrBracket);
if (openAttrBracket != std::string::npos &&
closeAttrBracket != std::string::npos)
{
size_t openAnnoBracket = line.find ("[", closeAttrBracket);
size_t closeAnnoBracket = line.find ("]", openAnnoBracket);
if (openAnnoBracket != std::string::npos &&
closeAnnoBracket != std::string::npos)
{
std::string tags = line.substr (
openTagBracket + 1, closeTagBracket - openTagBracket - 1);
std::vector <std::string> tagSet;
split (tagSet, tags, ' ');
addTags (tagSet);
std::string attributes = line.substr (
openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
std::vector <std::string> pairs;
split (pairs, attributes, ' ');
for (size_t i = 0; i < pairs.size (); ++i)
{
std::vector <std::string> pair;
split (pair, pairs[i], ':');
if (pair.size () == 2)
set (pair[0], pair[1]);
}
// Extract and split the annotations, which are of the form:
// 1234:"..." 5678:"..."
std::string annotations = line.substr (
openAnnoBracket + 1, closeAnnoBracket - openAnnoBracket - 1);
pairs.clear ();
std::string::size_type start = 0;
std::string::size_type end = 0;
do
{
end = annotations.find ('"', start);
if (end != std::string::npos)
{
end = annotations.find ('"', end + 1);
if (start != std::string::npos &&
end != std::string::npos)
{
pairs.push_back (annotations.substr (start, end - start + 1));
start = end + 2;
}
}
}
while (start != std::string::npos &&
end != std::string::npos);
for (size_t i = 0; i < pairs.size (); ++i)
{
std::string pair = pairs[i];
std::string::size_type colon = pair.find (":");
if (colon != std::string::npos)
{
std::string name = pair.substr (0, colon);
std::string value = pair.substr (colon + 2, pair.length () - colon - 3);
set ("annotation_" + name, value); // No i18n
}
}
set ("description", line.substr (closeAnnoBracket + 2, std::string::npos)); // No i18n
}
else
throw std::string ("Missing annotation brackets."); // TODO i18n
}
else
throw std::string ("Missing attribute brackets."); // TODO i18n
}
else
throw std::string ("Missing tag brackets."); // TODO i18n
}
else
throw std::string ("Line too short."); // TODO i18n
}
break;
default:
throw std::string ("Unrecognized task file format."); // TODO i18n
break;
}
}
////////////////////////////////////////////////////////////////////////////////
std::string Task::composeCSV () const
{
std::stringstream out;
// Deliberately no 'id'.
out << "'" << get ("uuid") << "',"; // No i18n
out << "'" << get ("status") << "',"; // No i18n
// Tags
std::vector <std::string> tags;
getTags (tags);
std::string allTags;
join (allTags, " ", tags); // No i18n
out << "'" << allTags << "',"; // No i18n
out << get ("entry") << ","; // No i18n
out << get ("start") << ","; // No i18n
if (has ("due"))
out << "'" << get ("due") << "',"; // No i18n
else
out << ","; // No i18n
if (has ("recur"))
out << "'" << get ("recur") << "',"; // No i18n
else
out << ","; // No i18n
out << get ("end") << ","; // No i18n
if (has ("project"))
out << "'" << get ("project") << "',"; // No i18n
else
out << ","; // No i18n
if (has ("priority"))
out << "'" << get ("priority") << "',"; // No i18n
else
out << ","; // No i18n
if (has ("fg"))
out << "'" << get ("fg") << "',"; // No i18n
else
out << ","; // No i18n
if (has ("bg"))
out << "'" << get ("bg") << "',"; // No i18n
else
out << ","; // No i18n
// Convert single quotes to double quotes, because single quotes are used to
// delimit the values that need it.
std::string clean = get ("description"); // No i18n
std::replace (clean.begin (), clean.end (), '\'', '"'); // No i18n
out << "'" << clean << "'\n";
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
void Task::getAnnotations (std::vector <Att>& annotations) const
{
annotations.clear ();
Record::const_iterator ci;
for (ci = this->begin (); ci != this->end (); ++ci)
if (ci->first.substr (0, 11) == "annotation_") // No i18n
annotations.push_back (ci->second);
}
////////////////////////////////////////////////////////////////////////////////
void Task::setAnnotations (const std::vector <Att>& annotations)
{
// Erase old annotations.
removeAnnotations ();
std::vector <Att>::const_iterator ci;
for (ci = annotations.begin (); ci != annotations.end (); ++ci)
(*this)[ci->name ()] = *ci;
}
////////////////////////////////////////////////////////////////////////////////
// The timestamp is part of the name:
// annotation_1234567890:"..."
//
void Task::addAnnotation (const std::string& description)
{
std::stringstream s;
s << "annotation_" << time (NULL); // No i18n
(*this)[s.str ()] = Att (s.str (), description);
}
////////////////////////////////////////////////////////////////////////////////
void Task::removeAnnotations ()
{
// Erase old annotations.
Record::iterator i;
for (i = this->begin (); i != this->end (); ++i)
if (i->first.substr (0, 11) == "annotation_") // No i18n
this->erase (i);
}
////////////////////////////////////////////////////////////////////////////////
int Task::getTagCount ()
{
std::vector <std::string> tags;
split (tags, get ("tags"), ','); // No i18n
return (int) tags.size ();
}
////////////////////////////////////////////////////////////////////////////////
bool Task::hasTag (const std::string& tag)
{
std::vector <std::string> tags;
split (tags, get ("tags"), ','); // No i18n
if (std::find (tags.begin (), tags.end (), tag) != tags.end ())
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
void Task::addTag (const std::string& tag)
{
std::vector <std::string> tags;
split (tags, get ("tags"), ','); // No i18n
if (std::find (tags.begin (), tags.end (), tag) == tags.end ())
{
tags.push_back (tag);
std::string combined;
join (combined, ",", tags); // No i18n
set ("tags", combined); // No i18n
}
}
////////////////////////////////////////////////////////////////////////////////
void Task::addTags (const std::vector <std::string>& tags)
{
remove ("tags"); // No i18n
std::vector <std::string>::const_iterator it;
for (it = tags.begin (); it != tags.end (); ++it)
addTag (*it);
}
////////////////////////////////////////////////////////////////////////////////
void Task::getTags (std::vector<std::string>& tags) const
{
split (tags, get ("tags"), ','); // No i18n
}
////////////////////////////////////////////////////////////////////////////////
void Task::removeTag (const std::string& tag)
{
std::vector <std::string> tags;
split (tags, get ("tags"), ','); // No i18n
std::vector <std::string>::iterator i;
i = std::find (tags.begin (), tags.end (), tag);
if (i != tags.end ())
{
tags.erase (i);
std::string combined;
join (combined, ",", tags); // No i18n
set ("tags", combined); // No i18n
}
}
////////////////////////////////////////////////////////////////////////////////
void Task::validate () const
{
// Every task needs an ID, entry and description attribute.
if (!has ("uuid") ||
!has ("entry") ||
!has ("description"))
throw std::string ("A task must have a uuid, entry date and description in order to be valid."); // TODO i18n
if (get ("description") == "") // No i18n
throw std::string ("Cannot add a task that is blank, or contains <CR> or <LF> characters."); // TODO i18n
if (has ("due"))
{
Date due (::atoi (get ("due").c_str ()));
// Verify until > due
if (has ("until"))
{
Date until (::atoi (get ("until").c_str ()));
if (due > until)
throw std::string ("An 'until' date must be after a 'due' date."); // TODO i18n
}
Date entry (::atoi (get ("entry").c_str ()));
if (has ("start"))
{
Date start (::atoi (get ("start").c_str ()));
if (entry > start)
throw std::string ("A 'start' date must be after an 'entry' date."); // TODO i18n
}
if (has ("end"))
{
Date end (::atoi (get ("end").c_str ()));
if (entry > end)
throw std::string ("An 'end' date must be after an 'entry' date."); // TODO i18n
}
}
// Recur durations must be valid.
if (has ("recur"))
{
Duration d;
if (! d.valid (get ("recur")))
throw std::string ("A recurrence value must be valid."); // TODO i18n
}
}
////////////////////////////////////////////////////////////////////////////////
int Task::determineVersion (const std::string& line)
{
// Version 2 looks like:
//
// uuid status [tags] [attributes] description\n
//
// Where uuid looks like:
//
// 27755d92-c5e9-4c21-bd8e-c3dd9e6d3cf7
//
// Scan for the hyphens in the uuid, the following space, and a valid status
// character.
if (line[8] == '-' &&
line[13] == '-' &&
line[18] == '-' &&
line[23] == '-' &&
line[36] == ' ' &&
(line[37] == '-' || line[37] == '+' || line[37] == 'X' || line[37] == 'r'))
{
// Version 3 looks like:
//
// uuid status [tags] [attributes] [annotations] description\n
//
// Scan for the number of [] pairs.
std::string::size_type tagAtts = line.find ("] [", 0); // No i18n
std::string::size_type attsAnno = line.find ("] [", tagAtts + 1); // No i18n
std::string::size_type annoDesc = line.find ("] ", attsAnno + 1); // No i18n
if (tagAtts != std::string::npos &&
attsAnno != std::string::npos &&
annoDesc != std::string::npos)
return 3;
else
return 2;
}
// Version 4 looks like:
//
// [name:"value" ...]
//
// Scan for [, ] and :".
else if (line[0] == '[' &&
line[line.length () - 1] == ']' &&
line.find ("uuid:\"") != std::string::npos) // No i18n
return 4;
// Version 1 looks like:
//
// [tags] [attributes] description\n
// X [tags] [attributes] description\n
//
// Scan for the first character being either the bracket or X.
else if (line.find ("X [") == 0 ||
line.find ("uuid") == std::string::npos || // No i18n
(line[0] == '[' &&
line.substr (line.length () - 1, 1) != "]")) // No i18n
return 1;
// Version 5?
//
// Fortunately, with the hindsight that will come with version 5, the
// identifying characteristics of 1, 2, 3 and 4 may be modified such that if 5
// has a UUID followed by a status, then there is still a way to differentiate
// between 2, 3, 4 and 5.
//
// The danger is that a version 3 binary reads and misinterprets a version 4
// file. This is why it is a good idea to rely on an explicit version
// declaration rather than chance positioning.
// Zero means 'no idea'.
return 0;
}
////////////////////////////////////////////////////////////////////////////////

83
src/Task.h Normal file
View File

@@ -0,0 +1,83 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TASK
#define INCLUDED_TASK
#include <string>
#include "Record.h"
#include "Subst.h"
#include "Sequence.h"
class Task : public Record
{
public:
Task (); // Default constructor
Task (const Task&); // Copy constructor
Task& operator= (const Task&); // Assignment operator
bool operator== (const Task&); // Comparison operator
Task (const std::string&); // Parse
~Task (); // Destructor
void parse (const std::string&);
std::string composeCSV () const;
// Status values.
enum status {pending, completed, deleted, recurring, waiting};
// Public data.
int id;
// Series of helper functions.
static status textToStatus (const std::string&);
static std::string statusToText (status);
void setEntry ();
status getStatus ();
void setStatus (status);
int getTagCount ();
bool hasTag (const std::string&);
void addTag (const std::string&);
void addTags (const std::vector <std::string>&);
void getTags (std::vector<std::string>&) const;
void removeTag (const std::string&);
void getAnnotations (std::vector <Att>&) const;
void setAnnotations (const std::vector <Att>&);
void addAnnotation (const std::string&);
void removeAnnotations ();
void validate () const;
private:
int determineVersion (const std::string&);
void legacyParse (const std::string&);
};
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -26,7 +26,11 @@
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <iomanip>
#include <Timer.h>
#include <sstream>
#include "Timer.h"
#include "Context.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
// Timer starts when the object is constructed.
@@ -37,19 +41,23 @@ Timer::Timer (const std::string& description)
}
////////////////////////////////////////////////////////////////////////////////
// Timer stops when the object is desctructed.
// Timer stops when the object is destructed.
Timer::~Timer ()
{
struct timeval end;
::gettimeofday (&end, NULL);
std::cout << "Timer "
<< mDescription
<< " "
<< std::setprecision (6)
<< ((end.tv_sec - mStart.tv_sec) +
((end.tv_usec - mStart.tv_usec ) / 1000000.0))
<< std::endl;
std::stringstream s;
s << "Timer " // No i18n
<< mDescription
<< " "
<< std::setprecision (6)
<< std::fixed
<< ((end.tv_sec - mStart.tv_sec) + ((end.tv_usec - mStart.tv_usec )
/ 1000000.0))
<< " sec";
context.debug (s.str ());
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -35,6 +35,8 @@ class Timer
public:
Timer (const std::string&);
~Timer ();
Timer (const Timer&);
Timer& operator= (const Timer&);
private:
std::string mDescription;

View File

@@ -25,144 +25,108 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <string>
#include "Context.h"
#include "text.h"
#include "util.h"
#include "i18n.h"
#include "color.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
namespace Text
{
static struct
{
color id;
int string_id;
std::string english_name;
std::string escape_sequence;
} allColors[] =
{
// Text::color i18n.h English vt220? xterm?
{ nocolor, 0, "", "" },
{ off, COLOR_OFF, "off", "" },
{ bold, COLOR_BOLD, "bold", "\033[1m" },
{ underline, COLOR_UL, "underline", "\033[4m" },
{ bold_underline, COLOR_B_UL, "bold_underline", "\033[1;4m" },
{ black, COLOR_BLACK, "black", "\033[30m" },
{ red, COLOR_RED, "red", "\033[31m" },
{ green, COLOR_GREEN, "green", "\033[32m" },
{ yellow, COLOR_YELLOW, "yellow", "\033[33m" },
{ blue, COLOR_BLUE, "blue", "\033[34m" },
{ magenta, COLOR_MAGENTA, "magenta", "\033[35m" },
{ cyan, COLOR_CYAN, "cyan", "\033[36m" },
{ white, COLOR_WHITE, "white", "\033[37m" },
{ bold_black, COLOR_B_BLACK, "bold_black", "\033[90m" },
{ bold_red, COLOR_B_RED, "bold_red", "\033[91m" },
{ bold_green, COLOR_B_GREEN, "bold_green", "\033[92m" },
{ bold_yellow, COLOR_B_YELLOW, "bold_yellow", "\033[93m" },
{ bold_blue, COLOR_B_BLUE, "bold_blue", "\033[94m" },
{ bold_magenta, COLOR_B_MAGENTA, "bold_magenta", "\033[95m" },
{ bold_cyan, COLOR_B_CYAN, "bold_cyan", "\033[96m" },
{ bold_white, COLOR_B_WHITE, "bold_white", "\033[97m" },
{ underline_black, COLOR_UL_BLACK, "underline_black", "\033[4;30m" },
{ underline_red, COLOR_UL_RED, "underline_red", "\033[4;31m" },
{ underline_green, COLOR_UL_GREEN, "underline_green", "\033[4;32m" },
{ underline_yellow, COLOR_UL_YELLOW, "underline_yellow", "\033[4;33m" },
{ underline_blue, COLOR_UL_BLUE, "underline_blue", "\033[4;34m" },
{ underline_magenta, COLOR_UL_MAGENTA, "underline_magenta", "\033[4;35m" },
{ underline_cyan, COLOR_UL_CYAN, "underline_cyan", "\033[4;36m" },
{ underline_white, COLOR_UL_WHITE, "underline_white", "\033[4;37m" },
{ bold_underline_black, COLOR_B_UL_BLACK, "bold_underline_black", "\033[1;4;30m" },
{ bold_underline_red, COLOR_B_UL_RED, "bold_underline_red", "\033[1;4;31m" },
{ bold_underline_green, COLOR_B_UL_GREEN, "bold_underline_green", "\033[1;4;32m" },
{ bold_underline_yellow, COLOR_B_UL_YELLOW, "bold_underline_yellow", "\033[1;4;33m" },
{ bold_underline_blue, COLOR_B_UL_BLUE, "bold_underline_blue", "\033[1;4;34m" },
{ bold_underline_magenta, COLOR_B_UL_MAGENTA, "bold_underline_magenta", "\033[1;4;35m" },
{ bold_underline_cyan, COLOR_B_UL_CYAN, "bold_underline_cyan", "\033[1;4;36m" },
{ bold_underline_white, COLOR_B_UL_WHITE, "bold_underline_white", "\033[1;4;37m" },
{ on_black, COLOR_ON_BLACK, "on_black", "\033[40m" },
{ on_red, COLOR_ON_RED, "on_red", "\033[41m" },
{ on_green, COLOR_ON_GREEN, "on_green", "\033[42m" },
{ on_yellow, COLOR_ON_YELLOW, "on_yellow", "\033[43m" },
{ on_blue, COLOR_ON_BLUE, "on_blue", "\033[44m" },
{ on_magenta, COLOR_ON_MAGENTA, "on_magenta", "\033[45m" },
{ on_cyan, COLOR_ON_CYAN, "on_cyan", "\033[46m" },
{ on_white, COLOR_ON_WHITE, "on_white", "\033[47m" },
{ on_bright_black, COLOR_ON_BRIGHT_BLACK, "on_bright_black", "\033[100m" },
{ on_bright_red, COLOR_ON_BRIGHT_RED, "on_bright_red", "\033[101m" },
{ on_bright_green, COLOR_ON_BRIGHT_GREEN, "on_bright_green", "\033[102m" },
{ on_bright_yellow, COLOR_ON_BRIGHT_YELLOW, "on_bright_yellow", "\033[103m" },
{ on_bright_blue, COLOR_ON_BRIGHT_BLUE, "on_bright_blue", "\033[104m" },
{ on_bright_magenta, COLOR_ON_BRIGHT_MAGENTA, "on_bright_magenta", "\033[105m" },
{ on_bright_cyan, COLOR_ON_BRIGHT_CYAN, "on_bright_cyan", "\033[106m" },
{ on_bright_white, COLOR_ON_BRIGHT_WHITE, "on_bright_white", "\033[107m" },
};
#define NUM_COLORS (sizeof (allColors) / sizeof (allColors[0]))
////////////////////////////////////////////////////////////////////////////////
std::string colorName (color c)
{
switch (c)
{
case nocolor: return "";
case off: return "off";
case bold: return "bold";
case underline: return "underline";
case bold_underline: return "bold_underline";
case black: return "black";
case red: return "red";
case green: return "green";
case yellow: return "yellow";
case blue: return "blue";
case magenta: return "magenta";
case cyan: return "cyan";
case white: return "white";
case bold_black: return "bold_black";
case bold_red: return "bold_red";
case bold_green: return "bold_green";
case bold_yellow: return "bold_yellow";
case bold_blue: return "bold_blue";
case bold_magenta: return "bold_magenta";
case bold_cyan: return "bold_cyan";
case bold_white: return "bold_white";
case underline_black: return "underline_black";
case underline_red: return "underline_red";
case underline_green: return "underline_green";
case underline_yellow: return "underline_yellow";
case underline_blue: return "underline_blue";
case underline_magenta: return "underline_magenta";
case underline_cyan: return "underline_cyan";
case underline_white: return "underline_white";
case bold_underline_black: return "bold_underline_black";
case bold_underline_red: return "bold_underline_red";
case bold_underline_green: return "bold_underline_green";
case bold_underline_yellow: return "bold_underline_yellow";
case bold_underline_blue: return "bold_underline_blue";
case bold_underline_magenta: return "bold_underline_magenta";
case bold_underline_cyan: return "bold_underline_cyan";
case bold_underline_white: return "bold_underline_white";
case on_black: return "on_black";
case on_red: return "on_red";
case on_green: return "on_green";
case on_yellow: return "on_yellow";
case on_blue: return "on_blue";
case on_magenta: return "on_magenta";
case on_cyan: return "on_cyan";
case on_white: return "on_white";
case on_bright_black: return "on_bright_black";
case on_bright_red: return "on_bright_red";
case on_bright_green: return "on_bright_green";
case on_bright_yellow: return "on_bright_yellow";
case on_bright_blue: return "on_bright_blue";
case on_bright_magenta: return "on_bright_magenta";
case on_bright_cyan: return "on_bright_cyan";
case on_bright_white: return "on_bright_white";
default: throw "Unknown Text::color value";
}
for (unsigned int i = 0; i < NUM_COLORS; ++i)
if (allColors[i].id == c)
return allColors[i].english_name;
throw context.stringtable.get (COLOR_UNKNOWN, "Unknown color value");
return "";
}
////////////////////////////////////////////////////////////////////////////////
color colorCode (const std::string& c)
{
if (c == "off") return off;
else if (c == "bold") return bold;
else if (c == "underline") return underline;
else if (c == "bold_underline") return bold_underline;
else if (c == "black") return black;
else if (c == "red") return red;
else if (c == "green") return green;
else if (c == "yellow") return yellow;
else if (c == "blue") return blue;
else if (c == "magenta") return magenta;
else if (c == "cyan") return cyan;
else if (c == "white") return white;
else if (c == "bold_black") return bold_black;
else if (c == "bold_red") return bold_red;
else if (c == "bold_green") return bold_green;
else if (c == "bold_yellow") return bold_yellow;
else if (c == "bold_blue") return bold_blue;
else if (c == "bold_magenta") return bold_magenta;
else if (c == "bold_cyan") return bold_cyan;
else if (c == "bold_white") return bold_white;
else if (c == "underline_black") return underline_black;
else if (c == "underline_red") return underline_red;
else if (c == "underline_green") return underline_green;
else if (c == "underline_yellow") return underline_yellow;
else if (c == "underline_blue") return underline_blue;
else if (c == "underline_magenta") return underline_magenta;
else if (c == "underline_cyan") return underline_cyan;
else if (c == "underline_white") return underline_white;
else if (c == "bold_underline_black") return bold_underline_black;
else if (c == "bold_underline_red") return bold_underline_red;
else if (c == "bold_underline_green") return bold_underline_green;
else if (c == "bold_underline_yellow") return bold_underline_yellow;
else if (c == "bold_underline_blue") return bold_underline_blue;
else if (c == "bold_underline_magenta") return bold_underline_magenta;
else if (c == "bold_underline_cyan") return bold_underline_cyan;
else if (c == "bold_underline_white") return bold_underline_white;
else if (c == "on_black") return on_black;
else if (c == "on_red") return on_red;
else if (c == "on_green") return on_green;
else if (c == "on_yellow") return on_yellow;
else if (c == "on_blue") return on_blue;
else if (c == "on_magenta") return on_magenta;
else if (c == "on_cyan") return on_cyan;
else if (c == "on_white") return on_white;
else if (c == "on_bright_black") return on_bright_black;
else if (c == "on_bright_red") return on_bright_red;
else if (c == "on_bright_green") return on_bright_green;
else if (c == "on_bright_yellow") return on_bright_yellow;
else if (c == "on_bright_blue") return on_bright_blue;
else if (c == "on_bright_magenta") return on_bright_magenta;
else if (c == "on_bright_cyan") return on_bright_cyan;
else if (c == "on_bright_white") return on_bright_white;
for (unsigned int i = 0; i < NUM_COLORS; ++i)
if (context.stringtable.get (allColors[i].string_id, allColors[i].english_name) == c)
return allColors[i].id;
return nocolor;
}
@@ -170,72 +134,11 @@ color colorCode (const std::string& c)
////////////////////////////////////////////////////////////////////////////////
std::string decode (color c)
{
switch (c)
{
case nocolor: return "";
case off: return "\033[0m";
case bold: return "\033[1m";
case underline: return "\033[4m";
case bold_underline: return "\033[1;4m";
case black: return "\033[30m";
case red: return "\033[31m";
case green: return "\033[32m";
case yellow: return "\033[33m";
case blue: return "\033[34m";
case magenta: return "\033[35m";
case cyan: return "\033[36m";
case white: return "\033[37m";
case bold_black: return "\033[90m";
case bold_red: return "\033[91m";
case bold_green: return "\033[92m";
case bold_yellow: return "\033[93m";
case bold_blue: return "\033[94m";
case bold_magenta: return "\033[95m";
case bold_cyan: return "\033[96m";
case bold_white: return "\033[97m";
case underline_black: return "\033[4;30m";
case underline_red: return "\033[4;31m";
case underline_green: return "\033[4;32m";
case underline_yellow: return "\033[4;33m";
case underline_blue: return "\033[4;34m";
case underline_magenta: return "\033[4;35m";
case underline_cyan: return "\033[4;36m";
case underline_white: return "\033[4;37m";
case bold_underline_black: return "\033[1;4;30m";
case bold_underline_red: return "\033[1;4;31m";
case bold_underline_green: return "\033[1;4;32m";
case bold_underline_yellow: return "\033[1;4;33m";
case bold_underline_blue: return "\033[1;4;34m";
case bold_underline_magenta: return "\033[1;4;35m";
case bold_underline_cyan: return "\033[1;4;36m";
case bold_underline_white: return "\033[1;4;37m";
case on_black: return "\033[40m";
case on_red: return "\033[41m";
case on_green: return "\033[42m";
case on_yellow: return "\033[43m";
case on_blue: return "\033[44m";
case on_magenta: return "\033[45m";
case on_cyan: return "\033[46m";
case on_white: return "\033[47m";
case on_bright_black: return "\033[100m";
case on_bright_red: return "\033[101m";
case on_bright_green: return "\033[102m";
case on_bright_yellow: return "\033[103m";
case on_bright_blue: return "\033[104m";
case on_bright_magenta: return "\033[105m";
case on_bright_cyan: return "\033[106m";
case on_bright_white: return "\033[107m";
default: throw "Unknown Text::color value";
}
for (unsigned int i = 0; i < NUM_COLORS; ++i)
if (allColors[i].id == c)
return allColors[i].escape_sequence;
throw context.stringtable.get (COLOR_UNKNOWN, "Unknown color value");
return "";
}
@@ -261,5 +164,33 @@ std::string colorize ()
return decode (off);
}
////////////////////////////////////////////////////////////////////////////////
std::string guessColor (const std::string& name)
{
std::vector <std::string> all;
for (unsigned int i = 0; i < NUM_COLORS; ++i)
all.push_back (context.stringtable.get (
allColors[i].string_id,
allColors[i].english_name));
std::vector <std::string> matches;
autoComplete (name, all, matches);
if (matches.size () == 0)
throw std::string ("Unrecognized color '") + name + "'";
else if (matches.size () != 1)
{
std::string error = "Ambiguous color '" + name + "' - could be either of "; // TODO i18n
std::string combined;
join (combined, ", ", matches);
throw error + combined;
}
return matches[0];
}
////////////////////////////////////////////////////////////////////////////////
}

View File

@@ -50,6 +50,7 @@ namespace Text
std::string colorize (color, color, const std::string& string);
std::string colorize (color, color);
std::string colorize ();
std::string guessColor (const std::string&);
}
#endif

File diff suppressed because it is too large Load Diff

618
src/custom.cpp Normal file
View File

@@ -0,0 +1,618 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
#include <fstream>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#include <time.h>
#include "Context.h"
#include "Date.h"
#include "Table.h"
#include "text.h"
#include "util.h"
#include "main.h"
#ifdef HAVE_LIBNCURSES
#include <ncurses.h>
#endif
extern Context context;
static std::vector <std::string> customReports;
////////////////////////////////////////////////////////////////////////////////
// This report will eventually become the one report that many others morph into
// via the .taskrc file.
std::string handleCustomReport (const std::string& report)
{
// Load report configuration.
std::string columnList = context.config.get ("report." + report + ".columns");
std::string labelList = context.config.get ("report." + report + ".labels");
std::string sortList = context.config.get ("report." + report + ".sort");
std::string filterList = context.config.get ("report." + report + ".filter");
std::vector <std::string> filterArgs;
split (filterArgs, filterList, ' ');
{
Cmd cmd (report);
Task task;
Sequence sequence;
Subst subst;
Filter filter;
context.parse (filterArgs, cmd, task, sequence, subst, filter);
context.sequence.combine (sequence);
// Allow limit to be overridden by the command line.
if (!context.task.has ("limit") && task.has ("limit"))
context.task.set ("limit", task.get ("limit"));
foreach (att, filter)
context.filter.push_back (*att);
}
// 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 ();
return runCustomReport (
report,
columnList,
labelList,
sortList,
filterList,
tasks);
}
////////////////////////////////////////////////////////////////////////////////
// This report will eventually become the one report that many others morph into
// via the .taskrc file.
std::string runCustomReport (
const std::string& report,
const std::string& columnList,
const std::string& labelList,
const std::string& sortList,
const std::string& filterList,
std::vector <Task>& tasks)
{
// Load report configuration.
std::vector <std::string> columns;
split (columns, columnList, ',');
validReportColumns (columns);
std::vector <std::string> labels;
split (labels, labelList, ',');
if (columns.size () != labels.size () && labels.size () != 0)
throw std::string ("There are a different number of columns than labels ") +
"for report '" + report + "'.";
std::map <std::string, std::string> columnLabels;
if (labels.size ())
for (unsigned int i = 0; i < columns.size (); ++i)
columnLabels[columns[i]] = labels[i];
std::vector <std::string> sortOrder;
split (sortOrder, sortList, ',');
validSortColumns (columns, sortOrder);
std::vector <std::string> filterArgs;
split (filterArgs, filterList, ' ');
{
Cmd cmd (report);
Task task;
Sequence sequence;
Subst subst;
Filter filter;
context.parse (filterArgs, cmd, task, sequence, subst, filter);
context.sequence.combine (sequence);
// Allow limit to be overridden by the command line.
if (!context.task.has ("limit") && task.has ("limit"))
context.task.set ("limit", task.get ("limit"));
foreach (att, filter)
context.filter.push_back (*att);
}
// Filter sequence.
if (context.sequence.size ())
context.filter.applySequence (tasks, context.sequence);
// Initialize colorization for subsequent auto colorization.
initializeColorRules ();
Table table;
table.setTableWidth (context.getWidth ());
table.setDateFormat (context.config.get ("dateformat", "m/d/Y"));
foreach (task, tasks)
table.addRow ();
int columnCount = 0;
int dueColumn = -1;
foreach (col, columns)
{
// Add each column individually.
if (*col == "id")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "ID");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
int row = 0;
foreach (task, tasks)
if (task->id != 0)
table.addCell (row++, columnCount, task->id);
else
table.addCell (row++, columnCount, "-");
}
else if (*col == "uuid")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "UUID");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
foreach (task, tasks)
table.addCell (row++, columnCount, task->get ("uuid"));
}
else if (*col == "project")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Project");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
foreach (task, tasks)
table.addCell (row++, columnCount, task->get ("project"));
}
else if (*col == "priority")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Pri");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
foreach (task, tasks)
table.addCell (row++, columnCount, task->get ("priority"));
}
else if (*col == "entry")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Added");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
std::string entered;
for (unsigned int row = 0; row < tasks.size(); ++row)
{
entered = tasks[row].get ("entry");
if (entered.length ())
{
Date dt (::atoi (entered.c_str ()));
entered = dt.toString (context.config.get ("dateformat", "m/d/Y"));
table.addCell (row, columnCount, entered);
}
}
}
else if (*col == "start")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Started");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
std::string started;
for (unsigned int row = 0; row < tasks.size(); ++row)
{
started = tasks[row].get ("start");
if (started.length ())
{
Date dt (::atoi (started.c_str ()));
started = dt.toString (context.config.get ("dateformat", "m/d/Y"));
table.addCell (row, columnCount, started);
}
}
}
else if (*col == "end")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Completed");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
std::string started;
for (unsigned int row = 0; row < tasks.size(); ++row)
{
started = tasks[row].get ("end");
if (started.length ())
{
Date dt (::atoi (started.c_str ()));
started = dt.toString (context.config.get ("dateformat", "m/d/Y"));
table.addCell (row, columnCount, started);
}
}
}
else if (*col == "due")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Due");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
int row = 0;
std::string due;
foreach (task, tasks)
table.addCell (row++, columnCount, getDueDate (*task));
dueColumn = columnCount;
}
else if (*col == "age")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
std::string created;
std::string age;
Date now;
for (unsigned int row = 0; row < tasks.size(); ++row)
{
created = tasks[row].get ("entry");
if (created.length ())
{
Date dt (::atoi (created.c_str ()));
age = formatSeconds ((time_t) (now - dt));
table.addCell (row, columnCount, age);
}
}
}
else if (*col == "age_compact")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Age");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
std::string created;
std::string age;
Date now;
for (unsigned int row = 0; row < tasks.size(); ++row)
{
created = tasks[row].get ("entry");
if (created.length ())
{
Date dt (::atoi (created.c_str ()));
age = formatSecondsCompact ((time_t) (now - dt));
table.addCell (row, columnCount, age);
}
}
}
else if (*col == "active")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Active");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::left);
for (unsigned int row = 0; row < tasks.size(); ++row)
if (tasks[row].has ("start"))
table.addCell (row, columnCount, "*");
}
else if (*col == "tags")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Tags");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
std::vector <std::string> all;
std::string tags;
foreach (task, tasks)
{
task->getTags (all);
join (tags, " ", all);
table.addCell (row++, columnCount, tags);
}
}
else if (*col == "description_only")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description");
table.setColumnWidth (columnCount, Table::flexible);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
foreach (task, tasks)
table.addCell (row++, columnCount, task->get ("description"));
}
else if (*col == "description")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Description");
table.setColumnWidth (columnCount, Table::flexible);
table.setColumnJustification (columnCount, Table::left);
int row = 0;
foreach (task, tasks)
table.addCell (row++, columnCount, getFullDescription (*task));
}
else if (*col == "recur")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Recur");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
for (unsigned int row = 0; row < tasks.size(); ++row)
{
std::string recur = tasks[row].get ("recur");
if (recur != "")
table.addCell (row, columnCount, recur);
}
}
else if (*col == "recurrence_indicator")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "R");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
for (unsigned int row = 0; row < tasks.size(); ++row)
if (tasks[row].has ("recur"))
table.addCell (row, columnCount, "R");
}
else if (*col == "tag_indicator")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "T");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
for (unsigned int row = 0; row < tasks.size(); ++row)
if (tasks[row].getTagCount ())
table.addCell (row, columnCount, "+");
}
else if (*col == "wait")
{
table.addColumn (columnLabels[*col] != "" ? columnLabels[*col] : "Wait");
table.setColumnWidth (columnCount, Table::minimum);
table.setColumnJustification (columnCount, Table::right);
int row = 0;
std::string wait;
foreach (task, tasks)
{
wait = task->get ("wait");
if (wait != "")
{
Date dt (::atoi (wait.c_str ()));
wait = dt.toString (context.config.get ("dateformat", "m/d/Y"));
table.addCell (row++, columnCount, wait);
}
}
}
// Common to all columns.
// Add underline.
if ((context.config.get (std::string ("color"), true) || context.config.get (std::string ("_forcecolor"), false)) &&
context.config.get (std::string ("fontunderline"), "true"))
table.setColumnUnderline (columnCount);
else
table.setTableDashedUnderline ();
++columnCount;
}
// Dynamically add sort criteria.
// Build a map of column names -> index.
std::map <std::string, unsigned int> columnIndex;
for (unsigned int c = 0; c < columns.size (); ++c)
columnIndex[columns[c]] = c;
foreach (sortColumn, sortOrder)
{
// Separate column and direction.
std::string column = sortColumn->substr (0, sortColumn->length () - 1);
char direction = (*sortColumn)[sortColumn->length () - 1];
if (column == "id")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingNumeric :
Table::descendingNumeric));
else if (column == "priority")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingPriority :
Table::descendingPriority));
else if (column == "entry" || column == "start" || column == "due" ||
column == "wait")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingDate :
Table::descendingDate));
else if (column == "recur")
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingPeriod :
Table::descendingPeriod));
else
table.sortOn (columnIndex[column],
(direction == '+' ?
Table::ascendingCharacter :
Table::descendingCharacter));
}
// Now auto colorize all rows.
std::string due;
bool imminent;
bool overdue;
for (unsigned int row = 0; row < tasks.size (); ++row)
{
imminent = false;
overdue = false;
due = tasks[row].get ("due");
if (due.length ())
{
switch (getDueState (due))
{
case 2: overdue = true; break;
case 1: imminent = true; break;
case 0:
default: break;
}
}
if (context.config.get ("color", true) || context.config.get (std::string ("_forcecolor"), false))
{
Text::color fg = Text::colorCode (tasks[row].get ("fg"));
Text::color bg = Text::colorCode (tasks[row].get ("bg"));
autoColorize (tasks[row], fg, bg);
table.setRowFg (row, fg);
table.setRowBg (row, bg);
if (fg == Text::nocolor)
{
if (dueColumn != -1)
{
if (overdue)
table.setCellFg (row, columnCount, Text::colorCode (context.config.get ("color.overdue", "red")));
else if (imminent)
table.setCellFg (row, columnCount, Text::colorCode (context.config.get ("color.due", "yellow")));
}
}
}
}
// Limit the number of rows according to the report definition.
int maximum = context.config.get (std::string ("report.") + report + ".limit", (int)0);
// If the custom report has a defined limit, then allow a numeric override.
// This is an integer specified as a filter (limit:10).
if (context.task.has ("limit"))
maximum = ::atoi (context.task.get ("limit").c_str ());
std::stringstream out;
if (table.rowCount ())
out << optionalBlankLine ()
<< table.render (maximum)
<< optionalBlankLine ()
<< table.rowCount ()
<< (table.rowCount () == 1 ? " task" : " tasks")
<< std::endl;
else
out << "No matches."
<< std::endl;
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
void validReportColumns (const std::vector <std::string>& columns)
{
std::vector <std::string> bad;
std::vector <std::string>::const_iterator it;
for (it = columns.begin (); it != columns.end (); ++it)
if (*it != "id" &&
*it != "uuid" &&
*it != "project" &&
*it != "priority" &&
*it != "entry" &&
*it != "start" &&
*it != "end" &&
*it != "due" &&
*it != "age" &&
*it != "age_compact" &&
*it != "active" &&
*it != "tags" &&
*it != "recur" &&
*it != "recurrence_indicator" &&
*it != "tag_indicator" &&
*it != "description_only" &&
*it != "description" &&
*it != "wait")
bad.push_back (*it);
if (bad.size ())
{
std::string error;
join (error, ", ", bad);
throw std::string ("Unrecognized column name: ") + error;
}
}
////////////////////////////////////////////////////////////////////////////////
void validSortColumns (
const std::vector <std::string>& columns,
const std::vector <std::string>& sortColumns)
{
std::vector <std::string> bad;
std::vector <std::string>::const_iterator sc;
for (sc = sortColumns.begin (); sc != sortColumns.end (); ++sc)
{
std::vector <std::string>::const_iterator co;
for (co = columns.begin (); co != columns.end (); ++co)
if (sc->substr (0, sc->length () - 1) == *co)
break;
if (co == columns.end ())
bad.push_back (*sc);
}
if (bad.size ())
{
std::string error;
join (error, ", ", bad);
throw std::string ("Sort column is not part of the report: ") + error;
}
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -33,7 +33,13 @@
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include "task.h"
#include "Date.h"
#include "Duration.h"
#include "text.h"
#include "util.h"
#include "main.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
static std::string findValue (
@@ -59,7 +65,6 @@ static std::string findValue (
////////////////////////////////////////////////////////////////////////////////
static std::string findDate (
Config& conf,
const std::string& text,
const std::string& name)
{
@@ -75,7 +80,7 @@ static std::string findDate (
if (value != "")
{
Date dt (value, conf.get ("dateformat", "m/d/Y"));
Date dt (value, context.config.get ("dateformat", "m/d/Y"));
char epoch [16];
sprintf (epoch, "%d", (int)dt.toEpoch ());
return std::string (epoch);
@@ -86,38 +91,23 @@ static std::string findDate (
return "";
}
////////////////////////////////////////////////////////////////////////////////
static std::string formatStatus (Tt& task)
{
switch (task.getStatus ())
{
case Tt::pending: return "Pending"; break;
case Tt::completed: return "Completed"; break;
case Tt::deleted: return "Deleted"; break;
case Tt::recurring: return "Recurring"; break;
}
return "";
}
////////////////////////////////////////////////////////////////////////////////
static std::string formatDate (
Config& conf,
Tt& task,
Task& task,
const std::string& attribute)
{
std::string value = task.getAttribute (attribute);
std::string value = task.get (attribute);
if (value.length ())
{
Date dt (::atoi (value.c_str ()));
value = dt.toString (conf.get ("dateformat", "m/d/Y"));
value = dt.toString (context.config.get ("dateformat", "m/d/Y"));
}
return value;
}
////////////////////////////////////////////////////////////////////////////////
static std::string formatTask (Config& conf, Tt task)
static std::string formatTask (Task task)
{
std::stringstream before;
before << "# The 'task edit <id>' command allows you to modify all aspects of a task" << std::endl
@@ -137,13 +127,13 @@ static std::string formatTask (Config& conf, Tt task)
<< "#" << std::endl
<< "# Name Editable details" << std::endl
<< "# ----------------- ----------------------------------------------------" << std::endl
<< "# ID: " << task.getId () << std::endl
<< "# UUID: " << task.getUUID () << std::endl
<< "# Status: " << formatStatus (task) << std::endl
<< "# Mask: " << task.getAttribute ("mask") << std::endl
<< "# iMask: " << task.getAttribute ("imask") << std::endl
<< " Project: " << task.getAttribute ("project") << std::endl
<< " Priority: " << task.getAttribute ("priority") << std::endl;
<< "# ID: " << task.id << std::endl
<< "# UUID: " << task.get ("uuid") << std::endl
<< "# Status: " << ucFirst (Task::statusToText (task.getStatus ())) << std::endl
<< "# Mask: " << task.get ("mask") << std::endl
<< "# iMask: " << task.get ("imask") << std::endl
<< " Project: " << task.get ("project") << std::endl
<< " Priority: " << task.get ("priority") << std::endl;
std::vector <std::string> tags;
task.getTags (tags);
@@ -153,26 +143,27 @@ static std::string formatTask (Config& conf, Tt task)
<< " Tags: " << allTags << std::endl
<< "# The description field is allowed to wrap and use multiple lines. Task" << std::endl
<< "# will combine them." << std::endl
<< " Description: " << task.getDescription () << std::endl
<< " Created: " << formatDate (conf, task, "entry") << std::endl
<< " Started: " << formatDate (conf, task, "start") << std::endl
<< " Ended: " << formatDate (conf, task, "end") << std::endl
<< " Due: " << formatDate (conf, task, "due") << std::endl
<< " Until: " << formatDate (conf, task, "until") << std::endl
<< " Recur: " << task.getAttribute ("recur") << std::endl
<< " Parent: " << task.getAttribute ("parent") << std::endl
<< " Foreground color: " << task.getAttribute ("fg") << std::endl
<< " Background color: " << task.getAttribute ("bg") << std::endl
<< " Description: " << task.get ("description") << std::endl
<< " Created: " << formatDate (task, "entry") << std::endl
<< " Started: " << formatDate (task, "start") << std::endl
<< " Ended: " << formatDate (task, "end") << std::endl
<< " Due: " << formatDate (task, "due") << std::endl
<< " Until: " << formatDate (task, "until") << std::endl
<< " Recur: " << task.get ("recur") << std::endl
<< " Wait until: " << task.get ("wait") << std::endl
<< " Parent: " << task.get ("parent") << std::endl
<< " Foreground color: " << task.get ("fg") << std::endl
<< " Background color: " << task.get ("bg") << std::endl
<< "# Annotations look like this: <date> <text>, and there can be any number" << std::endl
<< "# of them." << std::endl;
std::map <time_t, std::string> annotations;
std::vector <Att> annotations;
task.getAnnotations (annotations);
foreach (anno, annotations)
{
Date dt (anno->first);
before << " Annotation: " << dt.toString (conf.get ("dateformat", "m/d/Y"))
<< " " << anno->second << std::endl;
Date dt (::atoi (anno->name ().substr (11, std::string::npos).c_str ()));
before << " Annotation: " << dt.toString (context.config.get ("dateformat", "m/d/Y"))
<< " " << anno->value () << std::endl;
}
before << " Annotation: " << std::endl
@@ -182,40 +173,40 @@ static std::string formatTask (Config& conf, Tt task)
}
////////////////////////////////////////////////////////////////////////////////
static void parseTask (Config& conf, Tt& task, const std::string& after)
static void parseTask (Task& task, const std::string& after)
{
// project
std::string value = findValue (after, "Project:");
if (task.getAttribute ("project") != value)
if (task.get ("project") != value)
{
if (value != "")
{
std::cout << "Project modified." << std::endl;
task.setAttribute ("project", value);
task.set ("project", value);
}
else
{
std::cout << "Project deleted." << std::endl;
task.removeAttribute ("project");
task.remove ("project");
}
}
// priority
value = findValue (after, "Priority:");
if (task.getAttribute ("priority") != value)
if (task.get ("priority") != value)
{
if (value != "")
{
if (validPriority (value))
if (Att::validNameValue ("priority", "", value))
{
std::cout << "Priority modified." << std::endl;
task.setAttribute ("priority", value);
task.set ("priority", value);
}
}
else
{
std::cout << "Priority deleted." << std::endl;
task.removeAttribute ("priority");
task.remove ("priority");
}
}
@@ -223,177 +214,178 @@ static void parseTask (Config& conf, Tt& task, const std::string& after)
value = findValue (after, "Tags:");
std::vector <std::string> tags;
split (tags, value, ' ');
task.removeTags ();
task.remove ("tags");
task.addTags (tags);
// description.
value = findValue (after, "Description: ");
if (task.getDescription () != value)
if (task.get ("description") != value)
{
if (value != "")
{
std::cout << "Description modified." << std::endl;
task.setDescription (value);
task.set ("description", value);
}
else
throw std::string ("Cannot remove description.");
}
// entry
value = findDate (conf, after, "Created:");
value = findDate (after, "Created:");
if (value != "")
{
Date edited (::atoi (value.c_str ()));
Date original (::atoi (task.getAttribute ("entry").c_str ()));
Date original (::atoi (task.get ("entry").c_str ()));
if (!original.sameDay (edited))
{
std::cout << "Creation date modified." << std::endl;
task.setAttribute ("entry", value);
task.set ("entry", value);
}
}
else
throw std::string ("Cannot remove creation date");
// start
value = findDate (conf, after, "Started:");
value = findDate (after, "Started:");
if (value != "")
{
Date edited (::atoi (value.c_str ()));
if (task.getAttribute ("start") != "")
if (task.get ("start") != "")
{
Date original (::atoi (task.getAttribute ("start").c_str ()));
Date original (::atoi (task.get ("start").c_str ()));
if (!original.sameDay (edited))
{
std::cout << "Start date modified." << std::endl;
task.setAttribute ("start", value);
task.set ("start", value);
}
}
else
{
std::cout << "Start date modified." << std::endl;
task.setAttribute ("start", value);
task.set ("start", value);
}
}
else
{
if (task.getAttribute ("start") != "")
if (task.get ("start") != "")
{
std::cout << "Start date removed." << std::endl;
task.removeAttribute ("start");
task.remove ("start");
}
}
// end
value = findDate (conf, after, "Ended:");
value = findDate (after, "Ended:");
if (value != "")
{
Date edited (::atoi (value.c_str ()));
if (task.getAttribute ("end") != "")
if (task.get ("end") != "")
{
Date original (::atoi (task.getAttribute ("end").c_str ()));
Date original (::atoi (task.get ("end").c_str ()));
if (!original.sameDay (edited))
{
std::cout << "Done date modified." << std::endl;
task.setAttribute ("end", value);
task.set ("end", value);
}
}
else if (task.getStatus () != Tt::deleted)
else if (task.getStatus () != Task::deleted)
throw std::string ("Cannot set a done date on a pending task.");
}
else
{
if (task.getAttribute ("end") != "")
if (task.get ("end") != "")
{
std::cout << "Done date removed." << std::endl;
task.setStatus (Tt::pending);
task.removeAttribute ("end");
task.setStatus (Task::pending);
task.remove ("end");
}
}
// due
value = findDate (conf, after, "Due:");
value = findDate (after, "Due:");
if (value != "")
{
Date edited (::atoi (value.c_str ()));
if (task.getAttribute ("due") != "")
if (task.get ("due") != "")
{
Date original (::atoi (task.getAttribute ("due").c_str ()));
Date original (::atoi (task.get ("due").c_str ()));
if (!original.sameDay (edited))
{
std::cout << "Due date modified." << std::endl;
task.setAttribute ("due", value);
task.set ("due", value);
}
}
else
{
std::cout << "Due date modified." << std::endl;
task.setAttribute ("due", value);
task.set ("due", value);
}
}
else
{
if (task.getAttribute ("due") != "")
if (task.get ("due") != "")
{
if (task.getStatus () == Tt::recurring ||
task.getAttribute ("parent") != "")
if (task.getStatus () == Task::recurring ||
task.get ("parent") != "")
{
std::cout << "Cannot remove a due date from a recurring task." << std::endl;
}
else
{
std::cout << "Due date removed." << std::endl;
task.removeAttribute ("due");
task.remove ("due");
}
}
}
// until
value = findDate (conf, after, "Until:");
value = findDate (after, "Until:");
if (value != "")
{
Date edited (::atoi (value.c_str ()));
if (task.getAttribute ("until") != "")
if (task.get ("until") != "")
{
Date original (::atoi (task.getAttribute ("until").c_str ()));
Date original (::atoi (task.get ("until").c_str ()));
if (!original.sameDay (edited))
{
std::cout << "Until date modified." << std::endl;
task.setAttribute ("until", value);
task.set ("until", value);
}
}
else
{
std::cout << "Until date modified." << std::endl;
task.setAttribute ("until", value);
task.set ("until", value);
}
}
else
{
if (task.getAttribute ("until") != "")
if (task.get ("until") != "")
{
std::cout << "Until date removed." << std::endl;
task.removeAttribute ("until");
task.remove ("until");
}
}
// recur
value = findValue (after, "Recur:");
if (value != task.getAttribute ("recur"))
if (value != task.get ("recur"))
{
if (value != "")
{
if (validDuration (value))
Duration d;
if (d.valid (value))
{
std::cout << "Recurrence modified." << std::endl;
if (task.getAttribute ("due") != "")
if (task.get ("due") != "")
{
task.setAttribute ("recur", value);
task.setStatus (Tt::recurring);
task.set ("recur", value);
task.setStatus (Task::recurring);
}
else
throw std::string ("A recurring task must have a due date.");
@@ -404,64 +396,94 @@ static void parseTask (Config& conf, Tt& task, const std::string& after)
else
{
std::cout << "Recurrence removed." << std::endl;
task.setStatus (Tt::pending);
task.removeAttribute ("recur");
task.removeAttribute ("until");
task.removeAttribute ("mask");
task.removeAttribute ("imask");
task.setStatus (Task::pending);
task.remove ("recur");
task.remove ("until");
task.remove ("mask");
task.remove ("imask");
}
}
// wait
value = findDate (after, "Wait until:");
if (value != "")
{
Date edited (::atoi (value.c_str ()));
if (task.get ("wait") != "")
{
Date original (::atoi (task.get ("wait").c_str ()));
if (!original.sameDay (edited))
{
std::cout << "Wait date modified." << std::endl;
task.set ("wait", value);
}
}
else
{
std::cout << "Wait date modified." << std::endl;
task.set ("wait", value);
}
}
else
{
if (task.get ("wait") != "")
{
std::cout << "Wait date removed." << std::endl;
task.remove ("wait");
}
}
// parent
value = findValue (after, "Parent:");
if (value != task.getAttribute ("parent"))
if (value != task.get ("parent"))
{
if (value != "")
{
std::cout << "Parent UUID modified." << std::endl;
task.setAttribute ("parent", value);
task.set ("parent", value);
}
else
{
std::cout << "Parent UUID removed." << std::endl;
task.removeAttribute ("parent");
task.remove ("parent");
}
}
// fg
value = findValue (after, "Foreground color:");
if (value != task.getAttribute ("fg"))
if (value != task.get ("fg"))
{
if (value != "")
{
std::cout << "Foreground color modified." << std::endl;
task.setAttribute ("fg", value);
task.set ("fg", value);
}
else
{
std::cout << "Foreground color removed." << std::endl;
task.removeAttribute ("fg");
task.remove ("fg");
}
}
// bg
value = findValue (after, "Background color:");
if (value != task.getAttribute ("bg"))
if (value != task.get ("bg"))
{
if (value != "")
{
std::cout << "Background color modified." << std::endl;
task.setAttribute ("bg", value);
task.set ("bg", value);
}
else
{
std::cout << "Background color removed." << std::endl;
task.removeAttribute ("bg");
task.remove ("bg");
}
}
// Annotations
std::map <time_t, std::string> annotations;
std::vector <Att> annotations;
std::string::size_type found = 0;
while ((found = after.find ("Annotation:", found)) != std::string::npos)
{
@@ -478,8 +500,10 @@ static void parseTask (Config& conf, Tt& task, const std::string& after)
if (gap != std::string::npos)
{
Date when (value.substr (0, gap));
std::stringstream name;
name << "annotation_" << when.toEpoch ();
std::string text = trim (value.substr (gap, std::string::npos), "\t ");
annotations[when.toEpoch ()] = text;
annotations.push_back (Att (name.str (), text));
}
}
}
@@ -487,101 +511,132 @@ static void parseTask (Config& conf, Tt& task, const std::string& after)
task.setAnnotations (annotations);
}
////////////////////////////////////////////////////////////////////////////////
void editFile (Task& task)
{
// Check for file permissions.
std::string dataLocation = expandPath (context.config.get ("data.location"));
if (access (dataLocation.c_str (), X_OK))
throw std::string ("Your data.location directory is not writable.");
// Create a temp file name in data.location.
std::stringstream file;
file << dataLocation << "/task." << getpid () << "." << task.id << ".task";
// Format the contents, T -> text, write to a file.
std::string before = formatTask (task);
spit (file.str (), before);
// Determine correct editor: .taskrc:editor > $VISUAL > $EDITOR > vi
std::string editor = context.config.get ("editor", "");
char* peditor = getenv ("VISUAL");
if (editor == "" && peditor) editor = std::string (peditor);
peditor = getenv ("EDITOR");
if (editor == "" && peditor) editor = std::string (peditor);
if (editor == "") editor = "vi";
// Complete the command line.
editor += " ";
editor += file.str ();
ARE_THESE_REALLY_HARMFUL:
// Launch the editor.
std::cout << "Launching '" << editor << "' now..." << std::endl;
if (-1 == system (editor.c_str ()))
std::cout << "No editing performed." << std::endl;
else
std::cout << "Editing complete." << std::endl;
// Slurp file.
std::string after;
slurp (file.str (), after, false);
// Update task based on what can be parsed back out of the file, but only
// if changes were made.
if (before != after)
{
std::cout << "Edits were detected." << std::endl;
std::string problem = "";
bool oops = false;
try
{
parseTask (task, after);
}
catch (std::string& e)
{
problem = e;
oops = true;
}
if (oops)
{
std::cout << "Error: " << problem << std::endl;
// Preserve the edits.
before = after;
spit (file.str (), before);
if (confirm ("Task couldn't handle your edits. Would you like to try again?"))
goto ARE_THESE_REALLY_HARMFUL;
}
}
else
std::cout << "No edits were detected." << std::endl;
// Cleanup.
unlink (file.str ().c_str ());
}
////////////////////////////////////////////////////////////////////////////////
// Introducing the Silver Bullet. This feature is the catch-all fixative for
// various other ills. This is like opening up the hood and going in with a
// wrench. To be used sparingly.
std::string handleEdit (TDB& tdb, Tt& task, Config& conf)
std::string handleEdit ()
{
std::stringstream out;
std::vector <Tt> all;
tdb.allPendingT (all);
filterSequence (all, task);
foreach (seq, all)
std::vector <Task> tasks;
context.tdb.lock (context.config.get ("locking", true));
handleRecurrence ();
Filter filter;
context.tdb.loadPending (tasks, filter);
// Filter sequence.
std::vector <Task> all = tasks;
context.filter.applySequence (tasks, context.sequence);
foreach (task, tasks)
{
// Check for file permissions.
std::string dataLocation = expandPath (conf.get ("data.location"));
if (access (dataLocation.c_str (), X_OK))
throw std::string ("Your data.location directory is not writable.");
// Create a temp file name in data.location.
std::stringstream pattern;
pattern << dataLocation << "/task." << seq->getId () << ".XXXXXX";
char cpattern [PATH_MAX];
strcpy (cpattern, pattern.str ().c_str ());
mkstemp (cpattern);
char* file = cpattern;
// Format the contents, Tt -> text, write to a file.
std::string before = formatTask (conf, *seq);
spit (file, before);
// Determine correct editor: .taskrc:editor > $VISUAL > $EDITOR > vi
std::string editor = conf.get ("editor", "");
char* peditor = getenv ("VISUAL");
if (editor == "" && peditor) editor = std::string (peditor);
peditor = getenv ("EDITOR");
if (editor == "" && peditor) editor = std::string (peditor);
if (editor == "") editor = "vi";
// Complete the command line.
editor += " ";
editor += file;
ARE_THESE_REALLY_HARMFUL:
// Launch the editor.
std::cout << "Launching '" << editor << "' now..." << std::endl;
system (editor.c_str ());
std::cout << "Editing complete." << std::endl;
// Slurp file.
std::string after;
slurp (file, after, false);
// TODO Remove this debugging code.
//spit ("./before", before);
//spit ("./after", after);
// Update seq based on what can be parsed back out of the file, but only
// if changes were made.
if (before != after)
editFile (*task);
context.tdb.update (*task);
/*
foreach (other, all)
{
std::cout << "Edits were detected." << std::endl;
std::string problem = "";
bool oops = false;
try
if (other->id != task.id) // Don't edit the same task again.
{
parseTask (conf, *seq, after);
tdb.modifyT (*seq);
}
catch (std::string& e)
{
problem = e;
oops = true;
}
if (oops)
{
std::cout << "Error: " << problem << std::endl;
// Preserve the edits.
before = after;
spit (file, before);
if (confirm ("Task couldn't handle your edits. Would you like to try again?"))
goto ARE_THESE_REALLY_HARMFUL;
if (task.has ("parent") &&
if (other is parent of task)
{
// Transfer everything but mask, imask, uuid, parent.
}
else if (task is parent of other)
{
// Transfer everything but mask, imask, uuid, parent.
}
else if (task and other are siblings)
{
// Transfer everything but mask, imask, uuid, parent.
}
}
}
else
std::cout << "No edits were detected." << std::endl;
// Cleanup.
unlink (file);
*/
}
context.tdb.commit ();
context.tdb.unlock ();
return out.str ();
}

197
src/i18n.h Normal file
View File

@@ -0,0 +1,197 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Strings that should be localized:
// - All text output that the user sees or types
//
// Strings that should NOT be localized:
// - ./taskrc configuration variable names
// - certain literals associated with parsing
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_I18N
#define INCLUDED_I18N
// 1xx task shell
#define SHELL_UNKNOWN_ERROR 100
#define SHELL_READ_PASSWD 101
#define CONFIRM_YES_NO 102
#define SEQUENCE_BAD_SEQ 103
#define SEQUENCE_BAD_RANGE 104
#define SEQUENCE_INVERTED 105
#define SEQUENCE_RANGE_MAX 106
#define SEQUENCE_NOT_A_SEQUENCE 107
#define INTERACTIVE_NO_NCURSES 108
#define RECORD_EMPTY 109
#define RECORD_EXTRA 110
#define RECORD_NOT_FF4 111
#define SUBST_EMPTY 112
#define SUBST_BAD_CHARS 113
#define SUBST_MALFORMED 114
#define TAGS_NO_COMMA 115
#define CMD_MISSING 116
// 2xx Commands
#define CMD_ACTIVE 200
#define CMD_ADD 201
#define CMD_APPEND 202
#define CMD_ANNOTATE 203
#define CMD_CALENDAR 204
#define CMD_COLORS 205
#define CMD_COMPLETED 206
#define CMD_DELETE 207
#define CMD_DONE 208
#define CMD_DUPLICATE 209
#define CMD_EDIT 210
#define CMD_EXPORT 211
#define CMD_HELP 212
#define CMD_HISTORY 213
#define CMD_GHISTORY 214
#define CMD_IMPORT 215
#define CMD_INFO 216
#define CMD_OVERDUE 218
#define CMD_PROJECTS 219
#define CMD_START 220
#define CMD_STATS 221
#define CMD_STOP 222
#define CMD_SUMMARY 223
#define CMD_TAGS 224
#define CMD_TIMESHEET 225
#define CMD_UNDO 227
#define CMD_VERSION 228
#define CMD_SHELL 229
// 3xx Attributes
#define ATT_PROJECT 300
#define ATT_PRIORITY 301
#define ATT_FG 302
#define ATT_BG 303
#define ATT_DUE 304
#define ATT_ENTRY 305
#define ATT_START 306
#define ATT_END 307
#define ATT_RECUR 308
#define ATT_UNTIL 309
#define ATT_MASK 310
#define ATT_IMASK 311
#define ATT_MOD_BEFORE 350
#define ATT_MOD_AFTER 351
#define ATT_MOD_NOT 352
#define ATT_MOD_NONE 353
#define ATT_MOD_ANY 354
#define ATT_MOD_SYNTH 355
#define ATT_MOD_UNDER 356
#define ATT_MOD_OVER 357
#define ATT_MOD_FIRST 358
#define ATT_MOD_LAST 359
#define ATT_MOD_THIS 360
#define ATT_MOD_NEXT 361
#define ATT_MOD_IS 362
#define ATT_MOD_ISNT 363
#define ATT_MOD_HAS 364
#define ATT_MOD_HASNT 365
#define ATT_MOD_STARTSWITH 366
#define ATT_MOD_ENDSWITH 367
// 4xx Columns
// 5xx Colors
#define COLOR_BOLD 500
#define COLOR_UL 501
#define COLOR_B_UL 502
#define COLOR_BLACK 503
#define COLOR_RED 504
#define COLOR_GREEN 505
#define COLOR_YELLOW 506
#define COLOR_BLUE 507
#define COLOR_MAGENTA 508
#define COLOR_CYAN 509
#define COLOR_WHITE 510
#define COLOR_B_BLACK 511
#define COLOR_B_RED 512
#define COLOR_B_GREEN 513
#define COLOR_B_YELLOW 514
#define COLOR_B_BLUE 515
#define COLOR_B_MAGENTA 516
#define COLOR_B_CYAN 517
#define COLOR_B_WHITE 518
#define COLOR_UL_BLACK 519
#define COLOR_UL_RED 520
#define COLOR_UL_GREEN 521
#define COLOR_UL_YELLOW 522
#define COLOR_UL_BLUE 523
#define COLOR_UL_MAGENTA 524
#define COLOR_UL_CYAN 525
#define COLOR_UL_WHITE 526
#define COLOR_B_UL_BLACK 527
#define COLOR_B_UL_RED 528
#define COLOR_B_UL_GREEN 529
#define COLOR_B_UL_YELLOW 530
#define COLOR_B_UL_BLUE 531
#define COLOR_B_UL_MAGENTA 532
#define COLOR_B_UL_CYAN 533
#define COLOR_B_UL_WHITE 534
#define COLOR_ON_BLACK 535
#define COLOR_ON_RED 536
#define COLOR_ON_GREEN 537
#define COLOR_ON_YELLOW 538
#define COLOR_ON_BLUE 539
#define COLOR_ON_MAGENTA 540
#define COLOR_ON_CYAN 541
#define COLOR_ON_WHITE 542
#define COLOR_ON_BRIGHT_BLACK 543
#define COLOR_ON_BRIGHT_RED 544
#define COLOR_ON_BRIGHT_GREEN 545
#define COLOR_ON_BRIGHT_YELLOW 546
#define COLOR_ON_BRIGHT_BLUE 547
#define COLOR_ON_BRIGHT_MAGENTA 548
#define COLOR_ON_BRIGHT_CYAN 549
#define COLOR_ON_BRIGHT_WHITE 550
#define COLOR_OFF 551
#define COLOR_UNKNOWN 552
// 6xx Config
// 7xx TDB
// 8xx Reports
#endif

View File

@@ -29,7 +29,11 @@
#include <stdio.h>
#include <unistd.h>
#include "Date.h"
#include "task.h"
#include "text.h"
#include "util.h"
#include "main.h"
extern Context context;
////////////////////////////////////////////////////////////////////////////////
enum fileType
@@ -155,33 +159,34 @@ static fileType determineFileType (const std::vector <std::string>& lines)
}
////////////////////////////////////////////////////////////////////////////////
static void decorateTask (Tt& task, Config& conf)
static void decorateTask (Task& task)
{
char entryTime[16];
sprintf (entryTime, "%u", (unsigned int) time (NULL));
task.setAttribute ("entry", entryTime);
task.set ("entry", entryTime);
task.setStatus (Task::pending);
// Override with default.project, if not specified.
std::string defaultProject = conf.get ("default.project", "");
if (task.getAttribute ("project") == "" && defaultProject != "")
task.setAttribute ("project", defaultProject);
std::string defaultProject = context.config.get ("default.project", "");
if (!task.has ("project") && defaultProject != "")
task.set ("project", defaultProject);
// Override with default.priority, if not specified.
std::string defaultPriority = conf.get ("default.priority", "");
if (task.getAttribute ("priority") == "" &&
defaultPriority != "" &&
validPriority (defaultPriority))
task.setAttribute ("priority", defaultPriority);
std::string defaultPriority = context.config.get ("default.priority", "");
if (!task.has ("priority") &&
defaultPriority != "" &&
Att::validNameValue ("priority", "", defaultPriority))
task.set ("priority", defaultPriority);
}
////////////////////////////////////////////////////////////////////////////////
static std::string importTask_1_4_3 (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importTask_1_4_3 (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
@@ -227,7 +232,7 @@ static std::string importTask_1_4_3 (
throw "unrecoverable";
// Build up this task ready for insertion.
Tt task;
Task task;
// Handle the 12 fields.
for (unsigned int f = 0; f < fields.size (); ++f)
@@ -235,14 +240,14 @@ static std::string importTask_1_4_3 (
switch (f)
{
case 0: // 'uuid'
task.setUUID (fields[f].substr (1, 36));
task.set ("uuid", fields[f].substr (1, 36));
break;
case 1: // 'status'
if (fields[f] == "'pending'") task.setStatus (Tt::pending);
else if (fields[f] == "'recurring'") task.setStatus (Tt::recurring);
else if (fields[f] == "'deleted'") task.setStatus (Tt::deleted);
else if (fields[f] == "'completed'") task.setStatus (Tt::completed);
if (fields[f] == "'pending'") task.setStatus (Task::pending);
else if (fields[f] == "'recurring'") task.setStatus (Task::recurring);
else if (fields[f] == "'deleted'") task.setStatus (Task::deleted);
else if (fields[f] == "'completed'") task.setStatus (Task::completed);
break;
case 2: // 'tags'
@@ -257,53 +262,52 @@ static std::string importTask_1_4_3 (
break;
case 3: // entry
task.setAttribute ("entry", fields[f]);
task.set ("entry", fields[f]);
break;
case 4: // start
if (fields[f].length ())
task.setAttribute ("start", fields[f]);
task.set ("start", fields[f]);
break;
case 5: // due
if (fields[f].length ())
task.setAttribute ("due", fields[f]);
task.set ("due", fields[f]);
break;
case 6: // end
if (fields[f].length ())
task.setAttribute ("end", fields[f]);
task.set ("end", fields[f]);
break;
case 7: // 'project'
if (fields[f].length () > 2)
task.setAttribute ("project", fields[f].substr (1, fields[f].length () - 2));
task.set ("project", fields[f].substr (1, fields[f].length () - 2));
break;
case 8: // 'priority'
if (fields[f].length () > 2)
task.setAttribute ("priority", fields[f].substr (1, fields[f].length () - 2));
task.set ("priority", fields[f].substr (1, fields[f].length () - 2));
break;
case 9: // 'fg'
if (fields[f].length () > 2)
task.setAttribute ("fg", fields[f].substr (1, fields[f].length () - 2));
task.set ("fg", fields[f].substr (1, fields[f].length () - 2));
break;
case 10: // 'bg'
if (fields[f].length () > 2)
task.setAttribute ("bg", fields[f].substr (1, fields[f].length () - 2));
task.set ("bg", fields[f].substr (1, fields[f].length () - 2));
break;
case 11: // 'description'
if (fields[f].length () > 2)
task.setDescription (fields[f].substr (1, fields[f].length () - 2));
task.set ("description", fields[f].substr (1, fields[f].length () - 2));
break;
}
}
if (! tdb.addT (task))
failed.push_back (*it);
context.tdb.add (task);
}
catch (...)
@@ -312,6 +316,9 @@ static std::string importTask_1_4_3 (
}
}
context.tdb.commit ();
context.tdb.unlock ();
std::stringstream out;
out << "Imported "
<< (lines.size () - failed.size () - 1)
@@ -331,13 +338,12 @@ static std::string importTask_1_4_3 (
}
////////////////////////////////////////////////////////////////////////////////
static std::string importTask_1_5_0 (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importTask_1_5_0 (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
@@ -383,7 +389,7 @@ static std::string importTask_1_5_0 (
throw "unrecoverable";
// Build up this task ready for insertion.
Tt task;
Task task;
// Handle the 13 fields.
for (unsigned int f = 0; f < fields.size (); ++f)
@@ -391,14 +397,14 @@ static std::string importTask_1_5_0 (
switch (f)
{
case 0: // 'uuid'
task.setUUID (fields[f].substr (1, 36));
task.set ("uuid", fields[f].substr (1, 36));
break;
case 1: // 'status'
if (fields[f] == "'pending'") task.setStatus (Tt::pending);
else if (fields[f] == "'recurring'") task.setStatus (Tt::recurring);
else if (fields[f] == "'deleted'") task.setStatus (Tt::deleted);
else if (fields[f] == "'completed'") task.setStatus (Tt::completed);
if (fields[f] == "'pending'") task.setStatus (Task::pending);
else if (fields[f] == "'recurring'") task.setStatus (Task::recurring);
else if (fields[f] == "'deleted'") task.setStatus (Task::deleted);
else if (fields[f] == "'completed'") task.setStatus (Task::completed);
break;
case 2: // 'tags'
@@ -413,58 +419,57 @@ static std::string importTask_1_5_0 (
break;
case 3: // entry
task.setAttribute ("entry", fields[f]);
task.set ("entry", fields[f]);
break;
case 4: // start
if (fields[f].length ())
task.setAttribute ("start", fields[f]);
task.set ("start", fields[f]);
break;
case 5: // due
if (fields[f].length ())
task.setAttribute ("due", fields[f]);
task.set ("due", fields[f]);
break;
case 6: // recur
if (fields[f].length ())
task.setAttribute ("recur", fields[f]);
task.set ("recur", fields[f]);
break;
case 7: // end
if (fields[f].length ())
task.setAttribute ("end", fields[f]);
task.set ("end", fields[f]);
break;
case 8: // 'project'
if (fields[f].length () > 2)
task.setAttribute ("project", fields[f].substr (1, fields[f].length () - 2));
task.set ("project", fields[f].substr (1, fields[f].length () - 2));
break;
case 9: // 'priority'
if (fields[f].length () > 2)
task.setAttribute ("priority", fields[f].substr (1, fields[f].length () - 2));
task.set ("priority", fields[f].substr (1, fields[f].length () - 2));
break;
case 10: // 'fg'
if (fields[f].length () > 2)
task.setAttribute ("fg", fields[f].substr (1, fields[f].length () - 2));
task.set ("fg", fields[f].substr (1, fields[f].length () - 2));
break;
case 11: // 'bg'
if (fields[f].length () > 2)
task.setAttribute ("bg", fields[f].substr (1, fields[f].length () - 2));
task.set ("bg", fields[f].substr (1, fields[f].length () - 2));
break;
case 12: // 'description'
if (fields[f].length () > 2)
task.setDescription (fields[f].substr (1, fields[f].length () - 2));
task.set ("description", fields[f].substr (1, fields[f].length () - 2));
break;
}
}
if (! tdb.addT (task))
failed.push_back (*it);
context.tdb.add (task);
}
catch (...)
@@ -473,6 +478,9 @@ static std::string importTask_1_5_0 (
}
}
context.tdb.commit ();
context.tdb.unlock ();
std::stringstream out;
out << "Imported "
<< (lines.size () - failed.size () - 1)
@@ -492,13 +500,12 @@ static std::string importTask_1_5_0 (
}
////////////////////////////////////////////////////////////////////////////////
static std::string importTask_1_6_0 (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importTask_1_6_0 (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
@@ -544,7 +551,7 @@ static std::string importTask_1_6_0 (
throw "unrecoverable";
// Build up this task ready for insertion.
Tt task;
Task task;
// Handle the 13 fields.
for (unsigned int f = 0; f < fields.size (); ++f)
@@ -552,14 +559,15 @@ static std::string importTask_1_6_0 (
switch (f)
{
case 0: // 'uuid'
task.setUUID (fields[f].substr (1, 36));
task.set ("uuid", fields[f].substr (1, 36));
break;
case 1: // 'status'
if (fields[f] == "'pending'") task.setStatus (Tt::pending);
else if (fields[f] == "'recurring'") task.setStatus (Tt::recurring);
else if (fields[f] == "'deleted'") task.setStatus (Tt::deleted);
else if (fields[f] == "'completed'") task.setStatus (Tt::completed);
if (fields[f] == "'pending'") task.setStatus (Task::pending);
else if (fields[f] == "'recurring'") task.setStatus (Task::recurring);
else if (fields[f] == "'deleted'") task.setStatus (Task::deleted);
else if (fields[f] == "'completed'") task.setStatus (Task::completed);
else if (fields[f] == "'waiting'") task.setStatus (Task::waiting);
break;
case 2: // 'tags'
@@ -574,58 +582,57 @@ static std::string importTask_1_6_0 (
break;
case 3: // entry
task.setAttribute ("entry", fields[f]);
task.set ("entry", fields[f]);
break;
case 4: // start
if (fields[f].length ())
task.setAttribute ("start", fields[f]);
task.set ("start", fields[f]);
break;
case 5: // due
if (fields[f].length ())
task.setAttribute ("due", fields[f]);
task.set ("due", fields[f]);
break;
case 6: // recur
if (fields[f].length ())
task.setAttribute ("recur", fields[f]);
task.set ("recur", fields[f]);
break;
case 7: // end
if (fields[f].length ())
task.setAttribute ("end", fields[f]);
task.set ("end", fields[f]);
break;
case 8: // 'project'
if (fields[f].length () > 2)
task.setAttribute ("project", fields[f].substr (1, fields[f].length () - 2));
task.set ("project", fields[f].substr (1, fields[f].length () - 2));
break;
case 9: // 'priority'
if (fields[f].length () > 2)
task.setAttribute ("priority", fields[f].substr (1, fields[f].length () - 2));
task.set ("priority", fields[f].substr (1, fields[f].length () - 2));
break;
case 10: // 'fg'
if (fields[f].length () > 2)
task.setAttribute ("fg", fields[f].substr (1, fields[f].length () - 2));
task.set ("fg", fields[f].substr (1, fields[f].length () - 2));
break;
case 11: // 'bg'
if (fields[f].length () > 2)
task.setAttribute ("bg", fields[f].substr (1, fields[f].length () - 2));
task.set ("bg", fields[f].substr (1, fields[f].length () - 2));
break;
case 12: // 'description'
if (fields[f].length () > 2)
task.setDescription (fields[f].substr (1, fields[f].length () - 2));
task.set ("description", fields[f].substr (1, fields[f].length () - 2));
break;
}
}
if (! tdb.addT (task))
failed.push_back (*it);
context.tdb.add (task);
}
catch (...)
@@ -634,6 +641,9 @@ static std::string importTask_1_6_0 (
}
}
context.tdb.commit ();
context.tdb.unlock ();
std::stringstream out;
out << "Imported "
<< (lines.size () - failed.size () - 1)
@@ -653,10 +663,7 @@ static std::string importTask_1_6_0 (
}
////////////////////////////////////////////////////////////////////////////////
static std::string importTaskCmdLine (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importTaskCmdLine (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
@@ -667,17 +674,19 @@ static std::string importTaskCmdLine (
try
{
std::vector <std::string> args;
split (args, std::string ("add ") + line, ' ');
context.args.clear ();
split (context.args, std::string ("add ") + line, ' ');
Tt task;
std::string command;
parse (args, command, task, conf);
handleAdd (tdb, task, conf);
context.task.clear ();
context.cmd.command = "";
context.parse ();
handleAdd ();
context.clearMessages ();
}
catch (...)
{
context.clearMessages ();
failed.push_back (line);
}
}
@@ -701,20 +710,19 @@ static std::string importTaskCmdLine (
}
////////////////////////////////////////////////////////////////////////////////
static std::string importTodoSh_2_0 (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importTodoSh_2_0 (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
try
{
std::vector <std::string> args;
args.push_back ("add");
context.args.clear ();
context.args.push_back ("add");
bool isPending = true;
Date endDate;
@@ -727,8 +735,8 @@ static std::string importTodoSh_2_0 (
if (words[w].length () > 1 &&
words[w][0] == '+')
{
args.push_back (std::string ("project:") +
words[w].substr (1, std::string::npos));
context.args.push_back (std::string ("project:") +
words[w].substr (1, std::string::npos));
}
// Convert "+aaa" to "project:aaa".
@@ -736,8 +744,8 @@ static std::string importTodoSh_2_0 (
else if (words[w].length () > 1 &&
words[w][0] == '@')
{
args.push_back (std::string ("+") +
words[w].substr (1, std::string::npos));
context.args.push_back (std::string ("+") +
words[w].substr (1, std::string::npos));
}
// Convert "(A)" to "priority:H".
@@ -747,9 +755,9 @@ static std::string importTodoSh_2_0 (
words[w][0] == '(' &&
words[w][2] == ')')
{
if (words[w][1] == 'A') args.push_back ("priority:H");
else if (words[w][1] == 'B') args.push_back ("priority:M");
else args.push_back ("priority:L");
if (words[w][1] == 'A') context.args.push_back ("priority:H");
else if (words[w][1] == 'B') context.args.push_back ("priority:M");
else context.args.push_back ("priority:L");
}
// Set status, if completed.
@@ -772,38 +780,42 @@ static std::string importTodoSh_2_0 (
// Just an ordinary word.
else
{
args.push_back (words[w]);
context.args.push_back (words[w]);
}
}
Tt task;
std::string command;
parse (args, command, task, conf);
decorateTask (task, conf);
context.task.clear ();
context.cmd.command = "";
context.parse ();
decorateTask (context.task);
if (isPending)
{
task.setStatus (Tt::pending);
context.task.setStatus (Task::pending);
}
else
{
task.setStatus (Tt::completed);
context.task.setStatus (Task::completed);
char end[16];
sprintf (end, "%u", (unsigned int) endDate.toEpoch ());
task.setAttribute ("end", end);
context.task.set ("end", end);
}
if (! tdb.addT (task))
failed.push_back (*it);
context.tdb.add (context.task);
context.clearMessages ();
}
catch (...)
{
context.clearMessages ();
failed.push_back (*it);
}
}
context.tdb.commit ();
context.tdb.unlock ();
std::stringstream out;
out << "Imported "
<< (lines.size () - failed.size ())
@@ -818,19 +830,17 @@ static std::string importTodoSh_2_0 (
join (bad, "\n", failed);
return out.str () + "\nCould not import:\n\n" + bad;
}
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
static std::string importText (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importText (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
int count = 0;
context.tdb.lock (context.config.get ("locking", true));
std::vector <std::string>::const_iterator it;
for (it = lines.begin (); it != lines.end (); ++it)
{
@@ -847,25 +857,29 @@ static std::string importText (
try
{
++count;
std::vector <std::string> args;
split (args, std::string ("add ") + line, ' ');
context.args.clear ();
split (context.args, std::string ("add ") + line, ' ');
Tt task;
std::string command;
parse (args, command, task, conf);
decorateTask (task, conf);
context.task.clear ();
context.cmd.command = "";
context.parse ();
decorateTask (context.task);
if (! tdb.addT (task))
failed.push_back (*it);
context.tdb.add (context.task);
context.clearMessages ();
}
catch (...)
{
context.clearMessages ();
failed.push_back (line);
}
}
}
context.tdb.commit ();
context.tdb.unlock ();
std::stringstream out;
out << "Imported "
<< count
@@ -885,13 +899,12 @@ static std::string importText (
}
////////////////////////////////////////////////////////////////////////////////
static std::string importCSV (
TDB& tdb,
Config& conf,
const std::vector <std::string>& lines)
static std::string importCSV (const std::vector <std::string>& lines)
{
std::vector <std::string> failed;
context.tdb.lock (context.config.get ("locking", true));
// Set up mappings. Assume no fields match.
std::map <std::string, int> mapping;
mapping ["id"] = -1;
@@ -917,7 +930,7 @@ static std::string importCSV (
std::string name = lowerCase (trim (unquoteText (trim (headings[h]))));
// If there is a mapping for the field, use the value.
if (name == conf.get ("import.synonym.id") ||
if (name == context.config.get ("import.synonym.id") ||
name == "id" ||
name == "#" ||
name == "sequence" ||
@@ -926,7 +939,7 @@ static std::string importCSV (
mapping["id"] = (int)h;
}
else if (name == conf.get ("import.synonym.uuid") ||
else if (name == context.config.get ("import.synonym.uuid") ||
name == "uuid" ||
name == "guid" ||
name.find ("unique") != std::string::npos)
@@ -934,7 +947,7 @@ static std::string importCSV (
mapping["uuid"] = (int)h;
}
else if (name == conf.get ("import.synonym.status") ||
else if (name == context.config.get ("import.synonym.status") ||
name == "status" ||
name == "condition" ||
name == "state")
@@ -942,7 +955,7 @@ static std::string importCSV (
mapping["status"] = (int)h;
}
else if (name == conf.get ("import.synonym.tags") ||
else if (name == context.config.get ("import.synonym.tags") ||
name == "tags" ||
name.find ("categor") != std::string::npos ||
name.find ("tag") != std::string::npos)
@@ -950,7 +963,7 @@ static std::string importCSV (
mapping["tags"] = (int)h;
}
else if (name == conf.get ("import.synonym.entry") ||
else if (name == context.config.get ("import.synonym.entry") ||
name == "entry" ||
name.find ("added") != std::string::npos ||
name.find ("created") != std::string::npos ||
@@ -959,7 +972,7 @@ static std::string importCSV (
mapping["entry"] = (int)h;
}
else if (name == conf.get ("import.synonym.start") ||
else if (name == context.config.get ("import.synonym.start") ||
name == "start" ||
name.find ("began") != std::string::npos ||
name.find ("begun") != std::string::npos ||
@@ -968,21 +981,21 @@ static std::string importCSV (
mapping["start"] = (int)h;
}
else if (name == conf.get ("import.synonym.due") ||
else if (name == context.config.get ("import.synonym.due") ||
name == "due" ||
name.find ("expected") != std::string::npos)
{
mapping["due"] = (int)h;
}
else if (name == conf.get ("import.synonym.recur") ||
else if (name == context.config.get ("import.synonym.recur") ||
name == "recur" ||
name == "frequency")
{
mapping["recur"] = (int)h;
}
else if (name == conf.get ("import.synonym.end") ||
else if (name == context.config.get ("import.synonym.end") ||
name == "end" ||
name == "done" ||
name.find ("complete") != std::string::npos)
@@ -990,14 +1003,14 @@ static std::string importCSV (
mapping["end"] = (int)h;
}
else if (name == conf.get ("import.synonym.project") ||
else if (name == context.config.get ("import.synonym.project") ||
name == "project" ||
name.find ("proj") != std::string::npos)
{
mapping["project"] = (int)h;
}
else if (name == conf.get ("import.synonym.priority") ||
else if (name == context.config.get ("import.synonym.priority") ||
name == "priority" ||
name == "pri" ||
name.find ("importan") != std::string::npos)
@@ -1005,7 +1018,7 @@ static std::string importCSV (
mapping["priority"] = (int)h;
}
else if (name == conf.get ("import.synonym.fg") ||
else if (name == context.config.get ("import.synonym.fg") ||
name.find ("fg") != std::string::npos ||
name.find ("foreground") != std::string::npos ||
name.find ("color") != std::string::npos)
@@ -1013,14 +1026,14 @@ static std::string importCSV (
mapping["fg"] = (int)h;
}
else if (name == conf.get ("import.synonym.bg") ||
else if (name == context.config.get ("import.synonym.bg") ||
name == "bg" ||
name.find ("background") != std::string::npos)
{
mapping["bg"] = (int)h;
}
else if (name == conf.get ("import.synonym.description") ||
else if (name == context.config.get ("import.synonym.description") ||
name.find ("desc") != std::string::npos ||
name.find ("detail") != std::string::npos ||
name.find ("task") != std::string::npos ||
@@ -1040,20 +1053,21 @@ static std::string importCSV (
std::vector <std::string> fields;
split (fields, *it, ',');
Tt task;
Task task;
int f;
if ((f = mapping["uuid"]) != -1)
task.setUUID (lowerCase (unquoteText (trim (fields[f]))));
task.set ("uuid", lowerCase (unquoteText (trim (fields[f]))));
task.setStatus (Task::pending);
if ((f = mapping["status"]) != -1)
{
std::string value = lowerCase (unquoteText (trim (fields[f])));
if (value == "recurring") task.setStatus (Tt::recurring);
else if (value == "deleted") task.setStatus (Tt::deleted);
else if (value == "completed") task.setStatus (Tt::completed);
else task.setStatus (Tt::pending);
if (value == "recurring") task.setStatus (Task::recurring);
else if (value == "deleted") task.setStatus (Task::deleted);
else if (value == "completed") task.setStatus (Task::completed);
else if (value == "waiting") task.setStatus (Task::waiting);
}
if ((f = mapping["tags"]) != -1)
@@ -1066,41 +1080,40 @@ static std::string importCSV (
}
if ((f = mapping["entry"]) != -1)
task.setAttribute ("entry", lowerCase (unquoteText (trim (fields[f]))));
task.set ("entry", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["start"]) != -1)
task.setAttribute ("start", lowerCase (unquoteText (trim (fields[f]))));
task.set ("start", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["due"]) != -1)
task.setAttribute ("due", lowerCase (unquoteText (trim (fields[f]))));
task.set ("due", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["recur"]) != -1)
task.setAttribute ("recur", lowerCase (unquoteText (trim (fields[f]))));
task.set ("recur", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["end"]) != -1)
task.setAttribute ("end", lowerCase (unquoteText (trim (fields[f]))));
task.set ("end", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["project"]) != -1)
task.setAttribute ("project", unquoteText (trim (fields[f])));
task.set ("project", unquoteText (trim (fields[f])));
if ((f = mapping["priority"]) != -1)
{
std::string value = upperCase (unquoteText (trim (fields[f])));
if (value == "H" || value == "M" || value == "L")
task.setAttribute ("priority", value);
task.set ("priority", value);
}
if ((f = mapping["fg"]) != -1)
task.setAttribute ("fg", lowerCase (unquoteText (trim (fields[f]))));
task.set ("fg", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["bg"]) != -1)
task.setAttribute ("bg", lowerCase (unquoteText (trim (fields[f]))));
task.set ("bg", lowerCase (unquoteText (trim (fields[f]))));
if ((f = mapping["description"]) != -1)
task.setDescription (unquoteText (trim (fields[f])));
task.set ("description", unquoteText (trim (fields[f])));
if (! tdb.addT (task))
failed.push_back (*it);
context.tdb.add (task);
}
catch (...)
@@ -1109,6 +1122,9 @@ static std::string importCSV (
}
}
context.tdb.commit ();
context.tdb.unlock ();
std::stringstream out;
out << "Imported "
<< (lines.size () - failed.size () - 1)
@@ -1128,12 +1144,12 @@ static std::string importCSV (
}
////////////////////////////////////////////////////////////////////////////////
std::string handleImport (TDB& tdb, Tt& task, Config& conf)
std::string handleImport ()
{
std::stringstream out;
// Use the description as a file name.
std::string file = trim (task.getDescription ());
std::string file = trim (context.task.get ("description"));
if (file.length () > 0)
{
// Load the file.
@@ -1169,7 +1185,7 @@ std::string handleImport (TDB& tdb, Tt& task, Config& conf)
case task_cmd_line: identifier = "This looks like task command line arguments."; break;
case todo_sh_2_0: identifier = "This looks like a todo.sh 2.x file."; break;
case csv: identifier = "This looks like a CSV file, but not a task export file."; break;
case text: identifier = "This looks like a text file with one tasks per line."; break;
case text: identifier = "This looks like a text file with one task per line."; break;
case not_a_clue:
throw std::string ("Task cannot determine which type of file this is, "
"and cannot proceed.");
@@ -1183,13 +1199,13 @@ std::string handleImport (TDB& tdb, Tt& task, Config& conf)
// Determine which type it might be, then attempt an import.
switch (type)
{
case task_1_4_3: out << importTask_1_4_3 (tdb, conf, lines); break;
case task_1_5_0: out << importTask_1_5_0 (tdb, conf, lines); break;
case task_1_6_0: out << importTask_1_6_0 (tdb, conf, lines); break;
case task_cmd_line: out << importTaskCmdLine (tdb, conf, lines); break;
case todo_sh_2_0: out << importTodoSh_2_0 (tdb, conf, lines); break;
case csv: out << importCSV (tdb, conf, lines); break;
case text: out << importText (tdb, conf, lines); break;
case task_1_4_3: out << importTask_1_4_3 (lines); break;
case task_1_5_0: out << importTask_1_5_0 (lines); break;
case task_1_6_0: out << importTask_1_6_0 (lines); break;
case task_cmd_line: out << importTaskCmdLine (lines); break;
case todo_sh_2_0: out << importTodoSh_2_0 (lines); break;
case csv: out << importCSV (lines); break;
case text: out << importText (lines); break;
case not_a_clue: /* to stop the compiler from complaining. */ break;
}
}
@@ -1200,4 +1216,3 @@ std::string handleImport (TDB& tdb, Tt& task, Config& conf)
}
////////////////////////////////////////////////////////////////////////////////

169
src/interactive.cpp Normal file
View File

@@ -0,0 +1,169 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <sstream>
//#include <pwd.h>
//#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include "Context.h"
//#include "text.h"
//#include "util.h"
//#include "main.h"
#include "i18n.h"
#include "../auto.h"
#ifdef HAVE_LIBNCURSES
#include <ncurses.h>
#endif
////////////////////////////////////////////////////////////////////////////////
int Context::interactive ()
{
#ifdef HAVE_LIBNCURSES
// TODO init ncurses
// TODO create worker thread
// TODO create refresh thread
// TODO join refresh thread
// TODO join worker thread
// TODO take down ncurses
// throw std::string ("unimplemented Context::interactive");
// Fake interactive teaser...
#ifdef FEATURE_NCURSES_COLS
initscr ();
int width = COLS;
int height = LINES;
#else
WINDOW* w = initscr ();
int width = w->_maxx + 1;
int height = w->_maxy + 1;
#endif
(void) nonl ();
(void) cbreak ();
start_color ();
init_pair (1, COLOR_WHITE, COLOR_BLUE);
init_pair (2, COLOR_WHITE, COLOR_RED);
init_pair (3, COLOR_CYAN, COLOR_BLUE);
// Process commands.
std::string command = "";
int c;
while (command != "quit")
{
// Render title.
std::string title = "task 2.0.0";
while ((int) title.length () < width)
title += " ";
bkgdset (COLOR_PAIR (1));
mvprintw (0, 0, title.c_str ());
bkgdset (COLOR_PAIR (2));
int line = height / 2;
mvprintw (line, width / 2 - 16, " I n t e r a c t i v e t a s k ");
mvprintw (line + 1, width / 2 - 16, " Coming in version 2.0.0 ");
std::string footer = "Press 'q' to quit.";
while ((int) footer.length () < width)
footer = " " + footer;
bkgdset (COLOR_PAIR (3));
mvprintw (height - 1, 0, footer.c_str ());
move (1, 0);
refresh ();
if ((c = getch ()) != ERR)
{
// 'Esc' and 'Enter' clear the accumulated commands.
// TODO Looks like \n is not preserved by getch.
if (c == 033 || c == '\n')
{
command = "";
}
else if (c == 'q')
{
command = "quit";
break;
}
}
}
endwin ();
return 0;
#else
throw stringtable.get (INTERACTIVE_NO_NCURSES,
"Interactive task is only available when built with ncurses "
"support.");
#endif
}
////////////////////////////////////////////////////////////////////////////////
int Context::getWidth ()
{
// Determine window size, and set table accordingly.
int width = config.get ("defaultwidth", (int) 80);
#ifdef HAVE_LIBNCURSES
if (config.get ("curses", true))
{
#ifdef FEATURE_NCURSES_COLS
initscr ();
width = COLS;
#else
WINDOW* w = initscr ();
width = w->_maxx + 1;
#endif
endwin ();
std::stringstream out;
out << "Context::getWidth: ncurses determined width of " << width << " characters";
debug (out.str ());
}
else
debug ("Context::getWidth: ncurses available but disabled.");
#else
std::stringstream out;
out << "Context::getWidth: no ncurses, using width of " << width << " characters";
debug (out.str ());
#endif
return width;
}
////////////////////////////////////////////////////////////////////////////////

76
src/main.cpp Normal file
View File

@@ -0,0 +1,76 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <stdlib.h>
#include <sys/time.h>
#include "Context.h"
#include "../auto.h"
Context context;
int main (int argc, char** argv)
{
// Set up randomness.
struct timeval tv;
::gettimeofday (&tv, NULL);
#ifdef HAVE_SRANDOM
srandom (tv.tv_usec);
#else
srand (tv.tv_usec);
#endif
int status = 0;
try
{
context.initialize (argc, argv);
std::string::size_type itask = context.program.find ("/itask");
if (context.program == "itask" ||
(itask != std::string::npos && context.program.length () == itask + 5))
status = context.interactive ();
else
status = context.run ();
}
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;
}
////////////////////////////////////////////////////////////////////////////////

165
src/main.h Normal file
View File

@@ -0,0 +1,165 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_MAIN
#define INCLUDED_MAIN
#define FEATURE_TDB_OPT 1 // TDB Optimization reduces file I/O.
#define FEATURE_NEW_ID 1 // Echoes back new id.
#define FEATURE_SHELL 1 // Interactive shell.
#define FEATURE_NCURSES_COLS 1 // Shortcut that avoids WINDOW.
#include <string>
#include <vector>
#include <map>
#include <sys/types.h>
#include "Context.h"
#include "Table.h"
#include "Date.h"
#include "color.h"
#include "../auto.h"
// task.cpp
void gatherNextTasks (std::vector <Task>&);
void onChangeCallback ();
// recur.cpp
void handleRecurrence ();
Date getNextRecurrence (Date&, std::string&);
bool generateDueDates (Task&, std::vector <Date>&);
void updateRecurrenceMask (std::vector <Task>&, Task&);
int getDueState (const std::string&);
bool nag (Task&);
// command.cpp
std::string handleAdd ();
std::string handleAppend ();
std::string handleExport ();
std::string handleDone ();
std::string handleModify ();
std::string handleProjects ();
std::string handleCompletionProjects ();
std::string handleTags ();
std::string handleCompletionTags ();
std::string handleCompletionCommands ();
std::string handleCompletionIDs ();
std::string handleCompletionConfig ();
std::string handleVersion ();
std::string handleDelete ();
std::string handleStart ();
std::string handleStop ();
std::string handleColor ();
std::string handleAnnotate ();
std::string handleDuplicate ();
void handleUndo ();
#ifdef FEATURE_SHELL
void handleShell ();
#endif
int deltaAppend (Task&);
int deltaDescription (Task&);
int deltaTags (Task&);
int deltaAttributes (Task&);
int deltaSubstitutions (Task&);
// edit.cpp
std::string handleEdit ();
// report.cpp
std::string shortUsage ();
std::string longUsage ();
std::string handleInfo ();
std::string handleReportSummary ();
std::string handleReportNext ();
std::string handleReportHistory ();
std::string handleReportGHistory ();
std::string handleReportCalendar ();
std::string handleReportStats ();
std::string handleReportTimesheet ();
std::string getFullDescription (Task&);
std::string getDueDate (Task&);
// custom.cpp
std::string handleCustomReport (const std::string&);
std::string runCustomReport (const std::string&, const std::string&,
const std::string&, const std::string&,
const std::string&, std::vector <Task>&);
void validReportColumns (const std::vector <std::string>&);
void validSortColumns (const std::vector <std::string>&, const std::vector <std::string>&);
// rules.cpp
void initializeColorRules ();
void autoColorize (Task&, Text::color&, Text::color&);
std::string colorizeHeader (const std::string&);
std::string colorizeMessage (const std::string&);
std::string colorizeFootnote (const std::string&);
std::string colorizeDebug (const std::string&);
// import.cpp
std::string handleImport ();
// list template
///////////////////////////////////////////////////////////////////////////////
template <class T> void listDiff (
const T& left, const T& right, T& leftOnly, T& rightOnly)
{
leftOnly.clear ();
rightOnly.clear ();
for (unsigned int l = 0; l < left.size (); ++l)
{
bool found = false;
for (unsigned int r = 0; r < right.size (); ++r)
{
if (left[l] == right[r])
{
found = true;
break;
}
}
if (!found)
leftOnly.push_back (left[l]);
}
for (unsigned int r = 0; r < right.size (); ++r)
{
bool found = false;
for (unsigned int l = 0; l < left.size (); ++l)
{
if (left[l] == right[r])
{
found = true;
break;
}
}
if (!found)
rightOnly.push_back (right[r]);
}
}
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,623 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <stdlib.h>
#include <string>
#include <vector>
#include <map>
#include "Date.h"
#include "task.h"
#include "T.h"
////////////////////////////////////////////////////////////////////////////////
// NOTE: These are static arrays only because there is no initializer list for
// std::vector.
static const char* colors[] =
{
"bold",
"underline",
"bold_underline",
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"bold_black",
"bold_red",
"bold_green",
"bold_yellow",
"bold_blue",
"bold_magenta",
"bold_cyan",
"bold_white",
"underline_black",
"underline_red",
"underline_green",
"underline_yellow",
"underline_blue",
"underline_magenta",
"underline_cyan",
"underline_white",
"bold_underline_black",
"bold_underline_red",
"bold_underline_green",
"bold_underline_yellow",
"bold_underline_blue",
"bold_underline_magenta",
"bold_underline_cyan",
"bold_underline_white",
"on_black",
"on_red",
"on_green",
"on_yellow",
"on_blue",
"on_magenta",
"on_cyan",
"on_white",
"on_bright_black",
"on_bright_red",
"on_bright_green",
"on_bright_yellow",
"on_bright_blue",
"on_bright_magenta",
"on_bright_cyan",
"on_bright_white",
"",
};
static const char* attributes[] =
{
"project",
"priority",
"fg",
"bg",
"due",
"entry",
"start",
"end",
"recur",
"until",
"mask",
"imask",
"",
};
// Alphabetical please.
static const char* commands[] =
{
"active",
"add",
"append",
"annotate",
"calendar",
"colors",
"completed",
"delete",
"done",
"duplicate",
"edit",
"export",
"help",
"history",
"ghistory",
"import",
"info",
"next",
"overdue",
"projects",
"start",
"stats",
"stop",
"summary",
"tags",
"timesheet",
"undelete",
"undo",
"version",
"",
};
static std::vector <std::string> customReports;
////////////////////////////////////////////////////////////////////////////////
void guess (const std::string& type, const char** list, std::string& candidate)
{
std::vector <std::string> options;
for (int i = 0; list[i][0]; ++i)
options.push_back (list[i]);
std::vector <std::string> matches;
autoComplete (candidate, options, matches);
if (1 == matches.size ())
candidate = matches[0];
else if (0 == matches.size ())
candidate = "";
else
{
std::string error = "Ambiguous ";
error += type;
error += " '";
error += candidate;
error += "' - could be either of ";
for (size_t i = 0; i < matches.size (); ++i)
{
if (i)
error += ", ";
error += matches[i];
}
throw error;
}
}
////////////////////////////////////////////////////////////////////////////////
void guess (const std::string& type, std::vector<std::string>& options, std::string& candidate)
{
std::vector <std::string> matches;
autoComplete (candidate, options, matches);
if (1 == matches.size ())
candidate = matches[0];
else if (0 == matches.size ())
candidate = "";
else
{
std::string error = "Ambiguous ";
error += type;
error += " '";
error += candidate;
error += "' - could be either of ";
for (size_t i = 0; i < matches.size (); ++i)
{
if (i)
error += ", ";
error += matches[i];
}
throw error;
}
}
////////////////////////////////////////////////////////////////////////////////
static bool isCommand (const std::string& candidate)
{
std::vector <std::string> options;
for (int i = 0; commands[i][0]; ++i)
options.push_back (commands[i]);
std::vector <std::string> matches;
autoComplete (candidate, options, matches);
if (0 == matches.size ())
{
autoComplete (candidate, customReports, matches);
if (0 == matches.size ())
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool validDate (std::string& date, Config& conf)
{
Date test (date, conf.get ("dateformat", "m/d/Y"));
char epoch[16];
sprintf (epoch, "%d", (int) test.toEpoch ());
date = epoch;
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool validPriority (const std::string& input)
{
if (input != "H" &&
input != "M" &&
input != "L" &&
input != "")
throw std::string ("\"") +
input +
"\" is not a valid priority. Use H, M, L or leave blank.";
return true;
}
////////////////////////////////////////////////////////////////////////////////
static bool validAttribute (
std::string& name,
std::string& value,
Config& conf)
{
guess ("attribute", attributes, name);
if (name != "")
{
if ((name == "fg" || name == "bg") && value != "")
guess ("color", colors, value);
else if (name == "due" && value != "")
validDate (value, conf);
else if (name == "until" && value != "")
validDate (value, conf);
else if (name == "priority")
{
value = upperCase (value);
return validPriority (value);
}
// Some attributes are intended to be private.
else if (name == "entry" ||
name == "start" ||
name == "end" ||
name == "mask" ||
name == "imask")
throw std::string ("\"") +
name +
"\" is not an attribute you may modify directly.";
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
static bool validId (const std::string& input)
{
if (input.length () == 0)
return false;
for (size_t i = 0; i < input.length (); ++i)
if (!::isdigit (input[i]))
return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// 1,2-4,6
static bool validSequence (
const std::string& input,
std::vector <int>& ids)
{
std::vector <std::string> ranges;
split (ranges, input, ',');
std::vector <std::string>::iterator it;
for (it = ranges.begin (); it != ranges.end (); ++it)
{
std::vector <std::string> range;
split (range, *it, '-');
switch (range.size ())
{
case 1:
{
if (! validId (range[0]))
return false;
int id = ::atoi (range[0].c_str ());
ids.push_back (id);
}
break;
case 2:
{
if (! validId (range[0]) ||
! validId (range[1]))
return false;
int low = ::atoi (range[0].c_str ());
int high = ::atoi (range[1].c_str ());
if (low >= high)
return false;
for (int i = low; i <= high; ++i)
ids.push_back (i);
}
break;
default:
return false;
break;
}
}
return ids.size () ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
static bool validTag (const std::string& input)
{
if ((input[0] == '-' || input[0] == '+') &&
input.length () > 1)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
static bool validDescription (const std::string& input)
{
if (input.length () == 0) return false;
if (input.find ("\r") != std::string::npos) return false;
if (input.find ("\f") != std::string::npos) return false;
if (input.find ("\n") != std::string::npos) return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////
static bool validCommand (std::string& input)
{
std::string copy = input;
guess ("command", commands, copy);
if (copy == "")
{
copy = input;
guess ("command", customReports, copy);
if (copy == "")
return false;
}
input = copy;
return true;
}
////////////////////////////////////////////////////////////////////////////////
static bool validSubstitution (
std::string& input,
std::string& from,
std::string& to,
bool& global)
{
size_t first = input.find ('/');
if (first != std::string::npos)
{
size_t second = input.find ('/', first + 1);
if (second != std::string::npos)
{
size_t third = input.find ('/', second + 1);
if (third != std::string::npos)
{
if (first == 0 &&
first < second &&
second < third &&
(third == input.length () - 1 ||
third == input.length () - 2))
{
from = input.substr (first + 1, second - first - 1);
to = input.substr (second + 1, third - second - 1);
global = false;
if (third == input.length () - 2 &&
input.find ('g', third + 1) != std::string::npos)
global = true;
return true;
}
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool validDuration (std::string& input)
{
return (convertDuration (input) != 0) ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
// Token EBNF
// ------- ----------------------------------
// command first non-id recognized argument
//
// substitution ::= "/" from "/" to "/g"
// | "/" from "/" to "/" ;
//
// tags ::= "+" word
// | "-" word ;
//
// attributes ::= word ":" value
// | word ":"
//
// sequence ::= \d+ "," sequence
// | \d+ "-" \d+ ;
//
// description (whatever isn't one of the above)
void parse (
std::vector <std::string>& args,
std::string& command,
Tt& task,
Config& conf)
{
command = "";
bool foundSequence = false;
bool foundSomethingAfterSequence = false;
std::string descCandidate = "";
for (size_t i = 0; i < args.size (); ++i)
{
std::string arg (args[i]);
// Ignore any argument that is "rc:...", because that is the command line
// specified rc file.
if (arg.substr (0, 3) != "rc:")
{
size_t colon; // Pointer to colon in argument.
std::string from;
std::string to;
bool global;
std::vector <int> sequence;
// An id is the first argument found that contains all digits.
if (lowerCase (command) != "add" && // "add" doesn't require an ID
validSequence (arg, sequence) &&
! foundSomethingAfterSequence)
{
foundSequence = true;
foreach (id, sequence)
task.addId (*id);
}
// Tags begin with + or - and contain arbitrary text.
else if (validTag (arg))
{
if (foundSequence)
foundSomethingAfterSequence = true;
if (arg[0] == '+')
task.addTag (arg.substr (1, std::string::npos));
else if (arg[0] == '-')
task.addRemoveTag (arg.substr (1, std::string::npos));
}
// Attributes contain a constant string followed by a colon, followed by a
// value.
else if ((colon = arg.find (":")) != std::string::npos)
{
if (foundSequence)
foundSomethingAfterSequence = true;
std::string name = lowerCase (arg.substr (0, colon));
std::string value = arg.substr (colon + 1, std::string::npos);
if (validAttribute (name, value, conf))
{
if (name != "recur" || validDuration (value))
task.setAttribute (name, value);
}
// If it is not a valid attribute, then allow the argument as part of
// the description.
else
{
if (descCandidate.length ())
descCandidate += " ";
descCandidate += arg;
}
}
// Substitution of description text.
else if (validSubstitution (arg, from, to, global))
{
if (foundSequence)
foundSomethingAfterSequence = true;
task.setSubstitution (from, to, global);
}
// Command.
else if (command == "")
{
if (foundSequence)
foundSomethingAfterSequence = true;
std::string l = lowerCase (arg);
if (isCommand (l) && validCommand (l))
command = l;
else
{
if (descCandidate.length ())
descCandidate += " ";
descCandidate += arg;
}
}
// Anything else is just considered description.
else
{
if (foundSequence)
foundSomethingAfterSequence = true;
if (descCandidate.length ())
descCandidate += " ";
descCandidate += arg;
}
}
}
if (validDescription (descCandidate))
task.setDescription (descCandidate);
}
////////////////////////////////////////////////////////////////////////////////
void loadCustomReports (Config& conf)
{
std::vector <std::string> all;
conf.all (all);
foreach (i, all)
{
if (i->substr (0, 7) == "report.")
{
std::string report = i->substr (7, std::string::npos);
std::string::size_type columns = report.find (".columns");
if (columns != std::string::npos)
{
report = report.substr (0, columns);
customReports.push_back (report);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
bool isCustomReport (const std::string& report)
{
foreach (i, customReports)
if (*i == report)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
void allCustomReports (std::vector <std::string>& all)
{
all = customReports;
}
////////////////////////////////////////////////////////////////////////////////

467
src/recur.cpp Normal file
View File

@@ -0,0 +1,467 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "text.h"
#include "util.h"
#include "main.h"
#ifdef HAVE_LIBNCURSES
#include <ncurses.h>
#endif
// Global context for use by all.
extern Context context;
////////////////////////////////////////////////////////////////////////////////
// Scans all tasks, and for any recurring tasks, determines whether any new
// child tasks need to be generated to fill gaps.
void handleRecurrence ()
{
std::vector <Task> tasks;
Filter filter;
context.tdb.loadPending (tasks, filter);
std::vector <Task> modified;
// Look at all tasks and find any recurring ones.
foreach (t, tasks)
{
if (t->getStatus () == Task::recurring)
{
// Generate a list of due dates for this recurring task, regardless of
// the mask.
std::vector <Date> due;
if (!generateDueDates (*t, due))
{
std::cout << "Task "
<< t->get ("uuid")
<< " ("
<< trim (t->get ("description"))
<< ") is past its 'until' date, and has been deleted"
<< std::endl;
// Determine the end date.
char endTime[16];
sprintf (endTime, "%u", (unsigned int) time (NULL));
t->set ("end", endTime);
t->setStatus (Task::deleted);
context.tdb.update (*t);
continue;
}
// Get the mask from the parent task.
std::string mask = t->get ("mask");
// Iterate over the due dates, and check each against the mask.
bool changed = false;
unsigned int i = 0;
foreach (d, due)
{
if (mask.length () <= i)
{
mask += '-';
changed = true;
Task rec (*t); // Clone the parent.
rec.set ("uuid", uuid ()); // New UUID.
rec.setStatus (Task::pending); // Shiny.
rec.set ("parent", t->get ("uuid")); // Remember mom.
char dueDate[16];
sprintf (dueDate, "%u", (unsigned int) d->toEpoch ());
rec.set ("due", dueDate); // Store generated due date.
char indexMask[12];
sprintf (indexMask, "%u", (unsigned int) i);
rec.set ("imask", indexMask); // Store index into mask.
// Add the new task to the vector, for immediate use.
modified.push_back (rec);
// Add the new task to the DB.
context.tdb.add (rec);
}
++i;
}
// Only modify the parent if necessary.
if (changed)
{
t->set ("mask", mask);
context.tdb.update (*t);
}
}
else
modified.push_back (*t);
}
tasks = modified;
}
////////////////////////////////////////////////////////////////////////////////
// Determine a start date (due), an optional end date (until), and an increment
// period (recur). Then generate a set of corresponding dates.
//
// Returns false if the parent recurring task is depleted.
bool generateDueDates (Task& parent, std::vector <Date>& allDue)
{
// Determine due date, recur period and until date.
Date due (atoi (parent.get ("due").c_str ()));
std::string recur = parent.get ("recur");
bool specificEnd = false;
Date until;
if (parent.get ("until") != "")
{
until = Date (atoi (parent.get ("until").c_str ()));
specificEnd = true;
}
Date now;
for (Date i = due; ; i = getNextRecurrence (i, recur))
{
allDue.push_back (i);
if (specificEnd && i > until)
{
// If i > until, it means there are no more tasks to generate, and if the
// parent mask contains all + or X, then there never will be another task
// to generate, and this parent task may be safely reaped.
std::string mask = parent.get ("mask");
if (mask.length () == allDue.size () &&
mask.find ('-') == std::string::npos)
return false;
return true;
}
if (i > now)
return true;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
Date getNextRecurrence (Date& current, std::string& period)
{
int m = current.month ();
int d = current.day ();
int y = current.year ();
// Some periods are difficult, because they can be vague.
if (period == "monthly")
{
if (++m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
return Date (m, d, y);
}
if (period == "weekdays")
{
int dow = current.dayOfWeek ();
int days;
if (dow == 5) days = 3;
else if (dow == 6) days = 2;
else days = 1;
return current + (days * 86400);
}
if (isdigit (period[0]) && period[period.length () - 1] == 'm')
{
std::string numeric = period.substr (0, period.length () - 1);
int increment = atoi (numeric.c_str ());
m += increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
return Date (m, d, y);
}
else if (period == "quarterly")
{
m += 3;
if (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
return Date (m, d, y);
}
else if (isdigit (period[0]) && period[period.length () - 1] == 'q')
{
std::string numeric = period.substr (0, period.length () - 1);
int increment = atoi (numeric.c_str ());
m += 3 * increment;
while (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
return Date (m, d, y);
}
else if (period == "semiannual")
{
m += 6;
if (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
return Date (m, d, y);
}
else if (period == "bimonthly")
{
m += 2;
if (m > 12)
{
m -= 12;
++y;
}
while (! Date::valid (m, d, y))
--d;
return Date (m, d, y);
}
else if (period == "biannual" ||
period == "biyearly")
{
y += 2;
return Date (m, d, y);
}
else if (period == "annual" ||
period == "yearly")
{
y += 1;
// If the due data just happens to be 2/29 in a leap year, then simply
// incrementing y is going to create an invalid date.
if (m == 2 && d == 29)
d = 28;
return Date (m, d, y);
}
// If the period is an 'easy' one, add it to current, and we're done.
int days = 0;
try { Duration du (period); days = du; }
catch (...) { days = 0; }
return current + (days * 86400);
}
////////////////////////////////////////////////////////////////////////////////
// When the status of a recurring child task changes, the parent task must
// update it's mask.
void updateRecurrenceMask (
std::vector <Task>& all,
Task& task)
{
std::string parent = task.get ("parent");
if (parent != "")
{
std::vector <Task>::iterator it;
for (it = all.begin (); it != all.end (); ++it)
{
if (it->get ("uuid") == parent)
{
unsigned int index = ::atoi (task.get ("imask").c_str ());
std::string mask = it->get ("mask");
if (mask.length () > index)
{
mask[index] = (task.getStatus () == Task::pending) ? '-'
: (task.getStatus () == Task::completed) ? '+'
: (task.getStatus () == Task::deleted) ? 'X'
: (task.getStatus () == Task::waiting) ? 'W'
: '?';
it->set ("mask", mask);
context.tdb.update (*it);
}
else
{
std::string mask;
for (unsigned int i = 0; i < index; ++i)
mask += "?";
mask += (task.getStatus () == Task::pending) ? '-'
: (task.getStatus () == Task::completed) ? '+'
: (task.getStatus () == Task::deleted) ? 'X'
: (task.getStatus () == Task::waiting) ? 'W'
: '?';
}
return; // No point continuing the loop.
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Determines whether a task is overdue. Returns
// 0 = not due at all
// 1 = imminent
// 2 = overdue
int getDueState (const std::string& due)
{
if (due.length ())
{
Date dt (::atoi (due.c_str ()));
// rightNow is the current date + time.
Date rightNow;
Date midnight (rightNow.month (), rightNow.day (), rightNow.year ());
if (dt < midnight)
return 2;
Date nextweek = midnight + context.config.get ("due", 7) * 86400;
if (dt < nextweek)
return 1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
bool nag (Task& task)
{
std::string nagMessage = context.config.get ("nag", "");
if (nagMessage != "")
{
// Load all pending tasks.
std::vector <Task> tasks;
Filter filter;
// Piggy-back on existing locked TDB.
context.tdb.loadPending (tasks, filter);
// Counters.
int overdue = 0;
int high = 0;
int medium = 0;
int low = 0;
bool isOverdue = false;
char pri = ' ';
// Scan all pending tasks.
foreach (t, tasks)
{
if (t->id == task.id)
{
if (getDueState (t->get ("due")) == 2)
isOverdue = true;
std::string priority = t->get ("priority");
if (priority.length ())
pri = priority[0];
}
else if (t->getStatus () == Task::pending)
{
if (getDueState (t->get ("due")) == 2)
overdue++;
std::string priority = t->get ("priority");
if (priority.length ())
{
switch (priority[0])
{
case 'H': high++; break;
case 'M': medium++; break;
case 'L': low++; break;
}
}
}
}
// General form is "if there are no more deserving tasks", suppress the nag.
if (isOverdue ) return false;
if (pri == 'H' && !overdue ) return false;
if (pri == 'M' && !overdue && !high ) return false;
if (pri == 'L' && !overdue && !high && !medium ) return false;
if (pri == ' ' && !overdue && !high && !medium && !low) return false;
// All the excuses are made, all that remains is to nag the user.
context.footnote (nagMessage);
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -26,11 +26,14 @@
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <stdlib.h>
#include "Config.h"
#include "Context.h"
#include "Table.h"
#include "Date.h"
#include "T.h"
#include "task.h"
#include "text.h"
#include "util.h"
#include "main.h"
extern Context context;
static std::map <std::string, Text::color> gsFg;
static std::map <std::string, Text::color> gsBg;
@@ -62,17 +65,17 @@ static void parseColorRule (
}
////////////////////////////////////////////////////////////////////////////////
void initializeColorRules (Config& conf)
void initializeColorRules ()
{
std::vector <std::string> ruleNames;
conf.all (ruleNames);
context.config.all (ruleNames);
foreach (it, ruleNames)
{
if (it->substr (0, 6) == "color.")
{
Text::color fg;
Text::color bg;
parseColorRule (conf.get (*it), fg, bg);
parseColorRule (context.config.get (*it), fg, bg);
gsFg[*it] = fg;
gsBg[*it] = bg;
}
@@ -81,10 +84,9 @@ void initializeColorRules (Config& conf)
////////////////////////////////////////////////////////////////////////////////
void autoColorize (
Tt& task,
Task& task,
Text::color& fg,
Text::color& bg,
Config& conf)
Text::color& bg)
{
// Note: fg, bg already contain colors specifically assigned via command.
// Note: These rules form a hierarchy - the last rule is King.
@@ -93,9 +95,7 @@ void autoColorize (
if (gsFg["color.tagged"] != Text::nocolor ||
gsBg["color.tagged"] != Text::nocolor)
{
std::vector <std::string> tags;
task.getTags (tags);
if (tags.size ())
if (task.getTagCount ())
{
fg = gsFg["color.tagged"];
bg = gsBg["color.tagged"];
@@ -106,7 +106,7 @@ void autoColorize (
if (gsFg["color.pri.L"] != Text::nocolor ||
gsBg["color.pri.L"] != Text::nocolor)
{
if (task.getAttribute ("priority") == "L")
if (task.get ("priority") == "L")
{
fg = gsFg["color.pri.L"];
bg = gsBg["color.pri.L"];
@@ -117,7 +117,7 @@ void autoColorize (
if (gsFg["color.pri.M"] != Text::nocolor ||
gsBg["color.pri.M"] != Text::nocolor)
{
if (task.getAttribute ("priority") == "M")
if (task.get ("priority") == "M")
{
fg = gsFg["color.pri.M"];
bg = gsBg["color.pri.M"];
@@ -128,7 +128,7 @@ void autoColorize (
if (gsFg["color.pri.H"] != Text::nocolor ||
gsBg["color.pri.H"] != Text::nocolor)
{
if (task.getAttribute ("priority") == "H")
if (task.get ("priority") == "H")
{
fg = gsFg["color.pri.H"];
bg = gsBg["color.pri.H"];
@@ -139,7 +139,7 @@ void autoColorize (
if (gsFg["color.pri.none"] != Text::nocolor ||
gsBg["color.pri.none"] != Text::nocolor)
{
if (task.getAttribute ("priority") == "")
if (task.get ("priority") == "")
{
fg = gsFg["color.pri.none"];
bg = gsBg["color.pri.none"];
@@ -150,7 +150,7 @@ void autoColorize (
if (gsFg["color.active"] != Text::nocolor ||
gsBg["color.active"] != Text::nocolor)
{
if (task.getAttribute ("start") != "")
if (task.has ("start"))
{
fg = gsFg["color.active"];
bg = gsBg["color.active"];
@@ -178,7 +178,7 @@ void autoColorize (
if (it->first.substr (0, 14) == "color.project.")
{
std::string value = it->first.substr (14, std::string::npos);
if (task.getAttribute ("project") == value)
if (task.get ("project") == value)
{
fg = gsFg[it->first];
bg = gsBg[it->first];
@@ -192,7 +192,7 @@ void autoColorize (
if (it->first.substr (0, 14) == "color.keyword.")
{
std::string value = lowerCase (it->first.substr (14, std::string::npos));
std::string desc = lowerCase (task.getDescription ());
std::string desc = lowerCase (task.get ("description"));
if (desc.find (value) != std::string::npos)
{
fg = gsFg[it->first];
@@ -202,25 +202,24 @@ void autoColorize (
}
// Colorization of the due and overdue.
std::string due = task.getAttribute ("due");
if (due != "")
if (task.has ("due"))
{
Date dueDate (::atoi (due.c_str ()));
Date now;
Date then (now + conf.get ("due", 7) * 86400);
// Overdue
if (dueDate < now)
{
fg = gsFg["color.overdue"];
bg = gsBg["color.overdue"];
}
// Imminent
else if (dueDate < then)
std::string due = task.get ("due");
switch (getDueState (due))
{
case 1: // imminent
fg = gsFg["color.due"];
bg = gsBg["color.due"];
break;
case 2: // overdue
fg = gsFg["color.overdue"];
bg = gsBg["color.overdue"];
break;
case 0: // not due at all
default:
break;
}
}
@@ -228,7 +227,7 @@ void autoColorize (
if (gsFg["color.recurring"] != Text::nocolor ||
gsBg["color.recurring"] != Text::nocolor)
{
if (task.getAttribute ("recur") != "")
if (task.has ("recur"))
{
fg = gsFg["color.recurring"];
bg = gsBg["color.recurring"];
@@ -237,4 +236,64 @@ void autoColorize (
}
////////////////////////////////////////////////////////////////////////////////
std::string colorizeHeader (const std::string& input)
{
if (gsFg["color.header"] != Text::nocolor ||
gsBg["color.header"] != Text::nocolor)
{
return Text::colorize (
gsFg["color.header"],
gsBg["color.header"],
input);
}
return input;
}
////////////////////////////////////////////////////////////////////////////////
std::string colorizeMessage (const std::string& input)
{
if (gsFg["color.message"] != Text::nocolor ||
gsBg["color.message"] != Text::nocolor)
{
return Text::colorize (
gsFg["color.message"],
gsBg["color.message"],
input);
}
return input;
}
////////////////////////////////////////////////////////////////////////////////
std::string colorizeFootnote (const std::string& input)
{
if (gsFg["color.footnote"] != Text::nocolor ||
gsBg["color.footnote"] != Text::nocolor)
{
return Text::colorize (
gsFg["color.footnote"],
gsBg["color.footnote"],
input);
}
return input;
}
////////////////////////////////////////////////////////////////////////////////
std::string colorizeDebug (const std::string& input)
{
if (gsFg["color.debug"] != Text::nocolor ||
gsBg["color.debug"] != Text::nocolor)
{
return Text::colorize (
gsFg["color.debug"],
gsBg["color.debug"],
input);
}
return input;
}
////////////////////////////////////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -24,186 +24,60 @@
// USA
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TASK
#define INCLUDED_TASK
#include <string>
#include <vector>
#include <map>
#include <sys/types.h>
#include "Config.h"
#include "Table.h"
#include "Date.h"
#include "color.h"
#include "TDB.h"
#include "T.h"
#include "../auto.h"
#include "Record.h"
#include "Subst.h"
#include "Sequence.h"
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
#define foreach(i, c) \
for (typeof (c) *foreach_p = & (c); \
foreach_p; \
foreach_p = 0) \
for (typeof (foreach_p->begin()) i = foreach_p->begin(); \
i != foreach_p->end(); \
++i)
// parse.cpp
void parse (std::vector <std::string>&, std::string&, Tt&, Config&);
bool validPriority (const std::string&);
bool validDate (std::string&, Config&);
bool validDuration (std::string&);
void loadCustomReports (Config&);
bool isCustomReport (const std::string&);
void allCustomReports (std::vector <std::string>&);
// task.cpp
void gatherNextTasks (const TDB&, Tt&, Config&, std::vector <Tt>&, std::vector <int>&);
void nag (TDB&, Tt&, Config&);
int getDueState (const std::string&);
void handleRecurrence (TDB&, std::vector <Tt>&);
bool generateDueDates (Tt&, std::vector <Date>&);
Date getNextRecurrence (Date&, std::string&);
void updateRecurrenceMask (TDB&, std::vector <Tt>&, Tt&);
void onChangeCallback ();
std::string runTaskCommand (int, char**, TDB&, Config&, bool gc = true, bool shadow = true);
std::string runTaskCommand (std::vector <std::string>&, TDB&, Config&, bool gc = false, bool shadow = false);
// command.cpp
std::string handleAdd (TDB&, Tt&, Config&);
std::string handleAppend (TDB&, Tt&, Config&);
std::string handleExport (TDB&, Tt&, Config&);
std::string handleDone (TDB&, Tt&, Config&);
std::string handleModify (TDB&, Tt&, Config&);
std::string handleProjects (TDB&, Tt&, Config&);
std::string handleTags (TDB&, Tt&, Config&);
std::string handleUndelete (TDB&, Tt&, Config&);
std::string handleVersion (Config&);
std::string handleDelete (TDB&, Tt&, Config&);
std::string handleStart (TDB&, Tt&, Config&);
std::string handleStop (TDB&, Tt&, Config&);
std::string handleUndo (TDB&, Tt&, Config&);
std::string handleColor (Config&);
std::string handleAnnotate (TDB&, Tt&, Config&);
std::string handleDuplicate (TDB&, Tt&, Config&);
Tt findT (int, const std::vector <Tt>&);
int deltaAppend (Tt&, Tt&);
int deltaDescription (Tt&, Tt&);
int deltaTags (Tt&, Tt&);
int deltaAttributes (Tt&, Tt&);
int deltaSubstitutions (Tt&, Tt&);
// edit.cpp
std::string handleEdit (TDB&, Tt&, Config&);
// report.cpp
void filterSequence (std::vector<Tt>&, Tt&);
void filter (std::vector<Tt>&, Tt&);
std::string handleInfo (TDB&, Tt&, Config&);
std::string handleCompleted (TDB&, Tt&, Config&);
std::string handleReportSummary (TDB&, Tt&, Config&);
std::string handleReportNext (TDB&, Tt&, Config&);
std::string handleReportHistory (TDB&, Tt&, Config&);
std::string handleReportGHistory (TDB&, Tt&, Config&);
std::string handleReportCalendar (TDB&, Tt&, Config&);
std::string handleReportActive (TDB&, Tt&, Config&);
std::string handleReportOverdue (TDB&, Tt&, Config&);
std::string handleReportStats (TDB&, Tt&, Config&);
std::string handleReportTimesheet (TDB&, Tt&, Config&);
std::string handleCustomReport (TDB&, Tt&, Config&, const std::string&);
void validReportColumns (const std::vector <std::string>&);
void validSortColumns (const std::vector <std::string>&, const std::vector <std::string>&);
// text.cpp
void wrapText (std::vector <std::string>&, const std::string&, const int);
std::string trimLeft (const std::string& in, const std::string& t = " ");
std::string trimRight (const std::string& in, const std::string& t = " ");
std::string trim (const std::string& in, const std::string& t = " ");
std::string unquoteText (const std::string&);
void extractLine (std::string&, std::string&, int);
void split (std::vector<std::string>&, const std::string&, const char);
void split (std::vector<std::string>&, const std::string&, const std::string&);
void join (std::string&, const std::string&, const std::vector<std::string>&);
std::string commify (const std::string&);
std::string lowerCase (const std::string&);
std::string upperCase (const std::string&);
const char* optionalBlankLine (Config&);
// util.cpp
bool confirm (const std::string&);
void delay (float);
void formatTimeDeltaDays (std::string&, time_t);
std::string formatSeconds (time_t);
int autoComplete (const std::string&, const std::vector<std::string>&, std::vector<std::string>&);
const std::string uuid ();
int convertDuration (const std::string&);
std::string expandPath (const std::string&);
#ifdef SOLARIS
#define LOCK_SH 1
#define LOCK_EX 2
#define LOCK_NB 4
#define LOCK_UN 8
int flock (int, int);
#endif
bool slurp (const std::string&, std::vector <std::string>&, bool trimLines = false);
bool slurp (const std::string&, std::string&, bool trimLines = false);
void spit (const std::string&, const std::string&);
// rules.cpp
void initializeColorRules (Config&);
void autoColorize (Tt&, Text::color&, Text::color&, Config&);
// import.cpp
std::string handleImport (TDB&, Tt&, Config&);
// list template
///////////////////////////////////////////////////////////////////////////////
template <class T> void listDiff (
const T& left, const T& right, T& leftOnly, T& rightOnly)
class Task : public Record
{
leftOnly.clear ();
rightOnly.clear ();
public:
Task (); // Default constructor
Task (const Task&); // Copy constructor
Task& operator= (const Task&); // Assignment operator
bool operator== (const Task&); // Comparison operator
Task (const std::string&); // Parse
~Task (); // Destructor
for (unsigned int l = 0; l < left.size (); ++l)
{
bool found = false;
for (unsigned int r = 0; r < right.size (); ++r)
{
if (left[l] == right[r])
{
found = true;
break;
}
}
void parse (const std::string&);
std::string composeCSV () const;
if (!found)
leftOnly.push_back (left[l]);
}
// Status values.
enum status {pending, completed, deleted, recurring, waiting};
for (unsigned int r = 0; r < right.size (); ++r)
{
bool found = false;
for (unsigned int l = 0; l < left.size (); ++l)
{
if (left[l] == right[r])
{
found = true;
break;
}
}
// Public data.
int id;
if (!found)
rightOnly.push_back (right[r]);
}
}
// Series of helper functions.
static status textToStatus (const std::string&);
static std::string statusToText (status);
void setEntry ();
status getStatus ();
void setStatus (status);
int getTagCount ();
bool hasTag (const std::string&);
void addTag (const std::string&);
void addTags (const std::vector <std::string>&);
void getTags (std::vector<std::string>&) const;
void removeTag (const std::string&);
void getAnnotations (std::vector <Att>&) const;
void setAnnotations (const std::vector <Att>&);
void addAnnotation (const std::string&);
void removeAnnotations ();
void validate () const;
private:
int determineVersion (const std::string&);
void legacyParse (const std::string&);
};
#endif
////////////////////////////////////////////////////////////////////////////////

13
src/tests/.gitignore vendored
View File

@@ -5,4 +5,15 @@ date.t
duration.t
text.t
autocomplete.t
parse.t
seq.t
att.t
record.t
stringtable.t
nibbler.t
subst.t
filt.t
cmd.t
config.t
util.t
color.t
*.log

View File

@@ -1,8 +1,14 @@
PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \
parse.t
PROJECT = t.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \
config.t seq.t att.t stringtable.t record.t nibbler.t subst.t filt.t \
cmd.t util.t color.t
CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti
LFLAGS = -L/usr/local/lib
OBJECTS = ../TDB.o ../T.o ../parse.o ../text.o ../Date.o ../util.o ../Config.o
LFLAGS = -L/usr/local/lib -lncurses
OBJECTS = ../TDB.o ../Task.o ../text.o ../Date.o ../Table.o ../Duration.o \
../util.o ../Config.o ../Sequence.o ../Att.o ../Cmd.o ../Record.o \
../StringTable.o ../Subst.o ../Nibbler.o ../Location.o ../Filter.o \
../Context.o ../Keymap.o ../command.o ../interactive.o ../report.o \
../Grid.o ../color.o ../rules.o ../recur.o ../custom.o ../import.o \
../edit.o ../Timer.o ../Permission.o
all: $(PROJECT)
@@ -39,6 +45,36 @@ text.t: text.t.o $(OBJECTS) test.o
autocomplete.t: autocomplete.t.o $(OBJECTS) test.o
g++ autocomplete.t.o $(OBJECTS) test.o $(LFLAGS) -o autocomplete.t
parse.t: parse.t.o $(OBJECTS) test.o
g++ parse.t.o $(OBJECTS) test.o $(LFLAGS) -o parse.t
seq.t: seq.t.o $(OBJECTS) test.o
g++ seq.t.o $(OBJECTS) test.o $(LFLAGS) -o seq.t
record.t: record.t.o $(OBJECTS) test.o
g++ record.t.o $(OBJECTS) test.o $(LFLAGS) -o record.t
att.t: att.t.o $(OBJECTS) test.o
g++ att.t.o $(OBJECTS) test.o $(LFLAGS) -o att.t
stringtable.t: stringtable.t.o $(OBJECTS) test.o
g++ stringtable.t.o $(OBJECTS) test.o $(LFLAGS) -o stringtable.t
subst.t: subst.t.o $(OBJECTS) test.o
g++ subst.t.o $(OBJECTS) test.o $(LFLAGS) -o subst.t
nibbler.t: nibbler.t.o $(OBJECTS) test.o
g++ nibbler.t.o $(OBJECTS) test.o $(LFLAGS) -o nibbler.t
filt.t: filt.t.o $(OBJECTS) test.o
g++ filt.t.o $(OBJECTS) test.o $(LFLAGS) -o filt.t
cmd.t: cmd.t.o $(OBJECTS) test.o
g++ cmd.t.o $(OBJECTS) test.o $(LFLAGS) -o cmd.t
config.t: config.t.o $(OBJECTS) test.o
g++ config.t.o $(OBJECTS) test.o $(LFLAGS) -o config.t
util.t: util.t.o $(OBJECTS) test.o
g++ util.t.o $(OBJECTS) test.o $(LFLAGS) -o util.t
color.t: color.t.o $(OBJECTS) test.o
g++ color.t.o $(OBJECTS) test.o $(LFLAGS) -o color.t

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 22;
use Test::More tests => 23;
# Create the rc file.
if (open my $fh, '>', 'abbrev.rc')
@@ -92,6 +92,9 @@ like ($output, qr/ABSOLUTELY NO WARRANTY/, 'v');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'abbrev.rc';
ok (!-r 'abbrev.rc', 'Removed abbrev.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 14;
use Test::More tests => 13;
# Create the rc file.
if (open my $fh, '>', 'add.rc')
@@ -39,32 +39,34 @@ if (open my $fh, '>', 'add.rc')
}
# Test the add command.
my $output = qx{../task rc:add.rc add This is a test; ../task rc:add.rc info 1};
qx{../task rc:add.rc add This is a test};
my $output = qx{../task rc:add.rc info 1};
like ($output, qr/ID\s+1\n/, 'add ID');
like ($output, qr/Description\s+This is a test\n/, 'add ID');
like ($output, qr/Status\s+Pending\n/, 'add Pending');
like ($output, qr/UUID\s+[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\n/, 'add UUID');
# Test the /// modifier.
$output = qx{../task rc:add.rc 1 /test/TEST/; ../task rc:add.rc 1 "/is //"; ../task rc:add.rc info 1};
qx{../task rc:add.rc 1 /test/TEST/};
qx{../task rc:add.rc 1 "/is //"};
$output = qx{../task rc:add.rc info 1};
like ($output, qr/ID\s+1\n/, 'add ID');
like ($output, qr/Status\s+Pending\n/, 'add Pending');
like ($output, qr/Description\s+This a TEST\n/, 'add ID');
# Test delete.
$output = qx{../task rc:add.rc delete 1; ../task rc:add.rc info 1};
qx{../task rc:add.rc delete 1};
$output = qx{../task rc:add.rc info 1};
like ($output, qr/ID\s+1\n/, 'add ID');
like ($output, qr/Status\s+Deleted\n/, 'add Deleted');
# Test undelete.
$output = qx{../task rc:add.rc undelete 1; ../task rc:add.rc info 1};
like ($output, qr/ID\s+1\n/, 'add ID');
like ($output, qr/Status\s+Pending\n/, 'add Pending');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'add.rc';
ok (!-r 'add.rc', 'Removed add.rc');

69
src/tests/alias.t Executable file
View File

@@ -0,0 +1,69 @@
#! /usr/bin/perl
################################################################################
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 8;
# Create the rc file.
if (open my $fh, '>', 'alias.rc')
{
print $fh "data.location=.\n",
"alias.foo=_projects\n",
"alias.bar=foo\n";
close $fh;
ok (-r 'alias.rc', 'Created alias.rc');
}
# Add a task with certain project, then access that task via aliases.
qx{../task rc:alias.rc add project:ALIAS foo};
my $output = qx{../task rc:alias.rc _projects};
like ($output, qr/ALIAS/, 'task _projects -> ALIAS');
$output = qx{../task rc:alias.rc foo};
like ($output, qr/ALIAS/, 'task foo -> _projects -> ALIAS');
$output = qx{../task rc:alias.rc bar};
like ($output, qr/ALIAS/, 'task bar -> foo -> _projects -> ALIAS');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'alias.rc';
ok (!-r 'alias.rc', 'Removed alias.rc');
exit 0;

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 8;
use Test::More tests => 9;
# Create the rc file.
if (open my $fh, '>', 'annotate.rc')
@@ -67,6 +67,9 @@ like ($output, qr/2 tasks/, 'count');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'annotate.rc';
ok (!-r 'annotate.rc', 'Removed annotate.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 4;
use Test::More tests => 5;
# Create the rc file.
if (open my $fh, '>', 'append.rc')
@@ -48,6 +48,9 @@ like ($output, qr/Description\s+foo\sbar\n/, 'append worked');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'append.rc';
ok (!-r 'append.rc', 'Removed append.rc');

74
src/tests/args.t Executable file
View File

@@ -0,0 +1,74 @@
#! /usr/bin/perl
################################################################################
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 9;
# Create the rc file.
if (open my $fh, '>', 'args.rc')
{
print $fh "data.location=.\n",
"confirmation=no\n";
close $fh;
ok (-r 'args.rc', 'Created args.rc');
}
# Test the -- argument.
qx{../task rc:args.rc add project:p pri:H +tag foo};
my $output = qx{../task rc:args.rc info 1};
like ($output, qr/Description\s+foo\n/ms, 'task add project:p pri:H +tag foo');
qx{../task rc:args.rc 1 project:p pri:H +tag -- foo};
$output = qx{../task rc:args.rc info 1};
like ($output, qr/Description\s+foo\n/ms, 'task 1 project:p pri:H +tag -- foo');
qx{../task rc:args.rc 1 project:p pri:H -- +tag foo};
$output = qx{../task rc:args.rc info 1};
like ($output, qr/Description\s+\+tag\sfoo\n/ms, 'task 1 project:p pri:H -- +tag foo');
qx{../task rc:args.rc 1 project:p -- pri:H +tag foo};
$output = qx{../task rc:args.rc info 1};
like ($output, qr/Description\s+pri:H\s\+tag\sfoo\n/ms, 'task 1 project:p -- pri:H +tag foo');
qx{../task rc:args.rc 1 -- project:p pri:H +tag foo};
$output = qx{../task rc:args.rc info 1};
like ($output, qr/Description\s+project:p\spri:H\s\+tag\sfoo\n/ms, 'task 1 -- project:p pri:H +tag foo');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'args.rc';
ok (!-r 'args.rc', 'Removed args.rc');
exit 0;

287
src/tests/att.t.cpp Normal file
View File

@@ -0,0 +1,287 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <Context.h>
#include <Att.h>
#include <test.h>
Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (95);
Att a;
t.notok (a.valid ("name"), "Att::valid name -> fail");
t.notok (a.valid (":"), "Att::valid : -> fail");
t.notok (a.valid (":value"), "Att::valid :value -> fail");
t.ok (a.valid ("name:value"), "Att::valid name:value");
t.ok (a.valid ("name:value "), "Att::valid name:value\\s");
t.ok (a.valid ("name:'value'"), "Att::valid name:'value'");
t.ok (a.valid ("name:'one two'"), "Att::valid name:'one two'");
t.ok (a.valid ("name:\"value\""), "Att::valid name:\"value\"");
t.ok (a.valid ("name:\"one two\""), "Att::valid name:\"one two\"");
t.ok (a.valid ("name:"), "Att::valid name:");
t.ok (a.valid ("name:&quot;"), "Att::valid &quot;");
t.ok (a.valid ("name.one:value"), "Att::valid name.one.value");
t.ok (a.valid ("name.one.two:value"), "Att::valid name.one.two:value");
t.ok (a.valid ("name.:value"), "Att::valid name.:value");
t.ok (a.valid ("name..:value"), "Att::valid name..:value");
Att a1 ("name", "value");
t.is (a1.name (), "name", "Att::Att (name, value), Att.name");
t.is (a1.value (), "value", "Att::Att (name, value), Att.value");
Att a2;
a2.name ("name");
a2.value ("value");
t.is (a2.name (), "name", "Att::Att (), Att.name");
t.is (a2.value (), "value", "Att::Att (), Att.value");
Att a3 (a2);
t.is (a3.name (), "name", "Att::Att (Att), Att.name");
t.is (a3.value (), "value", "Att::Att (Att), Att.value");
Att a4;
a4 = a2;
t.is (a4.name (), "name", "Att::Att (), Att.operator=, Att.name");
t.is (a4.value (), "value", "Att::Att (), Att.operator=, Att.value");
Att a5 ("name", "value");
t.is (a5.composeF4 (), "name:\"value\"", "Att::composeF4 simple");
a5.value ("\"");
t.is (a5.composeF4 (), "name:\"&quot;\"", "Att::composeF4 encoded \"");
a5.value ("\t\",[]:");
t.is (a5.composeF4 (), "name:\"&tab;&quot;&comma;&open;&close;&colon;\"", "Att::composeF4 fully encoded \\t\",[]:");
Att a6 ("name", 6);
t.is (a6.value_int (), 6, "Att::value_int get");
a6.value_int (7);
t.is (a6.value_int (), 7, "Att::value_int set/get");
t.is (a6.value (), "7", "Att::value 7");
// Att::mod
bool good = true;
try {a6.mod ("is");} catch (...) {good = false;}
t.ok (good, "Att::mod (is)");
good = true;
try {a6.mod ("before");} catch (...) {good = false;}
t.ok (good, "Att::mod (before)");
good = true;
try {a6.mod ("after");} catch (...) {good = false;}
t.ok (good, "Att::mod (after)");
good = true;
try {a6.mod ("none");} catch (...) {good = false;}
t.ok (good, "Att::mod (none)");
good = true;
try {a6.mod ("any");} catch (...) {good = false;}
t.ok (good, "Att::mod (any)");
good = true;
try {a6.mod ("over");} catch (...) {good = false;}
t.ok (good, "Att::mod (over)");
good = true;
try {a6.mod ("under");} catch (...) {good = false;}
t.ok (good, "Att::mod (under)");
good = true;
try {a6.mod ("above");} catch (...) {good = false;}
t.ok (good, "Att::mod (above)");
good = true;
try {a6.mod ("below");} catch (...) {good = false;}
t.ok (good, "Att::mod (below)");
good = true;
try {a6.mod ("isnt");} catch (...) {good = false;}
t.ok (good, "Att::mod (isnt)");
good = true;
try {a6.mod ("has");} catch (...) {good = false;}
t.ok (good, "Att::mod (has)");
good = true;
try {a6.mod ("contains");} catch (...) {good = false;}
t.ok (good, "Att::mod (contains)");
good = true;
try {a6.mod ("hasnt");} catch (...) {good = false;}
t.ok (good, "Att::mod (hasnt)");
good = true;
try {a6.mod ("startswith");} catch (...) {good = false;}
t.ok (good, "Att::mod (startswith)");
good = true;
try {a6.mod ("endswith");} catch (...) {good = false;}
t.ok (good, "Att::mod (endswith)");
good = true;
try {a6.mod ("fartwizzle");} catch (...) {good = false;}
t.notok (good, "Att::mod (fartwizzle)");
// Att::parse
Nibbler n ("");
Att a7;
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse () -> throw");
n = Nibbler ("name:value");
a7.parse (n);
t.is (a7.composeF4 (), "name:\"value\"",
"Att::parse (name:value)");
n = Nibbler ("name:\"value\"");
a7.parse (n);
t.is (a7.composeF4 (), "name:\"value\"",
"Att::parse (name:\"value\")");
n = Nibbler ("name:\"one two\"");
a7.parse (n);
t.is (a7.composeF4 (), "name:\"one two\"",
"Att::parse (name:\"one two\")");
n = Nibbler ("name:\"&quot;\"");
a7.parse (n);
t.is (a7.composeF4 (), "name:\"&quot;\"",
"Att::parse (name:\"&quot;\")");
n = Nibbler ("name:\"&tab;&quot;&comma;&open;&close;&colon;\"");
a7.parse (n);
t.is (a7.composeF4 (), "name:\"&tab;&quot;&comma;&open;&close;&colon;\"",
"Att::parse (name:\"&tab;&quot;&comma;&open;&close;&colon;\")");
n = Nibbler ("total gibberish");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (total gibberish)");
n = Nibbler ("malformed");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (malformed)");
n = Nibbler (":malformed");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (:malformed)");
n = Nibbler (":\"\"");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (:\"\")");
n = Nibbler (":\"");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (:\")");
n = Nibbler ("name:");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.ok (good, "Att::parse (name:)");
n = Nibbler ("name:\"value");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.ok (good, "Att::parse (name:\"value)");
t.is (a7.composeF4 (), "name:\"&quot;value\"", "Att::composeF4 -> name:\"&quot;value\"");
n = Nibbler ("name:value\"");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.ok (good, "Att::parse (name:value\")");
t.is (a7.composeF4 (), "name:\"value&quot;\"", "Att::composeF4 -> name:\"value&quot;\"");
n = Nibbler ("name:val\"ue");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.ok (good, "Att::parse (name:val\"ue)");
t.is (a7.composeF4 (), "name:\"val&quot;ue\"", "Att::composeF4 -> name:\"val&quot;ue\"");
n = Nibbler ("name\"");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (name\")");
// Mods
n = Nibbler ("name.any:\"value\"");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.ok (good, "Att::parse (name.any:\"value\")");
t.is (a7.composeF4 (), "name:\"value\"", "Att::composeF4 -> name:\"value\"");
n = Nibbler ("name.bogus:\"value\"");
good = true;
try {a7.parse (n);} catch (...) {good = false;}
t.notok (good, "Att::parse (name.bogus:\"value\")");
// Att::type
t.is (a.type ("entry"), "date", "Att::type entry -> date");
t.is (a.type ("due"), "date", "Att::type due -> date");
t.is (a.type ("until"), "date", "Att::type until -> date");
t.is (a.type ("start"), "date", "Att::type start -> date");
t.is (a.type ("end"), "date", "Att::type end -> date");
t.is (a.type ("wait"), "date", "Att::type wait -> date");
t.is (a.type ("recur"), "duration", "Att::type recur -> duration");
t.is (a.type ("limit"), "number", "Att::type limit -> number");
t.is (a.type ("description"), "text", "Att::type description -> text");
t.is (a.type ("foo"), "text", "Att::type foo -> text");
// Att::validInternalName
t.ok (Att::validInternalName ("entry"), "internal entry");
t.ok (Att::validInternalName ("start"), "internal start");
t.ok (Att::validInternalName ("end"), "internal end");
t.ok (Att::validInternalName ("parent"), "internal parent");
t.ok (Att::validInternalName ("uuid"), "internal uuid");
t.ok (Att::validInternalName ("mask"), "internal mask");
t.ok (Att::validInternalName ("imask"), "internal imask");
t.ok (Att::validInternalName ("limit"), "internal limit");
t.ok (Att::validInternalName ("status"), "internal status");
t.ok (Att::validInternalName ("description"), "internal description");
// Att::validModifiableName
t.ok (Att::validModifiableName ("project"), "modifiable project");
t.ok (Att::validModifiableName ("priority"), "modifiable priority");
t.ok (Att::validModifiableName ("fg"), "modifiable fg");
t.ok (Att::validModifiableName ("bg"), "modifiable bg");
t.ok (Att::validModifiableName ("due"), "modifiable due");
t.ok (Att::validModifiableName ("recur"), "modifiable recur");
t.ok (Att::validModifiableName ("until"), "modifiable until");
t.ok (Att::validModifiableName ("wait"), "modifiable wait");
return 0;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -26,7 +26,10 @@
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <test.h>
#include <../task.h>
#include <util.h>
#include <main.h>
Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 7;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'basic.rc')
@@ -40,14 +40,13 @@ if (open my $fh, '>', 'basic.rc')
# Test the usage command.
my $output = qx{../task rc:basic.rc};
like ($output, qr/Usage: task/, 'usage');
like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'usage - url');
like ($output, qr/You must specify a command, or a task ID to modify/, 'missing command and ID');
# Test the version command.
$output = qx{../task rc:basic.rc version};
like ($output, qr/task \d+\.\d+\.\d+/, 'version - task version number');
like ($output, qr/ABSOLUTELY NO WARRANTY/, 'version - warranty');
like ($output, qr/http:\/\/www\.beckingham\.net\/task\.html/, 'version - url');
like ($output, qr/http:\/\/taskwarrior\.org/, 'version - url');
# Cleanup.
unlink 'basic.rc';

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 4;
use Test::More tests => 5;
# Create the rc file.
if (open my $fh, '>', 'bench.rc')
@@ -96,6 +96,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'bench.rc';
ok (!-r 'bench.rc', 'Removed bench.rc');

View File

@@ -38,3 +38,16 @@
ok 3 - Removed completed.data
ok 4 - Removed bench.rc
6/18/2009
1.8.0:
1..4
ok 1 - Created bench.rc
# start=1245372501
# 1000 tasks added in 4 seconds
# 600 tasks altered in 45 seconds
# stop=1245372747
# total=246
ok 2 - Removed pending.data
ok 3 - Removed completed.data
ok 4 - Removed bench.rc

57
src/tests/bug.annotate.t Executable file
View File

@@ -0,0 +1,57 @@
#! /usr/bin/perl
################################################################################
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 5;
# Create the rc file.
if (open my $fh, '>', 'bug_annotate.rc')
{
print $fh "data.location=.\n";
close $fh;
ok (-r 'bug_annotate.rc', 'Created bug_annotate.rc');
}
# Attempt a blank annotation.
qx{../task rc:bug_annotate.rc add foo};
my $output = qx{../task rc:bug_annotate.rc 1 annotate};
like ($output, qr/Cannot apply a blank annotation./, 'failed on blank annotation');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'bug_annotate.rc';
ok (!-r 'bug_annotate.rc', 'Removed bug_annotate.rc');
exit 0;

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 13;
use Test::More tests => 14;
# Create the rc file.
if (open my $fh, '>', 'annual.rc')
@@ -57,21 +57,24 @@ if (open my $fh, '>', 'annual.rc')
qx{../task rc:annual.rc add foo due:1/1/2000 recur:annual until:1/1/2009};
my $output = qx{../task rc:annual.rc list};
like ($output, qr/2\s+1\/1\/2000\s+- foo/, 'synthetic 1 no creep');
like ($output, qr/3\s+1\/1\/2001\s+- foo/, 'synthetic 2 no creep');
like ($output, qr/4\s+1\/1\/2002\s+- foo/, 'synthetic 3 no creep');
like ($output, qr/5\s+1\/1\/2003\s+- foo/, 'synthetic 4 no creep');
like ($output, qr/6\s+1\/1\/2004\s+- foo/, 'synthetic 5 no creep');
like ($output, qr/7\s+1\/1\/2005\s+- foo/, 'synthetic 6 no creep');
like ($output, qr/8\s+1\/1\/2006\s+- foo/, 'synthetic 7 no creep');
like ($output, qr/9\s+1\/1\/2007\s+- foo/, 'synthetic 8 no creep');
like ($output, qr/10\s+1\/1\/2008\s+- foo/, 'synthetic 9 no creep');
like ($output, qr/11\s+1\/1\/2009\s+- foo/, 'synthetic 10 no creep');
like ($output, qr/2\s+1\/1\/2000\s+-\s+foo/, 'synthetic 1 no creep');
like ($output, qr/3\s+1\/1\/2001\s+-\s+foo/, 'synthetic 2 no creep');
like ($output, qr/4\s+1\/1\/2002\s+-\s+foo/, 'synthetic 3 no creep');
like ($output, qr/5\s+1\/1\/2003\s+-\s+foo/, 'synthetic 4 no creep');
like ($output, qr/6\s+1\/1\/2004\s+-\s+foo/, 'synthetic 5 no creep');
like ($output, qr/7\s+1\/1\/2005\s+-\s+foo/, 'synthetic 6 no creep');
like ($output, qr/8\s+1\/1\/2006\s+-\s+foo/, 'synthetic 7 no creep');
like ($output, qr/9\s+1\/1\/2007\s+-\s+foo/, 'synthetic 8 no creep');
like ($output, qr/10\s+1\/1\/2008\s+-\s+foo/, 'synthetic 9 no creep');
like ($output, qr/11\s+1\/1\/2009\s+-\s+foo/, 'synthetic 10 no creep');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'annual.rc';
ok (!-r 'annual.rc', 'Removed annual.rc');

97
src/tests/bug.before.t Executable file
View File

@@ -0,0 +1,97 @@
#! /usr/bin/perl
################################################################################
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 20;
# Create the rc file.
if (open my $fh, '>', 'before.rc')
{
print $fh "data.location=.\n",
"confirmation=no\n",
"dateformat=m/d/Y\n";
close $fh;
ok (-r 'before.rc', 'Created before.rc');
}
# Create some exampel data directly.
if (open my $fh, '>', 'pending.data')
{
# 1230000000 = 12/22/2008
# 1240000000 = 4/17/2009
print $fh <<EOF;
[description:"foo" entry:"1230000000" start:"1230000000" status:"pending" uuid:"27097693-91c2-4cbe-ba89-ddcc87e5582c"]
[description:"bar" entry:"1240000000" start:"1240000000" status:"pending" uuid:"08f72d91-964c-424b-8fd5-556434648b6b"]
EOF
close $fh;
ok (-r 'pending.data', 'Created pending.data');
}
# Verify data is readable and just as expected.
my $output = qx{../task rc:before.rc 1 info};
like ($output, qr/Start\s+12\/22\/2008/, 'task 1 start date as expected');
$output = qx{../task rc:before.rc 2 info};
like ($output, qr/Start\s+4\/17\/2009/, 'task 2 start date as expected');
$output = qx{../task rc:before.rc ls start.before:12/1/2008};
unlike ($output, qr/foo/, 'no foo before 12/1/2008');
unlike ($output, qr/bar/, 'no bar before 12/1/2008');
$output = qx{../task rc:before.rc ls start.before:1/1/2009};
like ($output, qr/foo/, 'foo before 1/1/2009');
unlike ($output, qr/bar/, 'no bar before 1/1/2009');
$output = qx{../task rc:before.rc ls start.before:5/1/2009};
like ($output, qr/foo/, 'foo before 5/1/2009');
like ($output, qr/bar/, 'bar before 5/1/2009');
$output = qx{../task rc:before.rc ls start.after:12/1/2008};
like ($output, qr/foo/, 'foo after 12/1/2008');
like ($output, qr/bar/, 'bar after 12/1/2008');
$output = qx{../task rc:before.rc ls start.after:1/1/2009};
unlike ($output, qr/foo/, 'no foo after 1/1/2009');
like ($output, qr/bar/, 'bar after 1/1/2009');
$output = qx{../task rc:before.rc ls start.after:5/1/2009};
unlike ($output, qr/foo/, 'no foo after 5/1/2009');
unlike ($output, qr/bar/, 'no bar after 5/1/2009');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'before.rc';
ok (!-r 'before.rc', 'Removed before.rc');
exit 0;

86
src/tests/bug.bulk.t Executable file
View File

@@ -0,0 +1,86 @@
#! /usr/bin/perl
################################################################################
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 14;
# Create the rc file.
if (open my $fh, '>', 'bulk.rc')
{
print $fh "data.location=.\n",
"confirmation=yes\n",
"bulk=2\n";
close $fh;
ok (-r 'bulk.rc', 'Created bulk.rc');
}
# Add some tasks with project, prioriy and due date, some with only due date.
# Bulk add a project and priority to the tasks that were without.
qx{../task rc:bulk.rc add t1 pro:p1 pri:H due:monday};
qx{../task rc:bulk.rc add t2 pro:p1 pri:M due:tuesday};
qx{../task rc:bulk.rc add t3 pro:p1 pri:L due:wednesday};
qx{../task rc:bulk.rc add t4 due:thursday};
qx{../task rc:bulk.rc add t5 due:friday};
qx{../task rc:bulk.rc add t6 due:saturday};
my $output = qx{yes|../task rc:bulk.rc pro:p1 pri:M 4 5 6};
unlike ($output, qr/Task 4 "t4"\n - No changes were made/, 'Task 4 modified');
unlike ($output, qr/Task 5 "t5"\n - No changes were made/, 'Task 5 modified');
unlike ($output, qr/Task 6 "t6"\n - No changes were made/, 'Task 6 modified');
#diag ("---");
#diag ($output);
#diag ("---");
$output = qx{../task rc:bulk.rc info 4};
like ($output, qr/Project\s+p1/, 'project applied to 4');
like ($output, qr/Priority\s+M/, 'priority applied to 4');
$output = qx{../task rc:bulk.rc info 5};
like ($output, qr/Project\s+p1/, 'project applied to 5');
like ($output, qr/Priority\s+M/, 'priority applied to 5');
$output = qx{../task rc:bulk.rc info 6};
like ($output, qr/Project\s+p1/, 'project applied to 6');
like ($output, qr/Priority\s+M/, 'priority applied to 6');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'bulk.rc';
ok (!-r 'bulk.rc', 'Removed bulk.rc');
exit 0;

View File

@@ -28,12 +28,13 @@
use strict;
use warnings;
use Test::More tests => 6;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'bug_concat.rc')
{
print $fh "data.location=.\n";
print $fh "data.location=.\n",
"confirmation=no\n";
close $fh;
ok (-r 'bug_concat.rc', 'Created bug_concat.rc');
}
@@ -70,6 +71,9 @@ like ($output, qr/Description\s+aaa bbb:ccc ddd\n/, 'properly concatenated');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'bug_concat.rc';
ok (!-r 'bug_concat.rc', 'Removed bug_concat.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'hang.rc')
@@ -76,6 +76,9 @@ ok (!-r 'shadow.txt', 'Removed shadow.txt');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'hang.rc';
ok (!-r 'hang.rc', 'Removed hang.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 41;
use Test::More tests => 42;
# Create the rc file.
if (open my $fh, '>', 'period.rc')
@@ -75,61 +75,61 @@ Confirmed:
=cut
my $output = qx{../task rc:period.rc add daily due:tomorrow recur:daily};
like ($output, qr/^$/, 'recur:daily');
unlike ($output, qr/was not recignized/, 'recur:daily');
$output = qx{../task rc:period.rc add day due:tomorrow recur:day};
like ($output, qr/^$/, 'recur:day');
unlike ($output, qr/was not recignized/, 'recur:day');
$output = qx{../task rc:period.rc add weekly due:tomorrow recur:weekly};
like ($output, qr/^$/, 'recur:weekly');
unlike ($output, qr/was not recignized/, 'recur:weekly');
$output = qx{../task rc:period.rc add sennight due:tomorrow recur:sennight};
like ($output, qr/^$/, 'recur:sennight');
unlike ($output, qr/was not recignized/, 'recur:sennight');
$output = qx{../task rc:period.rc add biweekly due:tomorrow recur:biweekly};
like ($output, qr/^$/, 'recur:biweekly');
unlike ($output, qr/was not recignized/, 'recur:biweekly');
$output = qx{../task rc:period.rc add fortnight due:tomorrow recur:fortnight};
like ($output, qr/^$/, 'recur:fortnight');
unlike ($output, qr/was not recignized/, 'recur:fortnight');
$output = qx{../task rc:period.rc add monthly due:tomorrow recur:monthly};
like ($output, qr/^$/, 'recur:monthly');
unlike ($output, qr/was not recignized/, 'recur:monthly');
$output = qx{../task rc:period.rc add quarterly due:tomorrow recur:quarterly};
like ($output, qr/^$/, 'recur:quarterly');
unlike ($output, qr/was not recignized/, 'recur:quarterly');
$output = qx{../task rc:period.rc add semiannual due:tomorrow recur:semiannual};
like ($output, qr/^$/, 'recur:semiannual');
unlike ($output, qr/was not recignized/, 'recur:semiannual');
$output = qx{../task rc:period.rc add bimonthly due:tomorrow recur:bimonthly};
like ($output, qr/^$/, 'recur:bimonthly');
unlike ($output, qr/was not recignized/, 'recur:bimonthly');
$output = qx{../task rc:period.rc add biannual due:tomorrow recur:biannual};
like ($output, qr/^$/, 'recur:biannual');
unlike ($output, qr/was not recignized/, 'recur:biannual');
$output = qx{../task rc:period.rc add biyearly due:tomorrow recur:biyearly};
like ($output, qr/^$/, 'recur:biyearly');
unlike ($output, qr/was not recignized/, 'recur:biyearly');
$output = qx{../task rc:period.rc add annual due:tomorrow recur:annual};
like ($output, qr/^$/, 'recur:annual');
unlike ($output, qr/was not recignized/, 'recur:annual');
$output = qx{../task rc:period.rc add yearly due:tomorrow recur:yearly};
like ($output, qr/^$/, 'recur:yearly');
unlike ($output, qr/was not recignized/, 'recur:yearly');
$output = qx{../task rc:period.rc add 2d due:tomorrow recur:2d};
like ($output, qr/^$/, 'recur:2m');
unlike ($output, qr/was not recignized/, 'recur:2m');
$output = qx{../task rc:period.rc add 2w due:tomorrow recur:2w};
like ($output, qr/^$/, 'recur:2q');
unlike ($output, qr/was not recignized/, 'recur:2q');
$output = qx{../task rc:period.rc add 2m due:tomorrow recur:2m};
like ($output, qr/^$/, 'recur:2d');
unlike ($output, qr/was not recignized/, 'recur:2d');
$output = qx{../task rc:period.rc add 2q due:tomorrow recur:2q};
like ($output, qr/^$/, 'recur:2w');
unlike ($output, qr/was not recignized/, 'recur:2w');
$output = qx{../task rc:period.rc add 2y due:tomorrow recur:2y};
like ($output, qr/^$/, 'recur:2y');
unlike ($output, qr/was not recignized/, 'recur:2y');
# Verify that the recurring task instances get created. One of each.
$output = qx{../task rc:period.rc list};
@@ -157,6 +157,9 @@ like ($output, qr/\b2y\b/, 'verify 2y');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'period.rc';
ok (!-r 'period.rc', 'Removed period.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'bug_sort.rc')
@@ -54,6 +54,9 @@ like ($output, qr/three.*one.*two/msi, 'list did not hang after pri:H on 1');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'bug_sort.rc';
ok (!-r 'bug_sort.rc', 'Removed bug_sort.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 6;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'summary.rc')
@@ -60,6 +60,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'summary.rc';
ok (!-r 'summary.rc', 'Removed summary.rc');

98
src/tests/bug.uuid.t Executable file
View File

@@ -0,0 +1,98 @@
#! /usr/bin/perl
################################################################################
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'uuid.rc')
{
print $fh "data.location=.\n",
"confirmation=no\n";
close $fh;
ok (-r 'uuid.rc', 'Created uuid.rc');
}
# Add a task, dup it, add a recurring task, list. Then make sure they all have
# unique UUID values.
qx{../task rc:uuid.rc add simple};
qx{../task rc:uuid.rc 1 duplicate};
qx{../task rc:uuid.rc add periodic recur:daily due:yesterday};
my $output = qx{../task rc:uuid.rc stats};
my @all_uuids;
my %unique_uuids;
$output = qx{../task rc:uuid.rc 1 info};
my ($uuid) = $output =~ /UUID\s+(\S+)/;
push @all_uuids, $uuid;
$unique_uuids{$uuid} = undef;
$output = qx{../task rc:uuid.rc 2 info};
($uuid) = $output =~ /UUID\s+(\S+)/;
push @all_uuids, $uuid;
$unique_uuids{$uuid} = undef;
$output = qx{../task rc:uuid.rc 3 info};
($uuid) = $output =~ /UUID\s+(\S+)/;
push @all_uuids, $uuid;
$unique_uuids{$uuid} = undef;
$output = qx{../task rc:uuid.rc 4 info};
($uuid) = $output =~ /UUID\s+(\S+)/;
push @all_uuids, $uuid;
$unique_uuids{$uuid} = undef;
$output = qx{../task rc:uuid.rc 5 info};
($uuid) = $output =~ /UUID\s+(\S+)/;
push @all_uuids, $uuid;
$unique_uuids{$uuid} = undef;
$output = qx{../task rc:uuid.rc 6 info};
($uuid) = $output =~ /UUID\s+(\S+)/;
push @all_uuids, $uuid;
$unique_uuids{$uuid} = undef;
is (scalar (@all_uuids), 6, '6 tasks created');
is (scalar (keys %unique_uuids), 6, '6 unique UUIDs');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'uuid.rc';
ok (!-r 'uuid.rc', 'Removed uuid.rc');
exit 0;

125
src/tests/cal.t Executable file
View File

@@ -0,0 +1,125 @@
#! /usr/bin/perl
################################################################################
## task - a command line task list manager.
##
## Copyright 2006 - 2009, Paul Beckingham.
## All rights reserved.
##
## Unit test cal.t originally writen by Federico Hernandez
##
## 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
##
################################################################################
use strict;
use warnings;
use Test::More tests => 36;
# Create the rc file.
if (open my $fh, '>', 'cal.rc')
{
print $fh "data.location=.\n",
"dateformat=YMD\n",
"color=on\n",
"confirmation=no\n";
close $fh;
ok (-r 'cal.rc', 'Created cal.rc');
}
my ($day, $nmon, $nyear) = (localtime)[3,4,5];
my $nextmonth = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[($nmon+1) % 12];
my $month = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[($nmon) % 12];
my $prevmonth = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[($nmon-1) % 12];
my $nextyear = $nyear + 1901;
my $year = $nyear + 1900;
if ( $day <= 9)
{
$day = " ".$day;
}
# task cal and task cal y
my $output = qx{../task rc:cal.rc rc._forcecolor:on cal};
like ($output, qr/\[36m$day/, 'Current day is highlighted');
like ($output, qr/$month.* $year/, 'Current month and year are displayed');
qx{../task rc:cal.rc add zero};
unlike ($output, qr/\[41m\d+/, 'No overdue tasks are present');
unlike ($output, qr/\[43m\d+/, 'No due tasks are present');
$output = qx{../task rc:cal.rc rc.weekstart:Sunday cal};
like ($output, qr/Su Mo Tu/, 'Week starts on Sunday');
$output = qx{../task rc:cal.rc rc.weekstart:Monday cal};
like ($output, qr/Fr Sa Su/, 'Week starts on Monday');
$output = qx{../task rc:cal.rc cal y};
like ($output, qr/$month.* $year/, 'Current month and year are displayed');
like ($output, qr/$prevmonth.* $nextyear/, 'Month and year one year ahead are displayed');
unlike ($output, qr/$month.* $nextyear/, 'Current month and year ahead are not displayed');
# task cal due and task cal due y
qx{../task rc:cal.rc add due:20190515 one};
qx{../task rc:cal.rc add due:20200123 two};
$output = qx{../task rc:cal.rc rc._forcecolor:on cal due};
unlike ($output, qr/April 2019/, 'April 2019 is not displayed');
like ($output, qr/May 2019/, 'May 2019 is displayed');
unlike ($output, qr/January 2020/, 'January 2020 is not displayed');
like ($output, qr/\[43m15/, 'Task 1 is color-coded due');
$output = qx{../task rc:cal.rc rc._forcecolor:on cal due y};
like ($output, qr/\[43m23/, 'Task 2 is color-coded due');
like ($output, qr/April 2020/, 'April 2020 is displayed');
unlike ($output, qr/May 2020/, 'May 2020 is not displayed');
qx{../task rc:cal.rc ls};
qx{../task rc:cal.rc del 1-3};
qx{../task rc:cal.rc add due:20080408 three};
$output = qx{../task rc:cal.rc rc._forcecolor:on cal due};
like ($output, qr/April 2008/, 'April 2008 is displayed');
like ($output, qr/\[41m 8/, 'Task 3 is color-coded overdue');
# task cal 2016
$output = qx{../task rc:cal.rc rc.weekstart:Monday cal 2016};
unlike ($output, qr/2015/, 'Year 2015 is not displayed');
unlike ($output, qr/2017/, 'Year 2017 is not displayed');
like ($output, qr/January 2016/, 'January 2016 is displayed');
like ($output, qr/December 2016/, 'December 2016 is displayed');
like ($output, qr/53 +1/, '2015 has 53 weeks (ISO)');
like ($output, qr/1 +4/, 'First week in 2016 starts with Mon Jan 4 (ISO)');
like ($output, qr/52 +26/, 'Last week in 2016 starts with Mon Dec 26 (ISO)');
like ($output, qr/9 +29/, 'Leap year - Feb 29 is Monday in week 9 (ISO)');
$output = qx{../task rc:cal.rc rc.weekstart:Sunday cal 2016};
like ($output, qr/1 +1/, 'First week in 2016 starts with Fri Jan 1 (US)');
like ($output, qr/53 +25/, 'Last week in 2016 starts with Sun Dec 25 (US)');
$output = qx{../task rc:cal.rc rc.weekstart:Monday rc.displayweeknumber:off cal 2016};
unlike ($output, qr/53/, 'Weeknumbers are not displayed');
# task cal 4 2010
$output = qx{../task rc:cal.rc rc.monthsperline:1 cal 4 2010};
unlike ($output, qr/March 2010/, 'March 2010 is not displayed');
like ($output, qr/April 2010/, 'April 2010 is displayed');
unlike ($output, qr/May 2010/, 'May 2010 is not displayed');
# Cleanup.
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'cal.rc';
ok (!-r 'cal.rc', 'Removed cal.rc');
exit 0;

191
src/tests/cmd.t.cpp Normal file
View File

@@ -0,0 +1,191 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <Context.h>
#include <Cmd.h>
#include <test.h>
Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (78);
context.config.set ("report.foo.columns", "id");
Cmd cmd;
cmd.command = "active";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand active");
t.notok (cmd.isWriteCommand (), "not isWriteCommand active");
cmd.command = "calendar";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand calendar");
t.notok (cmd.isWriteCommand (), "not isWriteCommand calendar");
cmd.command = "colors";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand colors");
t.notok (cmd.isWriteCommand (), "not isWriteCommand colors");
cmd.command = "completed";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand completed");
t.notok (cmd.isWriteCommand (), "not isWriteCommand completed");
cmd.command = "export";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand export");
t.notok (cmd.isWriteCommand (), "not isWriteCommand export");
cmd.command = "help";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand help");
t.notok (cmd.isWriteCommand (), "not isWriteCommand help");
cmd.command = "history";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand history");
t.notok (cmd.isWriteCommand (), "not isWriteCommand history");
cmd.command = "ghistory";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand ghistory");
t.notok (cmd.isWriteCommand (), "not isWriteCommand ghistory");
cmd.command = "info";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand info");
t.notok (cmd.isWriteCommand (), "not isWriteCommand info");
cmd.command = "next";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand next");
t.notok (cmd.isWriteCommand (), "not isWriteCommand next");
cmd.command = "overdue";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand overdue");
t.notok (cmd.isWriteCommand (), "not isWriteCommand overdue");
cmd.command = "projects";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand projects");
t.notok (cmd.isWriteCommand (), "not isWriteCommand projects");
cmd.command = "stats";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand stats");
t.notok (cmd.isWriteCommand (), "not isWriteCommand stats");
cmd.command = "summary";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand summary");
t.notok (cmd.isWriteCommand (), "not isWriteCommand summary");
cmd.command = "tags";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand tags");
t.notok (cmd.isWriteCommand (), "not isWriteCommand tags");
cmd.command = "timesheet";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand timesheet");
t.notok (cmd.isWriteCommand (), "not isWriteCommand timesheet");
cmd.command = "version";
t.ok (cmd.isReadOnlyCommand (), "isReadOnlyCommand version");
t.notok (cmd.isWriteCommand (), "not isWriteCommand version");
cmd.command = "_projects";
t.ok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand _projects");
t.notok (cmd.isWriteCommand (), "isWriteCommand _projects");
cmd.command = "_tags";
t.ok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand _tags");
t.notok (cmd.isWriteCommand (), "isWriteCommand _tags");
cmd.command = "add";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand add");
t.ok (cmd.isWriteCommand (), "isWriteCommand add");
cmd.command = "append";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand append");
t.ok (cmd.isWriteCommand (), "isWriteCommand append");
cmd.command = "annotate";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand annotate");
t.ok (cmd.isWriteCommand (), "isWriteCommand annotate");
cmd.command = "delete";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand delete");
t.ok (cmd.isWriteCommand (), "isWriteCommand delete");
cmd.command = "done";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand done");
t.ok (cmd.isWriteCommand (), "isWriteCommand done");
cmd.command = "duplicate";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand duplicate");
t.ok (cmd.isWriteCommand (), "isWriteCommand duplicate");
cmd.command = "edit";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand edit");
t.ok (cmd.isWriteCommand (), "isWriteCommand edit");
cmd.command = "import";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand import");
t.ok (cmd.isWriteCommand (), "isWriteCommand import");
cmd.command = "start";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand start");
t.ok (cmd.isWriteCommand (), "isWriteCommand start");
cmd.command = "stop";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand stop");
t.ok (cmd.isWriteCommand (), "isWriteCommand stop");
cmd.command = "undo";
t.notok (cmd.isReadOnlyCommand (), "not isReadOnlyCommand undo");
t.ok (cmd.isWriteCommand (), "isWriteCommand undo");
t.ok (cmd.valid ("annotate"), "Cmd::valid annotate");
t.ok (cmd.valid ("annotat"), "Cmd::valid annotat");
t.ok (cmd.valid ("annota"), "Cmd::valid annota");
t.ok (cmd.valid ("annot"), "Cmd::valid annot");
t.ok (cmd.valid ("anno"), "Cmd::valid anno");
t.ok (cmd.valid ("ann"), "Cmd::valid ann");
t.ok (cmd.valid ("an"), "Cmd::valid an");
t.ok (cmd.valid ("ANNOTATE"), "Cmd::valid ANNOTATE");
t.ok (cmd.valid ("ANNOTAT"), "Cmd::valid ANNOTAT");
t.ok (cmd.valid ("ANNOTA"), "Cmd::valid ANNOTA");
t.ok (cmd.valid ("ANNOT"), "Cmd::valid ANNOT");
t.ok (cmd.valid ("ANNO"), "Cmd::valid ANNO");
t.ok (cmd.valid ("ANN"), "Cmd::valid ANN");
t.ok (cmd.valid ("AN"), "Cmd::valid AN");
t.ok (cmd.validCustom ("foo"), "Cmd::validCustom foo");
t.notok (cmd.validCustom ("bar"), "Cmd::validCustom bar -> fail");
bool good = true;
try { cmd.parse ("a"); } catch (...) { good = false; }
t.notok (good, "Cmd::parse a -> fail");
good = true;
try { cmd.parse ("add"); } catch (...) { good = false; }
t.ok (good, "Cmd::parse add");
return 0;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -53,6 +53,9 @@ like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.active');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 6;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -51,6 +51,9 @@ unlike ($output, qr/\033\[0m/, 'color.disable - no color reset');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m .* red .* \033\[0m/x, 'color.due');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 6;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -55,6 +55,9 @@ like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.keywo
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m .* red .* \033\[0m/x, 'color.overdue');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 7;
use Test::More tests => 8;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -59,6 +59,9 @@ like ($output, qr/ \033\[33m .* yellow .* \033\[0m /x, 'color.pri.none');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.project.re
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.recur
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

70
src/tests/color.t.cpp Normal file
View File

@@ -0,0 +1,70 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <Context.h>
#include <color.h>
#include <test.h>
Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
const char* colors[] =
{
"off",
"bold", "underline", "bold_underline",
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
"bold_black", "bold_red", "bold_green", "bold_yellow", "bold_blue",
"bold_magenta", "bold_cyan", "bold_white",
"underline_black", "underline_red", "underline_green", "underline_yellow",
"underline_blue", "underline_magenta", "underline_cyan", "underline_white",
"bold_underline_black", "bold_underline_red", "bold_underline_green",
"bold_underline_yellow", "bold_underline_blue", "bold_underline_magenta",
"bold_underline_cyan", "bold_underline_white",
"on_black", "on_red", "on_green", "on_yellow", "on_blue", "on_magenta",
"on_cyan", "on_white",
"on_bright_black", "on_bright_red", "on_bright_green", "on_bright_yellow",
"on_bright_blue", "on_bright_magenta", "on_bright_cyan", "on_bright_white",
};
#define NUM_COLORS (sizeof (colors) / sizeof (colors[0]))
UnitTest t (NUM_COLORS + 2);
for (unsigned int i = 0; i < NUM_COLORS; ++i)
t.is (std::string (colors[i]),
Text::colorName (Text::colorCode (colors[i])),
std::string ("round-trip ") + colors[i]);
t.is (Text::colorName (Text::nocolor), "", "nocolor == \'\'");
t.is (Text::colorCode (""), Text::nocolor, "\'\' == nocolor");
return 0;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 6;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -55,6 +55,9 @@ like ($output, qr/ \033\[32m .* green .* \033\[0m /x, 'color.tag.green'
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'color.rc')
@@ -52,6 +52,9 @@ like ($output, qr/ \033\[31m .* red .* \033\[0m /x, 'color.tagged');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'color.rc';
ok (!-r 'color.rc', 'Removed color.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 6;
use Test::More tests => 7;
# Create the rc file.
if (open my $fh, '>', 'completed.rc')
@@ -57,6 +57,9 @@ ok (!-r 'pending.data', 'Removed pending.data');
unlink 'completed.data';
ok (!-r 'completed.data', 'Removed completed.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'completed.rc';
ok (!-r 'completed.rc', 'Removed completed.rc');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 5;
use Test::More tests => 6;
# Create the rc file.
if (open my $fh, '>', 'obsolete.rc')
@@ -50,6 +50,9 @@ like ($output, qr/ foo\n/, 'unsupported configuration variable');
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'obsolete.rc';
ok (!-r 'obsolete.rc', 'Removed obsolete.rc');

111
src/tests/config.t.cpp Normal file
View File

@@ -0,0 +1,111 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <Context.h>
#include <test.h>
Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (18);
// void set (const std::string&, const int);
// int get (const std::string&, const int);
Config c;
c.set ("int1", 0);
t.is (c.get ("int1", 9), 0, "Config::set/get int");
c.set ("int2", 3);
t.is (c.get ("int2", 9), 3, "Config::set/get int");
c.set ("int3", -9);
t.is (c.get ("int3", 9), -9, "Config::set/get int");
// void set (const std::string&, const double);
// double get (const std::string&, const double);
c.set ("double1", 0.0);
t.is (c.get ("double1", 9.0), 0.0, "Config::set/get double");
c.set ("double2", 3.0);
t.is (c.get ("double2", 9.0), 3.0, "Config::set/get double");
c.set ("double3", -9.0);
t.is (c.get ("double3", 9.0), -9.0, "Config::set/get double");
// void set (const std::string&, const std::string&);
c.set ("str1", "one");
t.is (c.get ("str1", ""), "one", "Config::set/get std::string");
c.set ("str1", "");
t.is (c.get ("str1", "no"), "", "Config::set/get std::string");
// const std::string get (const char*);
c.set ("str1", "one");
t.is (c.get ((char*) "str1"), (char*)"one", "Config::set/get char*");
// const std::string get (const char*, const char*);
c.set ("str1", "one");
t.is (c.get ((char*)"str1", (char*)""), "one", "Config::set/get char*");
c.set ("str1", "");
t.is (c.get ((char*)"str1", (char*)"no"), "", "Config::set/get char*");
// const std::string get (const std::string&);
c.set ("str1", "one");
t.is (c.get (std::string ("str1")), "one", "Config::set/get std::string");
c.set ("str1", "");
t.is (c.get (std::string ("str1")), "", "Config::set/get std::string");
// const std::string get (const std::string&, const std::string&);
c.set ("str1", "one");
t.is (c.get (std::string ("str1"), std::string ("no")), "one", "Config::set/get std::string");
c.set ("str1", "");
t.is (c.get (std::string ("str1"), std::string ("no")), "", "Config::set/get std::string");
// bool get (const std::string&, const bool);
c.set ("bool1", false);
t.is (c.get (std::string ("bool1"), (bool)true), false, "Config::set/get bool");
c.set ("bool1", true);
t.is (c.get (std::string ("bool1"), (bool)false), true, "Config::set/get bool");
// void all (std::vector <std::string>&);
std::vector <std::string> all;
c.all (all);
// 8 created in this test program.
// 22 default report setting created in Config::Config.
t.ok (all.size () >= 8, "Config::all");
return 0;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 26;
use Test::More tests => 27;
# Create the rc file.
if (open my $fh, '>', 'confirm.rc')
@@ -99,6 +99,9 @@ like ($output, qr/(Permanently delete task 7 'foo'\? \(y\/n\)) \1 \1/, 'confirma
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'response.txt';
ok (!-r 'response.txt', 'Removed response.txt');

View File

@@ -28,7 +28,7 @@
use strict;
use warnings;
use Test::More tests => 4;
use Test::More tests => 5;
# Create the rc file.
if (open my $fh, '>', 'custom.rc')
@@ -50,6 +50,9 @@ like ($output, qr/Unrecognized column name: foo\n/, 'custom report spotted inval
unlink 'pending.data';
ok (!-r 'pending.data', 'Removed pending.data');
unlink 'undo.data';
ok (!-r 'undo.data', 'Removed undo.data');
unlink 'custom.rc';
ok (!-r 'custom.rc', 'Removed custom.rc');

Some files were not shown because too many files have changed in this diff Show More