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