From c59afe34c4453a2164c036acfc2c50afa6091bff Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sat, 12 Sep 2015 08:57:38 -0400 Subject: [PATCH] TW-1656: Implicitly parenthesize argv filter - Thanks to Daniel Shahaf. --- ChangeLog | 1 + src/CLI2.cpp | 125 +++++++++++++++++++++++++++++------ src/CLI2.h | 3 +- src/DOM.cpp | 9 ++- src/commands/CmdCalendar.cpp | 2 +- 5 files changed, 118 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4f74d3536..0d899d7a0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -136,6 +136,7 @@ David Patrick). - TW-1655 Inform "No changes made." when quitting early due to signal (thanks to Daniel Shahaf). +- TW-1656 Implicitly parenthesize argv filter (thanks to Daniel Shahaf). - TW-1660 Disabled sorting option (thanks to David Patrick). - TW-1664 Notify of waiting→pending promotion (thanks to Daniel Shahaf). - TW-1666 import should reject invalid data (thanks to Daniel Shahaf). diff --git a/src/CLI2.cpp b/src/CLI2.cpp index faea57448..c31f2b742 100644 --- a/src/CLI2.cpp +++ b/src/CLI2.cpp @@ -360,7 +360,9 @@ void CLI2::entity (const std::string& category, const std::string& name) // Capture a single argument. void CLI2::add (const std::string& argument) { - _original_args.push_back (trim (argument)); + A2 arg (trim (argument), Lexer::Type::word); + arg.tag ("ORIGINAL"); + _original_args.push_back (arg); // Adding a new argument invalidates prior analysis. _args.clear (); @@ -370,11 +372,11 @@ void CLI2::add (const std::string& argument) // Capture a set of arguments, inserted immediately after the binary. void CLI2::add (const std::vector & arguments) { - std::vector replacement; + std::vector replacement; replacement.push_back (_original_args[0]); for (auto& arg : arguments) - replacement.push_back (arg); + replacement.push_back (A2 (arg, Lexer::Type::word)); for (unsigned int i = 1; i < _original_args.size (); ++i) replacement.push_back (_original_args[i]); @@ -396,7 +398,7 @@ void CLI2::handleArg0 () { // Capture arg0 separately, because it is the command that was run, and could // need special handling. - std::string raw = _original_args[0]; + std::string raw = _original_args[0].attribute ("raw"); A2 a (raw, Lexer::Type::word); a.tag ("BINARY"); @@ -435,11 +437,11 @@ void CLI2::lexArguments () bool terminated = false; for (unsigned int i = 1; i < _original_args.size (); ++i) { - bool quoted = Lexer::wasQuoted (_original_args[i]); + bool quoted = Lexer::wasQuoted (_original_args[i].attribute ("raw")); std::string lexeme; Lexer::Type type; - Lexer lex (_original_args[i]); + Lexer lex (_original_args[i].attribute ("raw")); if (lex.token (lexeme, type) && (lex.isEOS () || // Token goes to EOS (quoted && type == Lexer::Type::pair)) // Quoted pairs automatically go to EOS @@ -450,18 +452,21 @@ void CLI2::lexArguments () else if (terminated) type = Lexer::Type::word; - A2 a (_original_args[i], type); + A2 a (_original_args[i].attribute ("raw"), type); if (terminated) a.tag ("TERMINATED"); if (quoted) a.tag ("QUOTED"); + if (_original_args[i].hasTag ("ORIGINAL")) + a.tag ("ORIGINAL"); + _args.push_back (a); } else { std::string quote = "'"; - std::string escaped = _original_args[i]; + std::string escaped = _original_args[i].attribute ("raw"); str_replace (escaped, quote, "\\'"); std::string::size_type cursor = 0; @@ -470,21 +475,27 @@ void CLI2::lexArguments () { Lexer::dequote (word); A2 unknown (word, Lexer::Type::word); - if (lex.wasQuoted (_original_args[i])) + if (lex.wasQuoted (_original_args[i].attribute ("raw"))) unknown.tag ("QUOTED"); + if (_original_args[i].hasTag ("ORIGINAL")) + unknown.tag ("ORIGINAL"); + _args.push_back (unknown); } // This branch may have no use-case. else { - A2 unknown (_original_args[i], Lexer::Type::word); + A2 unknown (_original_args[i].attribute ("raw"), Lexer::Type::word); unknown.tag ("UNKNOWN"); - if (lex.wasQuoted (_original_args[i])) + if (lex.wasQuoted (_original_args[i].attribute ("raw"))) unknown.tag ("QUOTED"); + if (_original_args[i].hasTag ("ORIGINAL")) + unknown.tag ("ORIGINAL"); + _args.push_back (unknown); } } @@ -549,6 +560,7 @@ void CLI2::analyze () // Determine arg types: FILTER, MODIFICATION, MISCELLANEOUS. categorizeArgs (); + parenthesizeOriginalFilter (); } //////////////////////////////////////////////////////////////////////////////// @@ -734,11 +746,16 @@ const std::string CLI2::dump (const std::string& title) const << " _original_args\n "; Color colorArgs ("gray10 on gray4"); + Color colorFilter ("black on rgb311"); for (auto i = _original_args.begin (); i != _original_args.end (); ++i) { if (i != _original_args.begin ()) out << ' '; - out << colorArgs.colorize (*i); + + if (i->hasTag ("ORIGINAL")) + out << colorArgs.colorize (i->attribute ("raw")); + else + out << colorFilter.colorize (i->attribute ("raw")); } out << "\n"; @@ -815,24 +832,24 @@ void CLI2::aliasExpansion () _args = reconstructed; - std::vector reconstructedOriginals; + std::vector reconstructedOriginals; bool terminated = false; for (auto& i : _original_args) { - if (i == "--") + if (i.attribute ("raw") == "--") terminated = true; if (terminated) { reconstructedOriginals.push_back (i); } - else if (_aliases.find (i) != _aliases.end ()) + else if (_aliases.find (i.attribute ("raw")) != _aliases.end ()) { std::string lexeme; Lexer::Type type; - Lexer lex (_aliases[i]); + Lexer lex (_aliases[i.attribute ("raw")]); while (lex.token (lexeme, type)) - reconstructedOriginals.push_back (lexeme); + reconstructedOriginals.push_back (A2 (lexeme, type)); action = true; changes = true; @@ -1011,6 +1028,76 @@ void CLI2::categorizeArgs () context.debug (dump ("CLI2::analyze categorizeArgs")); } +//////////////////////////////////////////////////////////////////////////////// +// The following command: +// +// task +home or +work list +// +// Is reasonable, and does not work unless the filter is parenthesized. Ignoring +// context, the 'list' report has a filter, which is inserted at the beginning +// like this: +// +// task ( status:pending ) +home or +work list +// +// Parenthesizing the user-provided (original) filter yields this: +// +// task ( status:pending ) ( +home or +work ) list +// +// And when the conjunction is added: +// +// task ( status:pending ) and ( +home or +work ) list +// +// the query is correct. +void CLI2::parenthesizeOriginalFilter () +{ + // Locate the first and last ORIGINAL FILTER args. + unsigned int firstOriginalFilter = 0; + unsigned int lastOriginalFilter = 0; + for (unsigned int i = 1; i < _args.size (); ++i) + { + if (_args[i].hasTag ("FILTER") && + _args[i].hasTag ("ORIGINAL")) + { + if (firstOriginalFilter == 0) + firstOriginalFilter = i; + + lastOriginalFilter = i; + } + } + + // If found, parenthesize the arg list accordingly. + if (firstOriginalFilter && + lastOriginalFilter) + { + std::vector reconstructed; + for (unsigned int i = 0; i < _args.size (); ++i) + { + if (i == firstOriginalFilter) + { + A2 openParen ("(", Lexer::Type::op); + openParen.tag ("ORIGINAL"); + openParen.tag ("FILTER"); + reconstructed.push_back (openParen); + } + + reconstructed.push_back (_args[i]); + + if (i == lastOriginalFilter) + { + A2 closeParen (")", Lexer::Type::op); + closeParen.tag ("ORIGINAL"); + closeParen.tag ("FILTER"); + reconstructed.push_back (closeParen); + } + } + + _args = reconstructed; + + if (context.config.getInteger ("debug.parser") >= 2) + context.debug (dump ("CLI2::analyze parenthesizeOriginalFilter")); + } +} + //////////////////////////////////////////////////////////////////////////////// // Scan all arguments and if any are an exact match for a command name, then // tag as CMD. If an argument is an exact match for an attribute, despite being @@ -1985,7 +2072,7 @@ void CLI2::defaultCommand () // Modify _args, _original_args to be: // [ ...] [...] - std::vector reconstructedOriginals {_original_args[0]}; + std::vector reconstructedOriginals {_original_args[0]}; std::vector reconstructed {_args[0]}; std::string lexeme; @@ -1994,7 +2081,7 @@ void CLI2::defaultCommand () while (lex.token (lexeme, type)) { - reconstructedOriginals.push_back (lexeme); + reconstructedOriginals.push_back (A2 (lexeme, type)); A2 cmd (lexeme, type); cmd.tag ("DEFAULT"); diff --git a/src/CLI2.h b/src/CLI2.h index 29818259e..854fb775d 100644 --- a/src/CLI2.h +++ b/src/CLI2.h @@ -91,6 +91,7 @@ private: void aliasExpansion (); void canonicalizeNames (); void categorizeArgs (); + void parenthesizeOriginalFilter (); bool findCommand (); bool exactMatch (const std::string&, const std::string&) const; void desugarFilterTags (); @@ -109,7 +110,7 @@ private: public: std::multimap _entities; std::map _aliases; - std::vector _original_args; + std::vector _original_args; std::vector _args; std::vector > _id_ranges; diff --git a/src/DOM.cpp b/src/DOM.cpp index 66eaec1fb..a1b6ab8f0 100644 --- a/src/DOM.cpp +++ b/src/DOM.cpp @@ -99,7 +99,14 @@ bool DOM::get (const std::string& name, Variant& value) else if (name == "context.args") { std::string commandLine; - join (commandLine, " ", context.cli2._original_args); + for (auto& arg : context.cli2._original_args) + { + if (commandLine != "") + commandLine += " "; + + commandLine += arg.attribute("raw"); + } + value = Variant (commandLine); return true; } diff --git a/src/commands/CmdCalendar.cpp b/src/commands/CmdCalendar.cpp index 2b0916cb3..8b47fd193 100644 --- a/src/commands/CmdCalendar.cpp +++ b/src/commands/CmdCalendar.cpp @@ -344,7 +344,7 @@ int CmdCalendar::execute (std::string& output) // calendar --> taskendar // If the executable was "cal" or equivalent, replace it with "task". - std::string executable = context.cli2._original_args[0]; + std::string executable = context.cli2._original_args[0].attribute ("raw"); auto cal = executable.find ("cal"); if (cal != std::string::npos) executable = executable.substr (0, cal) + PACKAGE;