failed approach

This commit is contained in:
2020-11-19 18:24:03 +01:00
parent 349897d878
commit 6c23b00c22
324 changed files with 57839 additions and 11 deletions

View File

@ -0,0 +1,754 @@
/* Copyright (c) 2013-2017 GomSpace A/S. All rights reserved. */
#include "command_local.h"
#include <stdlib.h>
#include <ctype.h>
#include <gs/util/vmem.h> // register commands
#include <gs/util/log.h> // register commands
#include <gs/util/string.h>
#include <gs/util/clock.h>
#include "../lock.h"
#define MAX_ARGC 30
#ifdef __AVR__
#include <avr/pgmspace.h>
#define cmd_strcmp strcmp_P
#define cmd_strncmp strncmp_P
#define cmd_strlen strlen_P
#define cmd_strcpy strcpy_P
#define cmd_read_ptr(ptr) ((void *) pgm_read_word(ptr))
#define cmd_read_int(ptr) pgm_read_word(ptr)
#else
#define cmd_strcmp strcmp
#define cmd_strncmp strncmp
#define cmd_strlen strlen
#define cmd_strcpy strcpy
#define cmd_read_ptr(ptr) *ptr
#define cmd_read_int(ptr) *ptr
#endif
// define common command log group.
static GS_LOG_GROUP(gs_command_log, "command", GS_LOG_CAT_COMMAND, LOG_DEFAULT_MASK | LOG_INFO_MASK);
#define LOG_DEFAULT gs_command_log
/**
Compile check that size of gs_command_t is multiplum of 4.
*/
GS_STATIC_ASSERT((sizeof(gs_command_t) % 4) == 0, gs_command_t_is_not_a_multiplum_of_4);
// Private context
typedef struct process_context {
// command context - must be first, as it is used to access private context (same address)
gs_command_context_t context;
// process function
gs_error_t (*process)(const gs_command_t * const cmds, int cmd_count, int arg_offset, struct process_context * pc);
// command error
gs_error_t error;
// only exact match (space after last argument)
bool requires_exact_match;
// first command match
const gs_command_t * cmd;
// number of hits when hunting commands, completion etc.
unsigned int hits;
// complete result
struct {
char * line;
size_t token_start;
} complete;
} private_context_t;
// command block
typedef struct gs_command_block {
//! Pointer to command block.
const gs_command_t * commands;
//! Number of commands in command block.
size_t count;
//! Reference to next command block.
struct gs_command_block * next;
} gs_command_block_t;
// commands
static gs_command_block_t g_commands;
// minimum stack size in bytes.
static size_t g_stack_size;
// command logger callback
static gs_command_log_t g_command_logger = NULL;
static void * g_command_logger_ctx = NULL;
static gs_error_t command_stdio_set_result(gs_command_context_t *ctx, const char *group, const char *key, const char *value)
{
static const char* printed_group_header = NULL;
/* Print Group header if Group string is non-empty */
if ((group != NULL) && (group[0] != '\0')) {
if (printed_group_header != group) {
fprintf(ctx->out, "%s:\r\n", group);
printed_group_header = group;
}
}
/* Print "<key>: <value>" if key string is non-empty */
if (key != NULL) {
if (key[0] != '\0') {
if ((group != NULL) && (group[0] != '\0')) {
fprintf(ctx->out, " %s: ", key);
} else {
fprintf(ctx->out, "%s: ", key);
}
}
}
fprintf(ctx->out, "%s\r\n", value);
return GS_OK;
}
gs_error_t gs_command_stdio_flush(gs_command_context_t *ctx)
{
fflush(ctx->out);
return GS_OK;
}
gs_error_t gs_command_stdio_wait_for_key(gs_command_context_t *ctx, int *ch, int timeout_ms)
{
return gs_stdio_getchar_timed(timeout_ms, ch);
}
static const gs_command_io_functions_t stdio_functions = {
.set_result = command_stdio_set_result,
.flush = gs_command_stdio_flush,
.wait_for_key = gs_command_stdio_wait_for_key
};
const char * gs_command_args(gs_command_context_t *ctx)
{
if (ctx->argc > 1) {
// find first matching argument (= starts with) - this is not 100% and doesn't handle arguments with spaces (quoted)
const char * arg = ctx->command_line;
while (arg && arg[0]) {
if (strncmp(arg, ctx->argv[1], strlen(ctx->argv[1])) == 0) {
return arg;
}
// skip argument
for (; *arg && (*arg != ' '); ++arg);
// skip spaces
// cppcheck-suppress redundantCondition
for (; *arg && (*arg == ' '); ++arg);
}
}
return "";
}
bool gs_command_build_argv(char *line, int *argc, char **argv, int max_argc)
{
// Skip spaces
for (; line && *line && isspace((unsigned int)*line); ++line);
*argc = 0;
argv[*argc] = line;
char quote = 0;
while (*line) {
// check for quote's: ' or "
if ((*line == '\'') || (*line == '\"')) {
if (quote == 0) {
quote = *line;
argv[*argc]++;
} else if (quote == *line) {
quote = 0;
*line = '\0';
}
}
// check for whitespace and no quotes active
else if (isspace((unsigned int)*line) && quote == 0) {
/* Delete space */
*line++ = '\0';
// skip spaces
for (; *line && isspace((unsigned int)*line); ++line);
/* If there is more data, we have another argument */
if (*line) {
(*argc)++;
if (*argc >= max_argc) {
return false;
}
argv[*argc] = line;
}
continue;
}
line++;
}
(*argc)++;
if (*argc >= max_argc) {
return false;
}
// According to C11 section 5.1.2.2.1, argv[argc] must be NULL
argv[*argc] = NULL;
// Check for invalid number of quotes
return (quote == 0) ? true : false;
}
static inline gs_error_t command_logger(const char *cmd_line, gs_error_t ret, gs_error_t cmd_ret, gs_timestamp_t ts, gs_timestamp_t te)
{
gs_lock_lock();
gs_command_log_t logger = g_command_logger;
void * log_ctx = g_command_logger_ctx;
gs_lock_unlock();
if (logger) {
return logger(cmd_line, ret, cmd_ret, ts, te, log_ctx);
}
return GS_OK;
}
static gs_error_t command_execute(const gs_command_t * const cmds, int cmd_count, int arg_offset, private_context_t * pc)
{
for (int i = 0; i < cmd_count; i++) {
const gs_command_t * cmd = &cmds[i];
if (cmd_strcmp(pc->context.argv[arg_offset], cmd->name) == 0) {
// check for sub-commands
const gs_command_t * list = (void*) cmd_read_ptr(&cmd->chain.list);
if (list) {
++arg_offset;
if (arg_offset >= pc->context.argc) {
return GS_ERROR_TYPE;
}
return command_execute(list, cmd_read_int(&cmd->chain.count), arg_offset, pc);
}
gs_command_handler_t handler = (void *) cmd_read_ptr(&cmd->handler);
if (handler == NULL) {
return GS_ERROR_NOT_IMPLEMENTED;
}
pc->context.argc -= arg_offset;
pc->context.argv = &pc->context.argv[arg_offset];
pc->context.command = cmd;
// check arguments - if specified
if (cmd->mandatory_args || cmd->optional_args) {
const int min_args = (cmd->mandatory_args == GS_COMMAND_NO_ARGS) ? 0 : cmd->mandatory_args;
const int args = (pc->context.argc - 1);
if (args < min_args) {
return GS_ERROR_ARG;
}
if (args > (min_args + cmd->optional_args)) {
return GS_ERROR_ARG;
}
}
pc->error = handler(&pc->context);
return GS_OK; // command was excecuted
}
}
return GS_ERROR_NOT_FOUND;
}
static gs_error_t command_process(private_context_t * pc)
{
const char * command = gs_string_skip_leading_spaces(pc->context.command_line);
// Make copy of string, because command parser mangles destroys it
const size_t command_len = strlen(command);
char command_copy[command_len + 1];
strcpy(command_copy, command);
if (command_len && command[command_len-1] == ' ') {
pc->requires_exact_match = true;
}
pc->context.optsp = 1;
pc->context.optind = 1;
pc->context.optopt = '?';
pc->context.command_line = command;
// split into arguments
char *argv[MAX_ARGC + 1];
if (gs_command_build_argv(command_copy, &pc->context.argc, argv, MAX_ARGC + 1) == false) {
return GS_ERROR_ARG;
}
pc->context.argv = argv;
gs_error_t error = GS_ERROR_NOT_FOUND;
for (const gs_command_block_t * block = &g_commands; block && (error == GS_ERROR_NOT_FOUND); block = block->next) {
if (block->commands) {
error = (pc->process)(block->commands, block->count, 0, pc);
}
}
return error;
}
gs_error_t gs_command_run(const char * command, gs_error_t * return_command_result)
{
return gs_command_execute_stdio(command, return_command_result);
}
gs_error_t gs_command_execute_stdio(const char * command, gs_error_t * return_command_result)
{
return gs_command_execute(command, return_command_result, stdout, &stdio_functions, NULL);
}
gs_error_t gs_command_execute(const char * command, gs_error_t * return_command_result, FILE *out,
const gs_command_io_functions_t *iof, void *iof_ctx)
{
command = gs_string_skip_leading_spaces(command);
GS_CHECK_ARG(gs_string_empty(command) == false);
private_context_t pc = {
.process = command_execute,
.error = GS_OK,
.context = {
.command_line = command,
.out = out,
.io_functions = iof,
.io_ctx = iof_ctx,
}
};
gs_timestamp_t tm_start, tm_end;
gs_clock_get_time(&tm_start);
gs_error_t error = command_process(&pc);
gs_clock_get_time(&tm_end);
command_logger(pc.context.command_line, error, pc.error, tm_start, tm_end);
if ((error == GS_OK) && return_command_result) {
*return_command_result = pc.error;
}
return error;
}
gs_error_t gs_command_set_output(gs_command_context_t *ctx, const char* group, const char* key, const char* value)
{
GS_CHECK_ARG(ctx);
if (ctx->io_functions && ctx->io_functions->set_result) {
return ctx->io_functions->set_result(ctx, group, key, value);
}
/* If no IO-function set - ignore the data and send Success */
return GS_OK;
}
gs_error_t gs_command_set_output_printf(gs_command_context_t *ctx, const char* group, const char* key, const char * format, ...)
{
GS_CHECK_ARG(ctx);
if (ctx->io_functions && ctx->io_functions->set_result)
{
va_list args;
va_start(args, format);
char value[256];
int size = vsnprintf(value, sizeof(value), format, args);
va_end(args);
/* Don't allow to set truncated results - Return error in this case */
if (size >= (int)sizeof(value)) {
return GS_ERROR_ALLOC;
}
return ctx->io_functions->set_result(ctx, group, key, value);
}
/* If no IO-function set - ignore the data and send Success */
return GS_OK;
}
gs_error_t gs_command_flush_output(gs_command_context_t *ctx)
{
GS_CHECK_ARG(ctx);
if (ctx->io_functions && ctx->io_functions->flush) {
return ctx->io_functions->flush(ctx);
}
/* If no IO-function set - ignore the data and send Success */
return GS_OK;
}
bool gs_command_wait_any_key(gs_command_context_t *ctx, int timeout_ms)
{
int ch;
gs_error_t ret = gs_command_wait_key(ctx, &ch, timeout_ms);
if (ret == GS_ERROR_TIMEOUT) {
return false;
}
/* Ensure that a commands handler will not stall if IO function if not available etc.
False will only be returned in case of a positive timeout */
return true;
}
gs_error_t gs_command_wait_key(gs_command_context_t *ctx, int* ch, int timeout_ms)
{
if (ctx && ctx->io_functions && ctx->io_functions->wait_for_key)
{
return ctx->io_functions->wait_for_key(ctx, ch, timeout_ms);
}
/* If no IO-function set set return GS_ERROR_HANDLE */
return GS_ERROR_HANDLE;
}
unsigned int gs_command_completer_add_token(gs_command_context_t * ctx, const char * token, bool exact)
{
private_context_t * pc = (private_context_t *) ctx;
char * line = &pc->complete.line[pc->complete.token_start];
if (token == NULL) {
// mark any pending partial token as exact
if ((line[0] == 0) || (pc->hits != 1)) {
return pc->hits;
}
exact = true;
}
if (exact) {
if (token) {
strcpy(line, token);
}
strcat(line, " ");
pc->complete.token_start = strlen(pc->complete.line);
pc->hits = 1;
} else {
if (pc->hits == 0) {
strcpy(line, token);
} else {
for (; *line && *token && (*line == *token); ++line, ++token);
*line = 0;
}
++pc->hits;
}
return pc->hits;
}
static unsigned int command_complete_add(private_context_t * pc, const gs_command_t * cmd, bool exact)
{
if (cmd) {
pc->cmd = cmd;
return gs_command_completer_add_token(&pc->context, cmd->name, exact);
} else {
return gs_command_completer_add_token(&pc->context, NULL, exact);
}
}
static gs_error_t command_complete(const gs_command_t * const cmds, int cmd_count, int arg_offset, private_context_t * pc)
{
if (arg_offset > 0) {
// command we are looking for must be in this block
pc->hits = 0;
}
pc->cmd = NULL;
bool exact_match = false;
for (int i = 0; i < cmd_count; i++) {
const gs_command_t * cmd = &cmds[i];
if (cmd_read_int(&cmd->mode) & GS_COMMAND_FLAG_HIDDEN) {
continue;
}
if (gs_string_empty(pc->context.argv[arg_offset])) {
// exceeding known token(s) - partial match
command_complete_add(pc, cmd, false);
continue;
}
if (pc->requires_exact_match || ((arg_offset+1) < pc->context.argc)) {
// must be an exact match
if (cmd_strcmp(pc->context.argv[arg_offset], cmd->name) == 0) {
command_complete_add(pc, cmd, true);
exact_match = true;
break;
}
} else if (cmd_strncmp(pc->context.argv[arg_offset], cmd->name,
strlen(pc->context.argv[arg_offset])) == 0) {
// partial match
command_complete_add(pc, cmd, false);
}
}
if (exact_match || ((arg_offset > 0) && (pc->hits == 1))) {
command_complete_add(pc, NULL, true);
if (strlen(pc->complete.line) > strlen(pc->context.command_line)) {
return GS_OK;
}
if (pc->cmd->chain.list) {
return command_complete(pc->cmd->chain.list, pc->cmd->chain.count, arg_offset+1, pc);
}
// command arguments
pc->context.argc -= arg_offset;
pc->context.argv = &pc->context.argv[arg_offset];
pc->context.command = pc->cmd;
// add the "already" completed ones
int arg_to_complete = 1;
for (; arg_to_complete < (pc->context.argc - 1); ++arg_to_complete) {
gs_command_completer_add_token(&pc->context, pc->context.argv[arg_to_complete], true);
}
// add the last - if its completed (space after)
if ((arg_to_complete < pc->context.argc) && pc->requires_exact_match) {
// cppcheck-suppress unreadVariable - not used on __AVR__ because it doesn't support 'completer'
gs_command_completer_add_token(&pc->context, pc->context.argv[arg_to_complete], true);
++arg_to_complete;
}
#if (__AVR__ == 0)
if (pc->cmd->completer) {
pc->hits = 0;
(pc->cmd->completer)(&pc->context, arg_to_complete);
} else
#endif
{
pc->hits = 1; // no completer - assume single hit
}
return GS_OK; // only used for breaking loop
}
return GS_ERROR_NOT_FOUND;
}
gs_error_t gs_command_complete(char *line, size_t max_line_length, FILE* out)
{
const size_t line_len = strlen(line);
char buffer[max_line_length];
buffer[0] = 0;
private_context_t pc = {
.process = command_complete,
.context = {
.command_line = line,
.out = out,
},
.complete = {
.line = buffer,
},
};
command_process(&pc);
gs_command_completer_add_token(&pc.context, NULL, true);
if (strlen(buffer) > line_len ) {
strcpy(line, buffer);
}
switch (pc.hits) {
case 0:
return GS_ERROR_NOT_FOUND;
case 1:
return GS_OK;
default:
return GS_ERROR_AMBIGUOUS;
}
}
static void command_help_print(const gs_command_t * const cmd, private_context_t * pc)
{
if (pc->hits == 1) {
if (cmd->help) {
fprintf(pc->context.out, "%s\r\n", cmd->help);
}
if (cmd->chain.count == 0) {
fprintf(pc->context.out, "usage: %s %s\r\n", cmd->name, cmd->usage ? cmd->usage : "");
} else {
for (unsigned int i = 0; i < cmd->chain.count; ++i) {
const gs_command_t * scmd = &cmd->chain.list[i];
if (scmd->mode & GS_COMMAND_FLAG_HIDDEN) {
continue;
}
fprintf(pc->context.out, " %-19s %s\r\n", scmd->name, scmd->help ? scmd->help : "");
}
}
} else {
fprintf(pc->context.out, " %-19s %s\r\n", cmd->name, cmd->help ? cmd->help : "");
}
}
static void command_help_hit(const gs_command_t * const cmd, private_context_t * pc)
{
pc->error = GS_OK;
++pc->hits;
if (pc->hits == 1) {
// single hit so far - hold off printing until we know if we get more
pc->cmd = cmd;
} else {
if (pc->cmd) {
command_help_print(pc->cmd, pc);
pc->cmd = NULL;
}
command_help_print(cmd, pc);
}
}
static gs_error_t command_help(const gs_command_t * const cmds, int cmd_count, int arg_offset, private_context_t * pc)
{
for (int i = 0; i < cmd_count; i++) {
const gs_command_t * cmd = &cmds[i];
if (cmd_read_int(&cmd->mode) & GS_COMMAND_FLAG_HIDDEN) {
continue;
}
if (pc->requires_exact_match || ((arg_offset+1) < pc->context.argc)) {
// must be an exact match
if (cmd_strcmp(pc->context.argv[arg_offset], cmd->name) == 0) {
const gs_command_t * list = (void*) cmd_read_ptr(&cmd->chain.list);
if (list && ((arg_offset+1) < pc->context.argc)) {
return command_help(list, cmd_read_int(&cmd->chain.count), arg_offset+1, pc);
}
command_help_hit(cmd, pc);
}
} else if (cmd_strncmp(pc->context.argv[arg_offset], cmd->name,
strlen(pc->context.argv[arg_offset])) == 0) {
command_help_hit(cmd, pc);
}
}
return GS_ERROR_NOT_FOUND;
}
gs_error_t gs_command_show_help(const char * command, FILE* out)
{
private_context_t pc = {
.process = command_help,
.error = GS_ERROR_NOT_FOUND,
.context = {
.command_line = command,
.out = out,
}
};
gs_error_t error = command_process(&pc);
if (pc.cmd) {
command_help_print(pc.cmd, &pc);
error = GS_OK;
} else if ((error == GS_ERROR_NOT_FOUND) && pc.hits) {
error = GS_OK;
}
return error;
}
gs_error_t gs_command_register(const gs_command_t * commands, size_t count)
{
GS_CHECK_ARG(commands != NULL);
GS_CHECK_ARG(count > 0);
gs_error_t error = GS_OK;
gs_lock_lock();
{
// check if command block already installed
gs_command_block_t * last_block = NULL;
for (gs_command_block_t * block = &g_commands; block; block = block->next) {
if (block->commands) {
const gs_command_t * cmd = block->commands;
// loop through because it may be in the linked blocks
for (size_t i = 0; i < block->count; ++i, ++cmd) {
if (cmd == commands) {
error = GS_ERROR_EXIST;
break;
}
}
}
last_block = block;
}
if (error == GS_OK) {
gs_command_block_t * block = calloc(1, sizeof(*block));
if (block) {
// Insert command last, so lock isn't needed when accessing commands
block->commands = commands;
block->count = count;
block->next = NULL;
last_block->next = block;
} else {
error = GS_ERROR_ALLOC;
}
}
}
gs_lock_unlock();
return (error != GS_ERROR_EXIST) ? error : GS_OK;
}
size_t gs_command_get_stack_size(void)
{
return g_stack_size;
}
gs_error_t gs_command_init_no_commands(size_t stack_size)
{
g_stack_size = stack_size;
gs_error_t error = gs_lock_init();
if (error) {
return error;
}
gs_log_group_register(gs_command_log);
#if (__linux__ == 0)
// look up static linked commands - only embedded (= none linux) systems
gs_command_block_t * block = &g_commands;
extern volatile unsigned int __command_start __attribute__ ((__weak__));
extern volatile unsigned int __command_end __attribute__ ((__weak__));
if (&__command_start) {
block->count = ((ptrdiff_t)&__command_end - (ptrdiff_t)&__command_start) / sizeof(gs_command_t);
block->commands = (gs_command_t *) &__command_start;
}
#endif
return GS_OK;
}
gs_error_t gs_command_init(size_t stack_size)
{
gs_error_t error = gs_command_init_no_commands(stack_size);
if (error == GS_OK) {
// register default commands
gs_command_register_default_commands();
gs_log_register_commands();
}
return error;
}
gs_error_t gs_command_logger_default(const char* cmd_line, gs_error_t ret, gs_error_t cmd_ret, gs_timestamp_t t_start, gs_timestamp_t t_end, void *log_ctx)
{
(void)log_ctx;
timestamp_diff(&t_end, &t_start);
if (ret == GS_OK) {
log_info_group(gs_command_log, "'%s' returned '%s' ["
"t: <%04"PRIu32".%06"PRIu32">, dt: <%01"PRIu32".%06"PRIu32">]",
cmd_line, gs_error_string(cmd_ret),
t_start.tv_sec, t_start.tv_nsec/1000, t_end.tv_sec, t_end.tv_nsec/1000);
} else {
log_info_group(gs_command_log, "'%s' could not be run, returned '%s' ["
"t: <%04"PRIu32".%06"PRIu32">]",
cmd_line, gs_error_string(ret),
t_start.tv_sec, t_start.tv_nsec/1000);
}
return GS_OK;
}
gs_error_t gs_command_register_logger(gs_command_log_t log_cb, void *log_ctx)
{
gs_lock_lock();
g_command_logger = log_cb;
g_command_logger_ctx = log_ctx;
gs_lock_unlock();
return GS_OK;
}

