The CommandProcessor is the interface to install a run-time menu into an embedded system.

Dependents:   A_CANAdapter USB2I2C

Files at this revision

API Documentation at this revision

Comitter:
WiredHome
Date:
Sat Oct 01 20:01:44 2011 +0000
Parent:
13:e1880be590c4
Child:
15:5f30da93e3e2
Commit message:
v1.04 Adds a simple command history permitting recall of previous commands.

Changed in this revision

CommandProcessor.c Show annotated file Show diff for this revision Revisions of this file
CommandProcessor.h Show annotated file Show diff for this revision Revisions of this file
--- 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, &params)) {
                     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, &params);
                     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) {
--- a/CommandProcessor.h	Wed Jun 15 12:50:56 2011 +0000
+++ b/CommandProcessor.h	Sat Oct 01 20:01:44 2011 +0000
@@ -5,7 +5,7 @@
 /// The CommandProcessor is the interface to install a run-time menu into an embedded system.
 /// This contains the complete interface to the CommandProcessor.
 ///
-/// @version 1.03
+/// @version 1.04
 ///
 /// @note The CommandProcessor is text-based, because it is intended to interact with a
 ///       user.
@@ -29,7 +29,7 @@
 /// \li Tab completion of a command is available - so long as the user has
 ///      typed at least the minimum number of unique characters. (e.g. 'He\<tab\>'
 ///   will be replaced with 'Help')
-/// \li Command cancellation is available - just enter the \<esc\> key and
+///    \li Command cancellation is available - just enter the \<esc\> key and
 ///   the buffer is erased.
 /// \li The user is not permitted to enter text longer than the defined buffer,
 ///   to avoid buffer overrun and the possible memory damaging results.
@@ -56,10 +56,10 @@
 ///
 /// RUNRESULT_T SignOnBanner(char *p)
 /// {
-///     puts("\r\nThis great program was built " __DATE__ " " __TIME__ ".");
+/// 	puts("\r\nThis great program was built " __DATE__ " " __TIME__ ".");
 ///     if (*p == '?')
 ///        puts("\r\nMore details shown here.\r\n");
-///     return runok;
+/// 	return runok;
 /// }
 /// RUNRESULT_T Who(char *p)
 /// {
@@ -100,6 +100,9 @@
 ///
 /// @note
 /// History
+/// v1.04 1 October 2011
+/// \li Added configurable command line history for easy recall of previous commands
+/// \li Clean up dead-code
 /// v1.03 29 May 2011
 /// \li Slightly improved internal documentation. No external interfaces affected.
 /// v1.02 2 May 2011
@@ -215,14 +218,15 @@
     ///
     /// This function has a number of parameters, which make the CommandProcessor quite flexible.
     ///
-    /// @param SignOnBanner function, which is used as a signon banner
+	/// @param SignOnBanner function, which is used as a signon banner
     /// @param config enables various default menu items, based on the bit values, combine the following:
-    ///   \li CFG_ENABLE_TERMINATE - enables the Exit command
-    ///   \li CFG_ENABLE_SYSTEM    - enables system commands Echo, Help, etc.
-    ///   \li CFG_ECHO_ON          - initialize with echo on
-    ///   \li CFG_CASE_INSENSITIVE - Command Parser is case insensitive
-    /// @param maxCmdLen sizes the buffer, and is the maximum number of characters in a single
-    ///        command, including all command arguments
+	///   \li CFG_ENABLE_TERMINATE - enables the Exit command
+	///   \li CFG_ENABLE_SYSTEM    - enables system commands Echo, Help, etc.
+	///   \li CFG_ECHO_ON          - initialize with echo on
+	///   \li CFG_CASE_INSENSITIVE - Command Parser is case insensitive
+	/// @param maxCmdLen sizes the buffer, and is the maximum number of characters in a single
+	///        command, including all command arguments
+	/// @param historyLen sets the number of items that can be recalled from history
     /// @param kbhit is a user provided function to detect if a character is available for the CommandProcessor,
     ///        and when using standard io, you can typically use kbhit, or _kbhit as your system provides.
     /// @param getch is a user provided function that provides a single character to the CommandProcessor
@@ -230,11 +234,12 @@
     /// @param puts is a user provided function that permits the CommandProcessor to output a string
     ///        to which is automatically appended a \\n
     /// @returns INITRESULT_T to indicate if the init was successful or failed
-    ///
+	///
     INITRESULT_T (*Init)(
-        CMD_T *SignOnBanner,
+		CMD_T *SignOnBanner,
         CONFIG_T config,
         int maxCmdLen,
+		int historyLen,
         int (*kbhit)(void),
         int (*getch)(void),
         int (*putch)(int ch),
@@ -389,7 +394,4 @@
 extern "C" CMDP_T * GetCommandProcessor(void);
 #endif
 
-
-
-//int mystrnicmp(const char *l, const char *r, size_t n);
 #endif // COMMANDPROCESSOR_H