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:
761
src/Att.cpp
Normal file
761
src/Att.cpp
Normal 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;
|
||||
// " -> "
|
||||
// , -> ,
|
||||
// [ -> &open;
|
||||
// ] -> &close;
|
||||
// : -> :
|
||||
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, """); // no i18n
|
||||
|
||||
while ((i = value.find (',')) != std::string::npos)
|
||||
value.replace (i, 1, ","); // 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, ":"); // no i18n
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Decode values after parse.
|
||||
// \t <- &tab;
|
||||
// " <- "
|
||||
// , <- ,
|
||||
// [ <- &open;
|
||||
// ] <- &close;
|
||||
// : <- :
|
||||
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 (""")) != std::string::npos) // no i18n
|
||||
value.replace (i, 6, "\"");
|
||||
|
||||
while ((i = value.find (",")) != 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 (":")) != std::string::npos) // no i18n
|
||||
value.replace (i, 7, ":");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
84
src/Att.h
Normal file
84
src/Att.h
Normal 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
234
src/Cmd.cpp
Normal 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
64
src/Cmd.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
362
src/Config.cpp
362
src/Config.cpp
@@ -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;
|
||||
|
||||
@@ -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
757
src/Context.cpp
Normal 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
98
src/Context.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
84
src/Date.cpp
84
src/Date.cpp
@@ -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.
|
||||
|
||||
@@ -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
205
src/Duration.cpp
Normal 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
55
src/Duration.h
Normal 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
134
src/Filter.cpp
Normal 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
43
src/Filter.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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);
|
||||
|
||||
@@ -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
66
src/Keymap.cpp
Normal 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
51
src/Keymap.h
Normal 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
73
src/Location.cpp
Normal 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
50
src/Location.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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
304
src/Nibbler.cpp
Normal 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
62
src/Nibbler.h
Normal 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
74
src/Permission.cpp
Normal 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
50
src/Permission.h
Normal 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
183
src/Record.cpp
Normal 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
56
src/Record.h
Normal 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
162
src/Sequence.cpp
Normal 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
51
src/Sequence.h
Normal 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
98
src/StringTable.cpp
Normal 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
47
src/StringTable.h
Normal 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
178
src/Subst.cpp
Normal 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
54
src/Subst.h
Normal 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
731
src/T.cpp
@@ -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
113
src/T.h
@@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
1042
src/TDB.cpp
1042
src/TDB.cpp
File diff suppressed because it is too large
Load Diff
62
src/TDB.h
62
src/TDB.h
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
642
src/Task.cpp
Normal 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
83
src/Task.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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 ());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -35,6 +35,8 @@ class Timer
|
||||
public:
|
||||
Timer (const std::string&);
|
||||
~Timer ();
|
||||
Timer (const Timer&);
|
||||
Timer& operator= (const Timer&);
|
||||
|
||||
private:
|
||||
std::string mDescription;
|
||||
|
||||
307
src/color.cpp
307
src/color.cpp
@@ -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", "[0m" },
|
||||
|
||||
{ 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];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
1345
src/command.cpp
1345
src/command.cpp
File diff suppressed because it is too large
Load Diff
618
src/custom.cpp
Normal file
618
src/custom.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
435
src/edit.cpp
435
src/edit.cpp
@@ -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
197
src/i18n.h
Normal 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
|
||||
|
||||
353
src/import.cpp
353
src/import.cpp
@@ -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
169
src/interactive.cpp
Normal 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
76
src/main.cpp
Normal 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
165
src/main.h
Normal 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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
623
src/parse.cpp
623
src/parse.cpp
@@ -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
467
src/recur.cpp
Normal 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
2847
src/report.cpp
2847
src/report.cpp
File diff suppressed because it is too large
Load Diff
129
src/rules.cpp
129
src/rules.cpp
@@ -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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
1395
src/task.cpp
1395
src/task.cpp
File diff suppressed because it is too large
Load Diff
220
src/task.h
220
src/task.h
@@ -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
13
src/tests/.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
69
src/tests/alias.t
Executable 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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
74
src/tests/args.t
Executable 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
287
src/tests/att.t.cpp
Normal 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:""), "Att::valid "");
|
||||
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:\""\"", "Att::composeF4 encoded \"");
|
||||
a5.value ("\t\",[]:");
|
||||
t.is (a5.composeF4 (), "name:\"&tab;",&open;&close;:\"", "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:\""\"");
|
||||
a7.parse (n);
|
||||
t.is (a7.composeF4 (), "name:\""\"",
|
||||
"Att::parse (name:\""\")");
|
||||
|
||||
n = Nibbler ("name:\"&tab;",&open;&close;:\"");
|
||||
a7.parse (n);
|
||||
t.is (a7.composeF4 (), "name:\"&tab;",&open;&close;:\"",
|
||||
"Att::parse (name:\"&tab;",&open;&close;:\")");
|
||||
|
||||
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:\""value\"", "Att::composeF4 -> name:\""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"\"", "Att::composeF4 -> name:\"value"\"");
|
||||
|
||||
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"ue\"", "Att::composeF4 -> name:\"val"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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
57
src/tests/bug.annotate.t
Executable 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;
|
||||
|
||||
@@ -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
97
src/tests/bug.before.t
Executable 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
86
src/tests/bug.bulk.t
Executable 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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
98
src/tests/bug.uuid.t
Executable 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
125
src/tests/cal.t
Executable 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
191
src/tests/cmd.t.cpp
Normal 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
70
src/tests/color.t.cpp
Normal 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
111
src/tests/config.t.cpp
Normal 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user