TW-1656: Implicitly parenthesize argv filter
- Thanks to Daniel Shahaf.
This commit is contained in:
@@ -136,6 +136,7 @@
|
|||||||
David Patrick).
|
David Patrick).
|
||||||
- TW-1655 Inform "No changes made." when quitting early due to signal (thanks
|
- TW-1655 Inform "No changes made." when quitting early due to signal (thanks
|
||||||
to Daniel Shahaf).
|
to Daniel Shahaf).
|
||||||
|
- TW-1656 Implicitly parenthesize argv filter (thanks to Daniel Shahaf).
|
||||||
- TW-1660 Disabled sorting option (thanks to David Patrick).
|
- TW-1660 Disabled sorting option (thanks to David Patrick).
|
||||||
- TW-1664 Notify of waiting→pending promotion (thanks to Daniel Shahaf).
|
- TW-1664 Notify of waiting→pending promotion (thanks to Daniel Shahaf).
|
||||||
- TW-1666 import should reject invalid data (thanks to Daniel Shahaf).
|
- TW-1666 import should reject invalid data (thanks to Daniel Shahaf).
|
||||||
|
|||||||
125
src/CLI2.cpp
125
src/CLI2.cpp
@@ -360,7 +360,9 @@ void CLI2::entity (const std::string& category, const std::string& name)
|
|||||||
// Capture a single argument.
|
// Capture a single argument.
|
||||||
void CLI2::add (const std::string& 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.
|
// Adding a new argument invalidates prior analysis.
|
||||||
_args.clear ();
|
_args.clear ();
|
||||||
@@ -370,11 +372,11 @@ void CLI2::add (const std::string& argument)
|
|||||||
// Capture a set of arguments, inserted immediately after the binary.
|
// Capture a set of arguments, inserted immediately after the binary.
|
||||||
void CLI2::add (const std::vector <std::string>& arguments)
|
void CLI2::add (const std::vector <std::string>& arguments)
|
||||||
{
|
{
|
||||||
std::vector <std::string> replacement;
|
std::vector <A2> replacement;
|
||||||
replacement.push_back (_original_args[0]);
|
replacement.push_back (_original_args[0]);
|
||||||
|
|
||||||
for (auto& arg : arguments)
|
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)
|
for (unsigned int i = 1; i < _original_args.size (); ++i)
|
||||||
replacement.push_back (_original_args[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
|
// Capture arg0 separately, because it is the command that was run, and could
|
||||||
// need special handling.
|
// need special handling.
|
||||||
std::string raw = _original_args[0];
|
std::string raw = _original_args[0].attribute ("raw");
|
||||||
A2 a (raw, Lexer::Type::word);
|
A2 a (raw, Lexer::Type::word);
|
||||||
a.tag ("BINARY");
|
a.tag ("BINARY");
|
||||||
|
|
||||||
@@ -435,11 +437,11 @@ void CLI2::lexArguments ()
|
|||||||
bool terminated = false;
|
bool terminated = false;
|
||||||
for (unsigned int i = 1; i < _original_args.size (); ++i)
|
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;
|
std::string lexeme;
|
||||||
Lexer::Type type;
|
Lexer::Type type;
|
||||||
Lexer lex (_original_args[i]);
|
Lexer lex (_original_args[i].attribute ("raw"));
|
||||||
if (lex.token (lexeme, type) &&
|
if (lex.token (lexeme, type) &&
|
||||||
(lex.isEOS () || // Token goes to EOS
|
(lex.isEOS () || // Token goes to EOS
|
||||||
(quoted && type == Lexer::Type::pair)) // Quoted pairs automatically go to EOS
|
(quoted && type == Lexer::Type::pair)) // Quoted pairs automatically go to EOS
|
||||||
@@ -450,18 +452,21 @@ void CLI2::lexArguments ()
|
|||||||
else if (terminated)
|
else if (terminated)
|
||||||
type = Lexer::Type::word;
|
type = Lexer::Type::word;
|
||||||
|
|
||||||
A2 a (_original_args[i], type);
|
A2 a (_original_args[i].attribute ("raw"), type);
|
||||||
if (terminated)
|
if (terminated)
|
||||||
a.tag ("TERMINATED");
|
a.tag ("TERMINATED");
|
||||||
if (quoted)
|
if (quoted)
|
||||||
a.tag ("QUOTED");
|
a.tag ("QUOTED");
|
||||||
|
|
||||||
|
if (_original_args[i].hasTag ("ORIGINAL"))
|
||||||
|
a.tag ("ORIGINAL");
|
||||||
|
|
||||||
_args.push_back (a);
|
_args.push_back (a);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string quote = "'";
|
std::string quote = "'";
|
||||||
std::string escaped = _original_args[i];
|
std::string escaped = _original_args[i].attribute ("raw");
|
||||||
str_replace (escaped, quote, "\\'");
|
str_replace (escaped, quote, "\\'");
|
||||||
|
|
||||||
std::string::size_type cursor = 0;
|
std::string::size_type cursor = 0;
|
||||||
@@ -470,21 +475,27 @@ void CLI2::lexArguments ()
|
|||||||
{
|
{
|
||||||
Lexer::dequote (word);
|
Lexer::dequote (word);
|
||||||
A2 unknown (word, Lexer::Type::word);
|
A2 unknown (word, Lexer::Type::word);
|
||||||
if (lex.wasQuoted (_original_args[i]))
|
if (lex.wasQuoted (_original_args[i].attribute ("raw")))
|
||||||
unknown.tag ("QUOTED");
|
unknown.tag ("QUOTED");
|
||||||
|
|
||||||
|
if (_original_args[i].hasTag ("ORIGINAL"))
|
||||||
|
unknown.tag ("ORIGINAL");
|
||||||
|
|
||||||
_args.push_back (unknown);
|
_args.push_back (unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This branch may have no use-case.
|
// This branch may have no use-case.
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
A2 unknown (_original_args[i], Lexer::Type::word);
|
A2 unknown (_original_args[i].attribute ("raw"), Lexer::Type::word);
|
||||||
unknown.tag ("UNKNOWN");
|
unknown.tag ("UNKNOWN");
|
||||||
|
|
||||||
if (lex.wasQuoted (_original_args[i]))
|
if (lex.wasQuoted (_original_args[i].attribute ("raw")))
|
||||||
unknown.tag ("QUOTED");
|
unknown.tag ("QUOTED");
|
||||||
|
|
||||||
|
if (_original_args[i].hasTag ("ORIGINAL"))
|
||||||
|
unknown.tag ("ORIGINAL");
|
||||||
|
|
||||||
_args.push_back (unknown);
|
_args.push_back (unknown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,6 +560,7 @@ void CLI2::analyze ()
|
|||||||
|
|
||||||
// Determine arg types: FILTER, MODIFICATION, MISCELLANEOUS.
|
// Determine arg types: FILTER, MODIFICATION, MISCELLANEOUS.
|
||||||
categorizeArgs ();
|
categorizeArgs ();
|
||||||
|
parenthesizeOriginalFilter ();
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -734,11 +746,16 @@ const std::string CLI2::dump (const std::string& title) const
|
|||||||
<< " _original_args\n ";
|
<< " _original_args\n ";
|
||||||
|
|
||||||
Color colorArgs ("gray10 on gray4");
|
Color colorArgs ("gray10 on gray4");
|
||||||
|
Color colorFilter ("black on rgb311");
|
||||||
for (auto i = _original_args.begin (); i != _original_args.end (); ++i)
|
for (auto i = _original_args.begin (); i != _original_args.end (); ++i)
|
||||||
{
|
{
|
||||||
if (i != _original_args.begin ())
|
if (i != _original_args.begin ())
|
||||||
out << ' ';
|
out << ' ';
|
||||||
out << colorArgs.colorize (*i);
|
|
||||||
|
if (i->hasTag ("ORIGINAL"))
|
||||||
|
out << colorArgs.colorize (i->attribute ("raw"));
|
||||||
|
else
|
||||||
|
out << colorFilter.colorize (i->attribute ("raw"));
|
||||||
}
|
}
|
||||||
out << "\n";
|
out << "\n";
|
||||||
|
|
||||||
@@ -815,24 +832,24 @@ void CLI2::aliasExpansion ()
|
|||||||
|
|
||||||
_args = reconstructed;
|
_args = reconstructed;
|
||||||
|
|
||||||
std::vector <std::string> reconstructedOriginals;
|
std::vector <A2> reconstructedOriginals;
|
||||||
bool terminated = false;
|
bool terminated = false;
|
||||||
for (auto& i : _original_args)
|
for (auto& i : _original_args)
|
||||||
{
|
{
|
||||||
if (i == "--")
|
if (i.attribute ("raw") == "--")
|
||||||
terminated = true;
|
terminated = true;
|
||||||
|
|
||||||
if (terminated)
|
if (terminated)
|
||||||
{
|
{
|
||||||
reconstructedOriginals.push_back (i);
|
reconstructedOriginals.push_back (i);
|
||||||
}
|
}
|
||||||
else if (_aliases.find (i) != _aliases.end ())
|
else if (_aliases.find (i.attribute ("raw")) != _aliases.end ())
|
||||||
{
|
{
|
||||||
std::string lexeme;
|
std::string lexeme;
|
||||||
Lexer::Type type;
|
Lexer::Type type;
|
||||||
Lexer lex (_aliases[i]);
|
Lexer lex (_aliases[i.attribute ("raw")]);
|
||||||
while (lex.token (lexeme, type))
|
while (lex.token (lexeme, type))
|
||||||
reconstructedOriginals.push_back (lexeme);
|
reconstructedOriginals.push_back (A2 (lexeme, type));
|
||||||
|
|
||||||
action = true;
|
action = true;
|
||||||
changes = true;
|
changes = true;
|
||||||
@@ -1011,6 +1028,76 @@ void CLI2::categorizeArgs ()
|
|||||||
context.debug (dump ("CLI2::analyze 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 <A2> 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
|
// 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
|
// 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:
|
// Modify _args, _original_args to be:
|
||||||
// <args0> [<def0> ...] <args1> [...]
|
// <args0> [<def0> ...] <args1> [...]
|
||||||
|
|
||||||
std::vector <std::string> reconstructedOriginals {_original_args[0]};
|
std::vector <A2> reconstructedOriginals {_original_args[0]};
|
||||||
std::vector <A2> reconstructed {_args[0]};
|
std::vector <A2> reconstructed {_args[0]};
|
||||||
|
|
||||||
std::string lexeme;
|
std::string lexeme;
|
||||||
@@ -1994,7 +2081,7 @@ void CLI2::defaultCommand ()
|
|||||||
|
|
||||||
while (lex.token (lexeme, type))
|
while (lex.token (lexeme, type))
|
||||||
{
|
{
|
||||||
reconstructedOriginals.push_back (lexeme);
|
reconstructedOriginals.push_back (A2 (lexeme, type));
|
||||||
|
|
||||||
A2 cmd (lexeme, type);
|
A2 cmd (lexeme, type);
|
||||||
cmd.tag ("DEFAULT");
|
cmd.tag ("DEFAULT");
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ private:
|
|||||||
void aliasExpansion ();
|
void aliasExpansion ();
|
||||||
void canonicalizeNames ();
|
void canonicalizeNames ();
|
||||||
void categorizeArgs ();
|
void categorizeArgs ();
|
||||||
|
void parenthesizeOriginalFilter ();
|
||||||
bool findCommand ();
|
bool findCommand ();
|
||||||
bool exactMatch (const std::string&, const std::string&) const;
|
bool exactMatch (const std::string&, const std::string&) const;
|
||||||
void desugarFilterTags ();
|
void desugarFilterTags ();
|
||||||
@@ -109,7 +110,7 @@ private:
|
|||||||
public:
|
public:
|
||||||
std::multimap <std::string, std::string> _entities;
|
std::multimap <std::string, std::string> _entities;
|
||||||
std::map <std::string, std::string> _aliases;
|
std::map <std::string, std::string> _aliases;
|
||||||
std::vector <std::string> _original_args;
|
std::vector <A2> _original_args;
|
||||||
std::vector <A2> _args;
|
std::vector <A2> _args;
|
||||||
|
|
||||||
std::vector <std::pair <std::string, std::string>> _id_ranges;
|
std::vector <std::pair <std::string, std::string>> _id_ranges;
|
||||||
|
|||||||
@@ -99,7 +99,14 @@ bool DOM::get (const std::string& name, Variant& value)
|
|||||||
else if (name == "context.args")
|
else if (name == "context.args")
|
||||||
{
|
{
|
||||||
std::string commandLine;
|
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);
|
value = Variant (commandLine);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ int CmdCalendar::execute (std::string& output)
|
|||||||
// calendar --> taskendar
|
// calendar --> taskendar
|
||||||
|
|
||||||
// If the executable was "cal" or equivalent, replace it with "task".
|
// 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");
|
auto cal = executable.find ("cal");
|
||||||
if (cal != std::string::npos)
|
if (cal != std::string::npos)
|
||||||
executable = executable.substr (0, cal) + PACKAGE;
|
executable = executable.substr (0, cal) + PACKAGE;
|
||||||
|
|||||||
Reference in New Issue
Block a user