diff --git a/src/Arguments.cpp b/src/Arguments.cpp index 025040359..c08016efe 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -541,6 +541,16 @@ std::vector Arguments::list () return all; } +//////////////////////////////////////////////////////////////////////////////// +std::vector Arguments::operator_list () +{ + std::vector all; + for (int i = 0; i < NUM_OPERATORS; ++i) + all.push_back (operators[i].op); + + return all; +} + //////////////////////////////////////////////////////////////////////////////// std::string Arguments::combine () { @@ -594,20 +604,29 @@ bool Arguments::is_command ( bool Arguments::is_attr (const std::string& input) { Nibbler n (input); - - // Ensure a clean parse. std::string name; std::string value; if (n.getUntilOneOf ("=:", name)) { + if (name.length () == 0) + return false; + if (n.skip (':') || n.skip ('=')) { + // Exclude certain URLs, that look like attrs. + if (input.find ('@') <= n.cursor () || + input.find ('/') <= n.cursor ()) + return false; + // Both quoted and unquoted Att's are accepted. // Consider removing this for a stricter parse. - if (n.getQuoted ('"', value) || - n.getUntilEOS (value)) + if (n.getQuoted ('"', value) || + n.getQuoted ('\'', value) || + n.getUntil (' ', value) || + n.getUntilEOS (value) || + n.depleted ()) { return true; } @@ -622,25 +641,39 @@ bool Arguments::is_attr (const std::string& input) bool Arguments::is_attmod (const std::string& input) { Nibbler n (input); + std::string name; + std::string modifier; + std::string value; - // Ensure a clean parse. - std::string ignored; - - if (n.getUntil (".", ignored)) + if (n.getUntilOneOf (".", name)) { + if (name.length () == 0) + return false; + if (n.skip ('.')) { n.skip ('~'); - n.getUntilOneOf (":=", ignored); + n.getUntilOneOf (":=", modifier); + + if (modifier.length () == 0) + return false; } if (n.skip (':') || n.skip ('=')) { + // Exclude certain URLs, that look like attrs. + if (input.find ('@') <= n.cursor () || + input.find ('/') <= n.cursor ()) + return false; + // Both quoted and unquoted Att's are accepted. // Consider removing this for a stricter parse. - if (n.getQuoted ('"', ignored) || - n.getUntilEOS (ignored)) + if (n.getQuoted ('"', value) || + n.getQuoted ('\'', value) || + n.getUntil (' ', value) || + n.getUntilEOS (value) || + n.depleted ()) { return true; } @@ -654,17 +687,20 @@ bool Arguments::is_attmod (const std::string& input) // ///[g] bool Arguments::is_subst (const std::string& input) { - std::string ignored; + std::string from; + std::string to; Nibbler n (input); if (n.skip ('/') && - n.getUntil ('/', ignored) && + n.getUntil ('/', from) && n.skip ('/') && - n.getUntil ('/', ignored) && + n.getUntil ('/', to) && n.skip ('/')) { n.skip ('g'); - if (n.depleted ()) - return ! Directory (input).exists (); // Ouch - expensive call. + if (n.depleted () && + ! Directory (input).exists () && // Ouch - expensive call. + from.length ()) + return true; } return false; @@ -747,8 +783,13 @@ bool Arguments::is_tag (const std::string& input) { if (input.length () > 1 && (input[0] == '+' || - input[0] == '-')) + input[0] == '-') && + noSpaces (input) && + input.find ('+', 1) == std::string::npos && + input.find ('-', 1) == std::string::npos) + { return true; + } return false; } diff --git a/src/Arguments.h b/src/Arguments.h index 070c0bdf1..c08f12e8f 100644 --- a/src/Arguments.h +++ b/src/Arguments.h @@ -51,6 +51,7 @@ public: void resolve_aliases (); std::vector list (); + static std::vector operator_list (); std::string combine (); bool find_command (std::string&); diff --git a/src/Expression.cpp b/src/Expression.cpp index 13e4e3d6a..346d59dac 100644 --- a/src/Expression.cpp +++ b/src/Expression.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include extern Context context; @@ -55,6 +56,22 @@ bool Expression::eval (Task& task) std::vector >::iterator arg; for (arg = _postfix.begin (); arg != _postfix.end (); ++arg) { + // if (arg->second != "op") + // eval_stack.push_back (*arg); + + // else + // { + // if (arg->first == "+") + // { + // pop + // pop + // add the two operands + // push result + // } + // else if () + // { + // } + // } } return true; @@ -85,7 +102,10 @@ void Expression::expand_sequence () // If there is no sequence, we're done. if (ids.size () == 0 && uuids.size () == 0) + { + _sequenced = _original; return; + } // Construct the algebraic form. std::stringstream sequence; @@ -166,6 +186,10 @@ void Expression::expand_attr (const std::string& input) std::string value; Arguments::extract_attr (input, name, value); + // Always quote the value, so that empty values, or values containing spaces + // are preserved. + value = "\"" + value + "\""; + _infix.push_back (std::make_pair (name, "dom")); _infix.push_back (std::make_pair ("=", "op")); _infix.push_back (std::make_pair (value, "exp")); @@ -185,6 +209,10 @@ void Expression::expand_attmod (const std::string& input) std::string sense; Arguments::extract_attmod (input, name, mod, value, sense); + // Always quote the value, so that empty values, or values containing spaces + // are preserved. + value = "\"" + value + "\""; + if (mod == "before" || mod == "under" || mod == "below") { _infix.push_back (std::make_pair (name, "dom")); @@ -284,12 +312,17 @@ void Expression::expand_expression () { Arguments temp; + // Get a list of all operators. + std::vector operators = Arguments::operator_list (); + + // Look for all 'exp' args. std::vector >::iterator arg; for (arg = _infix.begin (); arg != _infix.end (); ++arg) { if (arg->second == "exp") { - Lexer lexer (arg->first); +/* obsolete */ + Lexer lexer (unquoteText (arg->first)); lexer.skipWhitespace (true); lexer.coalesceAlpha (true); lexer.coalesceDigits (true); @@ -306,6 +339,12 @@ void Expression::expand_expression () else temp.push_back (std::make_pair (*token, "dom")); } +/* obsolete */ +/* proposed */ +/* + Nibbler n (arg->first); +*/ +/* proposed */ } else temp.push_back (*arg); @@ -322,8 +361,6 @@ void Expression::expand_expression () // Converts: // to: and // -// -// // Rules: // 1. Two adjacent non-operator arguments have an 'and' inserted between them. // 2. Any argument of type "exp" is lexed and replaced by tokens. @@ -368,7 +405,10 @@ void Expression::to_infix () else if (arg->second == "word") expand_word (arg->first); - // Expressions will be converted later. + else if (arg->second == "op") + _infix.push_back (*arg); + + // Skip expressions, convert later. else if (arg->second == "exp") _infix.push_back (*arg); @@ -383,6 +423,7 @@ void Expression::to_infix () //////////////////////////////////////////////////////////////////////////////// // Dijkstra Shunting Algorithm. +// http://en.wikipedia.org/wiki/Shunting-yard_algorithm // // While there are tokens to be read: // Read a token. @@ -501,9 +542,12 @@ bool Expression::is_new_style () } //////////////////////////////////////////////////////////////////////////////// -// TODO Remove? -void Expression::dump (const std::string& label) +void Expression::dump () { + _original.dump ("Original Arguments"); + _sequenced.dump ("Sequence Expanded"); + _infix.dump ("Converted to Infix"); + _postfix.dump ("Converted to Postfix"); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Expression.h b/src/Expression.h index 8b7dfc359..7d6c34aae 100644 --- a/src/Expression.h +++ b/src/Expression.h @@ -43,6 +43,7 @@ public: private: void expand_sequence (); void expand_expression (); + void expand_tag (const std::string&); void expand_attr (const std::string&); void expand_attmod (const std::string&); @@ -52,7 +53,7 @@ private: void to_infix (); void to_postfix (); bool is_new_style (); - void dump (const std::string&); + void dump (); private: Arguments _original; diff --git a/src/commands/CmdCustom.cpp b/src/commands/CmdCustom.cpp index ca573e388..beaec7118 100644 --- a/src/commands/CmdCustom.cpp +++ b/src/commands/CmdCustom.cpp @@ -97,15 +97,21 @@ int CmdCustom::execute (std::string& output) Expression e (f); return 0; - // TODO e.apply (tasks); + + std::vector filtered; + std::vector ::iterator task; + for (task = tasks.begin (); task != tasks.end (); ++task) + if (e.eval (*task)) + filtered.push_back (*task); + //////////////////////////////////// // Sort the tasks. std::vector sequence; - for (unsigned int i = 0; i < tasks.size (); ++i) + for (unsigned int i = 0; i < filtered.size (); ++i) sequence.push_back (i); - sort_tasks (tasks, sequence, reportSort); + sort_tasks (filtered, sequence, reportSort); // Configure the view. ViewTask view; @@ -153,21 +159,21 @@ return 0; // Render. // TODO Consider rc.verbose std::stringstream out; - if (tasks.size ()) + if (filtered.size ()) { view.truncateRows (maxrows); view.truncateLines (maxlines); out << optionalBlankLine () - << view.render (tasks, sequence) + << view.render (filtered, sequence) << optionalBlankLine () - << tasks.size () - << (tasks.size () == 1 ? " task" : " tasks"); + << filtered.size () + << (filtered.size () == 1 ? " task" : " tasks"); - if (maxrows && maxrows < (int)tasks.size ()) + if (maxrows && maxrows < (int)filtered.size ()) out << ", " << maxrows << " shown"; - if (maxlines && maxlines < (int)tasks.size ()) + if (maxlines && maxlines < (int)filtered.size ()) out << ", truncated to " << maxlines - table_header << " lines"; out << "\n"; diff --git a/test/arguments.t.cpp b/test/arguments.t.cpp index 2d7e0ab49..4dfcce0a5 100644 --- a/test/arguments.t.cpp +++ b/test/arguments.t.cpp @@ -34,7 +34,7 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (14); + UnitTest t (109); const char* fake[] = { @@ -57,45 +57,164 @@ int main (int argc, char** argv) // void capture (int, char**); Arguments a1; a1.capture (12, &fake[0]); - t.is (a1.size (), (size_t)11, "11 arguments expected"); - t.is (a1[0], "list", "Arguments properly strips argv[0]"); + t.is (a1.size (), (size_t)12, "12 arguments expected"); // std::string combine (); t.is (a1.combine (), - "list proj:foo 1,3,5-10 12 pattern1 rc.name1=value1 rc.name2:value2 " + "task list proj:foo 1,3,5-10 12 pattern1 rc.name1=value1 rc.name2:value2 " "234 -- pattern2 n:v", "combine good"); - // TODO void append_stdin (); - // TODO void rc_override (std::string&, File&, std::string&); - // TODO void get_data_location (std::string&); - // TODO void apply_overrides (std::string&); - // TODO void resolve_aliases (); - // TODO bool extract_command (const std::vector &, std::string&); + // bool is_attr (const std::string&); + t.ok (Arguments::is_attr ("name:"), "name: -> attr"); + t.ok (Arguments::is_attr ("name:\"\""), "name:\"\" -> attr"); + t.ok (Arguments::is_attr ("name:one"), "name:one -> attr"); + t.ok (Arguments::is_attr ("name:\"one\""), "name:\"one\" -> attr"); + t.ok (Arguments::is_attr ("name:\"one two\""), "name:\"one two\" -> attr"); - // void extract_sequence (std::vector &); - std::vector sequence; - a1.extract_sequence (sequence); - size_t s = sequence.size (); - t.is (s, (size_t)9, "1,3,5-10 12 --> 1,3,5,6,7,8,9,10,12 == 9"); + t.ok (Arguments::is_attr ("name="), "name= -> attr"); + t.ok (Arguments::is_attr ("name=\"\""), "name=\"\" -> attr"); + t.ok (Arguments::is_attr ("name=one"), "name=one -> attr"); + t.ok (Arguments::is_attr ("name=\"one\""), "name=\"one\" -> attr"); + t.ok (Arguments::is_attr ("name=\"one two\""), "name=\"one two\" -> attr"); - if (s > 0) t.is (sequence[0], 1, "sequence 1"); else t.fail ("sequence 1"); - if (s > 1) t.is (sequence[1], 3, "sequence 3"); else t.fail ("sequence 3"); - if (s > 2) t.is (sequence[2], 5, "sequence 5"); else t.fail ("sequence 5"); - if (s > 3) t.is (sequence[3], 6, "sequence 6"); else t.fail ("sequence 6"); - if (s > 4) t.is (sequence[4], 7, "sequence 7"); else t.fail ("sequence 7"); - if (s > 5) t.is (sequence[5], 8, "sequence 8"); else t.fail ("sequence 8"); - if (s > 6) t.is (sequence[6], 9, "sequence 9"); else t.fail ("sequence 9"); - if (s > 7) t.is (sequence[7], 10, "sequence 10"); else t.fail ("sequence 10"); - if (s > 8) t.is (sequence[8], 12, "sequence 12"); else t.fail ("sequence 12"); + // bool is_attmod (const std::string&); + t.ok (Arguments::is_attmod ("name.is:"), "name.is: -> attr"); + t.ok (Arguments::is_attmod ("name.is:\"\""), "name.is:\"\" -> attr"); + t.ok (Arguments::is_attmod ("name.is:one"), "name.is:one -> attr"); + t.ok (Arguments::is_attmod ("name.is:\"one\""), "name.is:\"one\" -> attr"); + t.ok (Arguments::is_attmod ("name.is:\"one two\""), "name.is:\"one two\" -> attr"); - t.is (a1.size (), (size_t)9, "a1 - = 9 args"); + t.ok (Arguments::is_attmod ("name.is="), "name.is= -> attr"); + t.ok (Arguments::is_attmod ("name.is=\"\""), "name.is=\"\" -> attr"); + t.ok (Arguments::is_attmod ("name.is=one"), "name.is=one -> attr"); + t.ok (Arguments::is_attmod ("name.is=\"one\""), "name.is=\"one\" -> attr"); + t.ok (Arguments::is_attmod ("name.is=\"one two\""), "name.is=\"one two\" -> attr"); + + // bool is_subst (const std::string&); + t.notok (Arguments::is_subst ("///"), "/// -> not subst"); + t.notok (Arguments::is_subst ("//to/"), "//to/ -> not subst"); + t.ok (Arguments::is_subst ("/from//"), "/from// -> subst"); + t.ok (Arguments::is_subst ("/from/to/"), "/from/to/ -> subst"); + t.ok (Arguments::is_subst ("/from from/to to/"), "/from from/to to/ -> subst"); + + t.notok (Arguments::is_subst ("///g"), "///g -> not subst"); + t.notok (Arguments::is_subst ("//to/g"), "//to/g -> not subst"); + t.ok (Arguments::is_subst ("/from//g"), "/from//g -> subst"); + t.ok (Arguments::is_subst ("/from/to/g"), "/from/to/g -> subst"); + t.ok (Arguments::is_subst ("/from from/to to/g"), "/from from/to to/g -> subst"); + + // bool is_pattern (const std::string&); + t.notok (Arguments::is_pattern ("//"), "// -> not pattern"); + t.notok (Arguments::is_pattern ("///"), "/// -> not pattern"); + t.ok (Arguments::is_pattern ("/one/"), "/one/ -> pattern"); + t.ok (Arguments::is_pattern ("/one two/"), "/one two/ -> pattern"); + t.ok (Arguments::is_pattern ("/ /"), "/ / -> pattern"); + + // bool is_id (const std::string&); + t.ok (Arguments::is_id ("1"), "1 -> id"); + t.ok (Arguments::is_id ("1,2"), "1,2 -> id"); + t.ok (Arguments::is_id ("1-2"), "1-2 -> id"); + t.ok (Arguments::is_id ("1,2,3"), "1,2,3 -> id"); + t.ok (Arguments::is_id ("1-2,3,4-5"), "1-2,3,4-5 -> id"); + t.notok (Arguments::is_id ("1-2-3"), "1-2-3 -> no id"); + + // bool is_uuid (const std::string&); + t.ok (Arguments::is_uuid ("00000000-0000-0000-0000-000000000000"), + "00000000-0000-0000-0000-000000000000 -> uuid"); + t.ok (Arguments::is_uuid ("eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee,ffffffff-ffff-ffff-ffff-ffffffffffff"), + "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee,ffffffff-ffff-ffff-ffff-ffffffffffff -> uuid"); + + // bool is_tag (const std::string&); + t.ok (Arguments::is_tag ("+one"), "+one -> tag"); + t.ok (Arguments::is_tag ("-one"), "-one -> tag"); + t.notok (Arguments::is_tag ("+one "), "+one -> not tag"); + t.notok (Arguments::is_tag ("-one "), "-one -> not tag"); + t.notok (Arguments::is_tag ("+"), "+ -> not tag"); + t.notok (Arguments::is_tag ("-"), "- -> not tag"); + t.notok (Arguments::is_tag ("++"), "++ -> not tag"); + t.notok (Arguments::is_tag ("--"), "-- -> not tag"); + t.notok (Arguments::is_tag ("+one two"), "+one two -> not tag"); + + // bool is_operator (const std::string&); + t.ok (Arguments::is_operator ("^"), "^ -> operator"); + t.ok (Arguments::is_operator ("!"), "! -> operator"); + t.ok (Arguments::is_operator ("not"), "not -> operator"); + t.ok (Arguments::is_operator ("-"), "- -> operator"); + t.ok (Arguments::is_operator ("*"), "* -> operator"); + t.ok (Arguments::is_operator ("/"), "/ -> operator"); + t.ok (Arguments::is_operator ("%"), "% -> operator"); + t.ok (Arguments::is_operator ("+"), "+ -> operator"); + t.ok (Arguments::is_operator ("-"), "- -> operator"); + t.ok (Arguments::is_operator ("<"), "< -> operator"); + t.ok (Arguments::is_operator ("lt"), "lt -> operator"); + t.ok (Arguments::is_operator ("<="), "<= -> operator"); + t.ok (Arguments::is_operator ("le"), "le -> operator"); + t.ok (Arguments::is_operator (">="), ">= -> operator"); + t.ok (Arguments::is_operator ("ge"), "ge -> operator"); + t.ok (Arguments::is_operator (">"), "> -> operator"); + t.ok (Arguments::is_operator ("gt"), "gt -> operator"); + t.ok (Arguments::is_operator ("~"), "~ -> operator"); + t.ok (Arguments::is_operator ("!~"), "!~ -> operator"); + t.ok (Arguments::is_operator ("="), "= -> operator"); + t.ok (Arguments::is_operator ("eq"), "eq -> operator"); + t.ok (Arguments::is_operator ("!="), "!= -> operator"); + t.ok (Arguments::is_operator ("ne"), "ne -> operator"); + t.ok (Arguments::is_operator ("and"), "and -> operator"); + t.ok (Arguments::is_operator ("or"), "or -> operator"); + t.ok (Arguments::is_operator ("("), "( -> operator"); + t.ok (Arguments::is_operator (")"), ") -> operator"); + t.notok (Arguments::is_operator ("$"), "$ -> not operator"); + + // bool is_expression (const std::string&); + t.notok (Arguments::is_expression ("foo"), "foo -> expression"); + t.ok (Arguments::is_expression ("1+1"), "1+1 -> expression"); + t.ok (Arguments::is_expression ("a~b"), "a~b -> expression"); + t.ok (Arguments::is_expression ("(1)"), "(1) -> expression"); + t.ok (Arguments::is_expression ("not a"), "not a -> expression"); + + // static bool valid_modifier (const std::string&); + t.ok (Arguments::valid_modifier ("before"), "before -> modifier"); + t.ok (Arguments::valid_modifier ("under"), "under -> modifier"); + t.ok (Arguments::valid_modifier ("below"), "below -> modifier"); + t.ok (Arguments::valid_modifier ("after"), "after -> modifier"); + t.ok (Arguments::valid_modifier ("over"), "over -> modifier"); + t.ok (Arguments::valid_modifier ("above"), "above -> modifier"); + t.ok (Arguments::valid_modifier ("none"), "none -> modifier"); + t.ok (Arguments::valid_modifier ("any"), "any -> modifier"); + t.ok (Arguments::valid_modifier ("is"), "is -> modifier"); + t.ok (Arguments::valid_modifier ("equals"), "equals -> modifier"); + t.ok (Arguments::valid_modifier ("isnt"), "isnt -> modifier"); + t.ok (Arguments::valid_modifier ("not"), "not -> modifier"); + t.ok (Arguments::valid_modifier ("has"), "has -> modifier"); + t.ok (Arguments::valid_modifier ("contains"), "contains -> modifier"); + t.ok (Arguments::valid_modifier ("hasnt"), "hasnt -> modifier"); + t.ok (Arguments::valid_modifier ("startswith"), "startswith -> modifier"); + t.ok (Arguments::valid_modifier ("left"), "left -> modifier"); + t.ok (Arguments::valid_modifier ("endswith"), "endswith -> modifier"); + t.ok (Arguments::valid_modifier ("right"), "right -> modifier"); + t.ok (Arguments::valid_modifier ("word"), "word -> modifier"); + t.ok (Arguments::valid_modifier ("noword"), "noword -> modifier"); + t.notok (Arguments::valid_modifier ("duck"), "duck -> not modified"); // TODO void extract_uuids (std::vector &); // TODO void extract_filter (); // TODO void extract_modifications (); // TODO void extract_text (); + // TODO bool extract_attr (const std::string&, std::string&, std::string&); + // TODO bool extract_attmod (const std::string&, std::string&, std::string&, std::string&, std::string&); + // TODO bool extract_subst (const std::string&, std::string&, std::string&, bool&); + // TODO bool extract_pattern (const std::string&, std::string&); + // TODO bool extract_id (const std::string&, std::vector &); + // TODO bool extract_uuid (const std::string&, std::vector &); + // TODO bool extract_tag (const std::string&, char&, std::string&); + // TODO bool extract_operator (const std::string&, std::string&); + + // TODO Arguments extract_read_only_filter (); + // TODO Arguments extract_write_filter (); + // TODO Arguments extract_modifications (); + return 0; }