mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Integration - attribute validation
- Implemented digitsOnly primitive. - Implemented noSpaces primitive. - Added unit tests for above. - Att now manages the lists of valid attributes and modifier names. - validName migrated to Att. - validModifiableName migrated to Att. - New Att::validNameValue. - Removed obsolete validDescription. - Removed obsolete validPriority. - Removed obsolete valid.cpp/guess. - Implemented text.cpp/noVerticalSpace. - Added unit tests for text.cpp/noVerticalSpace. - Removed final static lists from valid.cpp.
This commit is contained in:
parent
eda17772c9
commit
25d27bec93
11 changed files with 287 additions and 151 deletions
233
src/Att.cpp
233
src/Att.cpp
|
@ -28,9 +28,56 @@
|
|||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include "text.h"
|
||||
#include "color.h"
|
||||
#include "util.h"
|
||||
#include "Date.h"
|
||||
#include "Duration.h"
|
||||
#include "Att.h"
|
||||
|
||||
static char* internalNames[] =
|
||||
{
|
||||
"entry",
|
||||
"start",
|
||||
"end",
|
||||
"mask",
|
||||
"imask",
|
||||
// "limit",
|
||||
};
|
||||
|
||||
static char* modifiableNames[] =
|
||||
{
|
||||
"project",
|
||||
"priority",
|
||||
"fg",
|
||||
"bg",
|
||||
"due",
|
||||
"recur",
|
||||
"until",
|
||||
};
|
||||
|
||||
static char* modifierNames[] =
|
||||
{
|
||||
"before",
|
||||
"after",
|
||||
"under",
|
||||
"over",
|
||||
"below",
|
||||
"above",
|
||||
"none",
|
||||
"any",
|
||||
"is",
|
||||
"isnt",
|
||||
"has",
|
||||
"hasnt",
|
||||
"contains",
|
||||
"startswith",
|
||||
"endswith",
|
||||
};
|
||||
|
||||
#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 ("")
|
||||
|
@ -106,6 +153,7 @@ Att::~Att ()
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// For parsing.
|
||||
bool Att::valid (const std::string& input) const
|
||||
{
|
||||
Nibbler n (input);
|
||||
|
@ -132,6 +180,173 @@ bool Att::valid (const std::string& input) const
|
|||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TODO Obsolete
|
||||
bool Att::validName (const std::string& name)
|
||||
{
|
||||
if (validModifiableName (name))
|
||||
return true;
|
||||
|
||||
for (unsigned int i = 0; i < NUM_INTERNAL_NAMES; ++i)
|
||||
if (name == internalNames[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TODO Obsolete
|
||||
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;
|
||||
bool status = Att::validNameValue (writableName, writableMod, writableValue);
|
||||
|
||||
if (name != writableName)
|
||||
throw std::string ("The attribute '") + name + "' was not fully qualified.";
|
||||
|
||||
if (mod != writableMod)
|
||||
throw std::string ("The modifier '") + mod + "' was not fully qualified.";
|
||||
|
||||
if (value != writableValue)
|
||||
throw std::string ("The value '") + value + "' was not fully qualified.";
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
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)
|
||||
throw std::string ("Unrecognized attribute '") + name + "'";
|
||||
|
||||
else if (matches.size () != 1)
|
||||
{
|
||||
std::string error = "Ambiguous attribute '" + name + "' - could be either of "; // TODO i18n
|
||||
|
||||
std::string combined;
|
||||
join (combined, ", ", matches);
|
||||
error += combined;
|
||||
|
||||
throw error + combined;
|
||||
}
|
||||
|
||||
name = matches[0];
|
||||
|
||||
// Second, guess at the modifier name.
|
||||
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 '") + name + "'";
|
||||
|
||||
else if (matches.size () != 1)
|
||||
{
|
||||
std::string error = "Ambiguous modifier '" + name + "' - could be either of "; // TODO i18n
|
||||
|
||||
std::string combined;
|
||||
join (combined, ", ", matches);
|
||||
error += combined;
|
||||
|
||||
throw error + combined;
|
||||
}
|
||||
|
||||
mod = matches[0];
|
||||
|
||||
// Thirdly, make sure the value has the expected form or values.
|
||||
if (name == "project" && !noSpaces (value))
|
||||
throw std::string ("The '") + name + "' attribute may not contain spaces.";
|
||||
|
||||
else if (name == "priority" && 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" && (value != "" || !noVerticalSpace (value)))
|
||||
throw std::string ("The '") + name + "' attribute must not be blank, and must not contain vertical white space.";
|
||||
|
||||
else if ((name == "fg" || name == "bg") && value != "")
|
||||
Text::guessColor (value);
|
||||
|
||||
else if (name == "due" && value != "")
|
||||
Date (value);
|
||||
|
||||
else if (name == "until" && value != "")
|
||||
Date (value);
|
||||
|
||||
else if (name == "recur" && value != "")
|
||||
Duration (value);
|
||||
|
||||
else if (name == "limit" && (value == "" || !digitsOnly (value)))
|
||||
throw std::string ("The '") + name + "' attribute must be an integer.";
|
||||
|
||||
// Some attributes are intended to be private.
|
||||
else if (name == "entry" ||
|
||||
name == "start" ||
|
||||
name == "end" ||
|
||||
name == "mask" ||
|
||||
name == "imask" ||
|
||||
name == "uuid" ||
|
||||
name == "status")
|
||||
throw std::string ("\"") +
|
||||
name +
|
||||
"\" is not an attribute you may modify directly.";
|
||||
|
||||
else
|
||||
throw std::string ("'") + name + "' is an unrecognized 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// start --> name --> . --> mod --> : --> " --> value --> " --> end
|
||||
|
@ -187,22 +402,10 @@ void Att::parse (Nibbler& n)
|
|||
}
|
||||
else
|
||||
throw std::string ("Missing : after attribute name"); // TODO i18n
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool Att::validMod (const std::string& mod) const
|
||||
{
|
||||
if (mod == "before" || mod == "after" || // i18n: TODO
|
||||
mod == "under" || mod == "over" || // i18n: TODO
|
||||
mod == "below" || mod == "above" || // i18n: TODO
|
||||
mod == "none" || mod == "any" || // i18n: TODO
|
||||
mod == "is" || mod == "isnt" || // i18n: TODO
|
||||
mod == "has" || mod == "hasnt" || // i18n: TODO
|
||||
mod == "contains" || // i18n: TODO
|
||||
mod == "startswith" || mod == "endswith") // i18n: TODO
|
||||
return true;
|
||||
|
||||
return false;
|
||||
/* TODO This might be too slow to include. Test.
|
||||
validNameValue (mName, mMod, mValue);
|
||||
*/
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -44,9 +44,13 @@ public:
|
|||
~Att ();
|
||||
|
||||
bool valid (const std::string&) const;
|
||||
static bool validName (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&);
|
||||
void parse (const std::string&);
|
||||
void parse (Nibbler&);
|
||||
bool validMod (const std::string&) const;
|
||||
bool match (const Att&) const;
|
||||
|
||||
std::string composeF4 () const;
|
||||
|
|
|
@ -440,7 +440,7 @@ std::cout << "[1;31m# parse post-termination description '" << *arg << "'[0m"
|
|||
}
|
||||
}
|
||||
|
||||
if (validDescription (descCandidate))
|
||||
if (noVerticalSpace (descCandidate))
|
||||
task.set ("description", descCandidate);
|
||||
|
||||
// TODO task.validate ()
|
||||
|
|
|
@ -69,7 +69,7 @@ std::string handleAdd ()
|
|||
if (context.task.get ("priority") == "")
|
||||
{
|
||||
std::string defaultPriority = context.config.get ("default.priority", "");
|
||||
if (validPriority (defaultPriority))
|
||||
if (Att::validNameValue ("priority", "", defaultPriority))
|
||||
context.task.set ("priority", defaultPriority);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <string.h>
|
||||
#include "T.h"
|
||||
#include "Date.h"
|
||||
#include "Duration.h"
|
||||
#include "text.h"
|
||||
#include "util.h"
|
||||
#include "main.h"
|
||||
|
@ -211,7 +212,7 @@ static void parseTask (T& task, const std::string& after)
|
|||
{
|
||||
if (value != "")
|
||||
{
|
||||
if (validPriority (value))
|
||||
if (Att::validNameValue ("priority", "", value))
|
||||
{
|
||||
std::cout << "Priority modified." << std::endl;
|
||||
task.setAttribute ("priority", value);
|
||||
|
@ -392,7 +393,8 @@ static void parseTask (T& task, const std::string& after)
|
|||
{
|
||||
if (value != "")
|
||||
{
|
||||
if (validDuration (value))
|
||||
Duration d;
|
||||
if (d.valid (value))
|
||||
{
|
||||
std::cout << "Recurrence modified." << std::endl;
|
||||
if (task.getAttribute ("due") != "")
|
||||
|
|
|
@ -175,7 +175,7 @@ static void decorateTask (T& task)
|
|||
std::string defaultPriority = context.config.get ("default.priority", "");
|
||||
if (task.getAttribute ("priority") == "" &&
|
||||
defaultPriority != "" &&
|
||||
validPriority (defaultPriority))
|
||||
Att::validNameValue ("priority", "", defaultPriority))
|
||||
task.setAttribute ("priority", defaultPriority);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,14 +37,8 @@
|
|||
#include "../auto.h"
|
||||
|
||||
// valid.cpp
|
||||
void guess (const std::string&, const char**, std::string&);
|
||||
bool validPriority (const std::string&);
|
||||
bool validDescription (const std::string&);
|
||||
bool validDuration (std::string&);
|
||||
void validReportColumns (const std::vector <std::string>&);
|
||||
void validSortColumns (const std::vector <std::string>&, const std::vector <std::string>&);
|
||||
bool validAttribute (std::string&, std::string&);
|
||||
bool validId (const std::string&);
|
||||
bool validTag (const std::string&);
|
||||
|
||||
// task.cpp
|
||||
|
|
|
@ -34,7 +34,7 @@ Context context;
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
int main (int argc, char** argv)
|
||||
{
|
||||
UnitTest t (94);
|
||||
UnitTest t (109);
|
||||
|
||||
// void wrapText (std::vector <std::string>& lines, const std::string& text, const int width)
|
||||
std::string text = "This is a test of the line wrapping code.";
|
||||
|
@ -230,6 +230,27 @@ int main (int argc, char** argv)
|
|||
t.is (upperCase (""), "", "upperCase '' -> ''");
|
||||
t.is (upperCase ("pre01_:POST"), "PRE01_:POST", "upperCase 'pre01_:POST' -> 'PRE01_:POST'");
|
||||
|
||||
// bool digitsOnly (const std::string&);
|
||||
t.ok (digitsOnly (""), "digitsOnly '' -> true");
|
||||
t.ok (digitsOnly ("0"), "digitsOnly '0' -> true");
|
||||
t.ok (digitsOnly ("123"), "digitsOnly '123' -> true");
|
||||
t.notok (digitsOnly ("12fa"), "digitsOnly '12fa' -> false");
|
||||
|
||||
// bool noSpaces (const std::string&);
|
||||
t.ok (noSpaces (""), "noSpaces '' -> true");
|
||||
t.ok (noSpaces ("a"), "noSpaces 'a' -> true");
|
||||
t.ok (noSpaces ("abc"), "noSpaces 'abc' -> true");
|
||||
t.notok (noSpaces (" "), "noSpaces ' ' -> false");
|
||||
t.notok (noSpaces ("ab cd"), "noSpaces 'ab cd' -> false");
|
||||
|
||||
// bool noVerticalSpace (const std::string&);
|
||||
t.ok (noVerticalSpace (""), "noVerticalSpace '' -> true");
|
||||
t.ok (noVerticalSpace ("a"), "noVerticalSpace 'a' -> true");
|
||||
t.ok (noVerticalSpace ("abc"), "noVerticalSpace 'abc' -> true");
|
||||
t.notok (noVerticalSpace ("a\nb"), "noVerticalSpace 'a\\nb' -> false");
|
||||
t.notok (noVerticalSpace ("a\rb"), "noVerticalSpace 'a\\rb' -> false");
|
||||
t.notok (noVerticalSpace ("a\fb"), "noVerticalSpace 'a\\fb' -> false");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
30
src/text.cpp
30
src/text.cpp
|
@ -27,6 +27,7 @@
|
|||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ctype.h>
|
||||
#include "Context.h"
|
||||
#include "util.h"
|
||||
#include "text.h"
|
||||
|
@ -337,3 +338,32 @@ void guess (
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool digitsOnly (const std::string& input)
|
||||
{
|
||||
for (size_t i = 0; i < input.length (); ++i)
|
||||
if (!::isdigit (input[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool noSpaces (const std::string& input)
|
||||
{
|
||||
for (size_t i = 0; i < input.length (); ++i)
|
||||
if (::isspace (input[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool noVerticalSpace (const std::string& input)
|
||||
{
|
||||
if (input.find_first_of ("\n\r\f") != std::string::npos)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -46,6 +46,9 @@ std::string lowerCase (const std::string&);
|
|||
std::string upperCase (const std::string&);
|
||||
const char* optionalBlankLine ();
|
||||
void guess (const std::string&, std::vector<std::string>&, std::string&);
|
||||
bool digitsOnly (const std::string&);
|
||||
bool noSpaces (const std::string&);
|
||||
bool noVerticalSpace (const std::string&);
|
||||
|
||||
#endif
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
125
src/valid.cpp
125
src/valid.cpp
|
@ -40,141 +40,20 @@
|
|||
|
||||
extern Context context;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: These are static arrays only because there is no initializer list for
|
||||
// std::vector until C++0x.
|
||||
|
||||
// TODO Obsolete
|
||||
static const char* attributes[] =
|
||||
{
|
||||
"project",
|
||||
"priority",
|
||||
"fg",
|
||||
"bg",
|
||||
"due",
|
||||
"entry",
|
||||
"start",
|
||||
"end",
|
||||
"recur",
|
||||
"until",
|
||||
"mask",
|
||||
"imask",
|
||||
// "limit",
|
||||
"",
|
||||
};
|
||||
|
||||
// TODO Relocate inside Context.
|
||||
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]);
|
||||
|
||||
guess (type, options, candidate);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// All attributes, regardless of usage.
|
||||
// TODO Relocate to Att.cpp.
|
||||
bool validAttribute (std::string& name, std::string& value)
|
||||
{
|
||||
guess ("attribute", attributes, name);
|
||||
if (name != "")
|
||||
{
|
||||
if ((name == "fg" || name == "bg") && value != "")
|
||||
Text::guessColor (value);
|
||||
|
||||
else if (name == "due" && value != "")
|
||||
Date (value);
|
||||
|
||||
else if (name == "until" && value != "")
|
||||
Date (value);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool validTag (const std::string& input)
|
||||
{
|
||||
if ((input[0] == '-' || input[0] == '+') &&
|
||||
input.length () > 1)
|
||||
input.length () > 1 &&
|
||||
noSpaces (input))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool validDescription (const std::string& input)
|
||||
{
|
||||
if (input.length () &&
|
||||
input.find ("\r") == std::string::npos &&
|
||||
input.find ("\f") == std::string::npos &&
|
||||
input.find ("\n") == std::string::npos)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool validDuration (std::string& input)
|
||||
{
|
||||
try { Duration (input); }
|
||||
catch (...) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void validReportColumns (const std::vector <std::string>& columns)
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue