timewarrior/src/CLI.cpp
2016-04-03 13:17:48 -04:00

346 lines
9.9 KiB
C++

////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2016, 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 <cmake.h>
#include <CLI.h>
#include <algorithm>
#include <Color.h>
#include <shared.h>
////////////////////////////////////////////////////////////////////////////////
A2::A2 (const std::string& raw, Lexer::Type lextype)
{
_lextype = lextype;
attribute ("raw", raw);
}
////////////////////////////////////////////////////////////////////////////////
bool A2::hasTag (const std::string& tag) const
{
return std::find (_tags.begin (), _tags.end (), tag) != _tags.end ();
}
////////////////////////////////////////////////////////////////////////////////
void A2::tag (const std::string& tag)
{
if (! hasTag (tag))
_tags.push_back (tag);
}
////////////////////////////////////////////////////////////////////////////////
void A2::unTag (const std::string& tag)
{
for (auto i = _tags.begin (); i != _tags.end (); ++i)
if (*i == tag)
{
_tags.erase (i);
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// Accessor for attributes.
void A2::attribute (const std::string& name, const std::string& value)
{
_attributes[name] = value;
}
////////////////////////////////////////////////////////////////////////////////
// Accessor for attributes.
const std::string A2::attribute (const std::string& name) const
{
// Prevent autovivification.
auto i = _attributes.find (name);
if (i != _attributes.end ())
return i->second;
return "";
}
////////////////////////////////////////////////////////////////////////////////
const std::string A2::getToken () const
{
auto i = _attributes.find ("canonical");
if (i == _attributes.end ())
i = _attributes.find ("raw");
return i->second;
}
////////////////////////////////////////////////////////////////////////////////
const std::string A2::dump () const
{
auto output = Lexer::typeToString (_lextype);
// Dump attributes.
std::string atts;
for (const auto& a : _attributes)
atts += a.first + "='\033[33m" + a.second + "\033[0m' ";
// Dump tags.
std::string tags;
for (const auto& tag : _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 == "KEYWORD") tags += "\033[1;37;43m" + tag + "\033[0m ";
else tags += "\033[32m" + tag + "\033[0m ";
}
return output + " " + atts + tags;
}
////////////////////////////////////////////////////////////////////////////////
void CLI::entity (const std::string& category, const std::string& name)
{
// Walk the list of entities for category.
auto c = _entities.equal_range (category);
for (auto e = c.first; e != c.second; ++e)
if (e->second == name)
return;
// The category/name pair was not found, therefore add it.
_entities.insert (std::pair <std::string, std::string> (category, name));
}
////////////////////////////////////////////////////////////////////////////////
// Capture a single argument.
void CLI::add (const std::string& argument)
{
A2 arg (Lexer::trim (argument), Lexer::Type::word);
arg.tag ("ORIGINAL");
_original_args.push_back (arg);
// Adding a new argument invalidates prior analysis.
_args.clear ();
}
////////////////////////////////////////////////////////////////////////////////
// Arg0 is the first argument, which is the name and potentially a relative or
// absolute path to the invoked binary.
void CLI::handleArg0 ()
{
// Capture arg0 separately, because it is the command that was run, and could
// need special handling.
auto raw = _original_args[0].attribute ("raw");
A2 a (raw, Lexer::Type::word);
a.tag ("BINARY");
_args.push_back (a);
}
////////////////////////////////////////////////////////////////////////////////
// All arguments must be individually and wholly recognized by the Lexer. Any
// argument not recognized is considered a Lexer::Type::word.
void CLI::lexArguments ()
{
// Note: Starts iterating at index 1, because ::handleArg0 has already
// processed it.
for (unsigned int i = 1; i < _original_args.size (); ++i)
{
bool quoted = Lexer::wasQuoted (_original_args[i].attribute ("raw"));
std::string lexeme;
Lexer::Type type;
Lexer lex (_original_args[i].attribute ("raw"));
if (lex.token (lexeme, type) &&
lex.isEOS ())
{
A2 a (_original_args[i].attribute ("raw"), type);
if (quoted)
a.tag ("QUOTED");
if (_original_args[i].hasTag ("ORIGINAL"))
a.tag ("ORIGINAL");
_args.push_back (a);
}
else
{
std::string quote = "'";
auto escaped = str_replace (_original_args[i].attribute ("raw"), quote, "\\'");
std::string::size_type cursor = 0;
std::string word;
if (Lexer::readWord (quote + escaped + quote, quote, cursor, word))
{
Lexer::dequote (word);
A2 unknown (word, Lexer::Type::word);
if (Lexer::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].attribute ("raw"), Lexer::Type::word);
unknown.tag ("UNKNOWN");
if (Lexer::wasQuoted (_original_args[i].attribute ("raw")))
unknown.tag ("QUOTED");
if (_original_args[i].hasTag ("ORIGINAL"))
unknown.tag ("ORIGINAL");
_args.push_back (unknown);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Intended to be called after ::add() to perform the final analysis.
void CLI::analyze ()
{
// Process _original_args.
_args.clear ();
handleArg0 ();
lexArguments ();
canonicalizeNames ();
}
////////////////////////////////////////////////////////////////////////////////
// Search for 'value' in _entities category, return canonicalized value.
bool CLI::canonicalize (
std::string& canonicalized,
const std::string& category,
const std::string& value) const
{
// Extract a list of entities for category.
std::vector <std::string> options;
auto c = _entities.equal_range (category);
for (auto 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 <std::string> matches;
if (autoComplete (value, options, matches) == 1)
{
canonicalized = matches[0];
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
const std::string CLI::dump (const std::string& title) const
{
std::stringstream out;
out << "\033[1m" << title << "\033[0m\n"
<< " _original_args\n ";
Color colorArgs ("gray10 on gray4");
for (auto i = _original_args.begin (); i != _original_args.end (); ++i)
{
if (i != _original_args.begin ())
out << ' ';
out << colorArgs.colorize (i->attribute ("raw"));
}
out << "\n";
if (_args.size ())
{
out << " _args\n";
for (const auto& a : _args)
out << " " << a.dump () << "\n";
}
return out.str ();
}
////////////////////////////////////////////////////////////////////////////////
// Scan all arguments and canonicalize names that need it.
void CLI::canonicalizeNames ()
{
for (auto& a : _args)
{
auto raw = a.attribute ("raw");
std::string canonical;
// Commands.
if (exactMatch ("command", raw))
{
a.attribute ("canonical", raw);
a.tag ("CMD");
continue;
}
else if (canonicalize (canonical, "command", raw))
{
a.attribute ("canonical", canonical);
a.tag ("CMD");
continue;
}
// Commands.
if (exactMatch ("keyword", raw))
{
a.attribute ("canonical", raw);
a.tag ("KEYWORD");
continue;
}
else if (canonicalize (canonical, "keyword", raw))
{
a.attribute ("keyword", canonical);
a.tag ("KEYWORD");
continue;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Search for exact 'value' in _entities category.
bool CLI::exactMatch (
const std::string& category,
const std::string& value) const
{
// Extract a list of entities for category.
auto c = _entities.equal_range (category);
for (auto e = c.first; e != c.second; ++e)
if (value == e->second)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////