add jim_handler to command_registration
[fw/openocd] / src / helper / command.c
index 7a42ab20fbfaca4c887e12d0248d1c142dc34427..3cb36ea27590850c4f18131f10d265604b6af7d4 100644 (file)
 #include "jim-eventloop.h"
 
 
-int fast_and_dangerous = 0;
 Jim_Interp *interp = NULL;
 
-static int run_command(command_context_t *context,
-               command_t *c, const char *words[], unsigned num_words);
+static int run_command(struct command_context *context,
+               struct command *c, const char *words[], unsigned num_words);
 
 static void tcl_output(void *privData, const char *file, unsigned line,
                const char *function, const char *string)
@@ -57,7 +56,7 @@ static void tcl_output(void *privData, const char *file, unsigned line,
        Jim_AppendString(interp, tclOutput, string, strlen(string));
 }
 
-extern command_context_t *global_cmd_ctx;
+extern struct command_context *global_cmd_ctx;
 
 void script_debug(Jim_Interp *interp, const char *name,
                unsigned argc, Jim_Obj *const *argv)
@@ -76,15 +75,58 @@ void script_debug(Jim_Interp *interp, const char *name,
        }
 }
 
+static void script_command_args_free(const char **words, unsigned nwords)
+{
+       for (unsigned i = 0; i < nwords; i++)
+               free((void *)words[i]);
+       free(words);
+}
+static const char **script_command_args_alloc(
+               unsigned argc, Jim_Obj *const *argv, unsigned *nwords)
+{
+       const char **words = malloc(argc * sizeof(char *));
+       if (NULL == words)
+               return NULL;
+
+       unsigned i;
+       for (i = 0; i < argc; i++)
+       {
+               int len;
+               const char *w = Jim_GetString(argv[i], &len);
+               /* a comment may end the line early */
+               if (*w == '#')
+                       break;
+
+               words[i] = strdup(w);
+               if (words[i] == NULL)
+               {
+                       script_command_args_free(words, i);
+                       return NULL;
+               }
+       }
+       *nwords = i;
+       return words;
+}
+
+static struct command_context *current_command_context(void)
+{
+       /* grab the command context from the associated data */
+       struct command_context *cmd_ctx = Jim_GetAssocData(interp, "context");
+       if (NULL == cmd_ctx)
+       {
+               /* Tcl can invoke commands directly instead of via command_run_line(). This would
+                * happen when the Jim Tcl interpreter is provided by eCos.
+                */
+               cmd_ctx = global_cmd_ctx;
+       }
+       return cmd_ctx;
+}
+
 static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
        /* the private data is stashed in the interp structure */
-       command_t *c;
-       command_context_t *context;
+       struct command *c;
        int retval;
-       int i;
-       int nwords;
-       char **words;
 
        /* DANGER!!!! be careful what we invoke here, since interp->cmdPrivData might
         * get overwritten by running other Jim commands! Treat it as an
@@ -102,37 +144,10 @@ static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 
        script_debug(interp, c->name, argc, argv);
 
-       words = malloc(sizeof(char *) * argc);
-       for (i = 0; i < argc; i++)
-       {
-               int len;
-               const char *w = Jim_GetString(argv[i], &len);
-               if (*w=='#')
-               {
-                       /* hit an end of line comment */
-                       break;
-               }
-               words[i] = strdup(w);
-               if (words[i] == NULL)
-               {
-                       int j;
-                       for (j = 0; j < i; j++)
-                               free(words[j]);
-                       free(words);
-                       return JIM_ERR;
-               }
-       }
-       nwords = i;
-
-       /* grab the command context from the associated data */
-       context = Jim_GetAssocData(interp, "context");
-       if (context == NULL)
-       {
-               /* Tcl can invoke commands directly instead of via command_run_line(). This would
-                * happen when the Jim Tcl interpreter is provided by eCos.
-                */
-               context = global_cmd_ctx;
-       }
+       unsigned nwords;
+       const char **words = script_command_args_alloc(argc, argv, &nwords);
+       if (NULL == words)
+               return JIM_ERR;
 
        /* capture log output and return it */
        Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
@@ -141,7 +156,8 @@ static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 
        log_add_callback(tcl_output, tclOutput);
 