View File

@ -0,0 +1,35 @@
/* Copyright (c) 2013-2018 GomSpace A/S. All rights reserved. */
#include <gs/util/gosh/command.h>
/**
Command I/O function - flush stdout.
*/
gs_error_t gs_command_stdio_flush(gs_command_context_t *ctx);
/**
Command I/O function - wait for a key.
*/
gs_error_t gs_command_stdio_wait_for_key(gs_command_context_t *ctx, int *ch, int timeout_ms);
/**
Complete command.
@param[in] line command line to complete
@param[in] max \a length (size)
@param[in] out output stream, e.g. stdout
*/
gs_error_t gs_command_complete(char *line, size_t max_line_length, FILE* out);
/**
Show help.
@param line command line to show help for.
@param out output stream, e.g. stdout
*/
gs_error_t gs_command_show_help(const char * command, FILE * out);
/**
Change console mode.
@param[in] mode console mode, 'cci'
@return_gs_error_t
*/
int gs_console_change_mode(const char * mode);

View File

@ -0,0 +1,758 @@
/* Copyright (c) 2013-2017 GomSpace A/S. All rights reserved. */
/**
@file
The console interface provides support for executing commands over stdout (typically a serial port).
The connection can run in 2 modes:
- normal, standard GOSH interface (Human Machine Interface), echo characters, prompt, etc.
- cci, Computer Computer Interface. Simple text interface, but with tagged output format - easier to parse by a computer.
*/
#include "console_local.h"
#include "command_local.h"
#include <gs/util/string.h>
#include <gs/util/stdio.h>
#include <gs/util/thread.h>
#include <gs/util/mutex.h>
#include <gs/util/time.h>
#include <gs/util/log/appender/console.h>
#include <stdlib.h>
#include <ctype.h>
#include <conf_util.h> // console defines set through Waf options
#if (__linux__)
#include <gs/util/linux/signal.h>
#include <gs/util/linux/command_line.h>
#include <unistd.h>
#include <termios.h>
#endif
/* Max history length (elements) */
#ifndef GS_CONSOLE_HISTORY_LEN
#define GS_CONSOLE_HISTORY_LEN 10
#endif
/* Max input length */
#ifndef GS_CONSOLE_INPUT_LEN
#define GS_CONSOLE_INPUT_LEN 100
#endif
#define CONTROL(X) ((X) - '@')
typedef enum {
CONSOLE_NORMAL = 0,
CONSOLE_ESCAPE = 1,
CONSOLE_PRE_ESCAPE = 2,
} console_escape_t;
static const char hash_prompt[] = "\033[1;30m # ";
static const char backspace_char = '\b';
static const char space_char = ' ';
static const char cr_char = '\r';
static const char nl_char = '\n';
static const char * user_prompt = "gosh";
static console_escape_t escape = CONSOLE_NORMAL;
#if (GS_CONSOLE_HISTORY_LEN > 0)
static int history_elements;
static int history_cur;
static int history_browse;
static char history[GS_CONSOLE_HISTORY_LEN][GS_CONSOLE_INPUT_LEN+1];
#endif
static int size;
static int pos;
static char buf[GS_CONSOLE_INPUT_LEN+1];
static gs_thread_t console_thread;
#if (__linux__)
static bool termios_changed;
static struct termios old_stdin;
static struct termios old_stdout;
#endif
static gs_mutex_t g_cci_lock; // Lock for protecting stdout for async output, e.g. log messages
static gs_error_t command_io_cci_set_result(gs_command_context_t *ctx, const char *group, const char *key, const char *value);
static const gs_command_io_functions_t cci_io_functions = {
.set_result = command_io_cci_set_result,
.flush = gs_command_stdio_flush,
.wait_for_key = gs_command_stdio_wait_for_key,
};
#define CCI_START_TAG "[X["
#define CCI_END_TAG "]X]"
static void gs_console_write(const char *str, int length)
{
for (int i = 0; i < length; i++) {
putchar(str[i]);
}
}
static void gs_console_prompt(void)
{
static const char col_start[] = "\033[1;32m";
static const char col_end[] = "\033[0m";
gs_console_write(col_start, sizeof(col_start) - 1);
gs_console_write(user_prompt, strlen(user_prompt));
gs_console_write(hash_prompt, sizeof(hash_prompt) - 1);
gs_console_write(col_end, sizeof(col_end) - 1);
}
void gs_console_set_prompt(const char * _prompt)
{
if (gs_string_empty(_prompt) == false) {
user_prompt = _prompt;
}
}
static void gs_console_reset(void)
{
pos = size = 0;
buf[pos] = 0;
gs_console_prompt();
}
static void gs_console_rewind(void)
{
int plen = strlen(hash_prompt) + strlen(user_prompt);
gs_console_write(&cr_char, 1);
while (size-- + plen) {
gs_console_write(&space_char, 1);
}
pos = size = 0;
gs_console_write(&cr_char, 1);
}
void gs_console_clear(void)
{
static const char clear[] = "\033[H\033[2J";
gs_console_write(clear, sizeof(clear) - 1);
gs_console_rewind();
gs_console_reset();
}
void gs_console_update(void)
{
gs_console_rewind();
gs_console_prompt();
pos = size = strlen(buf);
gs_console_write(buf, size);
}
#if (GS_CONSOLE_HISTORY_LEN > 0)
static void gs_console_history_add(void)
{
strncpy(history[history_cur], buf, GS_CONSOLE_INPUT_LEN);
history[history_cur][GS_CONSOLE_INPUT_LEN] = 0;
history_browse = 0;
history_cur = (history_cur + 1) % GS_CONSOLE_HISTORY_LEN;
if (history_elements < GS_CONSOLE_HISTORY_LEN) {
history_elements++;
}
}
static void gs_console_last_line(void)
{
if (history_elements < 1) {
return;
}
if (history_browse >= history_elements) {
return;
}
gs_console_rewind();
history_browse++;
strcpy(buf, history[(history_cur - history_browse + GS_CONSOLE_HISTORY_LEN) % GS_CONSOLE_HISTORY_LEN]);
gs_console_update();
}
static void gs_console_next_line(void)
{
if (history_elements < 1) {
return;
}
if (history_browse < 1) {
return;
}
gs_console_rewind();
history_browse--;
if (history_browse > 0) {
strcpy(buf, history[(history_cur - history_browse + GS_CONSOLE_HISTORY_LEN) % GS_CONSOLE_HISTORY_LEN]);
} else {
buf[0] = '\0';
}
gs_console_update();
}
#endif
static void gs_console_forward_char(void)
{
if (pos < size) {
gs_console_write(&buf[pos], 1);
pos++;
}
}
static void gs_console_end_of_line(void)
{
while (pos < size) {
gs_console_forward_char();
}
}
static void gs_console_backward_char(void)
{
if (pos > 0) {
pos--;
gs_console_write(&backspace_char, 1);
}
}
static void gs_console_beginning_of_line(void)
{
while (pos) {
gs_console_backward_char();
}
}
static void gs_console_newline(void)
{
gs_console_write(&cr_char, 1);
gs_console_write(&nl_char, 1);
}
static bool gs_command_not_empty(const char *ibuf)
{
while (*ibuf) {
if (!isblank((int) *ibuf++)) {
return true;
}
}
return false;
}
static void show_help(const char * command)
{
gs_error_t error = gs_command_show_help(command, stdout);
if (error) {
printf("Could not show help for \'%s\': %s (%d)\r\n", command, gs_error_string(error), error);
}
}
static void gs_console_execute(void)
{
gs_console_newline();
buf[GS_CONSOLE_INPUT_LEN] = 0; // ensure 0 termination
if (size > 0 && gs_command_not_empty(buf)) {
#if (GS_CONSOLE_HISTORY_LEN > 0)
gs_console_history_add();
#endif
gs_error_t result = GS_OK;
gs_error_t error = gs_command_execute_stdio(buf, &result);
if (error == GS_ERROR_TYPE) {
show_help(buf);
} else if (error == GS_ERROR_NOT_FOUND) {
printf("Unknown command \'%s\'\r\n", buf);
} else if (error == GS_ERROR_ARG) {
show_help(buf);
} else if (error) {
printf("Command \'%s\' did not execute: %s (%d)\r\n", buf, gs_error_string(error), error);
} else if (result == GS_ERROR_ARG) {
show_help(buf);
} else if (result) {
printf("Command \'%s\' executed, but returned error: %s (%d)\r\n", buf, gs_error_string(result), result);
}
}
gs_console_reset();
}
static void gs_console_complete(void)
{
/* We don't expand in the middle of a line */
if (size != pos) {
return;
}
const size_t old_buf_len = strlen(buf);
gs_error_t ret = gs_command_complete(buf, sizeof(buf), stdout);
if ((ret == GS_OK) && (old_buf_len == strlen(buf))) {
// completed (again) and no change - show help
ret = GS_ERROR_AMBIGUOUS;
}
switch (ret) {
case GS_ERROR_AMBIGUOUS:
gs_console_newline();
show_help(buf);
gs_console_update();
break;
case GS_OK:
gs_console_update();
break;
default:
case GS_ERROR_NOT_FOUND:
break;
}
}
static void gs_console_insert(char c)
{
int i;
int diff = size - pos;
if (size >= GS_CONSOLE_INPUT_LEN) {
return;
}
memmove(&buf[pos + 1], &buf[pos], diff);
buf[pos] = c;
gs_console_write(&buf[pos], diff + 1);
for (i = 0; i < diff; i++) {
gs_console_write(&backspace_char, 1);
}
size++;
pos++;
buf[size] = '\0';
}
static void gs_console_insert_overwrite(char c)
{
buf[pos++] = c;
if (pos > size) {
size++;
}
gs_console_write(&c, 1);
}
static void gs_console_delete(void)
{
int i;
int diff = size - pos;
/* Nothing to delete */
if (size == pos) {
return;
}
size--;
memmove(&buf[pos], &buf[pos + 1], diff - 1);
buf[size] = '\0';
gs_console_write(&buf[pos], diff - 1);
gs_console_write(&space_char, 1);
for (i = 0; i < diff; i++) {
gs_console_write(&backspace_char, 1);
}
}
static void gs_console_backspace(void)
{
if (pos < 1) {
return;
}
gs_console_backward_char();
gs_console_delete();
}
static void gs_console_kill_line(void)
{
int i;
int diff;
diff = size - pos;
if (diff == 0) {
return;
}
for (i = 0; i < diff; i++) {
gs_console_write(&space_char, 1);
}
for (i = 0; i < diff; i++) {
gs_console_write(&backspace_char, 1);
}
memset(&buf[pos], 0, diff);
size = pos;
}
static void gs_console_kill_line_from_beginning(void)
{
gs_console_beginning_of_line();
gs_console_kill_line();
}
static void gs_console_backward_kill_word(void)
{
while (pos > 0 && buf[pos - 1] == ' ') {
gs_console_backspace();
}
while (pos > 0 && buf[pos - 1] != ' ') {
gs_console_backspace();
}
}
static void gs_console_transpose_chars(void)
{
char c1, c2;
if (size < 2 || pos < 1) {
return;
}
if (pos == size) {
c1 = buf[pos - 1];
c2 = buf[pos - 2];
gs_console_backward_char();
gs_console_backward_char();
gs_console_insert_overwrite(c1);
gs_console_insert_overwrite(c2);
} else {
c1 = buf[pos];
c2 = buf[pos - 1];
gs_console_backward_char();
gs_console_insert_overwrite(c1);
gs_console_insert_overwrite(c2);
}
}
static void gs_console_normal(char c)
{
switch (c) {
case CONTROL('A'):
gs_console_beginning_of_line();
break;
case CONTROL('B'):
gs_console_backward_char();
break;
case CONTROL('C'):
// Either ignored or handled through signals
break;
case CONTROL('D'):
gs_console_delete();
break;
case CONTROL('E'):
gs_console_end_of_line();
break;
case CONTROL('F'):
gs_console_forward_char();
break;
case CONTROL('K'):
gs_console_kill_line();
break;
case CONTROL('L'):
gs_console_clear();
break;
#if (GS_CONSOLE_HISTORY_LEN > 0)
case CONTROL('N'):
gs_console_next_line();
break;
case CONTROL('P'):
gs_console_last_line();
break;
#endif
case CONTROL('T'):
gs_console_transpose_chars();
break;
case CONTROL('U'):
gs_console_kill_line_from_beginning();
break;
case CONTROL('W'):
gs_console_backward_kill_word();
break;
case CONTROL('Z'):
// We cannot suspend
break;
case CONTROL('H'):
case 0x7f:
gs_console_backspace();
break;
case '\r':
case '\n':
gs_console_execute();
break;
case '\t':
gs_console_complete();
break;
case '\033':
escape = CONSOLE_ESCAPE;
break;
default:
if (escape == CONSOLE_ESCAPE) {
if ((c == '[') || (c == 'O')) {
c = getchar();
if (c == 'F')
gs_console_end_of_line();
if (c == 'H')
gs_console_beginning_of_line();
#if (GS_CONSOLE_HISTORY_LEN > 0)
if (c == 'A')
gs_console_last_line();
if (c == 'B')
gs_console_next_line();
#endif
if (c == 'C')
gs_console_forward_char();
if (c == 'D')
gs_console_backward_char();
if (c == '1')
if (getchar() == '~')
gs_console_beginning_of_line();
if (c == '3')
if (getchar() == '~')
gs_console_delete();
}
escape = CONSOLE_NORMAL;
break;
}
if (isprint((unsigned char) c)) {
gs_console_insert(c);
}
break;
}
}
static gs_error_t command_io_cci_set_result(gs_command_context_t *ctx, const char *group, const char *key, const char *value)
{
gs_mutex_lock(g_cci_lock);
{
printf(CCI_START_TAG "cmd_res,%s,%s,%s" CCI_END_TAG, group, key, value);
}
gs_mutex_unlock(g_cci_lock);
return GS_OK;
}
static void gs_console_cci_log(gs_log_appender_t *appender, gs_log_level_t level, const gs_log_group_t *group, const gs_timestamp_t * ts, const char * format, va_list va)
{
va_list my_va;
va_copy(my_va, va);
gs_mutex_lock(g_cci_lock);
{
printf(CCI_START_TAG "log,%04"PRIu32".%06"PRIu32",%c,%s,", ts->tv_sec, ts->tv_nsec / 1000, gs_log_level_to_char(level), group->name);
vprintf(format, my_va);
printf(CCI_END_TAG "\r\n");
}
gs_mutex_unlock(g_cci_lock);
va_end(my_va);
}
static void gs_console_cci(char c)
{
switch (c) {
case CONTROL('C'):
case CONTROL('L'):
size = 0;
buf[0] = 0;
break;
case '\r':
case '\n':
buf[GS_CONSOLE_INPUT_LEN] = 0; // ensure 0 termination
if (size > 0 && gs_command_not_empty(buf)) {
static unsigned int seq; // simple sequence number keep incrementing
gs_mutex_lock(g_cci_lock);
++seq;
printf(CCI_START_TAG "cmd_exec_begin,%u,%s" CCI_END_TAG "\r\n", seq, buf);
gs_mutex_unlock(g_cci_lock);
gs_error_t result = GS_OK;
gs_error_t error = gs_command_execute(buf, &result, stdout, &cci_io_functions, NULL);
gs_mutex_lock(g_cci_lock);
printf(CCI_START_TAG "cmd_exec_end,%u,%d,%d" CCI_END_TAG "\r\n", seq, error, result);
gs_mutex_unlock(g_cci_lock);
}
size = 0;
buf[0] = 0;
break;
default:
if (isprint((unsigned char) c) && (size < GS_CONSOLE_INPUT_LEN)) {
buf[size++] = c;
buf[size] = 0;
}
break;
}
}
// Currrent mode handler, switch by sending command
static void (*console_handler)(char c) = gs_console_normal;
int gs_console_change_mode(const char * mode)
{
if (strcasecmp(mode, "cci") == 0) {
gs_error_t error = GS_OK;
if (console_handler != gs_console_cci) {
error = gs_mutex_create(&g_cci_lock);
if (error == GS_OK) {
gs_log_appender_console_set_cb(gs_console_cci_log);
console_handler = gs_console_cci; // change console handler
}
}
return error;
}
return GS_ERROR_NOT_SUPPORTED;
}
static void * gs_console_thread(void * param)
{
gs_console_reset();
while (1) {
char c = getchar();
console_handler(c);
}
gs_thread_exit(NULL);
}
gs_error_t gs_console_exit(void)
{
#if (__linux__)
if (termios_changed) {
tcsetattr(STDIN_FILENO, TCSANOW, &old_stdin);
tcsetattr(STDOUT_FILENO, TCSANOW, &old_stdout);
}
#endif
return GS_OK;
}
#if (__linux__)
static inline void exithandler(void)
{
printf("\n");
gs_console_exit();
}
#endif
static gs_error_t gs_console_init2(uint32_t flags)
{
#if (__linux__)
// save current stdio setting, for restoring when terminating process
tcgetattr(STDIN_FILENO, &old_stdin);
tcgetattr(STDOUT_FILENO, &old_stdout);
// change stdin settings
{
struct termios new = old_stdin;
new.c_iflag &= ~(IGNCR | ICRNL);
new.c_lflag &= ~(ECHO | ICANON | IEXTEN);
new.c_cc[VTIME]=0;
new.c_cc[VMIN]=1;
tcsetattr(STDIN_FILENO, TCSANOW, &new);
}
// change stdout settings
{
struct termios new = old_stdout;
new.c_iflag &= ~(IGNCR | ICRNL);
new.c_lflag &= ~(ECHO | ICANON | IEXTEN);
new.c_cc[VTIME]=0;
new.c_cc[VMIN]=1;
tcsetattr(STDOUT_FILENO, TCSANOW, &new);
}
termios_changed = true;
// add exit-handler to restore original termianl settings
atexit(exithandler);
// install signal handlers to ensure terminal settings are restored
if ((flags & GS_CONSOLE_F_NO_SIGNAL_HANDLER) == 0) {
// install signal handler(s) to ensure atexit() is called
gs_signal_catch(SIGTERM, NULL);
if (gs_command_line_ignore_ctrlc() == false) {
gs_signal_catch(SIGINT, NULL);
}
}
#endif
#if (__AVR__ == 0)
/** This is very important on AVR32 */
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
#endif
return GS_OK;
}
gs_error_t gs_console_init()
{
return gs_console_init2(0);
}
static gs_error_t _console_create_thread(gs_thread_priority_t priority, gs_thread_t * handle, uint32_t thread_create_flags)
{
gs_error_t error = gs_thread_create("CONSOLE",
gs_console_thread, NULL,
gs_command_get_stack_size(),
priority,
thread_create_flags,
handle);
if (error == GS_OK) {
// give thread a few moments to print prompt
gs_time_sleep_ms(20);
}
return error;
}
gs_error_t gs_console_create_thread(gs_thread_t * handle)
{
return _console_create_thread(GS_THREAD_PRIORITY_LOW, handle, 0);
}
gs_error_t gs_console_create_thread_with_priority(gs_thread_priority_t priority, gs_thread_t * handle)
{
return _console_create_thread(priority, handle, 0);
}
gs_error_t gs_console_start(const char * prompt, uint32_t flags)
{
if (console_thread) {
return GS_ERROR_EXIST;
}
gs_console_init2(flags);
gs_console_set_prompt(prompt);
return _console_create_thread(GS_THREAD_PRIORITY_LOW, &console_thread, GS_THREAD_CREATE_JOINABLE);
}
gs_error_t gs_console_stop(void)
{
if (console_thread == 0) {
return GS_ERROR_HANDLE;
}
#if (__linux__)
if (pthread_cancel(console_thread) != 0) {
return GS_ERROR_IO;
}
gs_error_t error = gs_thread_join(console_thread, NULL);
if (error == GS_OK) {
console_thread = 0;
}
return error;
#else
return GS_ERROR_NOT_SUPPORTED;
#endif
}

