Port the generic control stuff from wxwidgets work
[lsnes.git] / generic / command.cpp
blob47ab496a9c9af8c29d97cce1fbb8f8b4f9b677fd
1 #include "command.hpp"
2 #include "misc.hpp"
3 #include "zip.hpp"
4 #include "globalwrap.hpp"
5 #include <set>
6 #include <map>
8 namespace
10 globalwrap<std::map<std::string, command*>> commands;
11 std::set<std::string> command_stack;
12 std::map<std::string, std::list<std::string>> aliases;
14 function_ptr_command<arg_filename> run_script("run-script", "run file as a script",
15 "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in the command line\n",
16 [](arg_filename filename) throw(std::bad_alloc, std::runtime_error) {
17 std::istream* o = NULL;
18 try {
19 o = &open_file_relative(filename, "");
20 messages << "Running '" << std::string(filename) << "'" << std::endl;
21 std::string line;
22 while(std::getline(*o, line))
23 command::invokeC(line);
24 delete o;
25 } catch(std::exception& e) {
26 if(o)
27 delete o;
28 throw;
30 });
32 function_ptr_command<> show_aliases("show-aliases", "show aliases",
33 "Syntax: show-aliases\nShow expansions of all aliases\n",
34 []() throw(std::bad_alloc, std::runtime_error) {
35 for(auto i : aliases)
36 for(auto j = i.second.begin(); j != i.second.end(); j++)
37 messages << "alias " << i.first << " " << *j << std::endl;
38 });
40 function_ptr_command<tokensplitter&> unalias_command("unalias-command", "unalias a command",
41 "Syntax: unalias-command <aliasname>\nClear expansion of alias <aliasname>\n",
42 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
43 std::string aliasname = t;
44 if(t)
45 throw std::runtime_error("This command only takes one argument");
46 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
47 throw std::runtime_error("Illegal alias name");
48 aliases[aliasname].clear();
49 messages << "Command '" << aliasname << "' unaliased" << std::endl;
50 });
52 function_ptr_command<tokensplitter&> alias_command("alias-command", "alias a command",
53 "Syntax: alias-command <aliasname> <command>\nAppend <command> to expansion of alias <aliasname>\n"
54 "Valid alias names can't be empty nor start with '*' or '?'\n",
55 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
56 std::string aliasname = t;
57 std::string command = t.tail();
58 if(command == "")
59 throw std::runtime_error("Alias name and command needed");
60 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
61 throw std::runtime_error("Illegal alias name");
62 aliases[aliasname].push_back(command);
63 messages << "Command '" << aliasname << "' aliased to '" << command << "'" << std::endl;
64 });
67 command::command(const std::string& cmd) throw(std::bad_alloc)
69 if(commands().count(cmd))
70 std::cerr << "WARNING: Command collision for " << cmd << "!" << std::endl;
71 commands()[commandname = cmd] = this;
74 command::~command() throw()
76 commands().erase(commandname);
79 void command::invokeC(const std::string& cmd) throw()
81 try {
82 std::string cmd2 = cmd;
83 if(cmd2[cmd2.length() - 1] == '\r')
84 cmd2 = cmd2.substr(0, cmd2.length() - 1);
85 if(cmd2 == "?") {
86 //The special ? command.
87 for(auto i : commands())
88 messages << i.first << ": " << i.second->get_short_help() << std::endl;
89 return;
91 if(cmd2.length() > 1 && cmd2[0] == '?') {
92 //?command.
93 size_t split = cmd2.find_first_of(" \t");
94 std::string rcmd;
95 if(split >= cmd2.length())
96 rcmd = cmd2.substr(1);
97 else
98 rcmd = cmd2.substr(1, split - 1);
99 if(rcmd.length() > 0 && rcmd[0] != '*') {
100 //This may be an alias.
101 std::string aname = cmd2.substr(1);
102 if(aliases.count(aname)) {
103 //Yup.
104 messages << aname << " is an alias for: " << std::endl;
105 size_t j = 0;
106 for(auto i : aliases[aname])
107 messages << "#" + (++j) << ": " << i << std::endl;
108 return;
111 if(rcmd.length() > 0 && rcmd[0] == '*')
112 rcmd = rcmd.substr(1);
113 if(!commands().count(rcmd)) {
114 if(rcmd != "")
115 messages << "Unknown command '" << rcmd << "'" << std::endl;
116 return;
118 messages << commands()[rcmd]->get_long_help() << std::endl;
119 return;
121 bool may_be_alias_expanded = true;
122 if(cmd2.length() > 0 && cmd2[0] == '*') {
123 may_be_alias_expanded = false;
124 cmd2 = cmd2.substr(1);
126 if(may_be_alias_expanded && aliases.count(cmd2)) {
127 for(auto i : aliases[cmd2])
128 invokeC(i);
129 return;
131 try {
132 size_t split = cmd2.find_first_of(" \t");
133 std::string rcmd;
134 if(split >= cmd2.length())
135 rcmd = cmd2;
136 else
137 rcmd = cmd2.substr(0, split);
138 split = cmd2.find_first_not_of(" \t", split);
139 std::string args;
140 if(split < cmd2.length())
141 args = cmd2.substr(split);
142 command* cmdh = NULL;
143 if(commands().count(rcmd))
144 cmdh = commands()[rcmd];
145 if(!cmdh) {
146 messages << "Unknown command '" << rcmd << "'" << std::endl;
147 return;
149 if(command_stack.count(cmd2))
150 throw std::runtime_error("Recursive command invocation");
151 command_stack.insert(cmd2);
152 cmdh->invoke(args);
153 command_stack.erase(cmd2);
154 return;
155 } catch(std::bad_alloc& e) {
156 OOM_panic();
157 } catch(std::exception& e) {
158 messages << "Error: " << e.what() << std::endl;
159 command_stack.erase(cmd2);
160 return;
162 } catch(std::bad_alloc& e) {
163 OOM_panic();
167 std::string command::get_short_help() throw(std::bad_alloc)
169 return "No description available";
172 std::string command::get_long_help() throw(std::bad_alloc)
174 return "No help available on command " + commandname;
177 std::set<std::string> command::get_aliases() throw(std::bad_alloc)
179 std::set<std::string> r;
180 for(auto i : aliases)
181 r.insert(i.first);
182 return r;
185 std::string command::get_alias_for(const std::string& aname) throw(std::bad_alloc)
187 if(!valid_alias_name(aname))
188 return "";
189 if(aliases.count(aname)) {
190 std::string x;
191 for(auto i : aliases[aname])
192 x = x + i + "\n";
193 return x;
194 } else
195 return "";
198 void command::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
200 if(!valid_alias_name(aname))
201 return;
202 std::list<std::string> newlist;
203 size_t avitr = 0;
204 while(avitr < avalue.length()) {
205 size_t nextsplit = avalue.find_first_of("\n");
206 if(nextsplit >= avalue.length())
207 nextsplit = avalue.length();
208 std::string x = avalue.substr(avitr, nextsplit - avitr);
209 if(x.length() > 0 && x[x.length() - 1] == '\r')
210 x = x.substr(0, x.length() - 1);
211 if(x.length() > 0)
212 newlist.push_back(x);
213 avitr = nextsplit + 1;
215 if(newlist.empty())
216 aliases.erase(aname);
217 else
218 aliases[aname] = newlist;
221 bool command::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
223 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
224 return false;
225 if(aliasname.find_first_of(" \t") < aliasname.length())
226 return false;
227 return true;
231 tokensplitter::tokensplitter(const std::string& _line) throw(std::bad_alloc)
233 line = _line;
234 position = 0;
237 tokensplitter::operator bool() throw()
239 return (position < line.length());
242 tokensplitter::operator std::string() throw(std::bad_alloc)
244 size_t nextp, oldp = position;
245 nextp = line.find_first_of(" \t", position);
246 if(nextp > line.length()) {
247 position = line.length();
248 return line.substr(oldp);
249 } else {
250 position = nextp;
251 while(position < line.length() && (line[position] == ' ' || line[position] == '\t'))
252 position++;
253 return line.substr(oldp, nextp - oldp);
257 std::string tokensplitter::tail() throw(std::bad_alloc)
259 return line.substr(position);
262 template<>
263 void invoke_command_fn(void (*fn)(const std::string& args), const std::string& args)
265 fn(args);
268 template<>
269 void invoke_command_fn(void (*fn)(), const std::string& args)
271 if(args != "")
272 throw std::runtime_error("This command does not take arguments");
273 fn();
276 template<>
277 void invoke_command_fn(void (*fn)(struct arg_filename a), const std::string& args)
279 if(args == "")
280 throw std::runtime_error("Filename required");
281 arg_filename b;
282 b.v = args;
283 fn(b);
286 template<>
287 void invoke_command_fn(void (*fn)(tokensplitter& a), const std::string& args)
289 tokensplitter t(args);
290 fn(t);