encapsulate and re-use log capture, retval setup
[fw/openocd] / src / helper / command.c
index df4667b92a7dafc7f66e9a0830979d2bc040fb1d..8b29ab4ac4b41a48ce1953adffb1ec2715d7bbf0 100644 (file)
@@ -56,6 +56,34 @@ static void tcl_output(void *privData, const char *file, unsigned line,
        Jim_AppendString(interp, tclOutput, string, strlen(string));
 }
 
+static Jim_Obj *command_log_capture_start(Jim_Interp *interp)
+{
+       /* capture log output and return it. A garbage collect can
+        * happen, so we need a reference count to this object */
+       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
+       if (NULL == tclOutput)
+               return NULL;
+       Jim_IncrRefCount(tclOutput);
+       log_add_callback(tcl_output, tclOutput);
+       return tclOutput;
+}
+
+static void command_log_capture_finish(Jim_Interp *interp, Jim_Obj *tclOutput)
+{
+       log_remove_callback(tcl_output, tclOutput);
+       Jim_SetResult(interp, tclOutput);
+       Jim_DecrRefCount(interp, tclOutput);
+}
+
+static int command_retval_set(Jim_Interp *interp, int retval)
+{
+       int *return_retval = Jim_GetAssocData(interp, "retval");
+       if (return_retval != NULL)
+               *return_retval = retval;
+
+       return (retval == ERROR_OK) ? JIM_OK : JIM_ERR;
+}
+
 extern struct command_context *global_cmd_ctx;
 
 void script_debug(Jim_Interp *interp, const char *name,
@@ -108,24 +136,27 @@ static const char **script_command_args_alloc(
        return words;
 }
 
-static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+static struct command_context *current_command_context(void)
 {
-       /* the private data is stashed in the interp structure */
-       struct command *c;
-       struct command_context *context;
-       int retval;
-
-       /* DANGER!!!! be careful what we invoke here, since interp->cmdPrivData might
-        * get overwritten by running other Jim commands! Treat it as an
-        * emphemeral global variable that is used in lieu of an argument
-        * to the fn and fish it out manually.
-        */
-       c = interp->cmdPrivData;
-       if (c == NULL)
+       /* grab the command context from the associated data */
+       struct command_context *cmd_ctx = Jim_GetAssocData(interp, "context");
+       if (NULL == cmd_ctx)
        {
-               LOG_ERROR("BUG: interp->cmdPrivData == NULL");
-               return JIM_ERR;
+               /* 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 */
+
+       struct command *c = interp->cmdPrivData;
+       assert(c);
+
        target_call_timer_callbacks_now();
        LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
 
@@ -136,40 +167,14 @@ static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
        if (NULL == words)
                return JIM_ERR;
 
-       /* 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;
-       }
-
-       /* capture log output and return it */
-       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
-       /* a garbage collect can happen, so we need a reference count to this object */
-       Jim_IncrRefCount(tclOutput);
+       Jim_Obj *tclOutput = command_log_capture_start(interp);
 
-       log_add_callback(tcl_output, tclOutput);
-
-       retval = run_command(context, c, (const char **)words, nwords);
-
-       log_remove_callback(tcl_output, tclOutput);
-
-       /* We dump output into this local variable */
-       Jim_SetResult(interp, tclOutput);
-       Jim_DecrRefCount(interp, tclOutput);
+       struct command_context *cmd_ctx = current_command_context();
+       int retval = run_command(cmd_ctx, c, (const char **)words, nwords);
 
+       command_log_capture_finish(interp, tclOutput);
        script_command_args_free(words, nwords);
-
-       int *return_retval = Jim_GetAssocData(interp, "retval");
-       if (return_retval != NULL)
-       {
-               *return_retval = retval;
-       }
-
-       return (retval == ERROR_OK)?JIM_OK:JIM_ERR;
+       return command_retval_set(interp, retval);
 }
 
 /* nice short description of source file */
@@ -189,6 +194,16 @@ static struct command *command_find(struct command *head, const char *name)
        }
        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 into the linked list, sorted by name.
@@ -224,23 +239,23 @@ static struct command **command_list_for_parent(
 }
 
 static struct command *command_new(struct command_context *cmd_ctx,
-               struct command *parent, const char *name,
-               command_handler_t handler, enum command_mode mode,
-               const char *help, const char *usage)
+               struct command *parent, const struct command_registration *cr)
 {
-       assert(name);
+       assert(cr->name);
 
        struct command *c = malloc(sizeof(struct command));
        memset(c, 0, sizeof(struct command));
 
-       c->name = strdup(name);
-       if (help)
-               c->help = strdup(help);
-       if (usage)
-               c->usage = strdup(usage);
+       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->handler = handler;
-       c->mode = mode;
+       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(command_list_for_parent(cmd_ctx, parent), c);
 
@@ -266,6 +281,38 @@ static void command_free(struct command *c)
        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);
