Make commands objects instead of functions calling each other. Now there is '?' command that can display command list and help about individual command. Also the command handling is more distributed into places where it belongs.
264 lines
7.5 KiB
C++
264 lines
7.5 KiB
C++
#include "command.hpp"
|
|
#include "misc.hpp"
|
|
#include "zip.hpp"
|
|
#include "window.hpp"
|
|
#include <set>
|
|
#include <map>
|
|
|
|
namespace
|
|
{
|
|
std::map<std::string, command*>* commands;
|
|
std::set<std::string> command_stack;
|
|
std::map<std::string, std::list<std::string>> aliases;
|
|
|
|
class run_command : public command
|
|
{
|
|
public:
|
|
run_command() throw(std::bad_alloc) : command("run-script") {}
|
|
void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
|
|
{
|
|
if(args == "")
|
|
throw std::runtime_error("Filename needed");
|
|
std::istream* o = NULL;
|
|
try {
|
|
o = &open_file_relative(args, "");
|
|
out(win) << "Running '" << args << "'" << std::endl;
|
|
std::string line;
|
|
while(std::getline(*o, line))
|
|
command::invokeC(line, win);
|
|
delete o;
|
|
} catch(std::exception& e) {
|
|
if(o)
|
|
delete o;
|
|
throw;
|
|
}
|
|
}
|
|
std::string get_short_help() throw(std::bad_alloc) { return "run a file as a script"; }
|
|
std::string get_long_help() throw(std::bad_alloc)
|
|
{
|
|
return "Syntax: run-script <file>\n"
|
|
"Runs file <file> just as it would have been entered in the command line\n";
|
|
}
|
|
} run;
|
|
|
|
class aliases_command : public command
|
|
{
|
|
public:
|
|
aliases_command() throw(std::bad_alloc) : command("show-aliases") {}
|
|
void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
|
|
{
|
|
if(args != "")
|
|
throw std::runtime_error("This command does not take parameters");
|
|
for(auto i = aliases.begin(); i != aliases.end(); i++)
|
|
for(auto j = i->second.begin(); j != i->second.end(); j++)
|
|
out(win) << "alias " << i->first << " " << *j << std::endl;
|
|
}
|
|
std::string get_short_help() throw(std::bad_alloc) { return "show aliases"; }
|
|
std::string get_long_help() throw(std::bad_alloc)
|
|
{
|
|
return "Syntax: show-aliases\n"
|
|
"Show expansions of all aliases\n";
|
|
}
|
|
} sh_aliases;
|
|
|
|
class unalias_command : public command
|
|
{
|
|
public:
|
|
unalias_command() throw(std::bad_alloc) : command("unalias-command") {}
|
|
void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
|
|
{
|
|
tokensplitter t(args);
|
|
std::string aliasname = t;
|
|
if(t)
|
|
throw std::runtime_error("This command only takes one argument");
|
|
if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
|
|
throw std::runtime_error("Illegal alias name");
|
|
aliases[aliasname].clear();
|
|
out(win) << "Command '" << aliasname << "' unaliased" << std::endl;
|
|
}
|
|
std::string get_short_help() throw(std::bad_alloc) { return "unalias a command"; }
|
|
std::string get_long_help() throw(std::bad_alloc)
|
|
{
|
|
return "Syntax: unalias-command <aliasname>\n"
|
|
"Clear expansion of alias <aliasname>\n";
|
|
}
|
|
} unalias;
|
|
|
|
class alias_command : public command
|
|
{
|
|
public:
|
|
alias_command() throw(std::bad_alloc) : command("alias-command") {}
|
|
void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
|
|
{
|
|
tokensplitter t(args);
|
|
std::string aliasname = t;
|
|
std::string command = t.tail();
|
|
if(command == "")
|
|
throw std::runtime_error("Alias name and command needed");
|
|
if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
|
|
throw std::runtime_error("Illegal alias name");
|
|
aliases[aliasname].push_back(command);
|
|
out(win) << "Command '" << aliasname << "' aliased to '" << command << "'" << std::endl;
|
|
}
|
|
std::string get_short_help() throw(std::bad_alloc) { return "alias a command"; }
|
|
std::string get_long_help() throw(std::bad_alloc)
|
|
{
|
|
return "Syntax: alias-command <aliasname> <command>\n"
|
|
"Append <command> to expansion of alias <aliasname>\n"
|
|
"Valid alias names can't be empty nor start with '*' or '?'\n";
|
|
}
|
|
} alias;
|
|
}
|
|
|
|
command::command(const std::string& cmd) throw(std::bad_alloc)
|
|
{
|
|
if(!commands)
|
|
commands = new std::map<std::string, command*>();
|
|
if(commands->count(cmd))
|
|
std::cerr << "WARNING: Command collision for " << cmd << "!" << std::endl;
|
|
(*commands)[commandname = cmd] = this;
|
|
}
|
|
|
|
command::~command() throw()
|
|
{
|
|
if(!commands)
|
|
return;
|
|
commands->erase(commandname);
|
|
}
|
|
|
|
void command::invokeC(const std::string& cmd, window* win) throw()
|
|
{
|
|
try {
|
|
if(command_stack.count(cmd)) {
|
|
out(win) << "Can not invoke recursively: " << cmd << std::endl;
|
|
return;
|
|
}
|
|
command_stack.insert(cmd);
|
|
std::string cmd2 = cmd;
|
|
if(cmd2 == "?") {
|
|
//The special ? command.
|
|
if(commands) {
|
|
for(auto i = commands->begin(); i != commands->end(); ++i)
|
|
out(win) << i->first << ": " << i->second->get_short_help() << std::endl;
|
|
}
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
if(cmd2.length() > 1 && cmd2[0] == '?') {
|
|
//?command.
|
|
size_t split = cmd2.find_first_of(" \t");
|
|
std::string rcmd;
|
|
if(split >= cmd2.length())
|
|
rcmd = cmd2.substr(1);
|
|
else
|
|
rcmd = cmd2.substr(1, split - 1);
|
|
if(rcmd.length() > 0 && rcmd[0] != '*') {
|
|
//This may be an alias.
|
|
std::string aname = cmd2.substr(1);
|
|
if(aliases.count(aname)) {
|
|
//Yup.
|
|
out(win) << aname << " is an alias for: " << std::endl;
|
|
size_t j = 0;
|
|
for(auto i = aliases[aname].begin(); i != aliases[aname].end(); ++i, ++j)
|
|
out(win) << "#" + (j + 1) << ": " << *i << std::endl;
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
}
|
|
if(rcmd.length() > 0 && rcmd[0] == '*')
|
|
rcmd = rcmd.substr(1);
|
|
if(!commands || !commands->count(rcmd)) {
|
|
if(rcmd != "")
|
|
out(win) << "Unknown command '" << rcmd << "'" << std::endl;
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
out(win) << (*commands)[rcmd]->get_long_help() << std::endl;
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
bool may_be_alias_expanded = true;
|
|
if(cmd2.length() > 0 && cmd2[0] == '*') {
|
|
may_be_alias_expanded = false;
|
|
cmd2 = cmd2.substr(1);
|
|
}
|
|
if(may_be_alias_expanded && aliases.count(cmd2)) {
|
|
for(auto i = aliases[cmd2].begin(); i != aliases[cmd2].end(); ++i)
|
|
invokeC(*i, win);
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
try {
|
|
size_t split = cmd2.find_first_of(" \t");
|
|
std::string rcmd;
|
|
if(split >= cmd2.length())
|
|
rcmd = cmd2;
|
|
else
|
|
rcmd = cmd2.substr(0, split);
|
|
split = cmd2.find_first_not_of(" \t", split);
|
|
std::string args;
|
|
if(split < cmd2.length())
|
|
args = cmd2.substr(split);
|
|
command* cmdh = NULL;
|
|
if(commands && commands->count(rcmd))
|
|
cmdh = (*commands)[rcmd];
|
|
if(!cmdh) {
|
|
out(win) << "Unknown command '" << rcmd << "'" << std::endl;
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
cmdh->invoke(args, win);
|
|
command_stack.erase(cmd);
|
|
return;
|
|
} catch(std::bad_alloc& e) {
|
|
OOM_panic(win);
|
|
} catch(std::exception& e) {
|
|
out(win) << "Error: " << e.what() << std::endl;
|
|
command_stack.erase(cmd);
|
|
return;
|
|
}
|
|
} catch(std::bad_alloc& e) {
|
|
OOM_panic(win);
|
|
}
|
|
}
|
|
|
|
std::string command::get_short_help() throw(std::bad_alloc)
|
|
{
|
|
return "No description available";
|
|
}
|
|
|
|
std::string command::get_long_help() throw(std::bad_alloc)
|
|
{
|
|
return "No help available on command " + commandname;
|
|
}
|
|
|
|
tokensplitter::tokensplitter(const std::string& _line) throw(std::bad_alloc)
|
|
{
|
|
line = _line;
|
|
position = 0;
|
|
}
|
|
|
|
tokensplitter::operator bool() throw()
|
|
{
|
|
return (position < line.length());
|
|
}
|
|
|
|
tokensplitter::operator std::string() throw(std::bad_alloc)
|
|
{
|
|
size_t nextp, oldp = position;
|
|
nextp = line.find_first_of(" \t", position);
|
|
if(nextp > line.length()) {
|
|
position = line.length();
|
|
return line.substr(oldp);
|
|
} else {
|
|
position = nextp;
|
|
while(position < line.length() && (line[position] == ' ' || line[position] == '\t'))
|
|
position++;
|
|
return line.substr(oldp, nextp - oldp);
|
|
}
|
|
}
|
|
|
|
std::string tokensplitter::tail() throw(std::bad_alloc)
|
|
{
|
|
return line.substr(position);
|
|
}
|