Provides a command-line interface framework that ties together the VT100 terminal line editor and the command line argv parser to deliver a complete interactive shell for embedded systems.
The module manages a list of registered commands, command history with arrow-key recall, input
character dispatch, argv parsing, handler invocation, and built-in help/syntax display.
The caller creates a VT100 editor context, a CLI context, registers one or more commands with
handler callbacks, and then feeds received bytes (from a UART, socket, or stdin in raw mode)
into SSFCLIProcessChar(). The framework handles everything else:
line editing, escape-sequence decoding, argv parsing, command lookup, handler dispatch,
syntax-error output, and prompt management.
Three helper functions —
SSFCLIGObjGetOptArgStrRef(),
SSFCLIGObjGetIsOpt(), and
SSFCLIGObjGetArgStrRef() — simplify extracting option values
and positional arguments inside command handlers.
Dependencies | Notes | Configuration | API Summary | Function Reference | Complete Example
↑ Dependencies
ssfport.hssf.hssfassert.hssfvted.h— VT100 Terminal Line Editorssfargv.h— Command Line Argv Parserssfll.h— Linked List (for the registered command list)ssfgobj.h— Generic Object (for parsed command trees)ssfoptions.h— compile-time configuration constants
↑ Notes
-
Two-phase initialization: the caller must initialize a
SSFVTEdContext_tviaSSFVTEdInit()first, then pass it toSSFCLIInit(). The CLI context does not own the VT100 context or its line buffer; it stores only a pointer. -
Zero-init before first use: both
SSFVTEdContext_tandSSFCLIContext_tmust be zeroed before the firstInitcall. Stack-allocated structs require an explicitmemset; static-allocated structs are automatically zeroed. -
SSFCLICmd_tmust be zeroed: the command struct contains anSSFLLItem_twhosellpointer must beNULLbefore the firstSSFCLIInitCmd()call. Static command arrays are auto-zeroed; stack or heap structs must be zeroed by the caller. -
Handler contract: a command handler returns
trueon success andfalseon failure. When the handler returnsfalse, the CLI automatically prints the command'scmdSyntaxStrto the terminal as usage help. -
Command history: when
maxCmdHist > 0, the CLI saves each non-empty command line into a caller-supplied ring buffer on Enter. The Up and Down arrow keys recall previous commands. Empty-line Enter does not save to history. The history buffer must be at leastlineSize * maxCmdHistbytes. -
Built-in help: when the user enters a command that does not match any registered command, the CLI prints the list of registered commands with their syntax strings. When the user enters a line that fails argv parsing (e.g. invalid characters), the CLI prints the command-line grammar.
-
No heap allocation: the CLI context itself uses no heap. The argv parser (
SSFArgvInit) allocates temporary gobj trees on the heap; these are freed bySSFArgvDeInitbeforeSSFCLIProcessChar()returns. -
Single-threaded: each
SSFCLIContext_tis independent and holds no shared state. Multiple CLI instances can coexist, but each must be accessed from only one thread at a time.
↑ Configuration
The following options are defined in ssfoptions.h:
The following option is defined in ssfport.h:
| Option | Default | Description |
|---|---|---|
SSF_CONFIG_CLI_UNIT_TEST |
1 |
Set to 1 to compile the SSFCLIUnitTest() entry point and enable assertion-test coverage |
↑ API Summary
| Symbol | Kind | Description |
|---|---|---|
SSFCLIHandler_t |
Typedef | bool (*)(SSFGObj_t *gobjCmd, uint32_t numOpts, SSFGObj_t *gobjOpts, uint32_t numArgs, SSFGObj_t *gobjArgs, SSFVTEdWriteStdoutFn_t writeStdoutFn) — command handler callback |
SSFCLICmd_t |
Struct | Registered command descriptor. Fields: item (linked-list header), cmdStr (command name), cmdSyntaxStr (usage string), cmdFn (SSFCLIHandler_t) |
SSFCLIContext_t |
Struct | Per-CLI state. Fields: vtEdCtx (pointer to the VT100 editor), cmds (linked list of registered commands), numCmdHist, cmdHistBufIndex, cmdHistBufSize, cmdHistBuf, magic |
| Function | Description | |
|---|---|---|
| e.g. | void SSFCLIInit(context, maxCmds, maxCmdHist, cmdHistBuf, cmdHistBufSize, vtEdCtx) |
Initialize a CLI context |
| e.g. | void SSFCLIDeInit(context) |
Tear down a CLI context |
| e.g. | void SSFCLIInitCmd(context, cmd) |
Register a command |
| e.g. | void SSFCLIDeInitCmd(context, cmd) |
Unregister a command |
| e.g. | void SSFCLIProcessChar(context, inChar) |
Process one input byte |
| e.g. | bool SSFCLIGObjGetOptArgStrRef(optStr, gobjOpts, strOut, strLen) |
Get an option's argument string |
| e.g. | bool SSFCLIGObjGetIsOpt(optStr, gobjOpts) |
Check whether a value-less option is present |
| e.g. | bool SSFCLIGObjGetArgStrRef(argIndex, gobjArgs, strOut, strLen) |
Get a positional argument string by index |
↑ Function Reference
void SSFCLIInit(SSFCLIContext_t *context, uint16_t maxCmds, uint8_t maxCmdHist,
uint8_t *cmdHistBuf, size_t cmdHistBufSize, SSFVTEdContext_t *vtEdCtx);Initializes a CLI context and binds it to an already-initialized VT100 editor context.
The context must be zeroed by the caller before the first call. When maxCmdHist is 0,
command history is disabled and cmdHistBuf must be NULL with cmdHistBufSize equal to
0. When maxCmdHist > 0, the caller must provide a buffer of at least
vtEdCtx->lineSize * maxCmdHist bytes; the buffer is zeroed by SSFCLIInit().
| Parameter | Direction | Type | Description |
|---|---|---|---|
context |
in-out | SSFCLIContext_t * |
Caller-supplied context storage. Must not be NULL. context->magic must be 0 on entry. |
maxCmds |
in | uint16_t |
Maximum number of commands that can be registered. Must be > 0 and <= SSF_CLI_MAX_CMDS. |
maxCmdHist |
in | uint8_t |
Maximum number of command history entries. May be 0 to disable history. Must be <= SSF_CLI_MAX_CMD_HIST. |
cmdHistBuf |
in | uint8_t * |
Caller-supplied history ring buffer. Must be NULL when maxCmdHist == 0. |
cmdHistBufSize |
in | size_t |
Size of cmdHistBuf in bytes. Must be 0 when maxCmdHist == 0. Must be >= vtEdCtx->lineSize * maxCmdHist otherwise. |
vtEdCtx |
in | SSFVTEdContext_t * |
Pointer to an initialized VT100 editor context. Must not be NULL. Must be initialized via SSFVTEdInit(). |
SSFVTEdContext_t vtctx;
SSFCLIContext_t clictx;
char lineBuf[SSF_CLI_MAX_CMD_LINE_SIZE];
uint8_t histBuf[SSF_CLI_MAX_CMD_HIST * SSF_CLI_MAX_CMD_LINE_SIZE];
memset(&vtctx, 0, sizeof(vtctx));
SSFVTEdInit(&vtctx, (SSFCStrOut_t)lineBuf, sizeof(lineBuf), myWriteFn);
memset(&clictx, 0, sizeof(clictx));
SSFCLIInit(&clictx, SSF_CLI_MAX_CMDS, SSF_CLI_MAX_CMD_HIST,
histBuf, sizeof(histBuf), &vtctx);void SSFCLIDeInit(SSFCLIContext_t *context);Tears down a CLI context. All registered commands must have been removed via
SSFCLIDeInitCmd() before calling this function; if the command list
is not empty, a debug assertion fires. After this call context->magic is 0 and the
context may be re-initialized.
| Parameter | Direction | Type | Description |
|---|---|---|---|
context |
in-out | SSFCLIContext_t * |
Initialized CLI context. Must not be NULL. context->magic must equal SSF_CLI_MAGIC. Command list must be empty. |
SSFCLIDeInitCmd(&clictx, &myCmd); /* remove all commands first */
SSFCLIDeInit(&clictx);
/* clictx is now fully zeroed and may be re-used. */void SSFCLIInitCmd(SSFCLIContext_t *context, SSFCLICmd_t *cmd);Registers a command in the CLI context. The command struct must be zeroed before the first
registration (the embedded SSFLLItem_t requires item.ll == NULL). The cmdStr,
cmdSyntaxStr, and cmdFn fields must all be non-NULL. The command list must not already
be full.
| Parameter | Direction | Type | Description |
|---|---|---|---|
context |
in-out | SSFCLIContext_t * |
Initialized CLI context. Must not be NULL. |
cmd |
in-out | SSFCLICmd_t * |
Command descriptor. Must not be NULL. cmdStr, cmdSyntaxStr, and cmdFn must be set. |
SSFCLICmd_t helpCmd = { {0}, "help", "help", helpHandler };
SSFCLIInitCmd(&clictx, &helpCmd);void SSFCLIDeInitCmd(SSFCLIContext_t *context, SSFCLICmd_t *cmd);Removes a previously registered command from the CLI context. The command must currently be in the context's command list; if it is not, a debug assertion fires.
| Parameter | Direction | Type | Description |
|---|---|---|---|
context |
in-out | SSFCLIContext_t * |
Initialized CLI context. Must not be NULL. |
cmd |
in-out | SSFCLICmd_t * |
Command descriptor previously passed to SSFCLIInitCmd(). Must not be NULL. |
SSFCLIDeInitCmd(&clictx, &helpCmd);
/* helpCmd is no longer registered; the CLI will not dispatch to it. */void SSFCLIProcessChar(SSFCLIContext_t *context, uint8_t inChar);Feeds one input byte to the CLI framework. The byte is first forwarded to the VT100 editor
(SSFVTEdProcessChar()). If the editor signals a
high-level event the CLI handles it:
- Enter — the command line is saved to history (if enabled and non-empty), then parsed
via
SSFArgvInit(). On successful parse, the registered command list is searched for an exact match; if found, the handler is invoked. If no match, the list of registered commands is printed. On parse failure, the command-line grammar is printed (if the line was non-empty) or the command list is printed (if the line was empty). In all cases,SSFVTEdReset()is called to clear the line and redraw the prompt. - Ctrl-C — the line buffer is cleared and the prompt is redrawn via
SSFVTEdReset(). - Arrow Up — recalls the previous command from history (if enabled). Skips empty slots. Blanks the line if no history exists.
- Arrow Down — recalls the next command from history (if enabled). Skips empty slots. Blanks the line if no history exists.
All other input (printable characters, arrow-left/right, backspace, partial escape sequences) is handled internally by the VT100 editor.
| Parameter | Direction | Type | Description |
|---|---|---|---|
context |
in-out | SSFCLIContext_t * |
Initialized CLI context. Must not be NULL. |
inChar |
in | uint8_t |
One input byte from the terminal. |
/* Main loop: read one byte at a time from the terminal and feed it to the CLI. */
while (true)
{
uint8_t ch = myReadByte();
SSFCLIProcessChar(&clictx, ch);
}bool SSFCLIGObjGetOptArgStrRef(SSFCStrOut_t optStr, SSFGObj_t *gobjOpts,
SSFCStrOut_t *strOut, size_t *strLen);Looks up a named option in the parsed opts gobj and returns a pointer to its argument string.
Returns true for both valued options (/opt value) and value-less options (//opt); for
value-less options *strLen is 0. Returns false if the option is not present.
The returned strOut pointer is valid only while the gobj tree is alive (i.e., within the
command handler callback).
| Parameter | Direction | Type | Description |
|---|---|---|---|
optStr |
in | char * |
Null-terminated option name to look up. Must not be NULL. |
gobjOpts |
in | SSFGObj_t * |
The opts gobj passed to the command handler. Must not be NULL. |
strOut |
out | char ** |
Receives a pointer to the option's argument string on success. Must not be NULL. |
strLen |
out | size_t * |
Receives the length of the argument string (excluding null terminator) on success. Must not be NULL. |
Returns: true if the option was found and *strOut / *strLen have been set; false
if the option is not present.
bool myHandler(SSFGObj_t *gobjCmd, uint32_t numOpts, SSFGObj_t *gobjOpts,
uint32_t numArgs, SSFGObj_t *gobjArgs,
SSFVTEdWriteStdoutFn_t writeStdoutFn)
{
SSFCStrOut_t str;
size_t strLen;
/* Look up /dest <path> */
if (SSFCLIGObjGetOptArgStrRef("dest", gobjOpts, &str, &strLen))
{
/* str points to "path", strLen == strlen("path") */
writeStdoutFn((const uint8_t *)str, strLen);
}
return true;
}bool SSFCLIGObjGetIsOpt(SSFCStrOut_t optStr, SSFGObj_t *gobjOpts);Returns true if optStr is a value-less option (//opt) present in the parsed opts gobj.
Returns false if the option is missing or if it has an argument value (i.e., it was parsed
as /opt value rather than //opt).
| Parameter | Direction | Type | Description |
|---|---|---|---|
optStr |
in | char * |
Null-terminated option name. Must not be NULL. |
gobjOpts |
in | SSFGObj_t * |
The opts gobj passed to the command handler. Must not be NULL. |
Returns: true if the option is present and has no argument value; false otherwise.
/* Check for //verbose flag */
if (SSFCLIGObjGetIsOpt("verbose", gobjOpts))
{
/* verbose mode is on */
}bool SSFCLIGObjGetArgStrRef(size_t argIndex, SSFGObj_t *gobjArgs,
SSFCStrOut_t *strOut, size_t *strLen);Returns the positional argument at argIndex from the parsed args gobj. Arguments are
zero-indexed in the order they appeared on the command line.
The returned strOut pointer is valid only while the gobj tree is alive (i.e., within the
command handler callback).
| Parameter | Direction | Type | Description |
|---|---|---|---|
argIndex |
in | size_t |
Zero-based index of the argument. Must be < SSF_CLI_MAX_ARGS. |
gobjArgs |
in | SSFGObj_t * |
The args gobj passed to the command handler. Must not be NULL. |
strOut |
out | char ** |
Receives a pointer to the argument string on success. Must not be NULL. |
strLen |
out | size_t * |
Receives the length of the argument string (excluding null terminator) on success. Must not be NULL. |
Returns: true if the argument at argIndex exists and *strOut / *strLen have been
set; false if no argument exists at that index.
/* Read positional arguments: echo arg0 arg1 arg2 ... */
size_t i;
SSFCStrOut_t str;
size_t strLen;
for (i = 0; i < numArgs; i++)
{
if (SSFCLIGObjGetArgStrRef(i, gobjArgs, &str, &strLen) == false) return false;
writeStdoutFn((const uint8_t *)str, strLen);
}↑ Complete Example
The following example shows a complete CLI application with three commands: help, echo,
and math. It demonstrates initialization, command registration, the main input loop, and
handler implementations that use the option/argument getter helpers.
#include <string.h>
#include <stdio.h>
#include <conio.h>
#include "ssfport.h"
#include "ssfassert.h"
#include "ssfvted.h"
#include "ssfcli.h"
#include "ssfdec.h"
/* ---- Terminal write callback ------------------------------------------------ */
void writeFn(const uint8_t *data, size_t dataLen)
{
size_t i;
SSF_REQUIRE(data != NULL);
for (i = 0; i < dataLen; i++) { putchar(data[i]); }
}
/* ---- Command handlers ------------------------------------------------------- */
/* help
* Prints a help message. Accepts no options or arguments.
*/
bool help(SSFGObj_t *gobjCmd, uint32_t numOpts, SSFGObj_t *gobjOpts,
uint32_t numArgs, SSFGObj_t *gobjArgs,
SSFVTEdWriteStdoutFn_t writeStdoutFn)
{
SSF_UNUSED_PTR(gobjCmd);
SSF_UNUSED_PTR(gobjOpts);
SSF_UNUSED_PTR(gobjArgs);
if ((numOpts > 0) || (numArgs > 0)) return false;
writeStdoutFn((const uint8_t *)"This is help output.\r\n",
(uint16_t)strlen("This is help output.\r\n"));
return true;
}
/* echo <str> [<str>]...
* Echoes all positional arguments to the terminal.
*/
bool echo(SSFGObj_t *gobjCmd, uint32_t numOpts, SSFGObj_t *gobjOpts,
uint32_t numArgs, SSFGObj_t *gobjArgs,
SSFVTEdWriteStdoutFn_t writeStdoutFn)
{
SSFCStrOut_t str;
size_t strLen;
size_t i;
SSF_UNUSED_PTR(gobjCmd);
SSF_UNUSED_PTR(gobjOpts);
if ((numOpts != 0) || (numArgs == 0)) return false;
for (i = 0; i < numArgs; i++)
{
if (SSFCLIGObjGetArgStrRef(i, gobjArgs, &str, &strLen) == false) return false;
writeStdoutFn((const uint8_t *)str, strLen);
}
return true;
}
/* math (//add | //sub) [/mult <int>] <int> <int>
* Adds or subtracts two integers with an optional multiplier.
*/
bool math(SSFGObj_t *gobjCmd, uint32_t numOpts, SSFGObj_t *gobjOpts,
uint32_t numArgs, SSFGObj_t *gobjArgs,
SSFVTEdWriteStdoutFn_t writeStdoutFn)
{
bool hasAddOpt;
bool hasSubOpt;
SSFCStrOut_t str;
char strBuf[SSF_DEC_MAX_STR_SIZE];
size_t strLen;
int64_t mult = 1;
int64_t arg1;
int64_t arg2;
SSF_UNUSED_PTR(gobjCmd);
if ((numOpts < 1) || (numArgs != 2)) return false;
/* Must have exactly one of //add or //sub */
hasAddOpt = SSFCLIGObjGetIsOpt("add", gobjOpts);
hasSubOpt = SSFCLIGObjGetIsOpt("sub", gobjOpts);
if ((hasAddOpt && hasSubOpt) ||
((hasAddOpt == false) && (hasSubOpt == false))) return false;
/* Optional /mult <int> multiplier */
if (SSFCLIGObjGetOptArgStrRef("mult", gobjOpts, &str, &strLen))
{
if (SSFDecStrToInt(str, &mult) == false) return false;
}
/* Two required positional integer arguments */
if (SSFCLIGObjGetArgStrRef(0, gobjArgs, &str, &strLen) == false) return false;
if (SSFDecStrToInt(str, &arg1) == false) return false;
if (SSFCLIGObjGetArgStrRef(1, gobjArgs, &str, &strLen) == false) return false;
if (SSFDecStrToInt(str, &arg2) == false) return false;
arg1 = hasAddOpt ? (arg1 + arg2) : (arg1 - arg2);
arg1 *= mult;
strLen = SSFDecIntToStr(arg1, strBuf, sizeof(strBuf));
if (strLen == 0) return false;
writeStdoutFn((const uint8_t *)strBuf, strLen);
return true;
}
/* ---- Command table ---------------------------------------------------------- */
SSFCLICmd_t cmds[] =
{
{ {0}, "help", "help", help },
{ {0}, "echo", "echo <str> [<str>]...", echo },
{ {0}, "math", "math (//add | //sub) [/mult <int>] <int> <int>", math },
};
/* ---- Main ------------------------------------------------------------------- */
int main(void)
{
SSFVTEdContext_t vtctx;
SSFCLIContext_t clictx;
char lineBuf[SSF_CLI_MAX_CMD_LINE_SIZE];
uint8_t histBuf[SSF_CLI_MAX_CMD_HIST * SSF_CLI_MAX_CMD_LINE_SIZE];
uint8_t inChar;
size_t i;
/* Initialize VT100 editor context */
memset(&vtctx, 0, sizeof(vtctx));
SSFVTEdInit(&vtctx, (SSFCStrOut_t)lineBuf, sizeof(lineBuf), writeFn);
/* Initialize CLI context with command history */
memset(&clictx, 0, sizeof(clictx));
SSFCLIInit(&clictx, SSF_CLI_MAX_CMDS, SSF_CLI_MAX_CMD_HIST,
histBuf, sizeof(histBuf), &vtctx);
/* Register commands */
for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++)
{
SSFCLIInitCmd(&clictx, &cmds[i]);
}
/* Main input loop: read one byte at a time from the terminal */
while (true)
{
inChar = (uint8_t)_getch();
SSFCLIProcessChar(&clictx, inChar);
}
return 0;
}Example session (user input shown after the # prompt):
# help
This is help output.
# echo hello world
helloworld
# math //add 3 4
7
# math //sub /mult 10 100 30
700
# math
math (//add | //sub) [/mult <int>] <int> <int>
# math /add
# <cmd> [(//<opt> | /<opt> <arg> | <arg>)]...
<cmd> - One or more A-Z, a-z, and 0-9 chars
<opt> - One or more A-Z, a-z, and 0-9 chars
<arg> - One or more printable chars, ' ', '\', leading '/' escaped by '\'
<ENTER> - Run command
<BACKSPACE> - Delete char behind cursor
<CTRL-C> - New prompt
<LEFT ARROW> - Move cursor left
<RIGHT ARROW> - Move cursor right
<UP ARROW> - Show previous command
<DOWN ARROW> - Show next command
# unknown
help - help
echo - echo <str> [<str>]...
math - math (//add | //sub) [/mult <int>] <int> <int>
#
help - help
echo - echo <str> [<str>]...
math - math (//add | //sub) [/mult <int>] <int> <int>