+
+       /* we now need to add an overrideable proc */
+       const char *override_name = alloc_printf("proc %s {args} {"
+                       "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);
+
+       retval = ERROR_OK;
+
+free_full_name:
+       free((void *)full_name);
+       return retval;
+}
+
 struct command* register_command(struct command_context *context,
                struct command *parent, const struct command_registration *cr)
 {
@@ -282,26 +329,22 @@ struct command* register_command(struct command_context *context,
                return c;
        }
 
-       c = command_new(context, parent, name, cr->handler, cr->mode, cr->help, cr->usage);
-       /* if allocation failed or it is a placeholder (no handler), we're done */
-       if (NULL == c || NULL == c->handler)
-               return c;
-
-       const char *full_name = command_name(c, '_');
-
-       const char *ocd_name = alloc_printf("ocd_%s", full_name);
-       Jim_CreateCommand(interp, ocd_name, script_command, c, NULL);
-       free((void *)ocd_name);
+       c = command_new(context, parent, cr);
+       if (NULL == c)
+               return NULL;
 
-       /* we now need to add an overrideable proc */
-       const char *override_name = alloc_printf("proc %s {args} {"
-                       "if {[catch {eval ocd_%s $args}] == 0} "
-                       "{return \"\"} else {return -code error}}",
-                       full_name, full_name);
-       Jim_Eval_Named(interp, override_name, __THIS__FILE__, __LINE__);
-       free((void *)override_name);
+       if (NULL != c->handler)
+       {
+               int retval = register_command_handler(c);
+               if (ERROR_OK != retval)
+               {
+                       unregister_command(context, parent, name);
+                       return NULL;
+               }
+       }
 
-       free((void *)full_name);
+       if (NULL != cr->jim_handler && NULL == parent)
+               Jim_CreateCommand(interp, cr->name, cr->jim_handler, cr->jim_handler_data, NULL);
 
        return c;
 }
@@ -309,18 +352,36 @@ struct command* register_command(struct command_context *context,
 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; i++)
