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:
Paul Beckingham 2009-06-13 14:56:27 -04:00
parent eda17772c9
commit 25d27bec93
11 changed files with 287 additions and 151 deletions

View file

@ -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);
*/
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -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;

View file

@ -440,7 +440,7 @@ std::cout << "# parse post-termination description '" << *arg << "'"
}
}
if (validDescription (descCandidate))
if (noVerticalSpace (descCandidate))
task.set ("description", descCandidate);
// TODO task.validate ()

View file

@ -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);
}

View file

@ -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") != "")

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -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
////////////////////////////////////////////////////////////////////////////////

View file

@ -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)
{