-       retval = run_command(context, c, (const char **)words, nwords);
+       struct command_context *cmd_ctx = current_command_context();
+       retval = run_command(cmd_ctx, c, (const char **)words, nwords);
 
        log_remove_callback(tcl_output, tclOutput);
 
@@ -149,9 +165,7 @@ static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
        Jim_SetResult(interp, tclOutput);
        Jim_DecrRefCount(interp, tclOutput);
 
-       for (i = 0; i < nwords; i++)
-               free(words[i]);
-       free(words);
+       script_command_args_free(words, nwords);
 
        int *return_retval = Jim_GetAssocData(interp, "retval");
        if (return_retval != NULL)
@@ -162,56 +176,41 @@ static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
        return (retval == ERROR_OK)?JIM_OK:JIM_ERR;
 }
 
-static Jim_Obj *command_name_list(struct command_s *c)
-{
-       Jim_Obj *cmd_list = c->parent ?
-                       command_name_list(c->parent) :
-                       Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_list,
-                       Jim_NewStringObj(interp, c->name, -1));
-
-       return cmd_list;
-}
-
-static void command_helptext_add(Jim_Obj *cmd_list, const char *help)
-{
-       Jim_Obj *cmd_entry = Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_entry, cmd_list);
-       Jim_ListAppendElement(interp, cmd_entry,
-                       Jim_NewStringObj(interp, help ? : "", -1));
-
-       /* accumulate help text in Tcl helptext list.  */
-       Jim_Obj *helptext = Jim_GetGlobalVariableStr(interp,
-                       "ocd_helptext", JIM_ERRMSG);
-       if (Jim_IsShared(helptext))
-               helptext = Jim_DuplicateObj(interp, helptext);
-       Jim_ListAppendElement(interp, helptext, cmd_entry);
-}
-
 /* nice short description of source file */
 #define __THIS__FILE__ "command.c"
 
 /**
  * Find a command by name from a list of commands.
- * @returns The named command if found, or NULL.
+ * @returns Returns the named command if it exists in the list.
+ * Returns NULL otherwise.
  */
-static struct command_s *command_find(struct command_s **head, const char *name)
+static struct command *command_find(struct command *head, const char *name)
 {
-       assert(head);
-       for (struct command_s *cc = *head; cc; cc = cc->next)
+       for (struct command *cc = head; cc; cc = cc->next)
        {
                if (strcmp(cc->name, name) == 0)
                        return cc;
        }
        return NULL;
 }
+struct command *command_find_in_context(struct command_context *cmd_ctx,
+               const char *name)
+{
+       return command_find(cmd_ctx->commands, name);
+}
+struct command *command_find_in_parent(struct command *parent,
+               const char *name)
+{
+       return command_find(parent->children, name);
+}
 
 /**
- * Add the command to the end of linked list.
- * @returns Returns false if the named command already exists in the list.
- * Returns true otherwise.
+ * Add the command into the linked list, sorted by name.
+ * @param head Address to head of command list pointer, which may be
+ * updated if @c c gets inserted at the beginning of the list.
+ * @param c The command to add to the list pointed to by @c head.
  */
-static void command_add_child(struct command_s **head, struct command_s *c)
+static void command_add_child(struct command **head, struct command *c)
 {
        assert(head);
        if (NULL == *head)
@@ -219,43 +218,79 @@ static void command_add_child(struct command_s **head, struct command_s *c)
                *head = c;
                return;
        }
-       struct command_s *cc = *head;
-       while (cc->next) cc = cc->next;
-       cc->next = c;
+
+       while ((*head)->next && (strcmp(c->name, (*head)->name) > 0))
+               head = &(*head)->next;
+
+       if (strcmp(c->name, (*head)->name) > 0) {
+               c->next = (*head)->next;
+               (*head)->next = c;
+       } else {
+               c->next = *head;
+               *head = c;
+       }
 }
 
-command_t* register_command(command_context_t *context,
-               command_t *parent, char *name, command_handler_t handler,
-               enum command_mode mode, char *help)
+static struct command **command_list_for_parent(
+               struct command_context *cmd_ctx, struct command *parent)
 {
-       if (!context || !name)
-               return NULL;
+       return parent ? &parent->children : &cmd_ctx->commands;
+}
 