+       for (i = 0; cmds[i].name || cmds[i].chain; i++)
        {
-               struct command *c = register_command(cmd_ctx, parent, cmds + i);
-               if (NULL != c)
-                       continue;
+               const struct command_registration *cr = cmds + i;
 
+               struct command *c = NULL;
+               if (NULL != cr->name)
+               {
+                       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 ERROR_FAIL;
        }
-       return ERROR_OK;
+       return retval;
 }
 
 int unregister_all_commands(struct command_context *context,
@@ -706,23 +767,13 @@ static int jim_capture(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
        if (argc != 2)
                return JIM_ERR;
-       int retcode;
-       const char *str = Jim_GetString(argv[1], NULL);
 
-       /* capture log output and return it */
-       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
-       /* a garbage collect can happen, so we need a reference count to this object */
-       Jim_IncrRefCount(tclOutput);
+       Jim_Obj *tclOutput = command_log_capture_start(interp);
 
-       log_add_callback(tcl_output, tclOutput);
-
-       retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
+       const char *str = Jim_GetString(argv[1], NULL);
+       int retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
 
-       log_remove_callback(tcl_output, tclOutput);
-
-       /* We dump output into this local variable */
-       Jim_SetResult(interp, tclOutput);
-       Jim_DecrRefCount(interp, tclOutput);
+       command_log_capture_finish(interp, tclOutput);
 
        return retcode;
 }
@@ -751,20 +802,48 @@ static COMMAND_HELPER(command_help_show_list, struct command *head, unsigned n,
                CALL_COMMAND_HANDLER(command_help_show, c, n, show_help);
        return ERROR_OK;
 }
+
+#define HELP_LINE_WIDTH(_n) (int)(76 - (2 * _n))
+
+static void command_help_show_indent(unsigned n)
+{
+       for (unsigned i = 0; i < n; i++)
+               LOG_USER_N("  ");
+}
+static void command_help_show_wrap(const char *str, unsigned n, unsigned n2)
+{
+       const char *cp = str, *last = str;
+       while (*cp)
+       {
+               const char *next = last;
+               do {
+                       cp = next;
+                       do {
+                               next++;
+                       } while (*next != ' ' && *next != '\t' && *next != '\0');
+               } while ((next - last < HELP_LINE_WIDTH(n)) && *next != '\0');
+               if (next - last < HELP_LINE_WIDTH(n))
+                       cp = next;
+               command_help_show_indent(n);
+               LOG_USER_N("%.*s", (int)(cp - last), last);
+               LOG_USER_N("\n");
+               last = cp + 1;
+               n = n2;
+       }
+}
 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_help_show_indent(n);
+       LOG_USER_N("%s", command_name(c, ' '));
+       if (c->usage) {
+               LOG_USER_N(" ");
+               command_help_show_wrap(c->usage, 0, n + 5);
        }
-       command_run_linef(CMD_CTX, "cmd_help {%s} {%s%s%s} %d",
-                       command_name(c, ' '), usage, sep, help, n);
-
+       else
+               LOG_USER_N("\n");
+       if (show_help && c->help)
+               command_help_show_wrap(c->help, n + 3, n + 3);
        if (++n >= 2)
                return ERROR_OK;
 
@@ -773,32 +852,93 @@ static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
 }
 COMMAND_HANDLER(handle_help_command)
 {
+       bool full = strcmp(CMD_NAME, "help") == 0;
+
        struct command *c = CMD_CTX->commands;
 
        if (0 == CMD_ARGC)
-               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, true);
+               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, full);
 
        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);
+       return CALL_COMMAND_HANDLER(command_help_show, c, 0, full);
 }
 
-COMMAND_HANDLER(handle_usage_command)
+static int command_unknown_find(unsigned argc, Jim_Obj *const *argv,
+               struct command *head, struct command **out)
 {
-       struct command *c = CMD_CTX->commands;
+       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);
+}
 
-       if (0 == CMD_ARGC)
-               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, false);
+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;
+       }
 
-       int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
-       if (ERROR_OK != retval)
-               return retval;
+       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);
+       }
 
-       return CALL_COMMAND_HANDLER(command_help_show, c, 0, false);
-}
+       unsigned nwords;
+       const char **words = script_command_args_alloc(count, start, &nwords);
+       if (NULL == words)
+               return JIM_ERR;
 
+       Jim_Obj *tclOutput = command_log_capture_start(interp);
+
+       int retval = run_command(cmd_ctx, c, words, nwords);
+
+       command_log_capture_finish(interp, tclOutput);
+       script_command_args_free(words, nwords);
+
+       if (!found && ERROR_OK == retval)
+               retval = ERROR_FAIL;
+
+       return command_retval_set(interp, retval);
+}
 
 int help_add_command(struct command_context *cmd_ctx, struct command *parent,
                const char *cmd_name, const char *help_text, const char *usage)
@@ -821,8 +961,9 @@ int help_add_command(struct command_context *cmd_ctx, struct command *parent,
                        return ERROR_FAIL;
                }
                LOG_DEBUG("added '%s' help text", cmd_name);
+               return ERROR_OK;
        }
-       else
+       if (help_text)
        {
                bool replaced = false;
                if (nc->help)
@@ -831,12 +972,25 @@ int help_add_command(struct command_context *cmd_ctx, struct command *parent,
                        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);
        }
