Enhancements - Cmd object

- New Cmd object to handle localized commands, customReports and general
  command parsing.
- Localized new Subst methods.
- Relocate guess method from parse.cpp to text.cpp.
- Converted Att object to use new valid/parse scheme.
- Unit tests for Cmd object.
- Fixed att.t.cpp unit tests.
This commit is contained in:
Paul Beckingham 2009-06-07 14:00:14 -04:00
parent ffa0c6e758
commit 24f31eeb00
17 changed files with 416 additions and 52 deletions

View file

@ -26,7 +26,7 @@
113 Unrecognized character(s) at end of substitution
114 Malformed substitution
# 2xx Commands
# 2xx Commands - must be sequential
200 active
201 add
202 append
@ -57,7 +57,7 @@
227 undo
228 version
# 3xx Attributes
# 3xx Attributes - must be sequential
300 project
301 priority
302 fg
@ -71,7 +71,7 @@
310 mask
311 imask
# 35x Attribute modifiers
# 35x Attribute modifiers - must be sequential
350 before
351 after
352 not

View file

@ -86,13 +86,39 @@ Att::~Att ()
{
}
////////////////////////////////////////////////////////////////////////////////
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)))
return true;
return false;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
//
// start --> name --> . --> mod --> : --> " --> value --> " --> end
// ^ |
// |__________|
//
bool Att::parse (Nibbler& n)
void Att::parse (Nibbler& n)
{
// Ensure a clean object first.
mName = "";
@ -126,7 +152,6 @@ bool Att::parse (Nibbler& n)
n.getUntil (' ', mValue))
{
decode (mValue);
return true;
}
else
throw std::string ("Missing attribute value"); // TODO i18n
@ -136,8 +161,6 @@ bool Att::parse (Nibbler& n)
}
else
throw std::string ("Missing : after attribute name"); // TODO i18n
return false;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -41,7 +41,8 @@ public:
Att& operator= (const Att&); // Assignment operator
~Att (); // Destructor
bool parse (Nibbler&);
bool valid (const std::string&) const;
void parse (Nibbler&);
bool validMod (const std::string&) const;
bool match (const Att&) const;

175
src/Cmd.cpp Normal file
View file