-       struct command_s **head = parent ? &parent->children : &context->commands;
-       struct command_s *c = command_find(head, name);
-       if (NULL != c)
-               return c;
+static struct command *command_new(struct command_context *cmd_ctx,
+               struct command *parent, const struct command_registration *cr)
+{
+       assert(cr->name);
 
-       c = malloc(sizeof(command_t));
+       struct command *c = malloc(sizeof(struct command));
+       memset(c, 0, sizeof(struct command));
 
-       c->name = strdup(name);
+       c->name = strdup(cr->name);
+       if (cr->help)
+               c->help = strdup(cr->help);
+       if (cr->usage)
+               c->usage = strdup(cr->usage);
        c->parent = parent;
-       c->children = NULL;
-       c->handler = handler;
-       c->mode = mode;
-       c->next = NULL;
+       c->handler = cr->handler;
+       c->jim_handler = cr->jim_handler;
+       c->jim_handler_data = cr->jim_handler_data;
+       c->mode = cr->mode;
 
-       command_add_child(head, c);
+       command_add_child(command_list_for_parent(cmd_ctx, parent), c);
 
-       command_helptext_add(command_name_list(c), help);
+       return c;
+}
+static void command_free(struct command *c)
+{
+       /// @todo if command has a handler, unregister its jim command!
 
-       /* just a placeholder, no handler */
-       if (c->handler == NULL)
-               return c;
+       while (NULL != c->children)
+       {
+               struct command *tmp = c->children;
+               c->children = tmp->next;
+               command_free(tmp);
+       }
 
+       if (c->name)
+               free(c->name);
+       if (c->help)
+               free((void*)c->help);
+       if (c->usage)
+               free((void*)c->usage);
+       free(c);
+}
+
+static int register_command_handler(struct command *c)
+{
+       int retval = -ENOMEM;
        const char *full_name = command_name(c, '_');
+       if (NULL == full_name)
+               return retval;
 
        const char *ocd_name = alloc_printf("ocd_%s", full_name);
+       if (NULL == full_name)
+               goto free_full_name;
+
        Jim_CreateCommand(interp, ocd_name, script_command, c, NULL);
        free((void *)ocd_name);
 
@@ -264,106 +299,140 @@ command_t* register_command(command_context_t *context,
                        "if {[catch {eval ocd_%s $args}] == 0} "
                        "{return \"\"} else {return -code error}}",
                        full_name, full_name);
+       if (NULL == full_name)
+               goto free_full_name;
+
        Jim_Eval_Named(interp, override_name, __THIS__FILE__, __LINE__);
        free((void *)override_name);
 
-       free((void *)full_name);
+       retval = ERROR_OK;
 
-       return c;
+free_full_name:
+       free((void *)full_name);
+       return retval;
 }
 
-int unregister_all_commands(command_context_t *context)
+struct command* register_command(struct command_context *context,
+               struct command *parent, const struct command_registration *cr)
 {
-       command_t *c, *c2;
+       if (!context || !cr->name)
+               return NULL;
 
-       if (context == NULL)
-               return ERROR_OK;
+       const char *name = cr->name;
+       struct command **head = command_list_for_parent(context, parent);
+       struct command *c = command_find(*head, name);
+       if (NULL != c)
+       {
+               LOG_ERROR("command '%s' is already registered in '%s' context",
+                               name, parent ? parent->name : "<global>");
+               return c;
+       }
+
+       c = command_new(context, parent, cr);
+       if (NULL == c)
+               return NULL;
+
+       if (NULL != c->handler)
+       {
+               int retval = register_command_handler(c);
+               if (ERROR_OK != retval)
+               {
+                       unregister_command(context, parent, name);
+                       return NULL;
+               }
+       }
+
+       if (NULL != cr->jim_handler && NULL == parent)
+               Jim_CreateCommand(interp, cr->name, cr->jim_handler, cr->jim_handler_data, NULL);
 
-       while (NULL != context->commands)
+       return c;
+}
+
+int register_commands(struct command_context *cmd_ctx, struct command *parent,
+               const struct command_registration *cmds)
+{
+       int retval = ERROR_OK;
+       unsigned i;
+       for (i = 0; cmds[i].name || cmds[i].chain; i++)
        {
-               c = context->commands;
+               const struct command_registration *cr = cmds + i;
 
-               while (NULL != c->children)
+               struct command *c = NULL;
+               if (NULL != cr->name)
                {
-                       c2 = c->children;
-                       c->children = c->children->next;
-                       free(c2->name);
-                       c2->name = NULL;
-                       free(c2);
-                       c2 = NULL;
+                       c = register_command(cmd_ctx, parent, cr);
+                       if (NULL == c)
+                       {
+                               retval = ERROR_FAIL;
+                               break;
+                       }
+               }
+               if (NULL != cr->chain)
+               {
+                       struct command *p = c ? : parent;
+                       retval = register_commands(cmd_ctx, p, cr->chain);
+                       if (ERROR_OK != retval)
+                               break;
                }
+       }
+       if (ERROR_OK != retval)
+       {
+               for (unsigned j = 0; j < i; j++)
+                       unregister_command(cmd_ctx, parent, cmds[j].name);
+       }
+       return retval;
+}
 
