1 /*
2 * Makes it easy to support "ensembles". i.e. commands with subcommands
3 * like [string] and [array]
4 *
5 * (c) 2008 Steve Bennett <steveb@workware.net.au>
6 *
7 */
8 #include <stdio.h>
9 #include <string.h>
10
11 #include <jim-subcmd.h>
12
13 /**
14 * Implements the common 'commands' subcommand
15 */
subcmd_null(Jim_Interp * interp,int argc,Jim_Obj * const * argv)16 static int subcmd_null(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
17 {
18 /* Nothing to do, since the result has already been created */
19 return JIM_OK;
20 }
21
22 /**
23 * Do-nothing command to support -commands and -usage
24 */
25 static const jim_subcmd_type dummy_subcmd = {
26 "dummy", NULL, subcmd_null, 0, 0, JIM_MODFLAG_HIDDEN
27 };
28
add_commands(Jim_Interp * interp,const jim_subcmd_type * ct,const char * sep)29 static void add_commands(Jim_Interp *interp, const jim_subcmd_type * ct, const char *sep)
30 {
31 const char *s = "";
32
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;
37 }
38 }
39 }
40
bad_subcmd(Jim_Interp * interp,const jim_subcmd_type * command_table,const char * type,Jim_Obj * cmd,Jim_Obj * subcmd)41 static void bad_subcmd(Jim_Interp *interp, const jim_subcmd_type * command_table, const char *type,
42 Jim_Obj *cmd, Jim_Obj *subcmd)
43 {
44 Jim_SetResultFormatted(interp, "%#s, %s command \"%#s\": should be ", cmd, type, subcmd);
45 add_commands(interp, command_table, ", ");
46 }
47
show_cmd_usage(Jim_Interp * interp,const jim_subcmd_type * command_table,int argc,Jim_Obj * const * argv)48 static void show_cmd_usage(Jim_Interp *interp, const jim_subcmd_type * command_table, int argc,
49 Jim_Obj *const *argv)
50 {
51 Jim_SetResultFormatted(interp, "Usage: \"%#s command ... \", where command is one of: ", argv[0]);
52 add_commands(interp, command_table, ", ");
53 }
54
add_cmd_usage(Jim_Interp * interp,const jim_subcmd_type * ct,Jim_Obj * cmd)55 static void add_cmd_usage(Jim_Interp *interp, const jim_subcmd_type * ct, Jim_Obj *cmd)
56 {
57 if (cmd) {
58 Jim_AppendStrings(interp, Jim_GetResult(interp), Jim_String(cmd), " ", NULL);
59 }
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);
63 }
64 }
65
set_wrong_args(Jim_Interp * interp,const jim_subcmd_type * command_table,Jim_Obj * subcmd)66 static void set_wrong_args(Jim_Interp *interp, const jim_subcmd_type * command_table, Jim_Obj *subcmd)
67 {
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);
71 }
72
73 /* internal rep is stored in ptrIntvalue
74 * ptr = command_table
75 * int1 = index
76 */
77 static const Jim_ObjType subcmdLookupObjType = {
78 "subcmd-lookup",
79 NULL,
80 NULL,
81 NULL,
82 JIM_TYPE_REFERENCES
83 };
84
Jim_ParseSubCmd(Jim_Interp * interp,const jim_subcmd_type * command_table,int argc,Jim_Obj * const * argv)85 const jim_subcmd_type *Jim_ParseSubCmd(Jim_Interp *interp, const jim_subcmd_type * command_table,
86 int argc, Jim_Obj *const *argv)
87 {
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;
94
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;
99 }
100
101 cmd = argv[1];
102
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;
108 }
109 }
110
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;
117 }
118 help = 1;
119
120 /* Skip the 'help' command */
121 cmd = argv[2];
122 }
123
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;
130 }
131
132 cmdstr = Jim_GetString(cmd, &cmdlen);
133
134 for (ct = command_table; ct->cmd; ct++) {
135 if (Jim_CompareStringImmediate(interp, cmd, ct->cmd)) {
136 /* Found an exact match */
137 break;
138 }
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;
146 }
147 bad_subcmd(interp, command_table, "ambiguous", argv[0], argv[1 + help]);
148 return 0;
149 }
150 partial = ct;
151 }
152 continue;
153 }
154
155 /* If we had an unambiguous partial match */
156 if (partial && !ct->cmd) {
157 ct = partial;
158 }
159
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;
166 }
167 bad_subcmd(interp, command_table, "unknown", argv[0], argv[1 + help]);
168 return 0;
169 }
170
171 if (help) {
172 Jim_SetResultString(interp, "Usage: ", -1);
173 /* subcmd */
174 add_cmd_usage(interp, ct, argv[0]);
175 return &dummy_subcmd;
176 }
177
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;
183
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);
191
192 return 0;
193 }
194
195 /* Good command */
196 return ct;
197 }
198
Jim_CallSubCmd(Jim_Interp * interp,const jim_subcmd_type * ct,int argc,Jim_Obj * const * argv)199 int Jim_CallSubCmd(Jim_Interp *interp, const jim_subcmd_type * ct, int argc, Jim_Obj *const *argv)
200 {
201 int ret = JIM_ERR;
202
203 if (ct) {
204 if (ct->flags & JIM_MODFLAG_FULLARGV) {
205 ret = ct->function(interp, argc, argv);
206 }
207 else {
208 ret = ct->function(interp, argc - 2, argv + 2);
209 }
210 if (ret < 0) {
211 set_wrong_args(interp, ct, argv[0]);
212 ret = JIM_ERR;
213 }
214 }
215 return ret;
216 }
217
Jim_SubCmdProc(Jim_Interp * interp,int argc,Jim_Obj * const * argv)218 int Jim_SubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
219 {
220 const jim_subcmd_type *ct =
221 Jim_ParseSubCmd(interp, (const jim_subcmd_type *)Jim_CmdPrivData(interp), argc, argv);
222
223 return Jim_CallSubCmd(interp, ct, argc, argv);
224 }
225