1 //-----------------------------------------------------------------------------
2 // Commands
3 //-----------------------------------------------------------------------------
4
5 #include "cake.h"
6 #include "commands.h"
7 #include "alias.h"
8 #include "console.h"
9 #include "logfile.h"
10 #include "render.h"
11 #include "vars.h"
12 #include "definitions.h"
13 #include "mem.h"
14 #include "system.h"
15
16 #include <stdio.h>
17 #ifdef WIN32
18 #include <tchar.h> // for _vsntprintf
19 #else
20 #include <stdlib.h>
21 #include <stdarg.h>
22 //#include <sys/va_list.h>
23 #define _vsntprintf vsnprintf
24 #endif
25
26 // For function completion
27 typedef struct _charlist
28 {
29 commline name;
30 struct _charlist *next;
31 struct _charlist *prev;
32 } charlist;
33
34 charlist *llist = NULL;
35 static charlist *curr = NULL;
36
cmd_help(int argc,char * argv[])37 void cmd_help (int argc, char *argv[])
38 {
39 if (argc > 1)
40 {
41 cmd_function_t* cmd = gCommands->GetFunction(argv[1]);
42 if (cmd)
43 gConsole->Insertln("^5%s^0: %s", argv[1], cmd->description);
44 else
45 gConsole->Insertln("^5WARNING: %s -> Command not found", argv[1]);
46 }
47 else gConsole->Insertln("usage: %s <string>", argv[0]);
48 }
49
cmd_commands(int argc,char * argv[])50 void cmd_commands(int argc, char *argv[])
51 {
52 gCommands->DisplayCommands(argc>1?1:0);
53 }
54
cmd_echo(int argc,char * argv[])55 void cmd_echo(int argc, char *argv[])
56 {
57 if (argc > 1)
58 {
59 if (!stricmp(argv[1],"on") || !stricmp(argv[1],"1")) gCommands->AddToConsole = true;
60 else if (!stricmp(argv[1],"off") || !stricmp(argv[1],"0")) gCommands->AddToConsole = false;
61 else
62 {
63 gConsole->Insertln("^5Argument not valid");
64 }
65 }
66 else gConsole->Insertln("echo : %s", gCommands->AddToConsole?"on":"off");
67 }
68
cmd_rem(int argc,char * argv[])69 void cmd_rem(int argc, char *argv[])
70 {
71 if (argc > 1)
72 {
73 char command[1024] = { '\0' };
74 for (int i = 0; i < argc; ++i)
75 {
76 strcat(command, argv[i]);
77 if (i != argc-1) strcat(command, " ");
78 }
79 if (!stricmp(argv[0],"rem")) gConsole->Insertln(command+4);
80 else if (!stricmp(argv[0],"//")) gConsole->Insertln(command+3);
81 else if (!stricmp(argv[0],"#")) gConsole->Insertln(command+2);
82 }
83 else gConsole->Insertln("usage: %s <string>", argv[0]);
84 }
85
cmd_togglehistory(int argc,char * argv[])86 void cmd_togglehistory(int argc, char *argv[])
87 {
88 gCommands->AddToHistory = !gCommands->AddToHistory;
89 }
90
Commands(void)91 Commands::Commands(void)
92 {
93 cmd_argc = 0;
94 cmd_argv = NULL;
95
96 NbrAllocatedHistLines = HISTBLOC;
97 NbrCommHistLines = 0;
98
99 memset(last_command, '\0', COMMAND_LINELENGTH*sizeof(char));
100
101 cmd_functions = NULL;
102
103 CommandsHistory = (commline*) cake_malloc(NbrAllocatedHistLines*sizeof(commline), "Commands::Commands.CommandsHistory");
104 for (int i = 0; i < NbrAllocatedHistLines; ++i) memset(CommandsHistory[i], '\0', CONSOLE_LINELENGTH);
105
106 AddCommand("help", cmd_help, "displays help about a command");
107 AddCommand("cmdlist", cmd_commands, "display all available commands in the console (if an argumentis present, the functions descriptions are given)");
108 AddCommand("echo", cmd_echo, "defines the echo value (if echo is on, the function callsare added to the console, if not, only the function results are written in the console)");
109 AddCommand("rem", cmd_rem, "writes a remark in the console");
110 AddCommand("//", cmd_rem, "writes a remark in the console");
111 AddCommand("#", cmd_rem, "writes a remark in the console");
112 AddCommand("togglehistory", cmd_togglehistory, "enables or disables the history for console commands");
113 }
114
~Commands(void)115 Commands::~Commands(void)
116 {
117 RemoveCommand("help");
118 RemoveCommand("cmdlist");
119 RemoveCommand("echo");
120 RemoveCommand("rem");
121 RemoveCommand("//");
122 RemoveCommand("#");
123 RemoveCommand("togglehistory");
124
125 cake_free(CommandsHistory); CommandsHistory = NULL;
126
127 if (cmd_argv)
128 {
129 for (int i = 0; i < cmd_argc; ++i) cake_free(cmd_argv[i]);
130 cake_free(cmd_argv);
131 cmd_argv = NULL;
132 }
133
134 // Unallocation of completion list
135 for (; llist;)
136 {
137 charlist *back = llist->next;
138 llist->next = NULL;
139 llist->prev = NULL;
140 cake_free(llist);
141 llist = back;
142 back = NULL;
143 }
144 llist = NULL;
145 curr = NULL;
146
147 // Clear functions table
148 cmd_function_t *next, *cmd = cmd_functions;
149 while (cmd)
150 {
151 if (cmd->description) cake_free(cmd->description);
152 next = cmd->next;
153 cmd->next = NULL;
154 cake_free(cmd);
155 cmd = next;
156 next = NULL;
157 }
158 }
159
AddCommand(char * cmd_name,xcommand_t function,const char * description_message)160 void Commands::AddCommand(char *cmd_name, xcommand_t function, const char *description_message)
161 {
162 cmd_function_t *cmd;
163
164 // Error if the command is an alias name
165 if (gAlias && gAlias->GetAlias(cmd_name)[0])
166 {
167 gConsole->Insertln("AddCommand: %s already defined as alias.", cmd_name);
168 return;
169 }
170
171 // Error if command is a variable name
172 if (gVars && gVars->IsKey(cmd_name))
173 {
174 gConsole->Insertln("AddCommand: %s already defined as variable.", cmd_name);
175 return;
176 }
177
178 // Error if command already exists
179 for (cmd = cmd_functions; cmd; cmd = cmd->next)
180 {
181 if (!stricmp(cmd_name, cmd->name))
182 {
183 gConsole->Insertln("AddCommand: %s already defined.", cmd_name);
184 return;
185 }
186 }
187
188 cmd = (cmd_function_t*) cake_malloc(sizeof(cmd_function_t), "Commands::AddCommand.cmd");
189 cmd->name = cmd_name;
190 cmd->function = function;
191 cmd->next = cmd_functions;
192 if (description_message)
193 {
194 cmd->description = (char*) cake_malloc(strlen(description_message)+1, "Commands::AddCommand.cmd->description");
195 memset(cmd->description, '\0', strlen(description_message)+1);
196 strcpy(cmd->description, description_message);
197 }
198 else cmd->description = NULL;
199 cmd_functions = cmd;
200 }
201
SetDescription(char * cmd_name,const char * description)202 void Commands::SetDescription(char *cmd_name, const char *description)
203 {
204 // Check functions
205 cmd_function_t *cmd;
206 for (cmd = cmd_functions; cmd; cmd = cmd->next)
207 {
208 if (!stricmp(cmd_name, cmd->name))
209 {
210 if (cmd->description) cake_free(cmd->description);
211 cmd->description = (char*) cake_malloc(strlen(description)+1, "Commands::SetDescription.cmd->description");
212 memset(cmd->description, '\0', strlen(description)+1);
213 strcpy(cmd->description, description);
214 return;
215 }
216 }
217 }
218
RemoveCommand(char * cmd_name)219 void Commands::RemoveCommand(char *cmd_name)
220 {
221 cmd_function_t* cmd, **back;
222
223 back = &cmd_functions;
224 while (1)
225 {
226 cmd = *back;
227 if (!cmd)
228 {
229 // The command cannot be found in ithe list
230 gConsole->Insertln("RemoveCommand: %s not in command list.", cmd_name);
231 return;
232 }
233 if (!stricmp(cmd_name, cmd->name))
234 {
235 *back = cmd->next;
236 if (cmd->description) cake_free(cmd->description);
237 cake_free(cmd);
238 return;
239 }
240 back = &cmd->next;
241 }
242 }
243
GetFunction(char * cmd_name)244 cmd_function_t* Commands::GetFunction(char *cmd_name)
245 {
246 // Check functions
247 for (cmd_function_t *cmd = cmd_functions; cmd; cmd = cmd->next)
248 {
249 if (!stricmp(cmd_name, cmd->name))
250 {
251 return cmd;
252 }
253 }
254
255 return NULL;
256 }
257
Exists(char * cmd_name)258 bool Commands::Exists(char *cmd_name)
259 {
260 for (cmd_function_t *cmd = cmd_functions; cmd; cmd = cmd->next)
261 {
262 if (!stricmp(cmd_name, cmd->name)) return true;
263 }
264 return false;
265 }
266
CompleteCommand(const char * partial,bool display_solutions,bool auto_complete_command)267 void Commands::CompleteCommand(const char *partial, bool display_solutions, bool auto_complete_command)
268 {
269 int len = (int) strlen(partial);
270 int n_partial_sol = 0, i;
271
272 if (!len) return;
273
274 /**
275 * @todo Find why this declaration is indispensable. Isn't is a consequence
276 * of bad construction of WriteLine ?
277 */
278 char *tmp_string = (char*) cake_malloc((len + 1)*sizeof(char), "Commands::CompleteCommand.tmp_string");
279 memset(tmp_string, '\0', len + 1);
280 strncpy(tmp_string, partial, len);
281
282 // Unallocation of last completion list
283 for (; llist;)
284 {
285 charlist *back = llist->next;
286 llist->next = NULL;
287 llist->prev = NULL;
288 cake_free(llist);
289 llist = back;
290 back = NULL;
291 }
292 llist = NULL;
293
294 for (cmd_function_t *cmd = cmd_functions; cmd; cmd = cmd->next)
295 {
296 if (!strnicmp(tmp_string, cmd->name, len))
297 {
298 charlist *tmp = (charlist*) cake_malloc(sizeof(charlist), "Commands::CompleteCommand.tmpA");
299 memset(tmp->name, '\0', COMMAND_LINELENGTH);
300 strcpy(tmp->name, cmd->name);
301 tmp->next = llist;
302 tmp->prev = NULL;
303 if (llist) llist->prev = tmp;
304 llist = tmp;
305
306 ++n_partial_sol;
307 }
308 }
309
310 // Add all matching variables to list
311 int nvars = gVars->GetNumber();
312 char varname[128];
313 for (i = 0; i < nvars; ++i)
314 {
315 strncpy(varname, gVars->GetName(i), 128);
316 if (!strnicmp(tmp_string, varname, len))
317 {
318 charlist *tmp = (charlist*) cake_malloc(sizeof(charlist), "Commands::CompleteCommand.tmpB");
319 memset(tmp->name, '\0', COMMAND_LINELENGTH);
320 strcpy(tmp->name, varname);
321 tmp->next = llist;
322 tmp->prev = NULL;
323 if (llist) llist->prev = tmp;
324 llist = tmp;
325
326 ++n_partial_sol;
327 }
328 }
329
330 // Add all matching aliases to list
331 int nalias = gAlias->GetNumber();
332 char aliasname[128];
333 for (i = 0; i < nalias; ++i)
334 {
335 strncpy(aliasname, gAlias->GetName(i), 128);
336 if (!strnicmp(tmp_string, aliasname, len))
337 {
338 charlist *tmp = (charlist*) cake_malloc(sizeof(charlist), "Commands::CompleteCommand.tmpC");
339 memset(tmp->name, '\0', COMMAND_LINELENGTH);
340 strcpy(tmp->name, aliasname);
341 tmp->next = llist;
342 tmp->prev = NULL;
343 if (llist) llist->prev = tmp;
344 llist = tmp;
345
346 ++n_partial_sol;
347 }
348 }
349
350 // Sort list
351
352 // Get common prefix of list
353 commline prefix = { '\0' };
354 char c;
355 int n = 0;
356 bool search_next = true;
357
358 while (search_next && n_partial_sol)
359 {
360 if ((int) strlen(llist->name) > n) c = llist->name[n];
361 else break;
362
363 for (charlist *tmp = llist; tmp; tmp = tmp->next)
364 {
365 if ((int) strlen(tmp->name) > n)
366 {
367 if (tmp->name[n] != c)
368 {
369 search_next = false;
370 break;
371 }
372 }
373 }
374
375 if (search_next) prefix[n++] = c;
376 }
377
378 // Display the list if more than one solution
379 if (display_solutions && n_partial_sol > 1)
380 {
381 gConsole->Insertln("%s%s", gConsole->GetPrompt(), tmp_string);
382 gLogFile->OpenFile();
383 for (charlist *tmp = llist; tmp; tmp = tmp->next)
384 {
385 gConsole->Insertln(" %s", tmp->name);
386 }
387 gLogFile->CloseFile();
388 }
389 else if (n_partial_sol == 1) prefix[n] = ' ';
390
391 // Complete current console command with prefix
392 if (auto_complete_command && n_partial_sol > 0) gConsole->SetCurrentCommand(prefix);
393
394 // Free memory
395 cake_free(tmp_string);
396 }
397
FirstSolution(const char * partial)398 void Commands::FirstSolution(const char *partial)
399 {
400 if (partial != NULL) CompleteCommand(partial, 0, 0);
401
402 if (llist == NULL) return;
403
404 curr = llist;
405
406 gConsole->SetCurrentCommand("%s ", llist->name);
407 }
408
NextSolution(void)409 void Commands::NextSolution(void)
410 {
411 if (llist == NULL) return;
412
413 if (curr) curr = curr->next;
414 if (curr == NULL) curr = llist;
415
416 gConsole->SetCurrentCommand("%s ", curr->name);
417 }
418
PrevSolution(void)419 void Commands::PrevSolution(void)
420 {
421 if (llist == NULL) return;
422
423 if (curr) curr = curr->prev;
424 if (curr == NULL)
425 for (charlist* tmp = llist; tmp; tmp = tmp->next)
426 curr = tmp;
427
428 gConsole->SetCurrentCommand("%s ", curr->name);
429 }
430
DisplayCommands(int display_descriptions)431 void Commands::DisplayCommands(int display_descriptions)
432 {
433 gLogFile->OpenFile();
434 for (cmd_function_t *cmd = cmd_functions; cmd; cmd = cmd->next)
435 {
436 if (display_descriptions)
437 gConsole->Insertln("^5%s^0 : %s", cmd->name, cmd->description);
438 else
439 gConsole->Insertln("\t%s", cmd->name);
440 }
441 gLogFile->CloseFile();
442 }
443
ExecuteCommand(const char * commandline,...)444 int Commands::ExecuteCommand(const char *commandline, ...)
445 {
446 va_list msg;
447 char buffer[COMMAND_LINELENGTH] = { '\0' };
448 char *command = buffer;
449
450 va_start (msg, commandline);
451 _vsntprintf (buffer, COMMAND_LINELENGTH - 1, commandline, msg);
452 va_end (msg);
453
454 if (strlen(command) <= 0) return 0;
455
456 strcpy(last_command, buffer); // Backup last command
457
458 int i;
459 int res = 0;
460
461 gConsole->addToMessages = false; // We don't dislay commands in the mini-commands,
462 // but only functions results
463 i = (int) strlen(command)-1;
464 while (command[i] == ';') // Skip the ; at the command end
465 command[i--] = '\0';
466
467 if (AddToConsole && command[0] != '@')
468 gConsole->Insertln("\n%s%s", gConsole->GetPrompt(), command); // Displays the command with prompt
469 gConsole->ReInitCurrentCommand();
470 gConsole->addToMessages = true;
471 if (AddToHistory) Commands::AddToCommHistory(command);
472
473 // Convertit la commande en minuscule
474 char c[COMMAND_LINELENGTH] = { '\0' };
475 while (command[0] == '@') ++command; // The @ don't appear at the line beginning
476 strcpy(c, command);
477 // _strlwr(c);
478
479 // (Re-)Initialization of last command arguments
480 if (cmd_argc)
481 {
482 for (i = 0; i < cmd_argc; ++i) cake_free(cmd_argv[i]);
483 cake_free(cmd_argv);
484 cmd_argv = NULL;
485 }
486 cmd_argc = 0;
487
488 // Separates the command arguments
489 // Get the number of aguments
490 i = 0;
491 while (i < (int) strlen(c))
492 {
493 while (c[i] == ' ' && i < (int) strlen(c)) ++i;
494 if (i < (int) strlen(c))
495 {
496 ++cmd_argc;
497 while (c[i] != ' ' && i < (int) strlen(c)) ++i;
498 }
499 }
500 cmd_argv = (char**) cake_malloc(cmd_argc*sizeof(char*), "Commands::ExecuteCommand.cmd_argv");
501 for (i = 0; i < cmd_argc; ++i)
502 {
503 cmd_argv[i] = (char*) cake_malloc(COMMAND_LINELENGTH*sizeof(char), "Commands::ExecuteCommand.cmd_argv[i]");
504 memset(cmd_argv[i], '\0', COMMAND_LINELENGTH - 1);
505 GetArg(c, i, cmd_argv[i]);
506 }
507
508 if (!cmd_argc) return 0;
509
510 // Check functions
511 cmd_function_t *cmd;
512 for (cmd = cmd_functions; cmd; cmd = cmd->next)
513 {
514 if (!stricmp(cmd_argv[0], cmd->name))
515 {
516 if (cmd->function)
517 {
518 if (cmd_argc > 1 && !stricmp(STR_ASK, cmd_argv[1]))
519 {
520 gConsole->Insertln("%s: %s", cmd_argv[0], cmd->description);
521 res = 1;
522 break;
523 }
524
525 cmd->function(cmd_argc, cmd_argv);
526 res = 1;
527 break;
528 }
529 }
530 }
531
532 // Check variables
533 if (!res && gVars)
534 {
535 Var* v = gVars->GetVar(cmd_argv[0]);
536 if (v != NULL)
537 {
538 char* a = v->svalue;
539 if (a[0])
540 {
541 if (cmd_argc > 1)
542 {
543 if (v->flags & VF_SYSTEM && gVars->IntForKey("sys_appstate") != INITIALIZING)
544 gConsole->Insertln("^4You are not allowed to modify \"%s\".", cmd_argv[0]);
545 else
546 {
547 gVars->SetKeyValue(cmd_argv[0], cmd_argv[1]);
548 if (v->flags & VF_LATCH && gVars->IntForKey("sys_appstate") != INITIALIZING)
549 gConsole->Insertln("%s will be changed upon restarting.", cmd_argv[0]);
550 if (v->flags & VF_DEBUG && gVars->IntForKey("sys_appstate") != INITIALIZING)
551 gConsole->Insertln("^1!! DEBUG MODE !! ^0Do enable this variable for debug only.");
552 }
553 }
554 else gConsole->Insertln("\"%s\" : \"%s\" [default: \"%s\"]", cmd_argv[0], a, v->default_value);
555
556 res = 1;
557 }
558 }
559 }
560
561 // Check alias
562 if (!res && gAlias)
563 {
564 char* a = gAlias->GetAlias(cmd_argv[0]);
565 if (a[0])
566 {
567 if (gAlias->depth++ < MAX_ALIAS_DEPTH)
568 {
569 gConsole->Insertln("alias redirection...");
570
571 char str[COMMAND_LINELENGTH];
572 sprintf(str, "%s ", a);
573 for (int i = 1; i < cmd_argc; ++i)
574 {
575 strcat(str, cmd_argv[i]);
576 strcat(str, " ");
577 }
578 res = ExecuteCommand(str);
579 }
580 else
581 {
582 gConsole->Insertln("^5WARNING: potential infinite recursive loop detected !");
583 }
584 }
585 }
586 gAlias->depth = 0;
587
588 return res;
589 }
590
RepeatLastCommand()591 void Commands::RepeatLastCommand()
592 {
593 if (strlen(last_command)) ExecuteCommand(last_command);
594 }
595
GetHistoryLine(int l)596 char* Commands::GetHistoryLine(int l)
597 {
598 if (!NbrCommHistLines || l < 1) return "";
599 else if (l > NbrCommHistLines) return CommandsHistory[NbrCommHistLines-1];
600 else return CommandsHistory[NbrCommHistLines-l];
601 }
602
GetNbrCommHistLines(void)603 int Commands::GetNbrCommHistLines(void)
604 {
605 return NbrCommHistLines;
606 }
607
AddToCommHistory(char * s)608 void Commands::AddToCommHistory(char* s)
609 {
610 // Rem : On alloue par blocs de taille HISTBLOC, pour �viter de trop fr�quentes allocations
611 if (NbrCommHistLines == NbrAllocatedHistLines)
612 {
613 NbrAllocatedHistLines += HISTBLOC;
614 CommandsHistory = (commline*) cake_realloc(CommandsHistory, NbrAllocatedHistLines*sizeof(commline), "Commands::AddToCommHistory.CommandsHistory");
615 }
616 ++NbrCommHistLines;
617 memset(CommandsHistory[NbrCommHistLines-1], '\0', CONSOLE_LINELENGTH);
618 strncpy(CommandsHistory[NbrCommHistLines-1], s, CONSOLE_LINELENGTH);
619 }
620