The CommandProcessor is the interface to install a run-time menu into an embedded system.
Dependents: A_CANAdapter USB2I2C
Diff: CommandProcessor.c
- Revision:
- 14:7971c8bd3f11
- Parent:
- 13:e1880be590c4
- Child:
- 15:5f30da93e3e2
--- a/CommandProcessor.c Wed Jun 15 12:50:56 2011 +0000 +++ b/CommandProcessor.c Sat Oct 01 20:01:44 2011 +0000 @@ -8,7 +8,7 @@ /// /// Even though it is a c interface, it is somewhat object oriented. /// -/// @version 1.03 +/// @version 1.04 /// /// @note Copyright &copr; 2011 by Smartware Computing, all rights reserved. /// Individuals may use this application for evaluation or non-commercial @@ -53,15 +53,17 @@ static CMDLINK_T * head = NULL; static char *buffer; // buffer space must be allocated based on the longest command - +static char *historyBuffer; // keeps the history of commands for recall +static int historyCount = 0; // and the count of +static int historyDepth = 0; static size_t longestCommand = 0; static struct { - CMD_T *SignOnBanner; - int showSignOnBanner; // Shows the sign-on banner at startup + CMD_T *SignOnBanner; + int showSignOnBanner; // Shows the sign-on banner at startup int caseinsensitive; // FALSE=casesensitive, TRUE=insensitive int echo; // TRUE=echo on, FALSE=echo off - int bufferSize; // size of the buffer + int bufferSize; // size of the command buffer int (*kbhit)(void); int (*getch)(void); int (*putch)(int ch); @@ -70,8 +72,9 @@ static INITRESULT_T CommandProcessor_Init( CMD_T *SignOnBanner, - CONFIG_T config, + CONFIG_T config, int maxCmdLen, + int historyCount, int (*kbhit)(void), int (*getch)(void), int (*putch)(int ch), @@ -81,6 +84,8 @@ static RUNRESULT_T CommandProcessor_Run(void); static RUNRESULT_T CommandProcessor_End(void); static RUNRESULT_T CommandProcessor_Echo(int echo); +static void EraseChars(int keycount); +static void EchoString(char * p); // helper functions static int myisprint(int c); @@ -97,14 +102,15 @@ }; static RUNRESULT_T Help(char *p); +static RUNRESULT_T History(char *p); static RUNRESULT_T Echo(char *p); static RUNRESULT_T Exit(char *p); //static RUNRESULT_T About(char *p); static CMD_T HelpMenu = {"Help", "Help or '?' shows this help, 'Help ?' shows more details.", Help, visible}; static CMD_T QuestionMenu = {"?", "Shows this help, '? ?' shows more details.", Help, invisible}; +static CMD_T HistoryMenu = {"History", "Show command history", History, visible}; static CMD_T EchoMenu = {"Echo", "Echo [1|on|0|off] turns echo on or off.", Echo, visible}; -//static CMD_T AboutMenu = {"About", "About this CommandProcessor", About, visible}; static CMD_T ExitMenu = {"Exit", "Exits the program", Exit, visible}; /// Gets a handle to the CommandProcessor @@ -119,23 +125,24 @@ } -/// Information about this command processor +/// History shows the command history /// -/// @param p is a pointer to command line arguments, of which there is -/// none for this function. +/// @param p is a pointer to a string that is ignored /// @returns runok /// -#if 0 -static RUNRESULT_T About(char *p) { - cfg.puts("\r\n About this CommandProcessor:\r\n" - " This CommandProcessor provides an easy facility for creating a\r\n" - " runtime interactive interpreter in an embedded system.\r\n" - " Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n" - " Author: David Smart, Smartware Computing\r\n" - ); - return runok; +static RUNRESULT_T History(char *p) { + int whereInHistory = 0; + char buf[100]; + + cfg.puts(""); + for (whereInHistory = 0; whereInHistory < historyCount; whereInHistory++) { + sprintf(buf, " %2i: %s", whereInHistory - historyCount, &historyBuffer[whereInHistory * cfg.bufferSize]); + cfg.puts(buf); + } + sprintf(buf, " %2i: %s", 0, buffer); + cfg.puts(buf); + return runok; } -#endif /// Turns command prompt echo on and off /// @@ -197,15 +204,16 @@ " * <esc> can be used to cancel a command.\r\n" " * <tab> can be used to complete the entry of a partial command.\r\n" ""); - cfg.puts("\r\n About this CommandProcessor:\r\n" - " This CommandProcessor provides an easy facility for creating an\r\n" - " interactive runtime interpreter in an embedded system.\r\n" - " Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n" - " Author: David Smart, Smartware Computing\r\n"); + cfg.puts("\r\n About this CommandProcessor:\r\n" + " This CommandProcessor provides an easy facility for creating an\r\n" + " interactive runtime interpreter in an embedded system.\r\n" + " Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n" + " Author: David Smart, Smartware Computing\r\n"); } return runok; } + /// CommandMatches is the function that determines if the user is entering a valid /// command /// @@ -227,6 +235,7 @@ int compareLength; int foundCount = 0; CMDLINK_T *link = head; + char * alternateBuffer; if (strlen(buffer)) { // simple sanity check // Try to process the buffer. A command could be "Help", or it could be "Test1 123 abc" @@ -261,22 +270,17 @@ // If they type "He 1234 5678", we backup and rewrite as "Help 1234 5678" int diff = strlen((*menu)->command) - compareLength; // e.g. 5 - 3 - if (diff > 0) { - int back = 0; - char *p; - if (strlen(space)) - back = strlen(space) + 1; - - while (back--) - cfg.putch(0x08); - p = (*menu)->command + compareLength; - while (*p) - cfg.putch(*p++); - cfg.putch(' '); - p = space; - while (*p) - cfg.putch(*p++); - //cfg.printf("%s %s", link->menu->command + compareLength, space); + // or if they entered it in a case that doesn't match the command exactly + if (diff > 0 || 0 != strncmp(buffer, (*menu)->command, compareLength)) { + char *p = buffer; + alternateBuffer = (char *)malloc(cfg.bufferSize); + strcpy(alternateBuffer, (*menu)->command); + strcat(alternateBuffer, " "); + strcat(alternateBuffer, space); + EraseChars(strlen(buffer)); + strcpy(buffer, alternateBuffer); + free(alternateBuffer); + EchoString(p); } } } @@ -284,6 +288,7 @@ return foundCount; } + /// Init is the first function to call to configure the CommandProcessor. /// /// This function has a number of parameters, which make the CommandProcessor @@ -309,27 +314,31 @@ CMD_T (*SignOnBanner), CONFIG_T config, int maxCmdLen, + int numInHistory, int (*kbhit)(void), int (*getch)(void), int (*putch)(int ch), int (*puts)(const char * s) ) { - if (SignOnBanner) { - CommandProcessor.Add(SignOnBanner); - cfg.SignOnBanner = SignOnBanner; - cfg.showSignOnBanner = 1; - } + if (SignOnBanner) { + CommandProcessor.Add(SignOnBanner); + cfg.SignOnBanner = SignOnBanner; + cfg.showSignOnBanner = 1; + } if (maxCmdLen < 6) maxCmdLen = 6; - buffer = (char *)malloc(maxCmdLen+1); + buffer = (char *)malloc(maxCmdLen); // users often error by one, so we'll be generous + historyDepth = numInHistory; + historyBuffer = (char *)malloc(historyDepth * maxCmdLen); cfg.bufferSize = maxCmdLen; - if (buffer) { + if (buffer && historyBuffer) { if (config & CFG_ENABLE_SYSTEM) - { + { CommandProcessor.Add(&QuestionMenu); CommandProcessor.Add(&HelpMenu); + CommandProcessor.Add(&HistoryMenu); CommandProcessor.Add(&EchoMenu); - } + } if (config & CFG_ENABLE_TERMINATE) CommandProcessor.Add(&ExitMenu); //if (addDefaultMenu & 0x0002) @@ -345,6 +354,7 @@ return initfailed; } + /// Add a command to the CommandProcessor /// /// This adds a command to the CommandProcessor. A command has several components @@ -381,17 +391,31 @@ prev = ptr; ptr = ptr->next; } - if (prev == head) { - head = temp; - head->next = prev; - } else { - prev->next = temp; - prev = temp; - prev->next = ptr; - } + if (prev == head) { + head = temp; + head->next = prev; + } else { + prev->next = temp; + prev = temp; + prev->next = ptr; + } return addok; } +static void EchoString(char *p) { + while (*p) + cfg.putch(*p++); +} + +static void EraseChars(int keycount) { + while (keycount--) { + cfg.putch(0x08); // <bs> + cfg.putch(' '); + cfg.putch(0x08); + } +} + + /// Run the CommandProcessor /// /// This will peek to see if there is a keystroke ready. It will pull that into a @@ -409,14 +433,17 @@ RUNRESULT_T CommandProcessor_Run(void) { static int showPrompt = TRUE; static int keycount = 0; // how full? + static int leadinChar = 0; + static int whereInHistory = 0; // navigates history + int foundCount = 0; RUNRESULT_T val = runok; // return true when happy, false to exit the prog CMD_T *cbk = NULL; char * params = NULL; - if (cfg.showSignOnBanner) { - cfg.SignOnBanner->callback(""); - cfg.showSignOnBanner = 0; - } + if (cfg.showSignOnBanner) { + cfg.SignOnBanner->callback(""); + cfg.showSignOnBanner = 0; + } if (showPrompt && cfg.echo) { cfg.putch('>'); showPrompt = FALSE; @@ -424,7 +451,55 @@ if (cfg.kbhit()) { int c = cfg.getch(); + if (leadinChar) { switch (c) { + case 0x50: // down arrow - toward the newest (forward in time) + // if there is anything in the history, copy it out + if (historyCount && whereInHistory < historyCount) { + char *p; + + EraseChars(keycount); + p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]); + EchoString(p); + keycount = strlen(buffer); + whereInHistory++; + } + c = 0; + break; + case 0x48: // up arrow - from newest to oldest (backward in time) + // same as escape + if (historyCount && --whereInHistory >= 0) { + char *p; + + EraseChars(keycount); + p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]); + EchoString(p); + keycount = strlen(buffer); + c = 0; + } else { + whereInHistory = 0; + c = 0x1B; + } + break; + default: + // ignore this char + c = 0; + break; + } + leadinChar = 0; + } + switch (c) { + case 0: + // null - do nothing + break; + case 0xE0: + // Lead-in char + // 0xE0 0x48 is up arrow + // 0xE0 0x50 is down arrow + // 0xE0 0x4B is left arrow + // 0xE0 0x4D is right arrow + leadinChar = 1; + break; case 0x09: // <TAB> to request command completion if (1 == CommandMatches(buffer, FALSE, &cbk, ¶ms)) { size_t n; @@ -437,38 +512,41 @@ p = cbk->command + strlen(buffer); mystrcat(buffer, p); keycount = strlen(buffer); - while (*p) - cfg.putch(*p++); - //cfg.printf("%s", p); + EchoString(p); + //cfg.printf("%s", p); } } break; case 0x1b: // <ESC> to empty the command buffer - while (keycount--) { - cfg.putch(0x08); // <bs> - cfg.putch(' '); - cfg.putch(0x08); - } + EraseChars(keycount); keycount = 0; buffer[keycount] = '\0'; break; case '\x08': // <bs> if (keycount) { buffer[--keycount] = '\0'; - cfg.putch(0x08); - cfg.putch(' '); - cfg.putch(0x08); + EraseChars(1); } else cfg.putch(0x07); // bell break; case '\r': - case '\n': { - int foundCount = 0; - + case '\n': if (strlen(buffer)) { foundCount = CommandMatches(buffer, TRUE, &cbk, ¶ms); if (foundCount == 1) { val = (*cbk->callback)(params); // Execute the command + if (mystrnicmp(buffer, (const char *)&historyBuffer[(historyCount-1) * cfg.bufferSize], strlen(&historyBuffer[(historyCount-1) * cfg.bufferSize])) != 0) { + // not repeating the last command, so enter into the history + if (historyCount == historyDepth) { + int i; + historyCount--; + for (i=0; i<historyCount; i++) + strcpy(&historyBuffer[i * cfg.bufferSize], &historyBuffer[(i+1) * cfg.bufferSize]); + } + strcpy(&historyBuffer[historyCount * cfg.bufferSize], buffer); + whereInHistory = historyCount; + historyCount++; + } } else if (foundCount > 1) cfg.puts(" *** non-unique command ignored try 'Help' ***"); else if (foundCount == 0) @@ -478,7 +556,6 @@ keycount = 0; buffer[keycount] = '\0'; showPrompt = TRUE; // forces the prompt - } break; default: if (myisprint(c) && keycount < cfg.bufferSize) {