View File

@ -0,0 +1,10 @@
/* Copyright (c) 2013-2018 GomSpace A/S. All rights reserved. */
#include <gs/util/gosh/console.h>
/**
Change console mode.
@param[in] mode console mode, 'rgosh', 'normal'
@return_gs_error_t
*/
int gs_console_change_mode(const char * mode);

View File

@ -0,0 +1,277 @@
/* Copyright (c) 2013-2017 GomSpace A/S. All rights reserved. */
#include "command_local.h"
#include "console_local.h"
#include <stdlib.h>
#if defined(__linux__)
#include <unistd.h>
#include <termios.h>
#endif
#include <gs/util/stdio.h>
#include <gs/util/clock.h>
#include <gs/util/time.h>
#include <gs/util/string.h>
#include <conf_util.h>
static int cmd_help(gs_command_context_t * context)
{
return gs_command_show_help(gs_command_args(context), context->out);
}
static int cmd_sleep(gs_command_context_t * context)
{
uint32_t sleep_ms;
gs_error_t error = gs_string_to_uint32(context->argv[1], &sleep_ms);
if (error) {
return error;
}
gs_time_sleep_ms(sleep_ms);
return GS_OK;
}
static int cmd_watch(gs_command_context_t * context, bool check_error)
{
uint32_t sleep_ms;
gs_error_t error = gs_string_to_uint32(context->argv[1], &sleep_ms);
if (error) {
return error;
}
fprintf(context->out, "Execution delay: %" PRIu32 "\r\n", sleep_ms);
char * new_command = strstr(gs_command_args(context), " ");
if (new_command == NULL) {
return GS_ERROR_ARG;
} else {
new_command = new_command + 1;
}
fprintf(context->out, "Command: %s\r\n", new_command);
while(1) {
gs_error_t cmd_result;
error = gs_command_execute(new_command, &cmd_result, context->out, context->io_functions, context->io_ctx);
if (error) {
return error;
}
if (check_error && cmd_result) {
return cmd_result;
}
if (gs_stdio_getchar_timed(sleep_ms, NULL) != GS_ERROR_TIMEOUT) {
break;
}
}
return GS_OK;
}
static int cmd_watch_nocheck(gs_command_context_t * context)
{
return cmd_watch(context, false);
}
static int cmd_watch_check(gs_command_context_t * context)
{
return cmd_watch(context, true);
}
#define CONTROL(X) ((X) - '@')
static int cmd_batch(gs_command_context_t * ctx)
{
char c;
int quit = 0, execute = 0;
unsigned int batch_size = 100;
unsigned int batch_input = 0;
unsigned int batch_count = 0;
char * batch[20] = {};
printf("Type each command followed by enter, hit ctrl+e to end typing, ctrl+x to cancel:\r\n");
/* Wait for ^q to quit. */
while (quit == 0) {
/* Get character */
c = getchar();
switch (c) {
/* CTRL + X */
case 0x18:
quit = 1;
break;
/* CTRL + E */
case 0x05:
execute = 1;
quit = 1;
break;
/* Backspace */
case CONTROL('H'):
case 0x7f:
if (batch_input > 0) {
putchar('\b');
putchar(' ');
putchar('\b');
batch_input--;
}
break;
case '\r':
putchar('\r');
putchar('\n');
if ((batch[batch_count] != NULL) && (batch_input < batch_size))
batch[batch_count][batch_input++] = '\r';
if ((batch[batch_count] != NULL) && (batch_input < batch_size))
batch[batch_count][batch_input++] = '\0';
batch_count++;
batch_input = 0;
if (batch_count == 20)
quit = 1;
break;
default:
putchar(c);
if (batch[batch_count] == NULL) {
batch[batch_count] = calloc(GS_CONSOLE_INPUT_LEN+1, 1);
}
if ((batch[batch_count] != NULL) && (batch_input < batch_size))
batch[batch_count][batch_input++] = c;
break;
}
}
if (execute) {
printf("\r\n");
for (unsigned int i = 0; i <= batch_count; i++) {
if (batch[i])
printf("[%02u] %s\r\n", i, batch[i]);
}
printf("Press ctrl+e to execute, or any key to abort\r\n");
c = getchar();
if (c != 0x05)
execute = 0;
}
/* Run/Free batch job */
for (unsigned int i = 0; i <= batch_count; i++) {
if (execute && batch[i]) {
printf("EXEC [%02u] %s\r\n", i, batch[i]);
gs_command_run(batch[i], NULL);
}
free(batch[i]);
}
return GS_OK;
}
#if defined(__linux__)
static int cmd_exit(gs_command_context_t * context)
{
gs_console_exit();
exit(EXIT_SUCCESS);
return GS_OK;
}
#endif
static int cmd_clock(gs_command_context_t * ctx)
{
if (ctx->argc > 1) {
gs_timestamp_t ts;
gs_error_t error = gs_clock_from_string(ctx->argv[1], &ts);
if (error) {
return GS_ERROR_ARG;
}
error = gs_clock_set_time(&ts);
if (error) {
fprintf(ctx->out, "Failed to set time, error=%s\r\n", gs_error_string(error));
return GS_ERROR_DATA;
}
}
timestamp_t clock;
gs_clock_get_monotonic(&clock);
fprintf(ctx->out, "monotonic: %10"PRIu32".%09"PRIu32" sec\r\n", clock.tv_sec, clock.tv_nsec);
gs_command_set_output_printf(ctx, "", "monotonic", "%10"PRIu32".%09"PRIu32"", clock.tv_sec, clock.tv_nsec);
char tbuf[25];
gs_clock_get_time(&clock);
gs_clock_to_iso8601_string(&clock, tbuf, sizeof(tbuf));
fprintf(ctx->out, "realtime: %10"PRIu32".%09"PRIu32" sec -> %s\r\n", clock.tv_sec, clock.tv_nsec, tbuf);
gs_command_set_output_printf(ctx, "", "realtime", "%10"PRIu32".%09"PRIu32"", clock.tv_sec, clock.tv_nsec);
return GS_OK;
}
static int cmd_console_mode(gs_command_context_t * ctx)
{
return gs_console_change_mode(ctx->argv[1]);
}
static const gs_command_t GS_COMMAND_ROOT cmd_default[] = {
{
.name = "help",
.help = "Show help",
.usage = "[command[ subcommand[ arg ...]]]",
.handler = cmd_help,
.optional_args = 100,
},{
.name = "sleep",
.help = "Sleep X ms",
.usage = "<mS>",
.handler = cmd_sleep,
.mandatory_args = 1,
},{
.name = "watch",
.help = "Run commands at intervals (abort with key)",
.usage = "<interval mS> <command> [arg ...]",
.handler = cmd_watch_nocheck,
.mandatory_args = 2,
.optional_args = 100,
},{
.name = "watch_check",
.help = "Like 'watch', but abort if command fails",
.usage = "<interval mS> <command [arg ...]>",
.handler = cmd_watch_check,
.mandatory_args = 2,
.optional_args = 100,
},{
.name = "batch",
.help = "Run multiple commands",
.handler = cmd_batch,
.mode = GS_COMMAND_FLAG_HIDDEN,
},{
.name = "clock",
.help = "Get/set system clock",
.usage = "[<sec.nsec> | <YYYY-MM-DDTHH:MM:SSZ>]",
.handler = cmd_clock,
.optional_args = 1,
},{
.name = "console_mode",
.help = "Console mode(s): cci",
.usage = "<mode>",
.handler = cmd_console_mode,
.mode = GS_COMMAND_FLAG_HIDDEN,
.mandatory_args = 1,
},
#if defined(__linux__)
{
.name = "exit",
.help = "Exit program",
.handler = cmd_exit,
},
#endif
};
gs_error_t gs_command_register_default_commands(void)
{
return GS_COMMAND_REGISTER(cmd_default);
}

