build: Fix out-of-tree builds
[jimtcl.git] / jim-subcmd.c
blob1a090d5c9f375e20d9ec5f7f69009acaaf5a4b81
1 /*
2 * Makes it easy to support "ensembles". i.e. commands with subcommands
3 * like [string] and [array]
5 * (c) 2008 Steve Bennett <steveb@workware.net.au>
7 */
8 #include <stdio.h>
9 #include <string.h>
11 #include <jim-subcmd.h>
13 /**
14 * Implements the common 'commands' subcommand
16 static int subcmd_null(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
18 /* Nothing to do, since the result has already been created */
19 return JIM_OK;
22 /**
23 * Do-nothing command to support -commands and -usage
25 static const jim_subcmd_type dummy_subcmd = {
26 "dummy", NULL, subcmd_null, 0, 0, JIM_MODFLAG_HIDDEN
29 static void add_commands(Jim_Interp *interp, const jim_subcmd_type * ct, const char *sep)
31 const char *s = "";
33 for (; ct->cmd; ct++) {
34 if (!(ct->flags & JIM_MODFLAG_HIDDEN)) {
35 Jim_AppendStrings(interp, Jim_GetResult(interp), s, ct->cmd, NULL);
36 s = sep;
41 static void bad_subcmd(Jim_Interp *interp, const jim_subcmd_type * command_table, const char *type,
42 Jim_Obj *cmd, Jim_Obj *subcmd)
44 Jim_SetResultFormatted(interp, "%#s, %s command \"%#s\": should be ", cmd, type, subcmd);
45 add_commands(interp, command_table, ", ");
48 static void show_cmd_usage(Jim_Interp *interp, const jim_subcmd_type * command_table, int argc,
49 Jim_Obj *const *argv)
51 Jim_SetResultFormatted(interp, "Usage: \"%#s command ... \", where command is one of: ", argv[0]);
52 add_commands(interp, command_table, ", ");
55 static void add_cmd_usage(Jim_Interp *interp, const jim_subcmd_type * ct, Jim_Obj *cmd)
57 if (cmd) {
58 Jim_AppendStrings(interp, Jim_GetResult(interp), Jim_String(cmd), " ", NULL);
60 Jim_AppendStrings(interp, Jim_GetResult(interp), ct->cmd, NULL);
61 if (ct->args && *ct->args) {
62 Jim_AppendStrings(interp, Jim_GetResult(interp), " ", ct->args, NULL);
66 static void set_wrong_args(Jim_Interp *interp, const jim_subcmd_type * command_table, Jim_Obj *subcmd)
68 Jim_SetResultString(interp, "wrong # args: should be \"", -1);
69 add_cmd_usage(interp, command_table, subcmd);
70 Jim_AppendStrings(interp, Jim_GetResult(interp), "\"", NULL);
73 /* internal rep is stored in ptrIntvalue
74 * ptr = command_table
75 * int1 = index
77 static const Jim_ObjType subcmdLookupObjType = {
78 "subcmd-lookup",
79 NULL,
80 NULL,
81 NULL,
82 JIM_TYPE_REFERENCES
85 const jim_subcmd_type *Jim_ParseSubCmd(Jim_Interp *interp, const jim_subcmd_type * command_table,
86 int argc, Jim_Obj *const *argv)
88 const jim_subcmd_type *ct;
89 const jim_subcmd_type *partial = 0;
90 int cmdlen;
91 Jim_Obj *cmd;
92 const char *cmdstr;
93 int help = 0;
95 if (argc < 2) {
96 Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s command ...\"\n"
97 "Use \"%#s -help ?command?\" for help", argv[0], argv[0]);
98 return 0;
101 cmd = argv[1];
103 /* Use cached lookup if possible */
104 if (cmd->typePtr == &subcmdLookupObjType) {
105 if (cmd->internalRep.ptrIntValue.ptr == command_table) {
106 ct = command_table + cmd->internalRep.ptrIntValue.int1;
107 goto found;
111 /* Check for the help command */
112 if (Jim_CompareStringImmediate(interp, cmd, "-help")) {
113 if (argc == 2) {
114 /* Usage for the command, not the subcommand */
115 show_cmd_usage(interp, command_table, argc, argv);
116 return &dummy_subcmd;
118 help = 1;
120 /* Skip the 'help' command */
121 cmd = argv[2];
124 /* Check for special builtin '-commands' command first */
125 if (Jim_CompareStringImmediate(interp, cmd, "-commands")) {
126 /* Build the result here */
127 Jim_SetResult(interp, Jim_NewEmptyStringObj(interp));
128 add_commands(interp, command_table, " ");
129 return &dummy_subcmd;
132 cmdstr = Jim_GetString(cmd, &cmdlen);
134 for (ct = command_table; ct->cmd; ct++) {
135 if (Jim_CompareStringImmediate(interp, cmd, ct->cmd)) {
136 /* Found an exact match */
137 break;
139 if (strncmp(cmdstr, ct->cmd, cmdlen) == 0) {
140 if (partial) {
141 /* Ambiguous */
142 if (help) {
143 /* Just show the top level help here */
144 show_cmd_usage(interp, command_table, argc, argv);
145 return &dummy_subcmd;
147 bad_subcmd(interp, command_table, "ambiguous", argv[0], argv[1 + help]);
148 return 0;
150 partial = ct;
152 continue;
155 /* If we had an unambiguous partial match */
156 if (partial && !ct->cmd) {
157 ct = partial;
160 if (!ct->cmd) {
161 /* No matching command */
162 if (help) {
163 /* Just show the top level help here */
164 show_cmd_usage(interp, command_table, argc, argv);
165 return &dummy_subcmd;
167 bad_subcmd(interp, command_table, "unknown", argv[0], argv[1 + help]);
168 return 0;
171 if (help) {
172 Jim_SetResultString(interp, "Usage: ", -1);
173 /* subcmd */
174 add_cmd_usage(interp, ct, argv[0]);
175 return &dummy_subcmd;
178 /* Cache the result for a successful non-help lookup */
179 Jim_FreeIntRep(interp, cmd);
180 cmd->typePtr = &subcmdLookupObjType;
181 cmd->internalRep.ptrIntValue.ptr = (void *)command_table;
182 cmd->internalRep.ptrIntValue.int1 = ct - command_table;
184 found:
185 /* Check the number of args */
186 if (argc - 2 < ct->minargs || (ct->maxargs >= 0 && argc - 2 > ct->maxargs)) {
187 Jim_SetResultString(interp, "wrong # args: should be \"", -1);
188 /* subcmd */
189 add_cmd_usage(interp, ct, argv[0]);
190 Jim_AppendStrings(interp, Jim_GetResult(interp), "\"", NULL);
192 return 0;
195 /* Good command */
196 return ct;
199 int Jim_CallSubCmd(Jim_Interp *interp, const jim_subcmd_type * ct, int argc, Jim_Obj *const *argv)
201 int ret = JIM_ERR;
203 if (ct) {
204 if (ct->flags & JIM_MODFLAG_FULLARGV) {
205 ret = ct->function(interp, argc, argv);
207 else {
208 ret = ct->function(interp, argc - 2, argv + 2);
210 if (ret < 0) {
211 set_wrong_args(interp, ct, argv[0]);
212 ret = JIM_ERR;
215 return ret;
218 int Jim_SubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
220 const jim_subcmd_type *ct =
221 Jim_ParseSubCmd(interp, (const jim_subcmd_type *)Jim_CmdPrivData(interp), argc, argv);
223 return Jim_CallSubCmd(interp, ct, argc, argv);