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