View File

@ -0,0 +1,55 @@
/* Copyright (c) 2013-2017 GomSpace A/S. All rights reserved. */
#include <gs/util/gosh/command.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int gs_command_getopt(gs_command_context_t *ctx, const char *opts)
{
int c;
char *cp;
if (ctx->optsp == 1) {
if (ctx->optind >= ctx->argc ||
ctx->argv[ctx->optind][0] != '-' ||
ctx->argv[ctx->optind][1] == '\0') {
return EOF;
} else if (!strcmp(ctx->argv[ctx->optind], "--")) {
ctx->optind++;
return EOF;
}
}
ctx->optopt = c = ctx->argv[ctx->optind][ctx->optsp];
if (c == ':' || (cp = strchr(opts, c)) == NULL) {
printf("illegal option -- %c\r\n", c);
if (ctx->argv[ctx->optind][++ctx->optsp] == '\0') {
ctx->optind++;
ctx->optsp = 1;
}
return '?';
}
if (*++cp == ':') {
if (ctx->argv[ctx->optind][ctx->optsp+1] != '\0') {
ctx->optarg = &ctx->argv[ctx->optind++][ctx->optsp+1];
} else if(++ctx->optind >= ctx->argc) {
printf("option requires an argument -- %c\r\n", c);
ctx->optsp = 1;
return '?';
} else {
ctx->optarg = ctx->argv[ctx->optind++];
}
ctx->optsp = 1;
} else {
if (ctx->argv[ctx->optind][++ctx->optsp] == '\0') {
ctx->optsp = 1;
ctx->optind++;
}
ctx->optarg = NULL;
}
return c;
}