diff --git a/src/CLI.cpp b/src/CLI.cpp new file mode 100644 index 000000000..b1a68bcea --- /dev/null +++ b/src/CLI.cpp @@ -0,0 +1,1588 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2014, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include // TODO Remove. +#include +#include +#include +#include +#include +#include +#include +#include + +extern Context context; + +// Overridden by rc.abbreviation.minimum. +static int minimumMatchLength = 3; + +// Alias expansion limit. Any more indicates some kind of error. +static int safetyValveDefault = 10; + +//////////////////////////////////////////////////////////////////////////////// +A::A () +: _name ("") +{ +} + +//////////////////////////////////////////////////////////////////////////////// +A::A (const std::string& name, const std::string& raw) +{ + _name = name; + attribute ("raw", raw); +} + +//////////////////////////////////////////////////////////////////////////////// +A::A (const std::string& name, const int raw) +{ + _name = name; + attribute ("raw", raw); +} + +//////////////////////////////////////////////////////////////////////////////// +A::A (const std::string& name, const double raw) +{ + _name = name; + attribute ("raw", raw); +} + +//////////////////////////////////////////////////////////////////////////////// +A::~A () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +A::A (const A& other) +: _name (other._name) +, _tags (other._tags) +, _attributes (other._attributes) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +A& A::operator= (const A& other) +{ + if (this != &other) + { + _name = other._name; + _tags = other._tags; + _attributes = other._attributes; + } + + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +void A::clear () +{ + _name = ""; + _tags.clear (); + _attributes.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +bool A::hasTag (const std::string& tag) const +{ + if (std::find (_tags.begin (), _tags.end (), tag) != _tags.end ()) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void A::tag (const std::string& tag) +{ + if (! hasTag (tag)) + _tags.push_back (tag); +} + +//////////////////////////////////////////////////////////////////////////////// +void A::unTag (const std::string& tag) +{ + std::vector ::iterator i; + for (i = _tags.begin (); i != _tags.end (); ++i) + { + if (*i == tag) + { + _tags.erase (i); + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void A::unTagAll () +{ + _tags.clear (); +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessor for attributes. +void A::attribute (const std::string& name, const std::string& value) +{ + _attributes[name] = value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessor for attributes. +void A::attribute (const std::string& name, const int value) +{ + _attributes[name] = format (value); +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessor for attributes. +void A::attribute (const std::string& name, const double value) +{ + _attributes[name] = format (value, 1, 8); +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessor for attributes. +const std::string A::attribute (const std::string& name) const +{ + // Prevent autovivification. + std::map::const_iterator i = _attributes.find (name); + if (i != _attributes.end ()) + return i->second; + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +void A::removeAttribute (const std::string& name) +{ + _attributes.erase (name); +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string A::dump () const +{ + std::string output = _name; + + // Dump attributes. + std::string atts; + std::map ::const_iterator a; + for (a = _attributes.begin (); a != _attributes.end (); ++a) + { + if (a != _attributes.begin ()) + atts += " "; + + atts += a->first + "='\033[33m" + a->second + "\033[0m'"; + } + + if (atts.length ()) + output += " " + atts; + + // Dump tags. + std::string tags; + std::vector ::const_iterator tag; + for (tag = _tags.begin (); tag != _tags.end (); ++tag) + { + if (tags.length ()) + tags += ' '; + + if (*tag == "BINARY") tags += "\033[1;37;44m" + *tag + "\033[0m"; + else if (*tag == "CMD") tags += "\033[1;37;46m" + *tag + "\033[0m"; + else if (*tag == "FILTER") tags += "\033[1;37;42m" + *tag + "\033[0m"; + else if (*tag == "MODIFICATION") tags += "\033[1;37;43m" + *tag + "\033[0m"; + else if (*tag == "RC") tags += "\033[1;37;41m" + *tag + "\033[0m"; + else if (*tag == "CONFIG") tags += "\033[1;37;101m" + *tag + "\033[0m"; + else if (*tag == "?") tags += "\033[38;5;255;48;5;232m" + *tag + "\033[0m"; + else tags += "\033[32m" + *tag + "\033[0m"; + } + + if (tags.length ()) + output += ' ' + tags; + + return output; +} + +//////////////////////////////////////////////////////////////////////////////// +CLI::CLI () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +CLI::~CLI () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::alias (const std::string& name, const std::string& value) +{ + _aliases.insert (std::pair (name, value)); +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::entity (const std::string& name, const std::string& value) +{ + _entities.insert (std::pair (name, value)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Capture the original, intact command line arguments. +void CLI::initialize (int argc, const char** argv) +{ + // Clean what needs to be cleaned. Everything in this case. + _original_args.clear (); + _id_ranges.clear (); + _uuid_list.clear (); + + for (int i = 0; i < argc; ++i) + _original_args.push_back (argv[i]); + + analyze (); +} + +//////////////////////////////////////////////////////////////////////////////// +// Capture a single argument, and recalc everything. +void CLI::add (const std::string& arg) +{ + // Clean what needs to be cleaned. Most in this case. + _original_args.push_back (arg); + + analyze (); +} + +//////////////////////////////////////////////////////////////////////////////// +// Intended to be called after ::initialize() and ::add(), to perform the final +// analysis. Analysis is also performed directly after the above, because there +// is a need to extract overrides early, before entities are proviedd. +void CLI::analyze (bool parse /* = true */) +{ + // Clean what needs to be cleaned. Most in this case. + _args.clear (); + _id_ranges.clear (); + _uuid_list.clear (); + + for (int i = 0; i < _original_args.size (); ++i) + { + if (i == 0) + { + A a ("arg", _original_args[i]); + a.tag ("ORIGINAL"); + a.tag ("BINARY"); + + std::string basename = "task"; + std::string raw = _original_args[i]; + std::string::size_type slash = raw.rfind ('/'); + if (slash != std::string::npos) + basename = raw.substr (slash + 1); + + a.attribute ("basename", basename); + if (basename == "cal" || basename == "calendar") + a.tag ("CALENDAR"); + else if (basename == "task" || basename == "tw" || basename == "t") + a.tag ("TW"); + + _args.push_back (a); + } + else + { + A a ("arg", _original_args[i]); + a.tag ("ORIGINAL"); + _args.push_back (a); + } + } + + // Find argument types. + aliasExpansion (); + findOverrides (); + categorize (); + + if (parse) + { + // Remove all the syntactic sugar for FILTERs. + desugarTags (); + desugarAttributes (); + desugarAttributeModifiers (); + desugarPatterns (); + findIDs (); + findUUIDs (); + insertIDExpr (); + findOperators (); + insertJunctions (); + desugarPlainArgs (); + + // Decompose the elements for MODIFICATIONs. + decomposeModAttributes (); + decomposeModAttributeModifiers (); + decomposeModTags (); + decomposeModSubstitutions (); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Extract all the FILTER-tagged items. +const std::string CLI::getFilter () +{ + std::string filter = ""; + if (_args.size ()) + { + std::vector ::const_iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + if (filter != "") + filter += ' '; + + std::string term = a->attribute ("name"); + if (term == "") + term = a->attribute ("raw"); + + filter += term; + } + } + + filter = "( " + filter + " )"; + } + + return filter; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::vector CLI::getWords () +{ + // Re-analyze the arguments, but do not de-sugar or decompose any. Analysis + // only. + analyze (false); + + std::vector words; + std::vector ::const_iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (! a->hasTag ("BINARY") && + ! a->hasTag ("RC") && + ! a->hasTag ("CONFIG") && + ! a->hasTag ("CMD") && + ! a->hasTag ("TERMINATOR") && + a->hasTag ("ORIGINAL")) + { + words.push_back (a->attribute ("raw")); + } + } + + return words; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::vector CLI::getModifications () +{ + std::vector modifications; + + // TODO Processing here. + + return modifications; +} + +//////////////////////////////////////////////////////////////////////////////// +const std::string CLI::dump () const +{ + std::stringstream out; + + out << "\033[1mCLI Parser\033[0m\n" + << " _original_args\n "; + Color colorOrigArgs ("gray10 on gray4"); + std::vector ::const_iterator i; + for (i = _original_args.begin (); i != _original_args.end (); ++i) + { + if (i != _original_args.begin ()) + out << ' '; + out << colorOrigArgs.colorize (*i); + } + out << "\n"; + + out << " _args\n"; + std::vector ::const_iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + out << " " << a->dump () << "\n"; + + return out.str (); +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::aliasExpansion () +{ + bool action; + int counter = 0; + do + { + action = false; + std::vector reconstructed; + + bool terminated = false; + std::string raw; + std::vector ::iterator i; + for (i = _args.begin (); i != _args.end (); ++i) + { + raw = i->attribute ("raw"); + + if (raw == "--") + terminated = true; + + if (! terminated) + { + if (_aliases.find (raw) != _aliases.end ()) + { + std::vector lexed; + Lexer::token_split (lexed, _aliases[raw]); + + std::vector ::iterator l; + for (l = lexed.begin (); l != lexed.end (); ++l) + { + A a ("argLex", *l); + a.tag ("ALIAS"); + a.tag ("LEX"); + reconstructed.push_back (a); + } + + action = true; + } + else + reconstructed.push_back (*i); + } + else + reconstructed.push_back (*i); + } + + _args = reconstructed; + } + while (action && counter++ < safetyValveDefault); +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::findOverrides () +{ + std::string raw; + bool terminated = false; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + raw = a->attribute ("raw"); + + if (raw == "--") + terminated = true; + + if (terminated) + continue; + + if (raw.find ("rc:") == 0) + { + a->tag ("RC"); + a->attribute ("file", raw.substr (3)); + } + else if (raw.find ("rc.") == 0) + { + std::string::size_type sep = raw.find ('=', 3); + if (sep == std::string::npos) + sep = raw.find (':', 3); + if (sep != std::string::npos) + { + a->tag ("CONFIG"); + a->attribute ("name", raw.substr (3, sep - 3)); + a->attribute ("value", raw.substr (sep + 1)); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::categorize () +{ + bool foundCommand = false; + bool readOnly = false; + bool terminated = false; + + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + std::string raw = a->attribute ("raw"); + + if (raw == "--") + { + a->unTagAll (); + a->tag ("ORIGINAL"); + a->tag ("TERMINATOR"); + terminated = true; + continue; + } + + if (terminated) + { + a->unTagAll (); + a->tag ("ORIGINAL"); + a->tag ("TERMINATED"); + a->tag ("WORD"); + } + + std::string canonical; + if (! terminated && + ! foundCommand && + canonicalize (canonical, "cmd", raw)) + { + readOnly = ! exactMatch ("writecmd", canonical); + + a->tag ("CMD"); + a->tag (readOnly ? "READCMD" : "WRITECMD"); + a->attribute ("canonical", canonical); + foundCommand = true; + } + else if (a->hasTag ("TERMINATOR") || + a->hasTag ("BINARY") || + a->hasTag ("CONFIG") || + a->hasTag ("RC")) + { + // NOP + } + else if (foundCommand && ! readOnly) + { + a->tag ("MODIFICATION"); + } + else if (!foundCommand || (foundCommand && readOnly)) + { + a->tag ("FILTER"); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Search for exact 'value' in _entities category. +bool CLI::exactMatch ( + const std::string& category, + const std::string& value) const +{ + // Find the category. + std::pair ::const_iterator, std::multimap ::const_iterator> c; + c = _entities.equal_range (category); + + // Extract a list of entities for category. + std::vector options; + std::multimap ::const_iterator e; + for (e = c.first; e != c.second; ++e) + { + // Shortcut: if an exact match is found, success. + if (value == e->second) + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Search for 'value' in _entities category, return canonicalized value. +bool CLI::canonicalize ( + std::string& canonicalized, + const std::string& category, + const std::string& value) const +{ + // Find the category. + std::pair ::const_iterator, std::multimap ::const_iterator> c; + c = _entities.equal_range (category); + + // Extract a list of entities for category. + std::vector options; + std::multimap ::const_iterator e; + for (e = c.first; e != c.second; ++e) + { + // Shortcut: if an exact match is found, success. + if (value == e->second) + { + canonicalized = value; + return true; + } + + options.push_back (e->second); + } + + // Match against the options, throw away results. + std::vector matches; + if (autoComplete (value, options, matches, minimumMatchLength) == 1) + { + canonicalized = matches[0]; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// +tag --> tags _hastag_ tag +// -tag --> tags _notag_ tag +void CLI::desugarTags () +{ + std::vector reconstructed; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + Nibbler n (a->attribute ("raw")); + std::string tag; + std::string sign; + + if (n.getN (1, sign) && + (sign == "+" || sign == "-") && + n.getUntilEOS (tag) && + tag.find (' ') == std::string::npos) + { + A left ("argTag", "tags"); + left.tag ("ATTRIBUTE"); + left.tag ("FILTER"); + reconstructed.push_back (left); + + A op ("argTag", sign == "+" ? "_hastag_" : "_notag_"); + op.tag ("OP"); + op.tag ("FILTER"); + reconstructed.push_back (op); + + A right ("argTag", tag); + right.tag ("LITERAL"); + right.tag ("FILTER"); + reconstructed.push_back (right); + } + else + reconstructed.push_back (*a); + } + else + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +// :['"][]['"] --> name = value +void CLI::desugarAttributes () +{ + std::vector reconstructed; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + // Look for a valid attribute name. + bool found = false; + Nibbler n (a->attribute ("raw")); + std::string name; + if (n.getName (name) && + name.length ()) + { + if (n.skip (':')) + { + std::string value; + if (n.getQuoted ('"', value) || + n.getQuoted ('\'', value) || + n.getUntilEOS (value) || + n.depleted ()) + { + if (value == "") + value = "''"; + + std::string canonical; + if (canonicalize (canonical, "uda", name)) + { + A lhs ("argUDA", name); + lhs.attribute ("name", canonical); + lhs.tag ("UDA"); + lhs.tag ("ATTRIBUTE"); + lhs.tag ("FILTER"); + + A op ("argUDA", "="); + op.tag ("OP"); + op.tag ("FILTER"); + + A rhs ("argUDA", value); + rhs.tag ("LITERAL"); + rhs.tag ("FILTER"); + + reconstructed.push_back (lhs); + reconstructed.push_back (op); + reconstructed.push_back (rhs); + found = true; + } + + else if (canonicalize (canonical, "pseudo", name)) + { + A lhs ("argPseudo", name); + lhs.attribute ("name", canonical); + lhs.tag ("PSEUDO"); + reconstructed.push_back (lhs); + found = true; + } + + else if (canonicalize (canonical, "attribute", name)) + { + A lhs ("argAtt", name); + lhs.attribute ("name", canonical); + lhs.tag ("ATTRIBUTE"); + lhs.tag ("FILTER"); + + std::string operatorLiteral = "="; + if (canonical == "status") + operatorLiteral = "=="; + + A op ("argAtt", operatorLiteral); + op.tag ("OP"); + op.tag ("FILTER"); + + A rhs ("argAtt", value); + rhs.tag ("LITERAL"); + rhs.tag ("FILTER"); + + reconstructed.push_back (lhs); + reconstructed.push_back (op); + reconstructed.push_back (rhs); + found = true; + } + } + } + } + + if (!found) + reconstructed.push_back (*a); + } + else + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +// .[:=]['"]['"] --> name value +void CLI::desugarAttributeModifiers () +{ + std::vector reconstructed; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + // Look for a valid attribute name. + bool found = false; + Nibbler n (a->attribute ("raw")); + std::string name; + if (n.getUntil (".", name) && + name.length ()) + { + std::string canonical; + if (canonicalize (canonical, "attribute", name) || + canonicalize (canonical, "uda", name)) + { + if (n.skip ('.')) + { + std::string sense = "+"; + if (n.skip ('~')) + sense = "-"; + + std::string modifier; + n.getUntilOneOf (":=", modifier); + + if (n.skip (':') || + n.skip ('=')) + { + std::string value; + if (n.getQuoted ('"', value) || + n.getQuoted ('\'', value) || + n.getUntilEOS (value) || + n.depleted ()) + { + if (value == "") + value = "''"; + + A lhs ("argAttMod", name); + lhs.tag ("ATTMOD"); + lhs.tag ("FILTER"); + + lhs.attribute ("name", canonical); + lhs.attribute ("modifier", modifier); + lhs.attribute ("sense", sense); + + A op ("argAttmod", ""); + op.tag ("FILTER"); + + A rhs ("argAttMod", ""); + rhs.tag ("FILTER"); + + if (modifier == "before" || modifier == "under" || modifier == "below") + { + op.attribute ("raw", "<"); + op.tag ("OP"); + rhs.attribute ("raw", value); + rhs.tag ("LITERAL"); + } + else if (modifier == "after" || modifier == "over" || modifier == "above") + { + op.attribute ("raw", ">"); + op.tag ("OP"); + rhs.attribute ("raw", value); + rhs.tag ("LITERAL"); + } + else if (modifier == "none") + { + op.attribute ("raw", "=="); + op.tag ("OP"); + rhs.attribute ("raw", "''"); + rhs.tag ("LITERAL"); + } + else if (modifier == "any") + { + op.attribute ("raw", "!="); + op.tag ("OP"); + rhs.attribute ("raw", "''"); + rhs.tag ("LITERAL"); + } + else if (modifier == "is" || modifier == "equals") + { + op.attribute ("raw", "=="); + op.tag ("OP"); + rhs.attribute ("raw", value); + rhs.tag ("LITERAL"); + } + else if (modifier == "isnt" || modifier == "not") + { + op.attribute ("raw", "!="); + op.tag ("OP"); + rhs.attribute ("raw", value); + rhs.tag ("LITERAL"); + } + else if (modifier == "has" || modifier == "contains") + { + op.attribute ("raw", "~"); + op.tag ("OP"); + rhs.attribute ("raw", value); + rhs.tag ("LITERAL"); + } + else if (modifier == "hasnt") + { + op.attribute ("raw", "!~"); + op.tag ("OP"); + rhs.attribute ("raw", value); + rhs.tag ("LITERAL"); + } + else if (modifier == "startswith" || modifier == "left") + { + op.attribute ("raw", "~"); + op.tag ("OP"); + rhs.attribute ("raw", "'^" + value + "'"); + rhs.tag ("REGEX"); + } + else if (modifier == "endswith" || modifier == "right") + { + op.attribute ("raw", "~"); + op.tag ("OP"); + rhs.attribute ("raw", "'" + value + "$'"); + rhs.tag ("REGEX"); + } + else if (modifier == "word") + { + op.attribute ("raw", "~"); + op.tag ("OP"); +#if defined (DARWIN) + rhs.attribute ("raw", value); +#elif defined (SOLARIS) + rhs.attribute ("raw", "'\\<" + value + "\\>'"); +#else + rhs.attribute ("raw", "'\\b" + value + "\\b'"); +#endif + rhs.tag ("REGEX"); + } + else if (modifier == "noword") + { + op.attribute ("raw", "!~"); + op.tag ("OP"); +#if defined (DARWIN) + rhs.attribute ("raw", value); +#elif defined (SOLARIS) + rhs.attribute ("raw", "'\\<" + value + "\\>'"); +#else + rhs.attribute ("raw", "'\\b" + value + "\\b'"); +#endif + rhs.tag ("REGEX"); + } + else + throw format (STRING_PARSER_UNKNOWN_ATTMOD, modifier); + + reconstructed.push_back (lhs); + reconstructed.push_back (op); + reconstructed.push_back (rhs); + found = true; + } + } + } + } + } + + if (!found) + reconstructed.push_back (*a); + } + else + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +// /pattern/ --> description ~ 'pattern' +void CLI::desugarPatterns () +{ + std::vector reconstructed; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + Nibbler n (a->attribute ("raw")); + std::string pattern; + + if (n.getQuoted ('/', pattern) && + n.depleted () && + pattern.length () > 0) + { + A lhs ("argPattern", "description"); + lhs.tag ("ATTRIBUTE"); + lhs.tag ("FILTER"); + reconstructed.push_back (lhs); + + A op ("argPattern", "~"); + op.tag ("OP"); + op.tag ("FILTER"); + reconstructed.push_back (op); + + A rhs ("argPattern", "'" + pattern + "'"); + rhs.tag ("LITERAL"); + rhs.tag ("FILTER"); + reconstructed.push_back (rhs); + } + else + reconstructed.push_back (*a); + } + else + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +// An ID sequence can be: +// +// a single ID: 1 +// a list of IDs: 1,3,5 +// a list of IDs: 1 3 5 +// a range: 5-10 +// or a combination: 1,3,5-10 12 +// +void CLI::findIDs () +{ + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + bool found = false; + + // IDs have a limited character set. + std::string raw = a->attribute ("raw"); + if (raw.find_first_not_of ("0123456789,-") == std::string::npos) + { + // Container for min/max ID ranges. + std::vector > ranges; + + // Split the ID list into elements. + std::vector elements; + split (elements, raw, ','); + + bool is_an_id = true; + std::vector ::iterator e; + for (e = elements.begin (); e != elements.end (); ++e) + { + // Split the ID range into min/max. + std::vector terms; + split (terms, *e, '-'); + + if (terms.size () == 1) + { + if (! digitsOnly (terms[0])) + { + is_an_id = false; + break; + } + + Nibbler n (terms[0]); + int id; + if (n.getUnsignedInt (id) && + n.depleted ()) + { + ranges.push_back (std::pair (id, id)); + } + else + { + is_an_id = false; + break; + } + } + else if (terms.size () == 2) + { + if (! digitsOnly (terms[0]) || + ! digitsOnly (terms[1])) + { + is_an_id = false; + break; + } + + Nibbler n_min (terms[0]); + Nibbler n_max (terms[1]); + int id_min; + int id_max; + if (n_min.getUnsignedInt (id_min) && + n_min.depleted () && + n_max.getUnsignedInt (id_max) && + n_max.depleted ()) + { + if (id_min > id_max) + throw std::string (STRING_PARSER_RANGE_INVERTED); + + ranges.push_back (std::pair (id_min, id_max)); + } + else + { + is_an_id = false; + break; + } + } + else + { + is_an_id = false; + break; + } + } + + if (is_an_id) + { + a->tag ("ID"); + + // Save the ranges. + std::vector >::iterator r; + for (r = ranges.begin (); r != ranges.end (); ++r) + _id_ranges.push_back (*r); + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::findUUIDs () +{ + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + // UUIDs have a limited character set. + std::string raw = a->attribute ("raw"); + if (raw.find_first_not_of ("0123456789abcdefABCDEF-,") == std::string::npos) + { + Nibbler n (raw); + std::vector uuidList; + std::string uuid; + if (n.getUUID (uuid) || + n.getPartialUUID (uuid)) + { + uuidList.push_back (uuid); + + while (n.skip (',')) + { + if (! n.getUUID (uuid) && + ! n.getPartialUUID (uuid)) + throw std::string (STRING_PARSER_UUID_AFTER_COMMA); + + uuidList.push_back (uuid); + } + + if (n.depleted ()) + { + a->tag ("UUID"); + + // Save the list. + std::vector ::iterator u; + for (u = uuidList.begin (); u != uuidList.end (); ++u) + _uuid_list.push_back (*u); + } + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::insertIDExpr () +{ + // Iterate over all args. The first ID/UUID arg found will be replaced by + // the combined ID clause. All other ID/UUID args are removed. + bool foundID = false; + std::vector reconstructed; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER") && + (a->hasTag ("ID") || + a->hasTag ("UUID"))) + { + if (! foundID) + { + foundID = true; + + // Construct a single sequence that represents all _id_ranges and + // _uuid_list in one clause. This is essentially converting this: + // + // 1,2-3 uuid,uuid uuid 4 + // + // into: + // + // ( + // ( id == 1 ) + // or ( ( id >= 2 ) and ( id <= 3 ) ) + // or ( id == 4 ) + // or ( uuid = $UUID ) + // or ( uuid = $UUID ) + // ) + + // Building block operators. + A openParen ("argSeq", "("); openParen.tag ("FILTER"); openParen.tag ("OP"); + A closeParen ("argSeq", ")"); closeParen.tag ("FILTER"); closeParen.tag ("OP"); + A opOr ("argSeq", "or"); opOr.tag ("FILTER"); opOr.tag ("OP"); + A opAnd ("argSeq", "and"); opAnd.tag ("FILTER"); opAnd.tag ("OP"); + A opSimilar ("argSeq", "="); opSimilar.tag ("FILTER"); opSimilar.tag ("OP"); + A opEqual ("argSeq", "=="); opEqual.tag ("FILTER"); opEqual.tag ("OP"); + A opGTE ("argSeq", ">="); opGTE.tag ("FILTER"); opGTE.tag ("OP"); + A opLTE ("argSeq", "<="); opLTE.tag ("FILTER"); opLTE.tag ("OP"); + + // Building block attributes. + A argID ("argSeq", "id"); + argID.tag ("FILTER"); + argID.tag ("ATTRIBUTE"); + + A argUUID ("argSeq", "uuid"); + argUUID.tag ("FILTER"); + argUUID.tag ("ATTRIBUTE"); + + reconstructed.push_back (openParen); + + // Add all ID ranges. + std::vector >::iterator r; + for (r = _id_ranges.begin (); r != _id_ranges.end (); ++r) + { + if (r != _id_ranges.begin ()) + reconstructed.push_back (opOr); + + if (r->first == r->second) + { + reconstructed.push_back (openParen); + reconstructed.push_back (argID); + reconstructed.push_back (opEqual); + + A value ("argSeq", r->first); + value.tag ("FILTER"); + value.tag ("LITERAL"); + value.tag ("NUMBER"); + reconstructed.push_back (value); + + reconstructed.push_back (closeParen); + } + else + { + reconstructed.push_back (openParen); + reconstructed.push_back (argID); + reconstructed.push_back (opGTE); + + A startValue ("argSeq", r->first); + startValue.tag ("FILTER"); + startValue.tag ("LITERAL"); + startValue.tag ("NUMBER"); + reconstructed.push_back (startValue); + + reconstructed.push_back (opAnd); + reconstructed.push_back (argID); + reconstructed.push_back (opLTE); + + A endValue ("argSeq", r->second); + endValue.tag ("FILTER"); + endValue.tag ("LITERAL"); + endValue.tag ("NUMBER"); + reconstructed.push_back (endValue); + + reconstructed.push_back (closeParen); + } + } + + // Combine the ID and UUID sections wiþh 'or'. + if (_id_ranges.size () && + _uuid_list.size ()) + reconstructed.push_back (opOr); + + // Add all UUID list items. + std::vector ::iterator u; + for (u = _uuid_list.begin (); u != _uuid_list.end (); ++u) + { + if (u != _uuid_list.begin ()) + reconstructed.push_back (opOr); + + reconstructed.push_back (openParen); + reconstructed.push_back (argUUID); + reconstructed.push_back (opSimilar); + + A value ("argSeq", "'" + *u + "'"); + value.tag ("FILTER"); + value.tag ("LITERAL"); + value.tag ("STRING"); + reconstructed.push_back (value); + + reconstructed.push_back (closeParen); + } + + reconstructed.push_back (closeParen); + } + + // No 'else' which cause all other ID/UUID args to be eaten. + } + else + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::desugarPlainArgs () +{ + std::vector reconstructed; + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER") && + ! a->hasTag ("ATTRIBUTE") && + ! a->hasTag ("ATTMOD") && + ! a->hasTag ("OP") && + ! a->hasTag ("LITERAL")) + { + A lhs ("argPattern", "description"); + lhs.tag ("ATTRIBUTE"); + lhs.tag ("FILTER"); + reconstructed.push_back (lhs); + + A op ("argPattern", "~"); + op.tag ("OP"); + op.tag ("FILTER"); + reconstructed.push_back (op); + + A rhs ("argPattern", "'" + a->attribute ("raw") + "'"); + rhs.tag ("LITERAL"); + rhs.tag ("FILTER"); + reconstructed.push_back (rhs); + } + else + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::findOperators () +{ + // Find the category. + std::pair ::const_iterator, std::multimap ::const_iterator> c; + c = _entities.equal_range ("operator"); + + // Extract a list of entities for category. + std::vector options; + std::multimap ::const_iterator e; + for (e = c.first; e != c.second; ++e) + options.push_back (e->second); + + // Walk the arguments and tag as OP. + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + if (a->hasTag ("FILTER")) + if (std::find (options.begin (), options.end (), a->attribute ("raw")) != options.end ()) + a->tag ("OP"); +} + +//////////////////////////////////////////////////////////////////////////////// +// Two consecutive FILTER, non-OP arguments that are not "(" or ")" need an +// "and" operator inserted between them. +// +// ) --> ) and +// ( --> ( +// ) ( --> ) and ( +// --> and +// +void CLI::insertJunctions () +{ + std::vector reconstructed; + std::vector ::iterator prev = _args.begin (); + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("FILTER")) + { + // The prev iterator should be the first FILTER arg. + if (prev == _args.begin ()) + prev = a; + + // Insert AND between terms. + else if (a != prev) + { + if ((! prev->hasTag ("OP") && a->attribute ("raw") == "(") || + (! prev->hasTag ("OP") && ! a->hasTag ("OP")) || + (prev->attribute ("raw") == ")" && ! a->hasTag ("OP")) || + (prev->attribute ("raw") == ")" && a->attribute ("raw") == "(")) + { + A opOr ("argOp", "and"); + opOr.tag ("FILTER"); + opOr.tag ("OP"); + reconstructed.push_back (opOr); + } + } + + // Previous FILTER arg. + prev = a; + } + + reconstructed.push_back (*a); + } + + _args = reconstructed; +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::decomposeModAttributes () +{ + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("TERMINATOR")) + break; + + if (a->hasTag ("MODIFICATION")) + { + // Look for a valid attribute name. + Nibbler n (a->attribute ("raw")); + std::string name; + if (n.getName (name) && + name.length ()) + { + if (n.skip (':')) + { + std::string value; + if (n.getQuoted ('"', value) || + n.getQuoted ('\'', value) || + n.getUntilEOS (value) || + n.depleted ()) + { + if (value == "") + value = "''"; + + std::string canonical; + if (canonicalize (canonical, "uda", name)) + { + a->attribute ("name", canonical); + a->attribute ("value", value); + a->tag ("UDA"); + a->tag ("MODIFIABLE"); + } + + else if (canonicalize (canonical, "attribute", name)) + { + a->attribute ("name", canonical); + a->attribute ("value", value); + a->tag ("ATTRIBUTE"); + + std::map ::const_iterator col; + col = context.columns.find (canonical); + if (col != context.columns.end () && + col->second->modifiable ()) + { + a->tag ("MODIFIABLE"); + } + } + } + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::decomposeModAttributeModifiers () +{ + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("TERMINATOR")) + break; + + if (a->hasTag ("MODIFICATION")) + { + // Look for a valid attribute name. + Nibbler n (a->attribute ("raw")); + std::string name; + if (n.getUntil (".", name) && + name.length ()) + { + std::string canonical; + if (canonicalize (canonical, "attribute", name) || + canonicalize (canonical, "uda", name)) + { + if (n.skip ('.')) + { + std::string sense = "+"; + if (n.skip ('~')) + sense = "-"; + + std::string modifier; + n.getUntilOneOf (":=", modifier); + + if (n.skip (':') || + n.skip ('=')) + { + std::string value; + if (n.getQuoted ('"', value) || + n.getQuoted ('\'', value) || + n.getUntilEOS (value) || + n.depleted ()) + { + if (value == "") + value = "''"; + + std::string canonical; + if (canonicalize (canonical, "uda", name)) + { + a->attribute ("name", canonical); + a->attribute ("modifier", modifier); + a->attribute ("sense", sense); + a->attribute ("value", value); + a->tag ("UDA"); + a->tag ("MODIFIABLE"); + } + + else if (canonicalize (canonical, "attribute", name)) + { + a->attribute ("name", canonical); + a->attribute ("modifier", modifier); + a->attribute ("sense", sense); + a->attribute ("value", value); + a->tag ("ATTMOD"); + + std::map ::const_iterator col; + col = context.columns.find (canonical); + if (col != context.columns.end () && + col->second->modifiable ()) + { + a->tag ("MODIFIABLE"); + } + } + } + } + } + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::decomposeModTags () +{ + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("TERMINATOR")) + break; + + if (a->hasTag ("MODIFICATION")) + { + Nibbler n (a->attribute ("raw")); + std::string tag; + std::string sign; + + if (n.getN (1, sign) && + (sign == "+" || sign == "-") && + n.getUntilEOS (tag) && + tag.find (' ') == std::string::npos) + { + a->attribute ("name", tag); + a->attribute ("sign", sign); + a->tag ("TAG"); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +void CLI::decomposeModSubstitutions () +{ + std::vector ::iterator a; + for (a = _args.begin (); a != _args.end (); ++a) + { + if (a->hasTag ("TERMINATOR")) + break; + + if (a->hasTag ("MODIFICATION")) + { + std::string raw = a->attribute ("raw"); + Nibbler n (raw); + std::string from; + std::string to; + bool global = false; + if (n.getQuoted ('/', from) && + n.backN () && + n.getQuoted ('/', to)) + { + if (n.skip ('g')) + global = true; + + if (n.depleted () && + ! Directory (raw).exists ()) + { + a->tag ("SUBSTITUTION"); + a->attribute ("from", from); + a->attribute ("to", to); + a->attribute ("global", global ? 1 : 0); + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/CLI.h b/src/CLI.h new file mode 100644 index 000000000..2ebadc31f --- /dev/null +++ b/src/CLI.h @@ -0,0 +1,110 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2014, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDED_CLI +#define INCLUDED_CLI + +#include +#include +#include + +// Represents a single argument. +class A +{ +public: + A (); + A (const std::string&, const std::string&); + A (const std::string&, const int); + A (const std::string&, const double); + ~A (); + A (const A&); + A& operator= (const A&); + void clear (); + bool hasTag (const std::string&) const; + void tag (const std::string&); + void unTag (const std::string&); + void unTagAll (); + void attribute (const std::string&, const std::string&); + void attribute (const std::string&, const int); + void attribute (const std::string&, const double); + const std::string attribute (const std::string&) const; + void removeAttribute (const std::string&); + const std::string dump () const; + +public: + std::string _name; + std::vector _tags; + std::map _attributes; +}; + +// Represents the command line. +class CLI +{ +public: + CLI (); + ~CLI (); + void alias (const std::string&, const std::string&); + void entity (const std::string&, const std::string&); + void initialize (int, const char**); + void add (const std::string&); + void analyze (bool parse = true); + const std::string getFilter (); + const std::vector getWords (); + const std::vector getModifications (); + const std::string dump () const; + +private: + void aliasExpansion (); + void findOverrides (); + void categorize (); + bool exactMatch (const std::string&, const std::string&) const; + bool canonicalize (std::string&, const std::string&, const std::string&) const; + void desugarTags (); + void desugarAttributes (); + void desugarAttributeModifiers (); + void desugarPatterns (); + void findIDs (); + void findUUIDs (); + void insertIDExpr (); + void desugarPlainArgs (); + void findOperators (); + void insertJunctions (); + void decomposeModAttributes (); + void decomposeModAttributeModifiers (); + void decomposeModTags (); + void decomposeModSubstitutions (); + +public: + std::multimap _entities; + std::map _aliases; + std::vector _original_args; + std::vector _args; + + std::vector > _id_ranges; + std::vector _uuid_list; +}; + +#endif + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 73cd46e7c..b1b76ff5f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,7 +5,8 @@ include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/columns ${TASK_INCLUDE_DIRS}) -set (task_SRCS Color.cpp Color.h +set (task_SRCS CLI.cpp CLI.h + Color.cpp Color.h Config.cpp Config.h Context.cpp Context.h DOM.cpp DOM.h diff --git a/src/Context.cpp b/src/Context.cpp index 10a6abe8d..67c90c39c 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -108,6 +108,7 @@ int Context::initialize (int argc, const char** argv) // Scan command line for 'rc:' only. Parser::getOverrides (argc, argv, rc_file._data); + cli.initialize (argc, argv); // task arg0 arg1 ... // TASKRC environment variable overrides the command line. char* override = getenv ("TASKRC"); @@ -133,11 +134,27 @@ int Context::initialize (int argc, const char** argv) // Process 'rc:' command line override. parser.findOverrides (); // rc: rc.: parser.getOverrides (home_dir, rc_file); // <-- +/* + home_dir = rc_file; + std::string::size_type last_slash = rc._data.rfind ("/"); + if (last_slash != std::string::npos) + home_dir = rc_file.parent (); + else + home_dir = "."; + std::cout << "# home_dir=" << home_dir << "\n"; +*/ // The data location, Context::data_dir, is determined from the assumed // location (~/.task), or set by data.location in the config file, or // overridden by rc.data.location on the command line. parser.getDataLocation (data_dir); // <-- rc.data.location= +/* + if (cli._overrides.find ("data.location") != cli._overrides.end ()) + data_dir = cli._overrides["data.location"]; + else + data_dir = config.get ("data.location"); + std::cout << "# data_dir=" << data_dir << "\n"; +*/ override = getenv ("TASKDATA"); if (override) @@ -171,35 +188,55 @@ int Context::initialize (int argc, const char** argv) for (cmd = commands.begin (); cmd != commands.end (); ++cmd) { parser.entity ("cmd", cmd->first); + cli.entity ("cmd", cmd->first); if (cmd->first[0] == '_') + { parser.entity ("helper", cmd->first); + cli.entity ("helper", cmd->first); + } if (cmd->second->read_only ()) + { parser.entity ("readcmd", cmd->first); + cli.entity ("readcmd", cmd->first); + } else + { parser.entity ("writecmd", cmd->first); + cli.entity ("writecmd", cmd->first); + } } // Instantiate built-in column objects. Column::factory (columns); std::map ::iterator col; for (col = columns.begin (); col != columns.end (); ++col) + { parser.entity ("attribute", col->first); + cli.entity ("attribute", col->first); + } // Entities: Pseudo-attributes. Hard-coded. parser.entity ("pseudo", "limit"); + cli.entity ("pseudo", "limit"); // Entities: Modifiers. for (unsigned int i = 0; i < NUM_MODIFIER_NAMES; ++i) + { parser.entity ("modifier", modifierNames[i]); + cli.entity ("modifier", modifierNames[i]); + } // Entities: Operators. std::vector operators; Eval::getOperators (operators); std::vector ::iterator op; for (op = operators.begin (); op != operators.end (); ++op) + { parser.entity ("operator", *op); + cli.entity ("operator", *op); + } // Now the entities are loaded, parsing may resume. parser.findBinary (); // @@ -211,6 +248,7 @@ int Context::initialize (int argc, const char** argv) staticInitialization (); // Decouple code from Context. parser.parse (); // Parse all elements. + cli.analyze (); // Parse all elements. tdb2.set_location (data_dir); // Prepare the task database. @@ -752,7 +790,10 @@ void Context::loadAliases () std::map ::iterator i; for (i = config.begin (); i != config.end (); ++i) if (i->first.substr (0, 6) == "alias.") + { parser.alias (i->first.substr (6), i->second); + cli.alias (i->first.substr (6), i->second); + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Context.h b/src/Context.h index f4c8ce4de..eb34f97a4 100644 --- a/src/Context.h +++ b/src/Context.h @@ -38,6 +38,7 @@ #include #include #include +#include #include class Context @@ -84,6 +85,7 @@ private: public: std::string program; Parser parser; + CLI cli; std::string home_dir; File rc_file; Path data_dir; diff --git a/src/Filter.cpp b/src/Filter.cpp index 7636ee68f..c1b52c1e0 100644 --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -74,6 +74,8 @@ void Filter::subset (const std::vector & input, std::vector & output if (context.config.getInteger ("debug.parser") >= 1) { + context.debug (context.cli.dump ()); + Tree* t = context.parser.tree (); if (t) context.debug (t->dump ()); @@ -121,6 +123,8 @@ void Filter::subset (std::vector & output) if (context.config.getInteger ("debug.parser") >= 1) { + context.debug (context.cli.dump ()); + Tree* t = context.parser.tree (); if (t) context.debug (t->dump ()); @@ -210,24 +214,27 @@ bool Filter::pendingOnly () Tree* tree = context.parser.tree (); // To skip loading completed.data, there should be: - // - 'status:pending' - // - no 'or' operators - // - no 'xor' operators + // - 'status' in filter + // - no 'completed' + // - no 'deleted' + // - no 'xor' + // - no 'or' int countStatus = 0; int countPending = 0; int countId = 0; int countOr = 0; int countXor = 0; - std::vector ::iterator i; - for (i = tree->_branches.begin (); i != tree->_branches.end (); ++i) + + std::vector ::iterator a; + for (a = context.cli._args.begin (); a != context.cli._args.end (); ++a) { - if ((*i)->hasTag ("FILTER")) + if (a->hasTag ("FILTER")) { - if ((*i)->hasTag ("ID")) ++countId; - if ((*i)->hasTag ("OP") && (*i)->attribute ("raw") == "or") ++countOr; - if ((*i)->hasTag ("OP") && (*i)->attribute ("raw") == "xor") ++countXor; - if ((*i)->hasTag ("ATTRIBUTE") && (*i)->attribute ("name") == "status") ++countStatus; - if ((*i)->hasTag ("ATTRIBUTE") && (*i)->attribute ("raw") == "pending") ++countPending; + if (a->hasTag ("ID")) ++countId; + if (a->hasTag ("OP") && a->attribute ("raw") == "or") ++countOr; + if (a->hasTag ("OP") && a->attribute ("raw") == "xor") ++countXor; + if (a->hasTag ("ATTRIBUTE") && a->attribute ("name") == "status") ++countStatus; + if ( a->attribute ("raw") == "pending") ++countPending; } } @@ -240,7 +247,7 @@ bool Filter::pendingOnly () return false; // Only one 'status == pending' is allowed. - if (countStatus == 1 && countPending != 1) + if (countStatus != 1 || countPending != 1) return false; return true; diff --git a/src/Parser.cpp b/src/Parser.cpp index 433c905ae..03ab8d20c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -851,7 +851,7 @@ void Parser::findSubstitution () } //////////////////////////////////////////////////////////////////////////////// -// +tag +// +tag|-tag void Parser::findTag () { bool action = true; diff --git a/src/Task.cpp b/src/Task.cpp index 2d4ac1966..7a7b4c14d 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -1907,9 +1907,13 @@ float Task::urgency_blocking () const void Task::modify (modType type, bool text_required /* = false */) { std::string text = ""; + Tree* tree = context.parser.tree (); - if (tree) + if (context.config.getInteger ("debug.parser") >= 1) + { + context.debug (context.cli.dump ()); context.debug (tree->dump ()); + } context.debug ("Task::modify"); std::string label = " MODIFICATION "; diff --git a/src/commands/CmdCustom.cpp b/src/commands/CmdCustom.cpp index 964e49553..b6894de44 100644 --- a/src/commands/CmdCustom.cpp +++ b/src/commands/CmdCustom.cpp @@ -81,15 +81,6 @@ int CmdCustom::execute (std::string& output) split (sortOrder, reportSort, ','); validateSortColumns (sortOrder); -/* - TODO Wow, this addition causes memory errors. - - // Surround the command-line filter with parentheses, to protect it from - // the 'and' placed between the report filter and the command line filter. - context.parser.captureFirst ("("); - context.parser.captureLast (")"); -*/ - // Prepend the argument list with those from the report filter. std::string lexeme; Lexer::Type type; @@ -97,7 +88,10 @@ int CmdCustom::execute (std::string& output) lex.ambiguity (false); std::vector filterArgs; while (lex.token (lexeme, type)) + { filterArgs.push_back (lexeme); + context.cli.add (lexeme); + } std::vector ::reverse_iterator arg; for (arg = filterArgs.rbegin (); arg != filterArgs.rend (); ++ arg)