+       if (usage)
+       {
+               bool replaced = false;
+               if (nc->usage)
+               {
+                       free((void *)nc->usage);
+                       replaced = true;
+               }
+               nc->usage = strdup(usage);
+               if (replaced)
+                       LOG_INFO("replaced existing '%s' usage", cmd_name);
+               else
+                       LOG_DEBUG("added '%s' usage text", cmd_name);
+       }
        return ERROR_OK;
 }
 
@@ -849,7 +1003,14 @@ COMMAND_HANDLER(handle_help_add_command)
        }
 
        // save help text and remove it from argument list
-       const char *help_text = CMD_ARGV[--CMD_ARGC];
+       const char *str = CMD_ARGV[--CMD_ARGC];
+       const char *help = !strcmp(CMD_NAME, "add_help_text") ? str : NULL;
+       const char *usage = !strcmp(CMD_NAME, "add_usage_text") ? str : NULL;
+       if (!help && !usage)
+       {
+               LOG_ERROR("command name '%s' is unknown", CMD_NAME);
+               return ERROR_INVALID_ARGUMENTS;
+       }
        // likewise for the leaf command name
        const char *cmd_name = CMD_ARGV[--CMD_ARGC];
 
@@ -861,7 +1022,7 @@ COMMAND_HANDLER(handle_help_add_command)
                if (ERROR_OK != retval)
                        return retval;
        }
-       return help_add_command(CMD_CTX, c, cmd_name, help_text, NULL);
+       return help_add_command(CMD_CTX, c, cmd_name, help, usage);
 }
 
 /* sleep command sleeps for <n> miliseconds
@@ -900,6 +1061,46 @@ COMMAND_HANDLER(handle_sleep_command)
        return ERROR_OK;
 }
 
+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 = "add_usage_text",
+               .handler = &handle_help_add_command,
+               .mode = COMMAND_ANY,
+               .help = "add new command usage text",
+               .usage = "<command> [...] <usage_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 full command help",
+               .usage = "[<command> ...]",
+       },
+       {
+               .name = "usage",
+               .handler = &handle_help_command,
+               .mode = COMMAND_ANY,
+               .help = "show basic command usage",
+               .usage = "[<command> ...]",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
 struct command_context* command_init(const char *startup_tcl)
 {
        struct command_context* context = malloc(sizeof(struct command_context));
@@ -945,6 +1146,7 @@ struct command_context* command_init(const char *startup_tcl)
        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);
@@ -959,10 +1161,7 @@ struct command_context* command_init(const char *startup_tcl)
        interp->cb_fflush = openocd_jim_fflush;
        interp->cb_fgets = openocd_jim_fgets;
 
-       COMMAND_REGISTER(context, NULL, "add_help_text",
-                       handle_help_add_command, COMMAND_ANY,
-                       "<command> [...] <help_text>] - "
-                       "add new command help text");
+       register_commands(context, NULL, command_builtin_handlers);
 
 #if !BUILD_ECOSBOARD
        Jim_EventLoopOnLoad(interp);
@@ -976,19 +1175,6 @@ struct command_context* command_init(const char *startup_tcl)
        }
        Jim_DeleteAssocData(interp, "context");
 
-       COMMAND_REGISTER(context, NULL, "sleep",
-                       handle_sleep_command, COMMAND_ANY,
-                       "<n> [busy] - sleep for n milliseconds. "
-                       "\"busy\" means busy wait");
-
-       COMMAND_REGISTER(context, NULL, "help",
-                       &handle_help_command, COMMAND_ANY,
-                       "[<command_name> ...] - show built-in command help");
-       COMMAND_REGISTER(context, NULL, "usage",
-                       &handle_usage_command, COMMAND_ANY,
-                       "[<command_name> ...] | "
-                       "show command usage");
-
        return context;
 }
 
@@ -1015,18 +1201,6 @@ void process_jim_events(void)
 #endif
 }
 
-void register_jim(struct command_context *cmd_ctx, const char *name,
-               Jim_CmdProc cmd, 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));
-
-       help_add_command(cmd_ctx, NULL, name, help, NULL);
-}
-
 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
        int parse##name(const char *str, type *ul) \
        { \