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
41static void
42mandoc_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
113SL_cmd *
114sl_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
136void
137sl_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
183char *readline(char *prompt);
184void add_history(char *p);
185
186#else
187
188static char *
189readline(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
200static void
201add_history(char *p)
202{
203}
204
205#endif
206
207int
208sl_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
217struct sl_data {
218    int max_count;
219    char **ptr;
220};
221
222int
223sl_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;
283failed:
284    free(argv);
285    return ERANGE;
286}
287
288static jmp_buf sl_jmp;
289
290static void sl_sigint(int sig)
291{
292    longjmp(sl_jmp, 1);
293}
294
295static 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 */
312int
313sl_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
344int
345sl_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
354void
355sl_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
366void
367sl_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
404static int
405osad(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
456void
457sl_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