diff --git a/i18n/strings.en-US b/i18n/strings.en-US index 1c2526fc1..6493b21b0 100644 --- a/i18n/strings.en-US +++ b/i18n/strings.en-US @@ -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 diff --git a/src/Att.cpp b/src/Att.cpp index ddc4f4959..84cc87bba 100644 --- a/src/Att.cpp +++ b/src/Att.cpp @@ -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; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Att.h b/src/Att.h index c5e069ba0..fab2ad32d 100644 --- a/src/Att.h +++ b/src/Att.h @@ -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; diff --git a/src/Cmd.cpp b/src/Cmd.cpp new file mode 100644 index 000000000..fd59d3473 --- /dev/null +++ b/src/Cmd.cpp @@ -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 +#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 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 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 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 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); + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Cmd.h b/src/Cmd.h new file mode 100644 index 000000000..98780d90e --- /dev/null +++ b/src/Cmd.h @@ -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 +#include +#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 commands; + std::vector customReports; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Context.cpp b/src/Context.cpp index 394a285ee..ca330abe3 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -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 { diff --git a/src/Makefile.am b/src/Makefile.am index 3457dfb36..9e31ed01f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/Record.cpp b/src/Record.cpp index 81f605253..97f657601 100644 --- a/src/Record.cpp +++ b/src/Record.cpp @@ -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 (' '); } diff --git a/src/i18n.h b/src/i18n.h index 36186f60a..bba7b05c2 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -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 diff --git a/src/parse.cpp b/src/parse.cpp index dd48092c7..b96c89d18 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -158,38 +158,6 @@ static const char* commands[] = static std::vector customReports; -//////////////////////////////////////////////////////////////////////////////// -void guess ( - const std::string& type, - std::vector& options, - std::string& candidate) -{ - std::vector 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); diff --git a/src/task.h b/src/task.h index c6e7416c3..8acd218bf 100644 --- a/src/task.h +++ b/src/task.h @@ -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 &); diff --git a/src/tests/.gitignore b/src/tests/.gitignore index 2e028dfa2..630fdb8ad 100644 --- a/src/tests/.gitignore +++ b/src/tests/.gitignore @@ -14,3 +14,4 @@ stringtable.t nibbler.t subst.t filt.t +cmd.t diff --git a/src/tests/Makefile b/src/tests/Makefile index d012f9640..2e1d5f2a3 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -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 + diff --git a/src/tests/att.t.cpp b/src/tests/att.t.cpp index 3ee651195..7ae61823b 100644 --- a/src/tests/att.t.cpp +++ b/src/tests/att.t.cpp @@ -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:""), "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"); diff --git a/src/tests/cmd.t.cpp b/src/tests/cmd.t.cpp new file mode 100644 index 000000000..01a10f3d7 --- /dev/null +++ b/src/tests/cmd.t.cpp @@ -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 +#include +#include + +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; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/text.cpp b/src/text.cpp index a96a0bb80..dda69d606 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -305,3 +305,35 @@ const char* optionalBlankLine () } //////////////////////////////////////////////////////////////////////////////// +void guess ( + const std::string& type, + std::vector& options, + std::string& candidate) +{ + std::vector 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; + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/text.h b/src/text.h index 121c1babf..125195f1d 100644 --- a/src/text.h +++ b/src/text.h @@ -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&); #endif ////////////////////////////////////////////////////////////////////////////////