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