@ -0,0 +1,175 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "Cmd.h"
#include "Context.h"
#include "util.h"
#include "text.h"
#include "i18n.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)
{
loadCommands ();
loadCustomReports ();
std::string candidate = lowerCase (input);
std::vector <std::string> matches;
autoComplete (candidate, commands, matches);
if (0 == matches.size ())
return false;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Determines whether the string represents a valid custom report name.
bool Cmd::validCustom (const std::string& input)
{
loadCustomReports ();
std::vector <std::string> matches;
autoComplete (lowerCase (input), customReports, matches);
return matches.size () == 1 ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::parse (const std::string& input)
{
loadCommands ();
loadCustomReports ();
std::string candidate = lowerCase (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);
error += combined;
throw error + combined;
}
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::loadCommands ()
{
if (commands.size () == 0)
{
commands.push_back (context.stringtable.get (CMD_ACTIVE, "active"));
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_COMPLETED, "completed"));
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_NEXT, "next"));
commands.push_back (context.stringtable.get (CMD_OVERDUE, "overdue"));
commands.push_back (context.stringtable.get (CMD_PROJECTS, "projects"));
commands.push_back (context.stringtable.get (CMD_START, "start"));
commands.push_back (context.stringtable.get (CMD_STATS, "statistics"));
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_UNDELETE, "undelete"));
commands.push_back (context.stringtable.get (CMD_UNDO, "undo"));
commands.push_back (context.stringtable.get (CMD_VERSION, "version"));
}
}
////////////////////////////////////////////////////////////////////////////////
void Cmd::loadCustomReports ()
{
if (customReports.size () == 0)
{
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);
// A custom report is also a command.
customReports.push_back (report);
commands.push_back (report);
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////

58
src/Cmd.h Normal file
View file

@ -0,0 +1,58 @@
////////////////////////////////////////////////////////////////////////////////
// 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>
#include "Cmd.h"
class Cmd
{
public:
Cmd (); // Default constructor
Cmd (const std::string&); // Default constructor
~Cmd (); // Destructor
bool valid (const std::string&);
bool validCustom (const std::string&);
void parse (const std::string&);
public:
std::string command;
private:
void loadCommands ();
void loadCustomReports ();
private:
std::vector <std::string> commands;
std::vector <std::string> customReports;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -275,7 +275,8 @@ void Context::parse ()
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)
@ -312,7 +313,6 @@ void Context::parse ()
std::cout << "# found subst" << std::endl;
subst.parse (*arg);
}
/*
// Command.
else if (command == "")
@ -331,7 +331,6 @@ void Context::parse ()
}
}
*/
// Anything else is just considered description.
else
{

View file

@ -2,10 +2,10 @@ bin_PROGRAMS = task
task_SOURCES = Config.cpp Date.cpp Record.cpp T.cpp T2.cpp TDB.cpp TDB2.cpp \
Att.cpp Filter.cpp Sequence.cpp Table.cpp Grid.cpp Timer.cpp \
Duration.cpp StringTable.cpp Location.cpp Subst.cpp Keymap.cpp \
Nibbler.cpp Context.cpp color.cpp parse.cpp task.cpp edit.cpp \
command.cpp report.cpp util.cpp text.cpp rules.cpp import.cpp \
interactive.cpp \
Nibbler.cpp Context.cpp Cmd.cpp color.cpp parse.cpp task.cpp \
edit.cpp command.cpp report.cpp util.cpp text.cpp rules.cpp \
import.cpp interactive.cpp \
Config.h Date.h Record.h T.h TDB.h Att.h Filter.h Sequence.h \
Table.h Grid.h Timer.h Duration.h StringTable.h Location.h \
Subst.h Keymap.h Nibbler.h Context.h color.h task.h
Subst.h Keymap.h Nibbler.h Context.h Cmd.h color.h task.h

View file

@ -95,8 +95,9 @@ void Record::parse (const std::string& input)
Nibbler nl (line);
Att a;
while (!nl.depleted () && a.parse (nl))
while (!nl.depleted ())
{
a.parse (nl);
(*this)[a.name ()] = a;
nl.skip (' ');
}

View file

@ -25,6 +25,17 @@
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// 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

View file

@ -158,38 +158,6 @@ static const char* commands[] =
static std::vector <std::string> customReports;
////////////////////////////////////////////////////////////////////////////////
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;
}
}
////////////////////////////////////////////////////////////////////////////////
void guess (
const std::string& type,
@ -375,7 +343,7 @@ bool validDescription (const std::string& input)
}
////////////////////////////////////////////////////////////////////////////////
static bool validCommand (std::string& input)
bool validCommand (std::string& input)
{
std::string copy = input;
guess ("command", commands, copy);

View file

@ -43,6 +43,7 @@ bool validPriority (const std::string&);
bool validDate (std::string&);
bool validDuration (std::string&);
bool validDescription (const std::string&);
bool validCommand (std::string&);
void loadCustomReports ();
bool isCustomReport (const std::string&);
void allCustomReports (std::vector <std::string>&);

View file

@ -14,3 +14,4 @@ stringtable.t
nibbler.t
subst.t
filt.t
cmd.t

View file

@ -1,11 +1,12 @@
PROJECT = t.t t2.t tdb.t date.t duration.t t.benchmark.t text.t autocomplete.t \
parse.t seq.t att.t stringtable.t record.t nibbler.t subst.t filt.t
parse.t seq.t att.t stringtable.t record.t nibbler.t subst.t filt.t \
cmd.t
CFLAGS = -I. -I.. -Wall -pedantic -ggdb3 -fno-rtti
LFLAGS = -L/usr/local/lib
OBJECTS = ../TDB.o ../TDB2.o ../T.o ../T2.o ../parse.o ../text.o ../Date.o \
../Duration.o ../util.o ../Config.o ../Sequence.o ../Att.o \
../Record.o ../StringTable.o ../Subst.o ../Nibbler.o ../Location.o \
../Filter.o ../Context.o ../Keymap.o
../Filter.o ../Context.o ../Keymap.o ../Cmd.o
all: $(PROJECT)
@ -69,3 +70,6 @@ nibbler.t: nibbler.t.o $(OBJECTS) test.o
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

View file

@ -33,7 +33,25 @@ Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (59);
UnitTest t (74);
Att a;
t.notok (a.valid ("name"), "Att::valid name -> fail");
t.notok (a.valid (":"), "Att::valid : -> fail");
t.notok (a.valid (":value"), "Att::valid :value -> fail");
t.ok (a.valid ("name:value"), "Att::valid name:value");
t.ok (a.valid ("name:value "), "Att::valid name:value\\s");
t.ok (a.valid ("name:'value'"), "Att::valid name:'value'");
t.ok (a.valid ("name:'one two'"), "Att::valid name:'one two'");
t.ok (a.valid ("name:\"value\""), "Att::valid name:\"value\"");
t.ok (a.valid ("name:\"one two\""), "Att::valid name:\"one two\"");
t.ok (a.valid ("name:"), "Att::valid name:");
t.ok (a.valid ("name:&quot;"), "Att::valid &quot;");
t.ok (a.valid ("name.one:value"), "Att::valid name.one.value");
t.ok (a.valid ("name.one.two:value"), "Att::valid name.one.two:value");
t.ok (a.valid ("name.:value"), "Att::valid name.:value");
t.ok (a.valid ("name..:value"), "Att::valid name..:value");
Att a1 ("name", "value");
t.is (a1.name (), "name", "Att::Att (name, value), Att.name");

71
src/tests/cmd.t.cpp Normal file
View file

@ -0,0 +1,71 @@
////////////////////////////////////////////////////////////////////////////////
// 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 (18);
context.config.set ("report.foo.columns", "id");
Cmd cmd;
t.ok (cmd.valid ("annotate"), "Cmd::valid annotate");
t.ok (cmd.valid ("annotat"), "Cmd::valid annotat");
t.ok (cmd.valid ("annota"), "Cmd::valid annota");
t.ok (cmd.valid ("annot"), "Cmd::valid annot");
t.ok (cmd.valid ("anno"), "Cmd::valid anno");
t.ok (cmd.valid ("ann"), "Cmd::valid ann");
t.ok (cmd.valid ("an"), "Cmd::valid an");
t.ok (cmd.valid ("ANNOTATE"), "Cmd::valid ANNOTATE");
t.ok (cmd.valid ("ANNOTAT"), "Cmd::valid ANNOTAT");
t.ok (cmd.valid ("ANNOTA"), "Cmd::valid ANNOTA");
t.ok (cmd.valid ("ANNOT"), "Cmd::valid ANNOT");
t.ok (cmd.valid ("ANNO"), "Cmd::valid ANNO");
t.ok (cmd.valid ("ANN"), "Cmd::valid ANN");
t.ok (cmd.valid ("AN"), "Cmd::valid AN");
t.ok (cmd.validCustom ("foo"), "Cmd::validCustom foo");
t.notok (cmd.validCustom ("bar"), "Cmd::validCustom bar -> fail");
bool good = true;
try { cmd.parse ("a"); } catch (...) { good = false; }
t.notok (good, "Cmd::parse a -> fail");
good = true;
try { cmd.parse ("add"); } catch (...) { good = false; }
t.ok (good, "Cmd::parse add");
return 0;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -305,3 +305,35 @@ const char* optionalBlankLine ()
}
////////////////////////////////////////////////////////////////////////////////
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 "; // TODO i18n
error += type;
error += " '";
error += candidate;
error += "' - could be either of "; // TODO i18n
for (size_t i = 0; i < matches.size (); ++i)
{
if (i)
error += ", ";
error += matches[i];
}
throw error;
}
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -45,6 +45,7 @@ std::string commify (const std::string&);
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&);
#endif
////////////////////////////////////////////////////////////////////////////////