-               context->commands = context->commands->next;
+int unregister_all_commands(struct command_context *context,
+               struct command *parent)
+{
+       if (context == NULL)
+               return ERROR_OK;
 
-               free(c->name);
-               c->name = NULL;
-               free(c);
-               c = NULL;
+       struct command **head = command_list_for_parent(context, parent);
+       while (NULL != *head)
+       {
+               struct command *tmp = *head;
+               *head = tmp->next;
+               command_free(tmp);
        }
 
        return ERROR_OK;
 }
 
-int unregister_command(command_context_t *context, char *name)
+int unregister_command(struct command_context *context,
+               struct command *parent, const char *name)
 {
-       command_t *c, *p = NULL, *c2;
-
        if ((!context) || (!name))
                return ERROR_INVALID_ARGUMENTS;
 
-       /* find command */
-       c = context->commands;
-
-       while (NULL != c)
+       struct command *p = NULL;
+       struct command **head = command_list_for_parent(context, parent);
+       for (struct command *c = *head; NULL != c; p = c, c = c->next)
        {
-               if (strcmp(name, c->name) == 0)
-               {
-                       /* unlink command */
-                       if (p)
-                       {
-                               p->next = c->next;
-                       }
-                       else
-                       {
-                               /* first element in command list */
-                               context->commands = c->next;
-                       }
-
-                       /* unregister children */
-                       while (NULL != c->children)
-                       {
-                               c2 = c->children;
-                               c->children = c->children->next;
-                               free(c2->name);
-                               c2->name = NULL;
-                               free(c2);
-                               c2 = NULL;
-                       }
+               if (strcmp(name, c->name) != 0)
+                       continue;
 
-                       /* delete command */
-                       free(c->name);
-                       c->name = NULL;
-                       free(c);
-                       c = NULL;
-                       return ERROR_OK;
-               }
+               if (p)
+                       p->next = c->next;
+               else
+                       *head = c->next;
 
-               /* remember the last command for unlinking */
-               p = c;
-               c = c->next;
+               command_free(c);
+               return ERROR_OK;
        }
 
        return ERROR_OK;
 }
 
-void command_output_text(command_context_t *context, const char *data)
+void command_output_text(struct command_context *context, const char *data)
 {
        if (context && context->output_handler && data) {
                context->output_handler(context, data);
        }
 }
 
-void command_print_sameline(command_context_t *context, const char *format, ...)
+void command_print_sameline(struct command_context *context, const char *format, ...)
 {
        char *string;
 
@@ -387,7 +456,7 @@ void command_print_sameline(command_context_t *context, const char *format, ...)
        va_end(ap);
 }
 
-void command_print(command_context_t *context, const char *format, ...)
+void command_print(struct command_context *context, const char *format, ...)
 {
        char *string;
 
@@ -412,7 +481,7 @@ void command_print(command_context_t *context, const char *format, ...)
        va_end(ap);
 }
 
