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

Dependents:   A_CANAdapter USB2I2C

Revision:
0:198f53da1bc8
Child:
5:a98bd1f2fd59
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CommandProcessor.c	Sun Mar 20 21:50:15 2011 +0000
@@ -0,0 +1,546 @@
+/// @file CommandProcessor.c is a simple interface to an interactive 
+///            command set of user defined commands.
+///
+///    With this, you can create functions that are exposed to a console
+///    interface. Each command may have 0 or more parameters.
+///    Typing the command, or at least the set of characters that make
+///    it unique from all other commands is enough to activate the command.
+///    
+///    Even though it is a c interface, it is somewhat object oriented.
+///
+/// @version 1.0
+///
+/// @note Copyright © 2011 by Smartware Computing, all rights reserved.
+///       This program may be used by others as long as this copyright notice
+///       remains intact.
+/// @author David Smart
+///
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+#ifdef WIN32
+#include "windows.h"
+#pragma warning (disable: 4996)
+#endif
+
+#include "CommandProcessor.h"
+
+/// This holds the single linked list of commands
+///
+typedef struct CMDLINK_T
+{
+    CMD_T * menu;                // handle to the menu item
+    struct CMDLINK_T * next;    // handle to the next link
+} CMDLINK_T;
+
+static CMDLINK_T * head = NULL;
+
+static char *buffer;        // buffer space must be allocated based on the longest command
+
+static struct
+{
+    int bufferSize;            // size of the buffer
+    int caseinsensitive;    // FALSE=casesensitive, TRUE=insensitive
+    int (*kbhit)(void);
+    int (*getch)(void);
+    int (*putch)(int ch);
+    int (*puts)(const char * s);
+} cfg;
+
+static INITRESULT_T CommandProcessor_Init(
+    int defaultMenu,
+    int caseinsensitive,
+    int maxCmdLen, 
+    int (*kbhit)(void),
+    int (*getch)(void),
+    int (*putch)(int ch),
+    int (*puts)(const char * s)
+    );
+static ADDRESULT_T CommandProcessor_Add(CMD_T *m);
+static RUNRESULT_T CommandProcessor_Run(void);
+static RUNRESULT_T CommandProcessor_End(void);
+
+static CMDP_T CommandProcessor = 
+{
+    CommandProcessor_Init,
+    CommandProcessor_Add,
+    CommandProcessor_Run,
+    CommandProcessor_End
+};
+
+static RUNRESULT_T Help(char *p);
+static RUNRESULT_T Exit(char *p);
+static RUNRESULT_T About(char *p);
+
+static CMD_T HelpMenu = {"Help", "Shows this help, 'Help ?' shows more details.", Help, 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
+///
+///    This returns a handle to the CommandProcessor, which then permits
+///    access to the CommandProcessor functions.
+///
+///    @returns handle to the CommandProcessor
+///
+CMDP_T * GetCommandProcessor(void)
+{
+    return &CommandProcessor;
+}
+
+
+static RUNRESULT_T About(char *p)
+{
+    cfg.puts("\r\n About this CommandProcessor:\r\n"
+        "    This CommandProcessor provides easy facility for creating a\r\n"
+        "      runtime CommandProcessor in an embedded systems.\r\n"
+        "    author: David Smart, Smartware Computing\r\n"
+        );
+    return runok;
+}
+
+static RUNRESULT_T Exit(char *p)
+{
+    (void)p;
+
+    cfg.puts("\r\nbye.");
+    return runexit;
+}
+
+static RUNRESULT_T Help(char *p)
+{
+    CMDLINK_T *link = head;
+    char buffer[100];
+
+    cfg.puts("\r\n");
+    sprintf(buffer, " %-10s: %s", "Command", "Description");
+    cfg.puts(buffer);
+    while (link && link->menu)
+    {
+        if (link->menu->visible)
+        {
+            if (strlen(link->menu->command) + strlen(link->menu->helptext) + 5 < sizeof(buffer))
+            {
+                sprintf(buffer, " %-10s: %s", link->menu->command, link->menu->helptext);
+                cfg.puts(buffer);
+            }
+        }
+        link = link->next;
+    }
+    cfg.puts("");
+    if (*p == '?')
+    {
+        cfg.puts("\r\n Extended Help\r\n"
+            "    The general form for entering commands is:\r\n"
+            "      >command option1 option2 ...\r\n"
+            "      [note that note all commands support optional parameters]\r\n"
+            "    * Abbreviations of commands may be entered so long as there\r\n"
+            "      is exactly one match in the list of commands. For example,\r\n"
+            "      'he' is the same as 'help', if there is no other command \r\n"
+            "       that starts with 'he'.\r\n"
+            "    * <bs> can be used to erase previously entered information.\r\n"
+            "    * <esc> can be used to cancel a command.\r\n"
+            "    * <tab> can be used to complete the entry of a partial command.\r\n"
+            "");
+    }
+    return runok;
+}
+
+/// mytolower exists because not all compiler libraries have this function
+///
+/// This takes a character and if it is upper-case, it converts it to
+/// lower-case and returns it.
+///
+///    @param a is the character to convert
+/// @returns the lower case equivalent to a
+///
+char mytolower(char a)
+{
+    if (a >= 'A' && a <= 'Z')
+        return (a - 'A' + 'a');
+    else
+        return a;
+}
+
+/// mystrnicmp exists because not all compiler libraries have this function.
+///
+/// Some have strnicmp, others _strnicmp, and others have C++ methods, which
+/// is outside the scope of this C-portable set of functions.
+///
+///    @param l is a pointer to the string on the left
+/// @param r is a pointer to the string on the right
+/// @param n is the number of characters to compare
+/// @returns -1 if l < r
+/// @returns 0 if l == r
+/// @returns +1 if l > r
+///
+int mystrnicmp(const char *l, const char *r, size_t n)
+{
+    int result = 0;
+
+    if (n != 0)
+    {
+        do {
+            result = mytolower(*l++) - mytolower(*r++);
+        } while ((result == 0) && (*l != '\0') && (--n > 0));
+    }
+    if (result < -1)
+        result = -1;
+    else if (result > 1)
+        result = 1;
+    return result;
+}
+
+/// mystrcat exists because not all compiler libraries have this function
+///
+/// This function concatinates one string onto another. It is generally 
+/// considered unsafe, because of the potential for buffer overflow.
+/// Some libraries offer a strcat_s as the safe version, and others may have
+/// _strcat. Because this is needed only internal to the CommandProcessor,
+/// this version was created.
+///
+///    @param dst is a pointer to the destination string
+/// @param src is a pointer to the source string
+/// @returns nothing
+///
+void mystrcat(char *dst, char *src)
+{
+    while (*dst)
+        dst++;
+    do 
+        *dst++ = *src;
+    while (*src++);
+}
+
+/// myisprint exists because not all compiler libraries have this function
+///
+/// This function tests a character to see if it is printable (a member
+/// of the standard ASCII set).
+///
+/// @param c is the character to test
+/// @returns TRUE if the character is printable
+/// @returns FALSE if the character is not printable
+///
+static int myisprint(int c)
+{
+    if (c >= ' ' && c <= '~')
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+/// CommandMatches is the function that determines if the user is entering a valid
+/// command
+///
+/// This function gets called whenever the user types a printable character or when
+/// they press \<enter\>.
+/// The buffer of user input is evaluated to determine if the command is legitimate.
+/// It also determines if they want to execute the command, or simply evaluate it.
+/// Finally, it identifies writes to user provided pointers, the menu item that 
+/// matches and a pointer to any parameters that the user entered.
+///
+///    @param buffer is the buffer that the user has entered commands into
+///    @param exec indicates the intention to execute the command, if found
+/// @param menu is a pointer to a pointer to a menu structure, which is updated based on the command
+/// @param params is a pointer to a pointer to the user entered parameters
+/// @returns the number of menu picks that match the user entered command
+///
+static int CommandMatches(char * buffer, int exec, CMD_T **menu, char ** params)
+{
+    char *space;
+    int compareLength;
+    int foundCount = 0;
+    CMDLINK_T *link = head;
+
+    if (strlen(buffer))    // simple sanity check
+    {
+        // Try to process the buffer. A command could be "Help", or it could be "Test1 123 abc"
+        space = strchr(buffer, ' ');        // if the command has parameters, find the delimiter
+        if (space)
+        {
+            compareLength = space - buffer;
+            space++;        // char after the space
+        }
+        else
+        {
+            compareLength = strlen(buffer);
+            space = buffer + compareLength;
+        }
+        while (link && link->menu)
+        {
+            int cmpResult;
+
+            if (cfg.caseinsensitive)
+            {
+                cmpResult = mystrnicmp(buffer, link->menu->command, compareLength);
+            }
+            else
+                cmpResult = strncmp(buffer, link->menu->command, compareLength);
+            if (0 == cmpResult)    // A match to what they typed
+            {
+                if (menu)    // yes, we have a callback
+                {
+                    if (exec)    // command wants to execute it, not just validate the command syntax
+                    {
+                        // If they type "He 1234 5678", we backup and rewrite as "Help 1234 5678"
+                        int diff = strlen(link->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 = link->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);
+                        }
+                    }
+                    *menu = link->menu;    // accessor to the command they want to execute
+                    *params = space;
+                }
+                foundCount++;    // how many command match what they typed so far
+            }
+            link = link->next;    // follow the link to the next command
+        }
+    }
+    return foundCount;
+}
+
+/// Initialize the CommandProcessor
+///
+///    This initializes the CommandProcessor by adding the built-in menus
+///
+///    @param addDefaultMenu configures it to add the Help, About, and Exit menus
+///    @param bufSize configures the size of the longest command, which must be 
+///            greater than 6 (the size of "About\0").
+///    @returns initok if it successfully initialized the CommandProcessor
+///    @returns initfailed if it could not allocate memory
+///
+INITRESULT_T CommandProcessor_Init(
+    int addDefaultMenu, 
+    int caseinsensitive,
+    int maxCmdLen, 
+    int (*kbhit)(void),
+    int (*getch)(void),
+    int (*putch)(int ch),
+    int (*puts)(const char * s)
+    )
+{
+    if (maxCmdLen < 6)
+        maxCmdLen = 6;
+    buffer = (char *)malloc(maxCmdLen+1);
+    cfg.bufferSize = maxCmdLen;
+    if (buffer)
+    {
+        if (addDefaultMenu & 0x0004)
+            CommandProcessor.Add(&HelpMenu);
+        if (addDefaultMenu & 0x0002)
+            CommandProcessor.Add(&AboutMenu);
+        if (addDefaultMenu & 0x0001)
+            CommandProcessor.Add(&ExitMenu);
+        cfg.caseinsensitive = caseinsensitive;
+        cfg.kbhit = kbhit;
+        cfg.getch = getch;
+        cfg.putch = putch;
+        cfg.puts = puts;
+        return initok;
+    }
+    else
+        return initfailed;
+}
+
+/// Add a command to the CommandProcessor
+///
+///    This adds a command to the CommandProcessor. A command has several components
+///    to it, including the command name, a brief description, the function to 
+///    activate when the command is entered, and a flag indicating if the command
+///    should show up in the built-in help.
+///
+///    @todo sort them when adding a menu item
+///
+///    @param m is the menu to add to the CommandProcessor
+///    @returns addok if the command was added
+///    @returns addfail if the command could not be added (failure to allocate memory for the linked list)
+///
+ADDRESULT_T CommandProcessor_Add(CMD_T * m)
+{
+    CMDLINK_T *p;
+
+    if (head == NULL)
+    {
+        head = (CMDLINK_T *)malloc(sizeof(CMDLINK_T));
+        if (!head)
+            return addfailed;
+        head->menu = NULL;
+        head->next = NULL;
+    }
+    p = head;
+    while (p->next)
+        p = p->next;
+    if (p->menu == NULL)
+    {
+        p->menu = m;
+        return addok;
+    }
+    else if (p->next == NULL)
+    {
+        p->next = (CMDLINK_T *)malloc(sizeof(CMDLINK_T));
+        if (!p->next)
+            return addfailed;
+        p = p->next;
+        p->menu = m;
+        p->next = NULL;
+        return addok;
+    }
+    return addfailed;
+}
+
+/// Run the CommandProcessor
+///
+///    This will peek to see if there is a keystroke ready. It will pull that into a
+///    buffer if it is part of a valid command in the command set. You may then enter
+/// arguments to the command to be run.
+///
+/// Primitive editing is permitted with <bs>.
+///    
+///    When you press <enter> it will evaluate the command and execute the command
+///    passing it the parameter string.
+///
+///    @returns runok if the command that was run allows continuation of the CommandProcessor
+///    @returns runfail if the command that was run is asking the CommandProcessor to exit
+///
+RUNRESULT_T CommandProcessor_Run(void)
+{
+    static int showPrompt = TRUE;
+    static int keycount = 0;    // how full?
+    RUNRESULT_T val = runok;            // return true when happy, false to exit the prog
+    CMD_T *cbk = NULL;
+    char * params = NULL;
+
+    if (showPrompt)
+    {
+        cfg.putch('>');
+        showPrompt = FALSE;
+    }
+    if (cfg.kbhit())
+    {
+        int c = cfg.getch();
+
+        switch (c)
+        {
+        case 0x09:    // <TAB> to request command completion
+            if (1 == CommandMatches(buffer, FALSE, &cbk, &params))
+            {
+                size_t n;
+                char *p = strchr(buffer, ' ');
+                if (p)
+                    n = p - buffer;
+                else
+                    n = strlen(buffer);
+                if (n < strlen(cbk->command))
+                {
+                    p = cbk->command + strlen(buffer);
+                    mystrcat(buffer, p);
+                    keycount = strlen(buffer);
+                    while (*p)
+                        cfg.putch(*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);
+            }
+            keycount = 0;
+            buffer[keycount] = '\0';
+            break;
+        case '\x08':    // <bs>
+            if (keycount)
+            {
+                buffer[--keycount] = '\0';
+                cfg.putch(0x08);
+                cfg.putch(' ');
+                cfg.putch(0x08);
+            }
+            else
+                cfg.putch(0x07);    // bell
+            break;
+        case '\r':
+        case '\n':
+            {
+                int foundCount = 0;
+
+                if (strlen(buffer))
+                {
+                    foundCount = CommandMatches(buffer, TRUE, &cbk, &params);
+                    if (foundCount == 1)
+                    {
+                        val = (*cbk->callback)(params);        // Execute the command
+                    }
+                    else if (foundCount > 1)
+                        cfg.puts(" *** non-unique command ignored      try 'Help' ***");
+                    else if (foundCount == 0)
+                        cfg.puts(" *** huh?                            try 'Help' ***");
+                }
+                else
+                    cfg.puts("");
+                keycount = 0;
+                buffer[keycount] = '\0';
+                showPrompt = TRUE;        // forces the prompt
+            }
+            break;
+        default:
+            if (myisprint(c) && keycount < cfg.bufferSize)
+            {
+                buffer[keycount++] = c;
+                buffer[keycount] = '\0';
+                if (CommandMatches(buffer, FALSE, &cbk, &params))
+                    cfg.putch(c);
+                else
+                {
+                    buffer[--keycount] = '\0';
+                    cfg.putch(0x07);    // bell
+                }
+            }
+            else
+                cfg.putch(0x07);    // bell
+            break;
+        }
+    }
+    return val;
+}
+
+
+/// End the CommandProcessor by freeing all the memory that was allocated
+///
+///    returns runok
+///
+RUNRESULT_T CommandProcessor_End(void)
+{
+    CMDLINK_T *p = head;
+    CMDLINK_T *n;
+
+    do
+    {
+        n = p->next;
+        free(p);            // free each of the allocated links to menu items.
+        p = n;
+    } while (n);
+    free(buffer);            // finally, free the command buffer
+    buffer = NULL;            // flag it as deallocated
+    return runok;
+}