Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: A_CANAdapter USB2I2C
CommandProcessor.c
- Committer:
- WiredHome
- Date:
- 2011-03-31
- Revision:
- 5:a98bd1f2fd59
- Parent:
- 0:198f53da1bc8
- Child:
- 6:1a0512faa75d
File content as of revision 5:a98bd1f2fd59:
/// @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 QuestionMenu = {"?", "Shows this 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(&QuestionMenu);
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, ¶ms))
{
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, ¶ms);
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, ¶ms))
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;
}