-static char *__command_name(struct command_s *c, char delim, unsigned extra)
+static char *__command_name(struct command *c, char delim, unsigned extra)
 {
        char *name;
        unsigned len = strlen(c->name);
@@ -429,15 +498,14 @@ static char *__command_name(struct command_s *c, char delim, unsigned extra)
        }
        return name;
 }
-char *command_name(struct command_s *c, char delim)
+char *command_name(struct command *c, char delim)
 {
        return __command_name(c, delim, 0);
 }
 
-static int run_command(command_context_t *context,
-               command_t *c, const char *words[], unsigned num_words)
+static int run_command(struct command_context *context,
+               struct command *c, const char *words[], unsigned num_words)
 {
-       int start_word = 0;
        if (!((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode)))
        {
                /* Config commands can not run after the config stage */
@@ -445,9 +513,13 @@ static int run_command(command_context_t *context,
                return ERROR_FAIL;
        }
 
-       unsigned argc = num_words - start_word - 1;
-       const char **args = words + start_word + 1;
-       int retval = c->handler(context, c->name, args, argc);
+       struct command_invocation cmd = {
+                       .ctx = context,
+                       .name = c->name,
+                       .argc = num_words - 1,
+                       .argv = words + 1,
+               };
+       int retval = c->handler(&cmd);
        if (retval == ERROR_COMMAND_SYNTAX_ERROR)
        {
                /* Print help for command */
@@ -473,7 +545,7 @@ static int run_command(command_context_t *context,
        return retval;
 }
 
-int command_run_line(command_context_t *context, char *line)
+int command_run_line(struct command_context *context, char *line)
 {
        /* all the parent commands have been registered with the interpreter
         * so, can just evaluate the line as a script and check for
@@ -543,7 +615,7 @@ int command_run_line(command_context_t *context, char *line)
        return retval;
 }
 
-int command_run_linef(command_context_t *context, const char *format, ...)
+int command_run_linef(struct command_context *context, const char *format, ...)
 {
        int retval = ERROR_FAIL;
        char *string;
@@ -558,23 +630,23 @@ int command_run_linef(command_context_t *context, const char *format, ...)
        return retval;
 }
 
-void command_set_output_handler(command_context_t* context,
+void command_set_output_handler(struct command_context* context,
                command_output_handler_t output_handler, void *priv)
 {
        context->output_handler = output_handler;
        context->output_handler_priv = priv;
 }
 
-command_context_t* copy_command_context(command_context_t* context)
+struct command_context* copy_command_context(struct command_context* context)
 {
-       command_context_t* copy_context = malloc(sizeof(command_context_t));
+       struct command_context* copy_context = malloc(sizeof(struct command_context));
 
        *copy_context = *context;
 
        return copy_context;
 }
 
-int command_done(command_context_t *context)
+int command_done(struct command_context *context)
 {
        free(context);
        context = NULL;
@@ -715,24 +787,231 @@ static int jim_capture(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
        return retcode;
 }
 
+static COMMAND_HELPER(command_help_find, struct command *head,
+               struct command **out)
+{
+       if (0 == CMD_ARGC)
+               return ERROR_INVALID_ARGUMENTS;
+       *out = command_find(head, CMD_ARGV[0]);
+       if (NULL == *out)
+               return ERROR_INVALID_ARGUMENTS;
+       if (--CMD_ARGC == 0)
+               return ERROR_OK;
+       CMD_ARGV++;
+       return CALL_COMMAND_HANDLER(command_help_find, (*out)->children, out);
+}
+
+static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
+               bool show_help);
+
+static COMMAND_HELPER(command_help_show_list, struct command *head, unsigned n,
+               bool show_help)
+{
+       for (struct command *c = head; NULL != c; c = c->next)
+               CALL_COMMAND_HANDLER(command_help_show, c, n, show_help);
+       return ERROR_OK;
+}
+static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
+               bool show_help)
+{
+       const char *usage = c->usage ? : "";
+       const char *help = "";
+       const char *sep = "";
+       if (show_help && c->help)
+       {
+               help = c->help ? : "";
+               sep = c->usage ? " | " : "";
+       }
+       command_run_linef(CMD_CTX, "cmd_help {%s} {%s%s%s} %d",
+                       command_name(c, ' '), usage, sep, help, n);
+
+       if (++n >= 2)
+               return ERROR_OK;
+
+       return CALL_COMMAND_HANDLER(command_help_show_list,
+                       c->children, n, show_help);
+}
+COMMAND_HANDLER(handle_help_command)
+{
+       struct command *c = CMD_CTX->commands;
+
+       if (0 == CMD_ARGC)
+               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, true);
+
+       int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
+       if (ERROR_OK != retval)
+               return retval;
+
+       return CALL_COMMAND_HANDLER(command_help_show, c, 0, true);
+}
+
+COMMAND_HANDLER(handle_usage_command)
+{
+       struct command *c = CMD_CTX->commands;
+
+       if (0 == CMD_ARGC)
+               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, false);
+
+       int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
+       if (ERROR_OK != retval)
+               return retval;
+
+       return CALL_COMMAND_HANDLER(command_help_show, c, 0, false);
+}
+
+static int command_unknown_find(unsigned argc, Jim_Obj *const *argv,
+               struct command *head, struct command **out)
+{
+       if (0 == argc)
+               return argc;
+       struct command *c = command_find(head, Jim_GetString(argv[0], NULL));
+       if (NULL == c)
+               return argc;
+       *out = c;
+       return command_unknown_find(--argc, ++argv, (*out)->children, out);
+}
+
+static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       const char *cmd_name = Jim_GetString(argv[0], NULL);
+       script_debug(interp, cmd_name, argc - 1, argv + 1);
+
+       struct command_context *cmd_ctx = current_command_context();
+       struct command *c = cmd_ctx->commands;
+       int remaining = command_unknown_find(argc - 1, argv + 1, c, &c);
+       // if nothing could be consumed, then it's really an unknown command
+       if (remaining == argc - 1)
+       {
+               const char *cmd = Jim_GetString(argv[1], NULL);
+               LOG_ERROR("Unknown command:\n  %s", cmd);
+               return JIM_OK;
+       }
+
+       bool found = true;
+       Jim_Obj *const *start;
+       unsigned count;
+       if (c->handler || c->jim_handler)
+       {
+               // include the command name in the list
+               count = remaining + 1;
+               start = argv + (argc - remaining - 1);
+       }
+       else
+       {
+               c = command_find(cmd_ctx->commands, "help");
+               if (NULL == c)
+               {
+                       LOG_ERROR("unknown command, but help is missing too");
+                       return JIM_ERR;
+               }
+               count = argc - remaining;
+               start = argv;
+               found = false;
+       }
+       // pass the command through to the intended handler
+       if (c->jim_handler)
+       {
+               interp->cmdPrivData = c->jim_handler_data;
+               return (*c->jim_handler)(interp, count, start);
+       }
+
+       unsigned nwords;
+       const char **words = script_command_args_alloc(count, start, &nwords);
+       if (NULL == words)
+               return JIM_ERR;
+
+       int retval = run_command(cmd_ctx, c, words, nwords);
+
+       script_command_args_free(words, nwords);
+
+       if (!found && ERROR_OK == retval)
+               retval = ERROR_FAIL;
+
+       return retval;
+}
+
+int help_add_command(struct command_context *cmd_ctx, struct command *parent,
+               const char *cmd_name, const char *help_text, const char *usage)
+{
+       struct command **head = command_list_for_parent(cmd_ctx, parent);
+       struct command *nc = command_find(*head, cmd_name);
+       if (NULL == nc)
+       {
+               // add a new command with help text
+               struct command_registration cr = {
+                               .name = cmd_name,
+                               .mode = COMMAND_ANY,
+                               .help = help_text,
+                               .usage = usage,
+                       };
+               nc = register_command(cmd_ctx, parent, &cr);
+               if (NULL == nc)
+               {
+                       LOG_ERROR("failed to add '%s' help text", cmd_name);
+                       return ERROR_FAIL;
+               }
+               LOG_DEBUG("added '%s' help text", cmd_name);
+       }
+       else
+       {
+               bool replaced = false;
+               if (nc->help)
+               {
+                       free((void *)nc->help);
+                       replaced = true;
+               }
+               nc->help = strdup(help_text);
+
+               if (replaced)
+                       LOG_INFO("replaced existing '%s' help", cmd_name);
+               else
+                       LOG_DEBUG("added '%s' help text", cmd_name);
+       }
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_help_add_command)
+{
+       if (CMD_ARGC < 2)
+       {
+               LOG_ERROR("%s: insufficient arguments", CMD_NAME);
+               return ERROR_INVALID_ARGUMENTS;
+       }
+
+       // save help text and remove it from argument list
+       const char *help_text = CMD_ARGV[--CMD_ARGC];
+       // likewise for the leaf command name
+       const char *cmd_name = CMD_ARGV[--CMD_ARGC];
+
+       struct command *c = NULL;
+       if (CMD_ARGC > 0)
+       {
+               c = CMD_CTX->commands;
+               int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
+               if (ERROR_OK != retval)
+                       return retval;
+       }
+       return help_add_command(CMD_CTX, c, cmd_name, help_text, NULL);
+}
+
 /* sleep command sleeps for <n> miliseconds
  * this is useful in target startup scripts
  */
 COMMAND_HANDLER(handle_sleep_command)
 {
        bool busy = false;
-       if (argc == 2)
+       if (CMD_ARGC == 2)
        {
-               if (strcmp(args[1], "busy") == 0)
+               if (strcmp(CMD_ARGV[1], "busy") == 0)
                        busy = true;
                else
                        return ERROR_COMMAND_SYNTAX_ERROR;
        }
-       else if (argc < 1 || argc > 2)
+       else if (CMD_ARGC < 1 || CMD_ARGC > 2)
                return ERROR_COMMAND_SYNTAX_ERROR;
 
        unsigned long duration = 0;
-       int retval = parse_ulong(args[0], &duration);
+       int retval = parse_ulong(CMD_ARGV[0], &duration);
        if (ERROR_OK != retval)
                return retval;
 
@@ -751,21 +1030,42 @@ COMMAND_HANDLER(handle_sleep_command)
        return ERROR_OK;
 }
 
-COMMAND_HANDLER(handle_fast_command)
-{
-       if (argc != 1)
-               return ERROR_COMMAND_SYNTAX_ERROR;
-
-       fast_and_dangerous = strcmp("enable", args[0]) == 0;
-
-       return ERROR_OK;
-}
-
-
-command_context_t* command_init()
+static const struct command_registration command_builtin_handlers[] = {
+       {
+               .name = "add_help_text",
+               .handler = &handle_help_add_command,
+               .mode = COMMAND_ANY,
+               .help = "add new command help text",
+               .usage = "<command> [...] <help_text>]",
+       },
+       {
+               .name = "sleep",
+               .handler = &handle_sleep_command,
+               .mode = COMMAND_ANY,
+               .help = "sleep for n milliseconds.  "
+                       "\"busy\" will busy wait",
+               .usage = "<n> [busy]",
+       },
+       {
+               .name = "help",
+               .handler = &handle_help_command,
+               .mode = COMMAND_ANY,
+               .help = "show built-in command help",
+               .usage = "[<command_name> ...]",
+       },
+       {
+               .name = "usage",
+               .handler = &handle_usage_command,
+               .mode = COMMAND_ANY,
+               .help = "show command usage",
+               .usage = "[<command_name> ...]",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct command_context* command_init(const char *startup_tcl)
 {
-       command_context_t* context = malloc(sizeof(command_context_t));
-       extern const char startup_tcl[];
+       struct command_context* context = malloc(sizeof(struct command_context));
        const char *HostOs;
 
        context->mode = COMMAND_EXEC;
@@ -808,6 +1108,7 @@ command_context_t* command_init()
        Jim_SetGlobalVariableStr(interp, "ocd_HOSTOS",
                        Jim_NewStringObj(interp, HostOs , strlen(HostOs)));
 
+       Jim_CreateCommand(interp, "unknown", &command_unknown, NULL, NULL);
        Jim_CreateCommand(interp, "ocd_find", jim_find, NULL, NULL);
        Jim_CreateCommand(interp, "echo", jim_echo, NULL, NULL);
        Jim_CreateCommand(interp, "capture", jim_capture, NULL, NULL);
@@ -822,29 +1123,24 @@ command_context_t* command_init()
        interp->cb_fflush = openocd_jim_fflush;
        interp->cb_fgets = openocd_jim_fgets;
 
+       register_commands(context, NULL, command_builtin_handlers);
+
 #if !BUILD_ECOSBOARD
        Jim_EventLoopOnLoad(interp);
 #endif
+       Jim_SetAssocData(interp, "context", NULL, context);
        if (Jim_Eval_Named(interp, startup_tcl, "embedded:startup.tcl",1) == JIM_ERR)
        {
                LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD)");
                Jim_PrintErrorMessage(interp);
                exit(-1);
        }
-
-       register_command(context, NULL, "sleep",
-                       handle_sleep_command, COMMAND_ANY,
-                       "<n> [busy] - sleep for n milliseconds. "
-                       "\"busy\" means busy wait");
-       register_command(context, NULL, "fast",
-                       handle_fast_command, COMMAND_ANY,
-                       "fast <enable/disable> - place at beginning of "
-                       "config files. Sets defaults to fast and dangerous.");
+       Jim_DeleteAssocData(interp, "context");
 
        return context;
 }
 
-int command_context_mode(command_context_t *cmd_ctx, enum command_mode mode)
+int command_context_mode(struct command_context *cmd_ctx, enum command_mode mode)
 {
        if (!cmd_ctx)
                return ERROR_INVALID_ARGUMENTS;
@@ -867,29 +1163,6 @@ void process_jim_events(void)
 #endif
 }
 
-void register_jim(struct command_context_s *cmd_ctx, const char *name, int (*cmd)(Jim_Interp *interp, int argc, Jim_Obj *const *argv), const char *help)
-{
-       Jim_CreateCommand(interp, name, cmd, NULL, NULL);
-
-       Jim_Obj *cmd_list = Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_list,
-                       Jim_NewStringObj(interp, name, -1));
-
-       command_helptext_add(cmd_list, help);
-}
-
-/* return global variable long value or 0 upon failure */
-long jim_global_long(const char *variable)
-{
-       Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, variable, JIM_ERRMSG);
-       long t;
-       if (Jim_GetLong(interp, objPtr, &t) == JIM_OK)
-       {
-               return t;
-       }
-       return 0;
-}
-
 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
        int parse##name(const char *str, type *ul) \
        { \
@@ -950,3 +1223,51 @@ DEFINE_PARSE_LONG(_int, int, n < INT_MIN, INT_MAX)
 DEFINE_PARSE_LONG(_s32, int32_t, n < INT32_MIN, INT32_MAX)
 DEFINE_PARSE_LONG(_s16, int16_t, n < INT16_MIN, INT16_MAX)
 DEFINE_PARSE_LONG(_s8, int8_t, n < INT8_MIN, INT8_MAX)
+
+static int command_parse_bool(const char *in, bool *out,
+               const char *on, const char *off)
+{
+       if (strcasecmp(in, on) == 0)
+               *out = true;
+       else if (strcasecmp(in, off) == 0)
+               *out = false;
+       else
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       return  ERROR_OK;
+}
+
+int command_parse_bool_arg(const char *in, bool *out)
+{
+       if (command_parse_bool(in, out, "on", "off") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "enable", "disable") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "true", "false") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "yes", "no") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "1", "0") == ERROR_OK)
+               return ERROR_OK;
+       return ERROR_INVALID_ARGUMENTS;
+}
+
+COMMAND_HELPER(handle_command_parse_bool, bool *out, const char *label)
+{
+       switch (CMD_ARGC) {
+       case 1: {
+               const char *in = CMD_ARGV[0];
+               if (command_parse_bool_arg(in, out) != ERROR_OK)
+               {
+                       LOG_ERROR("%s: argument '%s' is not valid", CMD_NAME, in);
+                       return ERROR_INVALID_ARGUMENTS;
+               }
+               // fall through
+       }
+       case 0:
+               LOG_INFO("%s is %s", label, *out ? "enabled" : "disabled");
+               break;
+       default:
+               return ERROR_INVALID_ARGUMENTS;
+       }
+       return ERROR_OK;
+}