summaryrefslogtreecommitdiff
path: root/lib/embedded-cli/embedded_cli.h
diff options
context:
space:
mode:
authorhathach <[email protected]>2022-11-18 21:38:03 +0700
committerhathach <[email protected]>2022-11-18 21:39:04 +0700
commitb00529e287c13197f80787f5126626b0d08c06a1 (patch)
tree5e1e41fbcf30c2e29923fd8baa34dfafb846230a /lib/embedded-cli/embedded_cli.h
parent2dc7a623616aee3986ef7751ad9299de08a073e5 (diff)
add msc file explorer example
- add lib/fatfs version 15 - add lib/embedded-cli
Diffstat (limited to 'lib/embedded-cli/embedded_cli.h')
-rw-r--r--lib/embedded-cli/embedded_cli.h1511
1 files changed, 1511 insertions, 0 deletions
diff --git a/lib/embedded-cli/embedded_cli.h b/lib/embedded-cli/embedded_cli.h
new file mode 100644
index 000000000..33354cd84
--- /dev/null
+++ b/lib/embedded-cli/embedded_cli.h
@@ -0,0 +1,1511 @@
+/**
+ * This header was automatically built using
+ * embedded_cli.h and embedded_cli.c
+ * @date 2022-11-03
+ *
+ * MIT License
+ *
+ * Copyright (c) 2021 Sviatoslav Kokurin (funbiscuit)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef EMBEDDED_CLI_H
+#define EMBEDDED_CLI_H
+
+
+#ifdef __cplusplus
+
+extern "C" {
+#else
+
+#include <stdbool.h>
+
+#endif
+
+// cstdint is available only since C++11, so use C header
+#include <stdint.h>
+
+// used for proper alignment of cli buffer
+#if UINTPTR_MAX == 0xFFFF
+#define CLI_UINT uint16_t
+#elif UINTPTR_MAX == 0xFFFFFFFF
+#define CLI_UINT uint32_t
+#elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
+#define CLI_UINT uint64_t
+#else
+#error unsupported pointer size
+#endif
+
+#define CLI_UINT_SIZE (sizeof(CLI_UINT))
+// convert size in bytes to size in terms of CLI_UINTs (rounded up
+// if bytes is not divisible by size of single CLI_UINT)
+#define BYTES_TO_CLI_UINTS(bytes) \
+ (((bytes) + CLI_UINT_SIZE - 1)/CLI_UINT_SIZE)
+
+typedef struct CliCommand CliCommand;
+typedef struct CliCommandBinding CliCommandBinding;
+typedef struct EmbeddedCli EmbeddedCli;
+typedef struct EmbeddedCliConfig EmbeddedCliConfig;
+
+
+struct CliCommand {
+ /**
+ * Name of the command.
+ * In command "set led 1 1" "set" is name
+ */
+ const char *name;
+
+ /**
+ * String of arguments of the command.
+ * In command "set led 1 1" "led 1 1" is string of arguments
+ * Is ended with double 0x00 char
+ * Use tokenize functions to easily get individual tokens
+ */
+ char *args;
+};
+
+/**
+ * Struct to describe binding of command to function and
+ */
+struct CliCommandBinding {
+ /**
+ * Name of command to bind. Should not be NULL.
+ */
+ const char *name;
+
+ /**
+ * Help string that will be displayed when "help <cmd>" is executed.
+ * Can have multiple lines separated with "\r\n"
+ * Can be NULL if no help is provided.
+ */
+ const char *help;
+
+ /**
+ * Flag to perform tokenization before calling binding function.
+ */
+ bool tokenizeArgs;
+
+ /**
+ * Pointer to any specific app context that is required for this binding.
+ * It will be provided in binding callback.
+ */
+ void *context;
+
+ /**
+ * Binding function for when command is received.
+ * If null, default callback (onCommand) will be called.
+ * @param cli - pointer to cli that is calling this binding
+ * @param args - string of args (if tokenizeArgs is false) or tokens otherwise
+ * @param context
+ */
+ void (*binding)(EmbeddedCli *cli, char *args, void *context);
+};
+
+struct EmbeddedCli {
+ /**
+ * Should write char to connection
+ * @param cli - pointer to cli that executed this function
+ * @param c - actual character to write
+ */
+ void (*writeChar)(EmbeddedCli *cli, char c);
+
+ /**
+ * Called when command is received and command not found in list of
+ * command bindings (or binding function is null).
+ * @param cli - pointer to cli that executed this function
+ * @param command - pointer to received command
+ */
+ void (*onCommand)(EmbeddedCli *cli, CliCommand *command);
+
+ /**
+ * Can be used by for any application context
+ */
+ void *appContext;
+
+ /**
+ * Pointer to actual implementation, do not use.
+ */
+ void *_impl;
+};
+
+/**
+ * Configuration to create CLI
+ */
+struct EmbeddedCliConfig {
+ /**
+ * Size of buffer that is used to store characters until they're processed
+ */
+ uint16_t rxBufferSize;
+
+ /**
+ * Size of buffer that is used to store current input that is not yet
+ * sended as command (return not pressed yet)
+ */
+ uint16_t cmdBufferSize;
+
+ /**
+ * Size of buffer that is used to store previously entered commands
+ * Only unique commands are stored in buffer. If buffer is smaller than
+ * entered command (including arguments), command is discarded from history
+ */
+ uint16_t historyBufferSize;
+
+ /**
+ * Maximum amount of bindings that can be added via addBinding function.
+ * Cli increases takes extra bindings for internal commands:
+ * - help
+ */
+ uint16_t maxBindingCount;
+
+ /**
+ * Buffer to use for cli and all internal structures. If NULL, memory will
+ * be allocated dynamically. Otherwise this buffer is used and no
+ * allocations are made
+ */
+ CLI_UINT *cliBuffer;
+
+ /**
+ * Size of buffer for cli and internal structures (in bytes).
+ */
+ uint16_t cliBufferSize;
+
+ /**
+ * Whether autocompletion should be enabled.
+ * If false, autocompletion is disabled but you still can use 'tab' to
+ * complete current command manually.
+ */
+ bool enableAutoComplete;
+};
+
+/**
+ * Returns pointer to default configuration for cli creation. It is safe to
+ * modify it and then send to embeddedCliNew().
+ * Returned structure is always the same so do not free and try to use it
+ * immediately.
+ * Default values:
+ * <ul>
+ * <li>rxBufferSize = 64</li>
+ * <li>cmdBufferSize = 64</li>
+ * <li>historyBufferSize = 128</li>
+ * <li>cliBuffer = NULL (use dynamic allocation)</li>
+ * <li>cliBufferSize = 0</li>
+ * <li>maxBindingCount = 8</li>
+ * <li>enableAutoComplete = true</li>
+ * </ul>
+ * @return configuration for cli creation
+ */
+EmbeddedCliConfig *embeddedCliDefaultConfig(void);
+
+/**
+ * Returns how many space in config buffer is required for cli creation
+ * If you provide buffer with less space, embeddedCliNew will return NULL
+ * This amount will always be divisible by CLI_UINT_SIZE so allocated buffer
+ * and internal structures can be properly aligned
+ * @param config
+ * @return
+ */
+uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config);
+
+/**
+ * Create new CLI.
+ * Memory is allocated dynamically if cliBuffer in config is NULL.
+ * After CLI is created, override function pointers to start using it
+ * @param config - config for cli creation
+ * @return pointer to created CLI
+ */
+EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config);
+
+/**
+ * Same as calling embeddedCliNew with default config.
+ * @return
+ */
+EmbeddedCli *embeddedCliNewDefault(void);
+
+/**
+ * Receive character and put it to internal buffer
+ * Actual processing is done inside embeddedCliProcess
+ * You can call this function from something like interrupt service routine,
+ * just make sure that you call it only from single place. Otherwise input
+ * might get corrupted
+ * @param cli
+ * @param c - received char
+ */
+void embeddedCliReceiveChar(EmbeddedCli *cli, char c);
+
+/**
+ * Process rx/tx buffers. Command callbacks are called from here
+ * @param cli
+ */
+void embeddedCliProcess(EmbeddedCli *cli);
+
+/**
+ * Add specified binding to list of bindings. If list is already full, binding
+ * is not added and false is returned
+ * @param cli
+ * @param binding
+ * @return true if binding was added, false otherwise
+ */
+bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding);
+
+/**
+ * Print specified string and account for currently entered but not submitted
+ * command.
+ * Current command is deleted, provided string is printed (with new line) after
+ * that current command is printed again, so user can continue typing it.
+ * @param cli
+ * @param string
+ */
+void embeddedCliPrint(EmbeddedCli *cli, const char *string);
+
+/**
+ * Free allocated for cli memory
+ * @param cli
+ */
+void embeddedCliFree(EmbeddedCli *cli);
+
+/**
+ * Perform tokenization of arguments string. Original string is modified and
+ * should not be used directly (only inside other token functions).
+ * Individual tokens are separated by single 0x00 char, double 0x00 is put at
+ * the end of token list. After calling this function, you can use other
+ * token functions to get individual tokens and token count.
+ *
+ * Important: Call this function only once. Otherwise information will be lost if
+ * more than one token existed
+ * @param args - string to tokenize (must have extra writable char after 0x00)
+ * @return
+ */
+void embeddedCliTokenizeArgs(char *args);
+
+/**
+ * Return specific token from tokenized string
+ * @param tokenizedStr
+ * @param pos (counted from 1)
+ * @return token
+ */
+const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos);
+
+/**
+ * Same as embeddedCliGetToken but works on non-const buffer
+ * @param tokenizedStr
+ * @param pos (counted from 1)
+ * @return token
+ */
+char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos);
+
+/**
+ * Find token in provided tokens string and return its position (counted from 1)
+ * If no such token is found - 0 is returned.
+ * @param tokenizedStr
+ * @param token - token to find
+ * @return position (increased by 1) or zero if no such token found
+ */
+uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token);
+
+/**
+ * Return number of tokens in tokenized string
+ * @param tokenizedStr
+ * @return number of tokens
+ */
+uint16_t embeddedCliGetTokenCount(const char *tokenizedStr);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif //EMBEDDED_CLI_H
+
+
+#ifdef EMBEDDED_CLI_IMPL
+#ifndef EMBEDDED_CLI_IMPL_GUARD
+#define EMBEDDED_CLI_IMPL_GUARD
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+
+#define CLI_TOKEN_NPOS 0xffff
+
+#define UNUSED(x) (void)x
+
+#define PREPARE_IMPL(t) \
+ EmbeddedCliImpl* impl = (EmbeddedCliImpl*)t->_impl
+
+#define IS_FLAG_SET(flags, flag) (((flags) & (flag)) != 0)
+
+#define SET_FLAG(flags, flag) ((flags) |= (flag))
+
+#define UNSET_U8FLAG(flags, flag) ((flags) &= (uint8_t) ~(flag))
+
+/**
+ * Marks binding as candidate for autocompletion
+ * This flag is updated each time getAutocompletedCommand is called
+ */
+#define BINDING_FLAG_AUTOCOMPLETE 1u
+
+/**
+ * Indicates that rx buffer overflow happened. In such case last command
+ * that wasn't finished (no \r or \n were received) will be discarded
+ */
+#define CLI_FLAG_OVERFLOW 0x01u
+
+/**
+ * Indicates that initialization is completed. Initialization is completed in
+ * first call to process and needed, for example, to print invitation message.
+ */
+#define CLI_FLAG_INIT_COMPLETE 0x02u
+
+/**
+ * Indicates that CLI structure and internal structures were allocated with
+ * malloc and should bre freed
+ */
+#define CLI_FLAG_ALLOCATED 0x04u
+
+/**
+ * Indicates that CLI structure and internal structures were allocated with
+ * malloc and should bre freed
+ */
+#define CLI_FLAG_ESCAPE_MODE 0x08u
+
+/**
+ * Indicates that CLI in mode when it will print directly to output without
+ * clear of current command and printing it back
+ */
+#define CLI_FLAG_DIRECT_PRINT 0x10u
+
+/**
+ * Indicates that live autocompletion is enabled
+ */
+#define CLI_FLAG_AUTOCOMPLETE_ENABLED 0x20u
+
+typedef struct EmbeddedCliImpl EmbeddedCliImpl;
+typedef struct AutocompletedCommand AutocompletedCommand;
+typedef struct FifoBuf FifoBuf;
+typedef struct CliHistory CliHistory;
+
+struct FifoBuf {
+ char *buf;
+ /**
+ * Position of first element in buffer. From this position elements are taken
+ */
+ uint16_t front;
+ /**
+ * Position after last element. At this position new elements are inserted
+ */
+ uint16_t back;
+ /**
+ * Size of buffer
+ */
+ uint16_t size;
+};
+
+struct CliHistory {
+ /**
+ * Items in buffer are separated by null-chars
+ */
+ char *buf;
+
+ /**
+ * Total size of buffer
+ */
+ uint16_t bufferSize;
+
+ /**
+ * Index of currently selected element. This allows to navigate history
+ * After command is sent, current element is reset to 0 (no element)
+ */
+ uint16_t current;
+
+ /**
+ * Number of items in buffer
+ * Items are counted from top to bottom (and are 1 based).
+ * So the most recent item is 1 and the oldest is itemCount.
+ */
+ uint16_t itemsCount;
+};
+
+struct EmbeddedCliImpl {
+ /**
+ * Invitation string. Is printed at the beginning of each line with user
+ * input
+ */
+ const char *invitation;
+
+ CliHistory history;
+
+ /**
+ * Buffer for storing received chars.
+ * Chars are stored in FIFO mode.
+ */
+ FifoBuf rxBuffer;
+
+ /**
+ * Buffer for current command
+ */
+ char *cmdBuffer;
+
+ /**
+ * Size of current command
+ */
+ uint16_t cmdSize;
+
+ /**
+ * Total size of command buffer
+ */
+ uint16_t cmdMaxSize;
+
+ CliCommandBinding *bindings;
+
+ /**
+ * Flags for each binding. Sizes are the same as for bindings array
+ */
+ uint8_t *bindingsFlags;
+
+ uint16_t bindingsCount;
+
+ uint16_t maxBindingsCount;
+
+ /**
+ * Total length of input line. This doesn't include invitation but
+ * includes current command and its live autocompletion
+ */
+ uint16_t inputLineLength;
+
+ /**
+ * Stores last character that was processed.
+ */
+ char lastChar;
+
+ /**
+ * Flags are defined as CLI_FLAG_*
+ */
+ uint8_t flags;
+};
+
+struct AutocompletedCommand {
+ /**
+ * Name of autocompleted command (or first candidate for autocompletion if
+ * there are multiple candidates).
+ * NULL if autocomplete not possible.
+ */
+ const char *firstCandidate;
+
+ /**
+ * Number of characters that can be completed safely. For example, if there
+ * are two possible commands "get-led" and "get-adc", then for prefix "g"
+ * autocompletedLen will be 4. If there are only one candidate, this number
+ * is always equal to length of the command.
+ */
+ uint16_t autocompletedLen;
+
+ /**
+ * Total number of candidates for autocompletion
+ */
+ uint16_t candidateCount;
+};
+
+static EmbeddedCliConfig defaultConfig;
+
+/**
+ * Number of commands that cli adds. Commands:
+ * - help
+ */
+static const uint16_t cliInternalBindingCount = 1;
+
+static const char *lineBreak = "\r\n";
+
+/**
+ * Navigate through command history back and forth. If navigateUp is true,
+ * navigate to older commands, otherwise navigate to newer.
+ * When history end is reached, nothing happens.
+ * @param cli
+ * @param navigateUp
+ */
+static void navigateHistory(EmbeddedCli *cli, bool navigateUp);
+
+/**
+ * Process escaped character. After receiving ESC+[ sequence, all chars up to
+ * ending character are sent to this function
+ * @param cli
+ * @param c
+ */
+static void onEscapedInput(EmbeddedCli *cli, char c);
+
+/**
+ * Process input character. Character is valid displayable char and should be
+ * added to current command string and displayed to client.
+ * @param cli
+ * @param c
+ */
+static void onCharInput(EmbeddedCli *cli, char c);
+
+/**
+ * Process control character (like \r or \n) possibly altering state of current
+ * command or executing onCommand callback.
+ * @param cli
+ * @param c
+ */
+static void onControlInput(EmbeddedCli *cli, char c);
+
+/**
+ * Parse command in buffer and execute callback
+ * @param cli
+ */
+static void parseCommand(EmbeddedCli *cli);
+
+/**
+ * Setup bindings for internal commands, like help
+ * @param cli
+ */
+static void initInternalBindings(EmbeddedCli *cli);
+
+/**
+ * Show help for given tokens (or default help if no tokens)
+ * @param cli
+ * @param tokens
+ * @param context - not used
+ */
+static void onHelp(EmbeddedCli *cli, char *tokens, void *context);
+
+/**
+ * Show error about unknown command
+ * @param cli
+ * @param name
+ */
+static void onUnknownCommand(EmbeddedCli *cli, const char *name);
+
+/**
+ * Return autocompleted command for given prefix.
+ * Prefix is compared to all known command bindings and autocompleted result
+ * is returned
+ * @param cli
+ * @param prefix
+ * @return
+ */
+static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix);
+
+/**
+ * Prints autocompletion result while keeping current command unchanged
+ * Prints only if autocompletion is present and only one candidate exists.
+ * @param cli
+ */
+static void printLiveAutocompletion(EmbeddedCli *cli);
+
+/**
+ * Handles autocomplete request. If autocomplete possible - fills current
+ * command with autocompleted command. When multiple commands satisfy entered
+ * prefix, they are printed to output.
+ * @param cli
+ */
+static void onAutocompleteRequest(EmbeddedCli *cli);
+
+/**
+ * Removes all input from current line (replaces it with whitespaces)
+ * And places cursor at the beginning of the line
+ * @param cli
+ */
+static void clearCurrentLine(EmbeddedCli *cli);
+
+/**
+ * Write given string to cli output
+ * @param cli
+ * @param str
+ */
+static void writeToOutput(EmbeddedCli *cli, const char *str);
+
+/**
+ * Returns true if provided char is a supported control char:
+ * \r, \n, \b or 0x7F (treated as \b)
+ * @param c
+ * @return
+ */
+static bool isControlChar(char c);
+
+/**
+ * Returns true if provided char is a valid displayable character:
+ * a-z, A-Z, 0-9, whitespace, punctuation, etc.
+ * Currently only ASCII is supported
+ * @param c
+ * @return
+ */
+static bool isDisplayableChar(char c);
+
+/**
+ * How many elements are currently available in buffer
+ * @param buffer
+ * @return number of elements
+ */
+static uint16_t fifoBufAvailable(FifoBuf *buffer);
+
+/**
+ * Return first character from buffer and remove it from buffer
+ * Buffer must be non-empty, otherwise 0 is returned
+ * @param buffer
+ * @return
+ */
+static char fifoBufPop(FifoBuf *buffer);
+
+/**
+ * Push character into fifo buffer. If there is no space left, character is
+ * discarded and false is returned
+ * @param buffer
+ * @param a - character to add
+ * @return true if char was added to buffer, false otherwise
+ */
+static bool fifoBufPush(FifoBuf *buffer, char a);
+
+/**
+ * Copy provided string to the history buffer.
+ * If it is already inside history, it will be removed from it and added again.
+ * So after addition, it will always be on top
+ * If available size is not enough (and total size is enough) old elements will
+ * be removed from history so this item can be put to it
+ * @param history
+ * @param str
+ * @return true if string was put in history
+ */
+static bool historyPut(CliHistory *history, const char *str);
+
+/**
+ * Get item from history. Items are counted from 1 so if item is 0 or greater
+ * than itemCount, NULL is returned
+ * @param history
+ * @param item
+ * @return true if string was put in history
+ */
+static const char *historyGet(CliHistory *history, uint16_t item);
+
+/**
+ * Remove specific item from history
+ * @param history
+ * @param str - string to remove
+ * @return
+ */
+static void historyRemove(CliHistory *history, const char *str);
+
+/**
+ * Return position (index of first char) of specified token
+ * @param tokenizedStr - tokenized string (separated by \0 with
+ * \0\0 at the end)
+ * @param pos - token position (counted from 1)
+ * @return index of first char of specified token
+ */
+static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos);
+
+EmbeddedCliConfig *embeddedCliDefaultConfig(void) {
+ defaultConfig.rxBufferSize = 64;
+ defaultConfig.cmdBufferSize = 64;
+ defaultConfig.historyBufferSize = 128;
+ defaultConfig.cliBuffer = NULL;
+ defaultConfig.cliBufferSize = 0;
+ defaultConfig.maxBindingCount = 8;
+ defaultConfig.enableAutoComplete = true;
+ return &defaultConfig;
+}
+
+uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config) {
+ uint16_t bindingCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
+ return (uint16_t) (CLI_UINT_SIZE * (
+ BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli)) +
+ BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl)) +
+ BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char)) +
+ BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char)) +
+ BYTES_TO_CLI_UINTS(config->historyBufferSize * sizeof(char)) +
+ BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding)) +
+ BYTES_TO_CLI_UINTS(bindingCount * sizeof(uint8_t))));
+}
+
+EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config) {
+ EmbeddedCli *cli = NULL;
+
+ uint16_t bindingCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
+
+ size_t totalSize = embeddedCliRequiredSize(config);
+
+ bool allocated = false;
+ if (config->cliBuffer == NULL) {
+ config->cliBuffer = (CLI_UINT *) malloc(totalSize); // malloc guarantees alignment.
+ if (config->cliBuffer == NULL)
+ return NULL;
+ allocated = true;
+ } else if (config->cliBufferSize < totalSize) {
+ return NULL;
+ }
+
+ CLI_UINT *buf = config->cliBuffer;
+
+ memset(buf, 0, totalSize);
+
+ cli = (EmbeddedCli *) buf;
+ buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli));
+
+ cli->_impl = (EmbeddedCliImpl *) buf;
+ buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl));
+
+ PREPARE_IMPL(cli);
+ impl->rxBuffer.buf = (char *) buf;
+ buf += BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char));
+
+ impl->cmdBuffer = (char *) buf;
+ buf += BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char));
+
+ impl->bindings = (CliCommandBinding *) buf;
+ buf += BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding));
+
+ impl->bindingsFlags = (uint8_t *) buf;
+ buf += BYTES_TO_CLI_UINTS(bindingCount);
+
+ impl->history.buf = (char *) buf;
+ impl->history.bufferSize = config->historyBufferSize;
+
+ if (allocated)
+ SET_FLAG(impl->flags, CLI_FLAG_ALLOCATED);
+
+ if (config->enableAutoComplete)
+ SET_FLAG(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED);
+
+ impl->rxBuffer.size = config->rxBufferSize;
+ impl->rxBuffer.front = 0;
+ impl->rxBuffer.back = 0;
+ impl->cmdMaxSize = config->cmdBufferSize;
+ impl->bindingsCount = 0;
+ impl->maxBindingsCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
+ impl->lastChar = '\0';
+ impl->invitation = "> ";
+
+ initInternalBindings(cli);
+
+ return cli;
+}
+
+EmbeddedCli *embeddedCliNewDefault(void) {
+ return embeddedCliNew(embeddedCliDefaultConfig());
+}
+
+void embeddedCliReceiveChar(EmbeddedCli *cli, char c) {
+ PREPARE_IMPL(cli);
+
+ if (!fifoBufPush(&impl->rxBuffer, c)) {
+ SET_FLAG(impl->flags, CLI_FLAG_OVERFLOW);
+ }
+}
+
+void embeddedCliProcess(EmbeddedCli *cli) {
+ if (cli->writeChar == NULL)
+ return;
+
+ PREPARE_IMPL(cli);
+
+
+ if (!IS_FLAG_SET(impl->flags, CLI_FLAG_INIT_COMPLETE)) {
+ SET_FLAG(impl->flags, CLI_FLAG_INIT_COMPLETE);
+ writeToOutput(cli, impl->invitation);
+ }
+
+ while (fifoBufAvailable(&impl->rxBuffer)) {
+ char c = fifoBufPop(&impl->rxBuffer);
+
+ if (IS_FLAG_SET(impl->flags, CLI_FLAG_ESCAPE_MODE)) {
+ onEscapedInput(cli, c);
+ } else if (impl->lastChar == 0x1B && c == '[') {
+ //enter escape mode
+ SET_FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE);
+ } else if (isControlChar(c)) {
+ onControlInput(cli, c);
+ } else if (isDisplayableChar(c)) {
+ onCharInput(cli, c);
+ }
+
+ printLiveAutocompletion(cli);
+
+ impl->lastChar = c;
+ }
+
+ // discard unfinished command if overflow happened
+ if (IS_FLAG_SET(impl->flags, CLI_FLAG_OVERFLOW)) {
+ impl->cmdSize = 0;
+ impl->cmdBuffer[impl->cmdSize] = '\0';
+ UNSET_U8FLAG(impl->flags, CLI_FLAG_OVERFLOW);
+ }
+}
+
+bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding) {
+ PREPARE_IMPL(cli);
+ if (impl->bindingsCount == impl->maxBindingsCount)
+ return false;
+
+ impl->bindings[impl->bindingsCount] = binding;
+
+ ++impl->bindingsCount;
+ return true;
+}
+
+void embeddedCliPrint(EmbeddedCli *cli, const char *string) {
+ if (cli->writeChar == NULL)
+ return;
+
+ PREPARE_IMPL(cli);
+
+ // remove chars for autocompletion and live command
+ if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT))
+ clearCurrentLine(cli);
+
+ // print provided string
+ writeToOutput(cli, string);
+ writeToOutput(cli, lineBreak);
+
+ // print current command back to screen
+ if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT)) {
+ writeToOutput(cli, impl->invitation);
+ writeToOutput(cli, impl->cmdBuffer);
+ impl->inputLineLength = impl->cmdSize;
+
+ printLiveAutocompletion(cli);
+ }
+}
+
+void embeddedCliFree(EmbeddedCli *cli) {
+ PREPARE_IMPL(cli);
+ if (IS_FLAG_SET(impl->flags, CLI_FLAG_ALLOCATED)) {
+ // allocation is done in single call to malloc, so need only single free
+ free(cli);
+ }
+}
+
+void embeddedCliTokenizeArgs(char *args) {
+ if (args == NULL)
+ return;
+
+ // for now only space, but can add more later
+ const char *separators = " ";
+
+ // indicates that arg is quoted so separators are copied as is
+ bool quotesEnabled = false;
+ // indicates that previous char was a slash, so next char is copied as is
+ bool escapeActivated = false;
+ int insertPos = 0;
+
+ int i = 0;
+ char currentChar;
+ while ((currentChar = args[i]) != '\0') {
+ ++i;
+
+ if (escapeActivated) {
+ escapeActivated = false;
+ } else if (currentChar == '\\') {
+ escapeActivated = true;
+ continue;
+ } else if (currentChar == '"') {
+ quotesEnabled = !quotesEnabled;
+ currentChar = '\0';
+ } else if (!quotesEnabled && strchr(separators, currentChar) != NULL) {
+ currentChar = '\0';
+ }
+
+ // null chars are only copied once and not copied to the beginning
+ if (currentChar != '\0' || (insertPos > 0 && args[insertPos - 1] != '\0')) {
+ args[insertPos] = currentChar;
+ ++insertPos;
+ }
+ }
+
+ // make args double null-terminated source buffer must be big enough to contain extra spaces
+ args[insertPos] = '\0';
+ args[insertPos + 1] = '\0';
+}
+
+const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos) {
+ uint16_t i = getTokenPosition(tokenizedStr, pos);
+
+ if (i != CLI_TOKEN_NPOS)
+ return &tokenizedStr[i];
+ else
+ return NULL;
+}
+
+char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos) {
+ uint16_t i = getTokenPosition(tokenizedStr, pos);
+
+ if (i != CLI_TOKEN_NPOS)
+ return &tokenizedStr[i];
+ else
+ return NULL;
+}
+
+uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token) {
+ if (tokenizedStr == NULL || token == NULL)
+ return 0;
+
+ uint16_t size = embeddedCliGetTokenCount(tokenizedStr);
+ for (uint16_t i = 1; i <= size; ++i) {
+ if (strcmp(embeddedCliGetToken(tokenizedStr, i), token) == 0)
+ return i;
+ }
+
+ return 0;
+}
+
+uint16_t embeddedCliGetTokenCount(const char *tokenizedStr) {
+ if (tokenizedStr == NULL || tokenizedStr[0] == '\0')
+ return 0;
+
+ int i = 0;
+ uint16_t tokenCount = 1;
+ while (true) {
+ if (tokenizedStr[i] == '\0') {
+ if (tokenizedStr[i + 1] == '\0')
+ break;
+ ++tokenCount;
+ }
+ ++i;
+ }
+
+ return tokenCount;
+}
+
+static void navigateHistory(EmbeddedCli *cli, bool navigateUp) {
+ PREPARE_IMPL(cli);
+ if (impl->history.itemsCount == 0 ||
+ (navigateUp && impl->history.current == impl->history.itemsCount) ||
+ (!navigateUp && impl->history.current == 0))
+ return;
+
+ clearCurrentLine(cli);
+
+ writeToOutput(cli, impl->invitation);
+
+ if (navigateUp)
+ ++impl->history.current;
+ else
+ --impl->history.current;
+
+ const char *item = historyGet(&impl->history, impl->history.current);
+ // simple way to handle empty command the same way as others
+ if (item == NULL)
+ item = "";
+ uint16_t len = (uint16_t) strlen(item);
+ memcpy(impl->cmdBuffer, item, len);
+ impl->cmdBuffer[len] = '\0';
+ impl->cmdSize = len;
+
+ writeToOutput(cli, impl->cmdBuffer);
+ impl->inputLineLength = impl->cmdSize;
+
+ printLiveAutocompletion(cli);
+}
+
+static void onEscapedInput(EmbeddedCli *cli, char c) {
+ PREPARE_IMPL(cli);
+
+ if (c >= 64 && c <= 126) {
+ // handle escape sequence
+ UNSET_U8FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE);
+
+ if (c == 'A' || c == 'B') {
+ // treat \e[..A as cursor up and \e[..B as cursor down
+ // there might be extra chars between [ and A/B, just ignore them
+ navigateHistory(cli, c == 'A');
+ }
+ }
+}
+
+static void onCharInput(EmbeddedCli *cli, char c) {
+ PREPARE_IMPL(cli);
+
+ // have to reserve two extra chars for command ending (used in tokenization)
+ if (impl->cmdSize + 2 >= impl->cmdMaxSize)
+ return;
+
+ impl->cmdBuffer[impl->cmdSize] = c;
+ ++impl->cmdSize;
+ impl->cmdBuffer[impl->cmdSize] = '\0';
+
+ cli->writeChar(cli, c);
+}
+
+static void onControlInput(EmbeddedCli *cli, char c) {
+ PREPARE_IMPL(cli);
+
+ // process \r\n and \n\r as single \r\n command
+ if ((impl->lastChar == '\r' && c == '\n') ||
+ (impl->lastChar == '\n' && c == '\r'))
+ return;
+
+ if (c == '\r' || c == '\n') {
+ // try to autocomplete command and then process it
+ onAutocompleteRequest(cli);
+
+ writeToOutput(cli, lineBreak);
+
+ if (impl->cmdSize > 0)
+ parseCommand(cli);
+ impl->cmdSize = 0;
+ impl->cmdBuffer[impl->cmdSize] = '\0';
+ impl->inputLineLength = 0;
+ impl->history.current = 0;
+
+ writeToOutput(cli, impl->invitation);
+ } else if ((c == '\b' || c == 0x7F) && impl->cmdSize > 0) {
+ // remove char from screen
+ cli->writeChar(cli, '\b');
+ cli->writeChar(cli, ' ');
+ cli->writeChar(cli, '\b');
+ // and from buffer
+ --impl->cmdSize;
+ impl->cmdBuffer[impl->cmdSize] = '\0';
+ } else if (c == '\t') {
+ onAutocompleteRequest(cli);
+ }
+
+}
+
+static void parseCommand(EmbeddedCli *cli) {
+ PREPARE_IMPL(cli);
+
+ bool isEmpty = true;
+
+ for (int i = 0; i < impl->cmdSize; ++i) {
+ if (impl->cmdBuffer[i] != ' ') {
+ isEmpty = false;
+ break;
+ }
+ }
+ // do not process empty commands
+ if (isEmpty)
+ return;
+ // push command to history before buffer is modified
+ historyPut(&impl->history, impl->cmdBuffer);
+
+ char *cmdName = NULL;
+ char *cmdArgs = NULL;
+ bool nameFinished = false;
+
+ // find command name and command args inside command buffer
+ for (int i = 0; i < impl->cmdSize; ++i) {
+ char c = impl->cmdBuffer[i];
+
+ if (c == ' ') {
+ // all spaces between name and args are filled with zeros
+ // so name is a correct null-terminated string
+ if (cmdArgs == NULL)
+ impl->cmdBuffer[i] = '\0';
+ if (cmdName != NULL)
+ nameFinished = true;
+
+ } else if (cmdName == NULL) {
+ cmdName = &impl->cmdBuffer[i];
+ } else if (cmdArgs == NULL && nameFinished) {
+ cmdArgs = &impl->cmdBuffer[i];
+ }
+ }
+
+ // we keep two last bytes in cmd buffer reserved so cmdSize is always by 2
+ // less than cmdMaxSize
+ impl->cmdBuffer[impl->cmdSize + 1] = '\0';
+
+ if (cmdName == NULL)
+ return;
+
+ // try to find command in bindings
+ for (int i = 0; i < impl->bindingsCount; ++i) {
+ if (strcmp(cmdName, impl->bindings[i].name) == 0) {
+ if (impl->bindings[i].binding == NULL)
+ break;
+
+ if (impl->bindings[i].tokenizeArgs)
+ embeddedCliTokenizeArgs(cmdArgs);
+ // currently, output is blank line, so we can just print directly
+ SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
+ impl->bindings[i].binding(cli, cmdArgs, impl->bindings[i].context);
+ UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
+ return;
+ }
+ }
+
+ // command not found in bindings or binding was null
+ // try to call default callback
+ if (cli->onCommand != NULL) {
+ CliCommand command;
+ command.name = cmdName;
+ command.args = cmdArgs;
+
+ // currently, output is blank line, so we can just print directly
+ SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
+ cli->onCommand(cli, &command);
+ UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
+ } else {
+ onUnknownCommand(cli, cmdName);
+ }
+}
+
+static void initInternalBindings(EmbeddedCli *cli) {
+ CliCommandBinding b = {
+ "help",
+ "Print list of commands",
+ true,
+ NULL,
+ onHelp
+ };
+ embeddedCliAddBinding(cli, b);
+}
+
+static void onHelp(EmbeddedCli *cli, char *tokens, void *context) {
+ UNUSED(context);
+ PREPARE_IMPL(cli);
+
+ if (impl->bindingsCount == 0) {
+ writeToOutput(cli, "Help is not available");
+ writeToOutput(cli, lineBreak);
+ return;
+ }
+
+ uint16_t tokenCount = embeddedCliGetTokenCount(tokens);
+ if (tokenCount == 0) {
+ for (int i = 0; i < impl->bindingsCount; ++i) {
+ writeToOutput(cli, " * ");
+ writeToOutput(cli, impl->bindings[i].name);
+ writeToOutput(cli, lineBreak);
+ if (impl->bindings[i].help != NULL) {
+ cli->writeChar(cli, '\t');
+ writeToOutput(cli, impl->bindings[i].help);
+ writeToOutput(cli, lineBreak);
+ }
+ }
+ } else if (tokenCount == 1) {
+ // try find command
+ const char *helpStr = NULL;
+ const char *cmdName = embeddedCliGetToken(tokens, 1);
+ bool found = false;
+ for (int i = 0; i < impl->bindingsCount; ++i) {
+ if (strcmp(impl->bindings[i].name, cmdName) == 0) {
+ helpStr = impl->bindings[i].help;
+ found = true;
+ break;
+ }
+ }
+ if (found && helpStr != NULL) {
+ writeToOutput(cli, " * ");
+ writeToOutput(cli, cmdName);
+ writeToOutput(cli, lineBreak);
+ cli->writeChar(cli, '\t');
+ writeToOutput(cli, helpStr);
+ writeToOutput(cli, lineBreak);
+ } else if (found) {
+ writeToOutput(cli, "Help is not available");
+ writeToOutput(cli, lineBreak);
+ } else {
+ onUnknownCommand(cli, cmdName);
+ }
+ } else {
+ writeToOutput(cli, "Command \"help\" receives one or zero arguments");
+ writeToOutput(cli, lineBreak);
+ }
+}
+
+static void onUnknownCommand(EmbeddedCli *cli, const char *name) {
+ writeToOutput(cli, "Unknown command: \"");
+ writeToOutput(cli, name);
+ writeToOutput(cli, "\". Write \"help\" for a list of available commands");
+ writeToOutput(cli, lineBreak);
+}
+
+static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix) {
+ AutocompletedCommand cmd = {NULL, 0, 0};
+
+ size_t prefixLen = strlen(prefix);
+
+ PREPARE_IMPL(cli);
+ if (impl->bindingsCount == 0 || prefixLen == 0)
+ return cmd;
+
+
+ for (int i = 0; i < impl->bindingsCount; ++i) {
+ const char *name = impl->bindings[i].name;
+ size_t len = strlen(name);
+
+ // unset autocomplete flag
+ UNSET_U8FLAG(impl->bindingsFlags[i], BINDING_FLAG_AUTOCOMPLETE);
+
+ if (len < prefixLen)
+ continue;
+
+ // check if this command is candidate for autocomplete
+ bool isCandidate = true;
+ for (size_t j = 0; j < prefixLen; ++j) {
+ if (prefix[j] != name[j]) {
+ isCandidate = false;
+ break;
+ }
+ }
+ if (!isCandidate)
+ continue;
+
+ impl->bindingsFlags[i] |= BINDING_FLAG_AUTOCOMPLETE;
+
+ if (cmd.candidateCount == 0 || len < cmd.autocompletedLen)
+ cmd.autocompletedLen = (uint16_t) len;
+
+ ++cmd.candidateCount;
+
+ if (cmd.candidateCount == 1) {
+ cmd.firstCandidate = name;
+ continue;
+ }
+
+ for (size_t j = impl->cmdSize; j < cmd.autocompletedLen; ++j) {
+ if (cmd.firstCandidate[j] != name[j]) {
+ cmd.autocompletedLen = (uint16_t) j;
+ break;
+ }
+ }
+ }
+
+ return cmd;
+}
+
+static void printLiveAutocompletion(EmbeddedCli *cli) {
+ PREPARE_IMPL(cli);
+
+ if (!IS_FLAG_SET(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED))
+ return;
+
+ AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer);
+
+ if (cmd.candidateCount == 0) {
+ cmd.autocompletedLen = impl->cmdSize;
+ }
+
+ // print live autocompletion (or nothing, if it doesn't exist)
+ for (size_t i = impl->cmdSize; i < cmd.autocompletedLen; ++i) {
+ cli->writeChar(cli, cmd.firstCandidate[i]);
+ }
+ // replace with spaces previous autocompletion
+ for (size_t i = cmd.autocompletedLen; i < impl->inputLineLength; ++i) {
+ cli->writeChar(cli, ' ');
+ }
+ impl->inputLineLength = cmd.autocompletedLen;
+ cli->writeChar(cli, '\r');
+ // print current command again so cursor is moved to initial place
+ writeToOutput(cli, impl->invitation);
+ writeToOutput(cli, impl->cmdBuffer);
+}
+
+static void onAutocompleteRequest(EmbeddedCli *cli) {
+ PREPARE_IMPL(cli);
+
+ AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer);
+
+ if (cmd.candidateCount == 0)
+ return;
+
+ if (cmd.candidateCount == 1 || cmd.autocompletedLen > impl->cmdSize) {
+ // can copy from index cmdSize, but prefix is the same, so copy everything
+ memcpy(impl->cmdBuffer, cmd.firstCandidate, cmd.autocompletedLen);
+ if (cmd.candidateCount == 1) {
+ impl->cmdBuffer[cmd.autocompletedLen] = ' ';
+ ++cmd.autocompletedLen;
+ }
+ impl->cmdBuffer[cmd.autocompletedLen] = '\0';
+
+ writeToOutput(cli, &impl->cmdBuffer[impl->cmdSize]);
+ impl->cmdSize = cmd.autocompletedLen;
+ impl->inputLineLength = impl->cmdSize;
+ return;
+ }
+
+ // with multiple candidates when we already completed to common prefix
+ // we show all candidates and print input again
+ // we need to completely clear current line since it begins with invitation
+ clearCurrentLine(cli);
+
+ for (int i = 0; i < impl->bindingsCount; ++i) {
+ // autocomplete flag is set for all candidates by last call to
+ // getAutocompletedCommand
+ if (!(impl->bindingsFlags[i] & BINDING_FLAG_AUTOCOMPLETE))
+ continue;
+
+ const char *name = impl->bindings[i].name;
+
+ writeToOutput(cli, name);
+ writeToOutput(cli, lineBreak);
+ }
+
+ writeToOutput(cli, impl->invitation);
+ writeToOutput(cli, impl->cmdBuffer);
+
+ impl->inputLineLength = impl->cmdSize;
+}
+
+static void clearCurrentLine(EmbeddedCli *cli) {
+ PREPARE_IMPL(cli);
+ size_t len = impl->inputLineLength + strlen(impl->invitation);
+
+ cli->writeChar(cli, '\r');
+ for (size_t i = 0; i < len; ++i) {
+ cli->writeChar(cli, ' ');
+ }
+ cli->writeChar(cli, '\r');
+ impl->inputLineLength = 0;
+}
+
+static void writeToOutput(EmbeddedCli *cli, const char *str) {
+ size_t len = strlen(str);
+
+ for (size_t i = 0; i < len; ++i) {
+ cli->writeChar(cli, str[i]);
+ }
+}
+
+static bool isControlChar(char c) {
+ return c == '\r' || c == '\n' || c == '\b' || c == '\t' || c == 0x7F;
+}
+
+static bool isDisplayableChar(char c) {
+ return (c >= 32 && c <= 126);
+}
+
+static uint16_t fifoBufAvailable(FifoBuf *buffer) {
+ if (buffer->back >= buffer->front)
+ return (uint16_t) (buffer->back - buffer->front);
+ else
+ return (uint16_t) (buffer->size - buffer->front + buffer->back);
+}
+
+static char fifoBufPop(FifoBuf *buffer) {
+ char a = '\0';
+ if (buffer->front != buffer->back) {
+ a = buffer->buf[buffer->front];
+ buffer->front = (uint16_t) (buffer->front + 1) % buffer->size;
+ }
+ return a;
+}
+
+static bool fifoBufPush(FifoBuf *buffer, char a) {
+ uint16_t newBack = (uint16_t) (buffer->back + 1) % buffer->size;
+ if (newBack != buffer->front) {
+ buffer->buf[buffer->back] = a;
+ buffer->back = newBack;
+ return true;
+ }
+ return false;
+}
+
+static bool historyPut(CliHistory *history, const char *str) {
+ size_t len = strlen(str);
+ // each item is ended with \0 so, need to have that much space at least
+ if (history->bufferSize < len + 1)
+ return false;
+
+ // remove str from history (if it's present) so we don't get duplicates
+ historyRemove(history, str);
+
+ size_t usedSize;
+ // remove old items if new one can't fit into buffer
+ while (history->itemsCount > 0) {
+ const char *item = historyGet(history, history->itemsCount);
+ size_t itemLen = strlen(item);
+ usedSize = ((size_t) (item - history->buf)) + itemLen + 1;
+
+ size_t freeSpace = history->bufferSize - usedSize;
+
+ if (freeSpace >= len + 1)
+ break;
+
+ // space not enough, remove last element
+ --history->itemsCount;
+ }
+ if (history->itemsCount > 0) {
+ // when history not empty, shift elements so new item is first
+ memmove(&history->buf[len + 1], history->buf, usedSize);
+ }
+ memcpy(history->buf, str, len + 1);
+ ++history->itemsCount;
+
+ return true;
+}
+
+static const char *historyGet(CliHistory *history, uint16_t item) {
+ if (item == 0 || item > history->itemsCount)
+ return NULL;
+
+ // items are stored in the same way (separated by \0 and counted from 1),
+ // so can use this call
+ return embeddedCliGetToken(history->buf, item);
+}
+
+static void historyRemove(CliHistory *history, const char *str) {
+ if (str == NULL || history->itemsCount == 0)
+ return;
+ char *item = NULL;
+ uint16_t itemPosition;
+ for (itemPosition = 1; itemPosition <= history->itemsCount; ++itemPosition) {
+ // items are stored in the same way (separated by \0 and counted from 1),
+ // so can use this call
+ item = embeddedCliGetTokenVariable(history->buf, itemPosition);
+ if (strcmp(item, str) == 0) {
+ break;
+ }
+ item = NULL;
+ }
+ if (item == NULL)
+ return;
+
+ --history->itemsCount;
+ if (itemPosition == (history->itemsCount + 1)) {
+ // if this is a last element, nothing is remaining to move
+ return;
+ }
+
+ size_t len = strlen(item);
+ size_t remaining = (size_t) (history->bufferSize - (item + len + 1 - history->buf));
+ // move everything to the right of found item
+ memmove(item, &item[len + 1], remaining);
+}
+
+static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos) {
+ if (tokenizedStr == NULL || pos == 0)
+ return CLI_TOKEN_NPOS;
+ uint16_t i = 0;
+ uint16_t tokenCount = 1;
+ while (true) {
+ if (tokenCount == pos)
+ break;
+
+ if (tokenizedStr[i] == '\0') {
+ ++tokenCount;
+ if (tokenizedStr[i + 1] == '\0')
+ break;
+ }
+
+ ++i;
+ }
+
+ if (tokenizedStr[i] != '\0')
+ return i;
+ else
+ return CLI_TOKEN_NPOS;
+}
+#ifdef __cplusplus
+}
+#endif
+#endif // EMBEDDED_CLI_IMPL_GUARD
+#endif // EMBEDDED_CLI_IMPL