1 /* $NetBSD: sl.c,v 1.3 2023/06/19 21:41:45 christos Exp $ */
2
3 /*
4 * Copyright (c) 1995 - 2006 Kungliga Tekniska Högskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <config.h>
37
38 #include "sl_locl.h"
39 #include <setjmp.h>
40
41 static void
mandoc_template(SL_cmd * cmds,const char * extra_string)42 mandoc_template(SL_cmd *cmds,
43 const char *extra_string)
44 {
45 SL_cmd *c, *prev;
46 char timestr[64], cmd[64];
47 const char *p;
48 time_t t;
49
50 printf(".\\\" Things to fix:\n");
51 printf(".\\\" * correct section, and operating system\n");
52 printf(".\\\" * remove Op from mandatory flags\n");
53 printf(".\\\" * use better macros for arguments (like .Pa for files)\n");
54 printf(".\\\"\n");
55 t = time(NULL);
56 strftime(timestr, sizeof(timestr), "%b %d, %Y", localtime(&t));
57 printf(".Dd %s\n", timestr);
58 #ifdef HAVE_GETPROGNAME
59 p = getprogname();
60 #else
61 p = "unknown-application";
62 #endif
63 strncpy(cmd, p, sizeof(cmd));
64 cmd[sizeof(cmd)-1] = '\0';
65 strupr(cmd);
66
67 printf(".Dt %s SECTION\n", cmd);
68 printf(".Os OPERATING_SYSTEM\n");
69 printf(".Sh NAME\n");
70 printf(".Nm %s\n", p);
71 printf(".Nd\n");
72 printf("in search of a description\n");
73 printf(".Sh SYNOPSIS\n");
74 printf(".Nm\n");
75 for(c = cmds; c->name; ++c) {
76 /* if (c->func == NULL)
77 continue; */
78 printf(".Op Fl %s", c->name);
79 printf("\n");
80
81 }
82 if (extra_string && *extra_string)
83 printf (".Ar %s\n", extra_string);
84 printf(".Sh DESCRIPTION\n");
85 printf("Supported options:\n");
86 printf(".Bl -tag -width Ds\n");
87 prev = NULL;
88 for(c = cmds; c->name; ++c) {
89 if (c->func) {
90 if (prev)
91 printf ("\n%s\n", prev->usage);
92
93 printf (".It Fl %s", c->name);
94 prev = c;
95 } else
96 printf (", %s\n", c->name);
97 }
98 if (prev)
99 printf ("\n%s\n", prev->usage);
100
101 printf(".El\n");
102 printf(".\\\".Sh ENVIRONMENT\n");
103 printf(".\\\".Sh FILES\n");
104 printf(".\\\".Sh EXAMPLES\n");
105 printf(".\\\".Sh DIAGNOSTICS\n");
106 printf(".\\\".Sh SEE ALSO\n");
107 printf(".\\\".Sh STANDARDS\n");
108 printf(".\\\".Sh HISTORY\n");
109 printf(".\\\".Sh AUTHORS\n");
110 printf(".\\\".Sh BUGS\n");
111 }
112
113 SL_cmd *
sl_match(SL_cmd * cmds,char * cmd,int exactp)114 sl_match (SL_cmd *cmds, char *cmd, int exactp)
115 {
116 SL_cmd *c, *current = NULL, *partial_cmd = NULL;
117 int partial_match = 0;
118
119 for (c = cmds; c->name; ++c) {
120 if (c->func)
121 current = c;
122 if (strcmp (cmd, c->name) == 0)
123 return current;
124 else if (strncmp (cmd, c->name, strlen(cmd)) == 0 &&
125 partial_cmd != current) {
126 ++partial_match;
127 partial_cmd = current;
128 }
129 }
130 if (partial_match == 1 && !exactp)
131 return partial_cmd;
132 else
133 return NULL;
134 }
135
136 void
sl_help(SL_cmd * cmds,int argc,char ** argv)137 sl_help (SL_cmd *cmds, int argc, char **argv)
138 {
139 SL_cmd *c, *prev_c;
140
141 if (getenv("SLMANDOC")) {
142 mandoc_template(cmds, NULL);
143 return;
144 }
145
146 if (argc == 1) {
147 prev_c = NULL;
148 for (c = cmds; c->name; ++c) {
149 if (c->func) {
150 if(prev_c)
151 printf ("\n\t%s%s", prev_c->usage ? prev_c->usage : "",
152 prev_c->usage ? "\n" : "");
153 prev_c = c;
154 printf ("%s", c->name);
155 } else
156 printf (", %s", c->name);
157 }
158 if(prev_c)
159 printf ("\n\t%s%s", prev_c->usage ? prev_c->usage : "",
160 prev_c->usage ? "\n" : "");
161 } else {
162 c = sl_match (cmds, argv[1], 0);
163 if (c == NULL)
164 printf ("No such command: %s. "
165 "Try \"help\" for a list of all commands\n",
166 argv[1]);
167 else {
168 printf ("%s\t%s\n", c->name, c->usage);
169 if(c->help && *c->help)
170 printf ("%s\n", c->help);
171 if((++c)->name && c->func == NULL) {
172 printf ("Synonyms:");
173 while (c->name && c->func == NULL)
174 printf ("\t%s", (c++)->name);
175 printf ("\n");
176 }
177 }
178 }
179 }
180
181 #ifdef HAVE_READLINE
182
183 char *readline(char *prompt);
184 void add_history(char *p);
185
186 #else
187
188 static char *
readline(char * prompt)189 readline(char *prompt)
190 {
191 char buf[BUFSIZ];
192 printf ("%s", prompt);
193 fflush (stdout);
194 if(fgets(buf, sizeof(buf), stdin) == NULL)
195 return NULL;
196 buf[strcspn(buf, "\r\n")] = '\0';
197 return strdup(buf);
198 }
199
200 static void
add_history(char * p)201 add_history(char *p)
202 {
203 }
204
205 #endif
206
207 int
sl_command(SL_cmd * cmds,int argc,char ** argv)208 sl_command(SL_cmd *cmds, int argc, char **argv)
209 {
210 SL_cmd *c;
211 c = sl_match (cmds, argv[0], 0);
212 if (c == NULL)
213 return -1;
214 return (*c->func)(argc, argv);
215 }
216
217 struct sl_data {
218 int max_count;
219 char **ptr;
220 };
221
222 int
sl_make_argv(char * line,int * ret_argc,char *** ret_argv)223 sl_make_argv(char *line, int *ret_argc, char ***ret_argv)
224 {
225 char *p, *begining;
226 int argc, nargv;
227 char **argv;
228 int quote = 0;
229
230 nargv = 10;
231 argv = malloc(nargv * sizeof(*argv));
232 if(argv == NULL)
233 return ENOMEM;
234 argc = 0;
235
236 p = line;
237
238 while(isspace((unsigned char)*p))
239 p++;
240 begining = p;
241
242 while (1) {
243 if (*p == '\0') {
244 ;
245 } else if (*p == '"') {
246 quote = !quote;
247 memmove(&p[0], &p[1], strlen(&p[1]) + 1);
248 continue;
249 } else if (*p == '\\') {
250 if (p[1] == '\0')
251 goto failed;
252 memmove(&p[0], &p[1], strlen(&p[1]) + 1);
253 p += 2;
254 continue;
255 } else if (quote || !isspace((unsigned char)*p)) {
256 p++;
257 continue;
258 } else
259 *p++ = '\0';
260 if (quote)
261 goto failed;
262 if(argc == nargv - 1) {
263 char **tmp;
264 nargv *= 2;
265 tmp = realloc (argv, nargv * sizeof(*argv));
266 if (tmp == NULL) {
267 free(argv);
268 return ENOMEM;
269 }
270 argv = tmp;
271 }
272 argv[argc++] = begining;
273 while(isspace((unsigned char)*p))
274 p++;
275 if (*p == '\0')
276 break;
277 begining = p;
278 }
279 argv[argc] = NULL;
280 *ret_argc = argc;
281 *ret_argv = argv;
282 return 0;
283 failed:
284 free(argv);
285 return ERANGE;
286 }
287
288 static jmp_buf sl_jmp;
289
sl_sigint(int sig)290 static void sl_sigint(int sig)
291 {
292 longjmp(sl_jmp, 1);
293 }
294
sl_readline(const char * prompt)295 static char *sl_readline(const char *prompt)
296 {
297 char *s;
298 void (*old)(int);
299 old = signal(SIGINT, sl_sigint);
300 if(setjmp(sl_jmp))
301 printf("\n");
302 s = readline(rk_UNCONST(prompt));
303 signal(SIGINT, old);
304 return s;
305 }
306
307 /* return values:
308 * 0 on success,
309 * -1 on fatal error,
310 * -2 if EOF, or
311 * return value of command */
312 int
sl_command_loop(SL_cmd * cmds,const char * prompt,void ** data)313 sl_command_loop(SL_cmd *cmds, const char *prompt, void **data)
314 {
315 int ret = 0;
316 char *buf;
317 int argc;
318 char **argv;
319
320 buf = sl_readline(prompt);
321 if(buf == NULL)
322 return -2;
323
324 if(*buf)
325 add_history(buf);
326 ret = sl_make_argv(buf, &argc, &argv);
327 if(ret) {
328 fprintf(stderr, "sl_loop: out of memory\n");
329 free(buf);
330 return -1;
331 }
332 if (argc >= 1) {
333 ret = sl_command(cmds, argc, argv);
334 if(ret == -1) {
335 sl_did_you_mean(cmds, argv[0]);
336 ret = 0;
337 }
338 }
339 free(buf);
340 free(argv);
341 return ret;
342 }
343
344 int
sl_loop(SL_cmd * cmds,const char * prompt)345 sl_loop(SL_cmd *cmds, const char *prompt)
346 {
347 void *data = NULL;
348 int ret;
349 while((ret = sl_command_loop(cmds, prompt, &data)) >= 0)
350 ;
351 return ret;
352 }
353
354 void
sl_apropos(SL_cmd * cmd,const char * topic)355 sl_apropos (SL_cmd *cmd, const char *topic)
356 {
357 for (; cmd->name != NULL; ++cmd)
358 if (cmd->usage != NULL && strstr(cmd->usage, topic) != NULL)
359 printf ("%-20s%s\n", cmd->name, cmd->usage);
360 }
361
362 /*
363 * Help to be used with slc.
364 */
365
366 void
sl_slc_help(SL_cmd * cmds,int argc,char ** argv)367 sl_slc_help (SL_cmd *cmds, int argc, char **argv)
368 {
369 if(argc == 0) {
370 sl_help(cmds, 1, argv - 1 /* XXX */);
371 } else {
372 SL_cmd *c = sl_match (cmds, argv[0], 0);
373 if(c == NULL) {
374 fprintf (stderr, "No such command: %s. "
375 "Try \"help\" for a list of commands\n",
376 argv[0]);
377 } else {
378 if(c->func) {
379 static char help[] = "--help";
380 char *fake[3];
381 fake[0] = argv[0];
382 fake[1] = help;
383 fake[2] = NULL;
384 (*c->func)(2, fake);
385 fprintf(stderr, "\n");
386 }
387 if(c->help && *c->help)
388 fprintf (stderr, "%s\n", c->help);
389 if((++c)->name && c->func == NULL) {
390 int f = 0;
391 fprintf (stderr, "Synonyms:");
392 while (c->name && c->func == NULL) {
393 fprintf (stderr, "%s%s", f ? ", " : " ", (c++)->name);
394 f = 1;
395 }
396 fprintf (stderr, "\n");
397 }
398 }
399 }
400 }
401
402 /* OptimalStringAlignmentDistance */
403
404 static int
osad(const char * s1,const char * s2)405 osad(const char *s1, const char *s2)
406 {
407 size_t l1 = strlen(s1), l2 = strlen(s2), i, j;
408 int *row0, *row1, *row2, *tmp, cost;
409
410 row0 = calloc(sizeof(int), l2 + 1);
411 row1 = calloc(sizeof(int), l2 + 1);
412 row2 = calloc(sizeof(int), l2 + 1);
413
414 for (j = 0; j < l2 + 1; j++)
415 row1[j] = j;
416
417 for (i = 0; i < l1; i++) {
418
419 row2[0] = i + 1;
420
421 for (j = 0; j < l2; j++) {
422
423 row2[j + 1] = row1[j] + (s1[i] != s2[j]); /* substitute */
424
425 if (row2[j + 1] > row1[j + 1] + 1) /* delete */
426 row2[j + 1] = row1[j + 1] + 1;
427 if (row2[j + 1] > row2[j] + 1) /* insert */
428 row2[j + 1] = row2[j] + 1;
429 if (j > 0 && i > 0 && s1[i - 1] != s2[j - 1] && s1[i - 1] == s2[j] && s1[i] == s2[j - 1] && row2[j + 1] < row0[j - 1]) /* transposition */
430 row2[j + 1] = row0[j - 1] + 1;
431 }
432
433 tmp = row0;
434 row0 = row1;
435 row1 = row2;
436 row2 = tmp;
437 }
438
439 cost = row1[l2];
440
441 free(row0);
442 free(row1);
443 free(row2);
444
445 return cost;
446 }
447
448 /**
449 * Will propose a list of command that are almost matching the command
450 * used, if there is no matching, will ask the user to use "help".
451 *
452 * @param cmds command array to use for matching
453 * @param match the command that didn't exists
454 */
455
456 void
sl_did_you_mean(SL_cmd * cmds,const char * match)457 sl_did_you_mean(SL_cmd *cmds, const char *match)
458 {
459 int *metrics, best_match = INT_MAX;
460 SL_cmd *c;
461 size_t n;
462
463 for (n = 0, c = cmds; c->name; c++, n++)
464 ;
465 if (n == 0)
466 return;
467 metrics = calloc(n, sizeof(metrics[0]));
468 if (metrics == NULL)
469 return;
470
471 for (n = 0; cmds[n].name; n++) {
472 metrics[n] = osad(match, cmds[n].name);
473 if (metrics[n] < best_match)
474 best_match = metrics[n];
475 }
476 if (best_match == INT_MAX) {
477 free(metrics);
478 fprintf(stderr, "What kind of command is %s", match);
479 return;
480 }
481
482 /* if match distance is low, propose that for the user */
483 if (best_match < 7) {
484
485 fprintf(stderr, "error: %s is not a known command, did you mean ?\n", match);
486 for (n = 0; cmds[n].name; n++) {
487 if (metrics[n] == best_match) {
488 fprintf(stderr, "\t%s\n", cmds[n].name);
489 }
490 }
491 fprintf(stderr, "\n");
492
493 } else {
494
495 fprintf(stderr, "error: %s is not a command, use \"help\" for more list of commands.\n", match);
496 }
497
498 free(metrics);
499
500 return;
501 }
502