Fossil SCM
The "fossil test" command offers suggestions if the help requested is not recognized.
Commit
a99d9173bf898d68028716cd3cc6149de1390695dbcd34aec7facfd81a78fdba
Parent
c7bb647f7ac04b2…
2 files changed
+113
-7
+5
+113
-7
| --- src/dispatch.c | ||
| +++ src/dispatch.c | ||
| @@ -344,10 +344,108 @@ | ||
| 344 | 344 | countCmds( CMDFLAG_WEBPAGE )); |
| 345 | 345 | fossil_print("settings: %4d\n", |
| 346 | 346 | countCmds( CMDFLAG_SETTING )); |
| 347 | 347 | fossil_print("total entries: %4d\n", MX_COMMAND); |
| 348 | 348 | } |
| 349 | + | |
| 350 | +/* | |
| 351 | +** Compute an estimate of the edit-distance between to input strings. | |
| 352 | +** | |
| 353 | +** The first string is the input. The second is the pattern. Only the | |
| 354 | +** first 100 characters of the pattern are considered. | |
| 355 | +*/ | |
| 356 | +static int edit_distance(const char *zA, const char *zB){ | |
| 357 | + int nA = (int)strlen(zA); | |
| 358 | + int nB = (int)strlen(zB); | |
| 359 | + int i, j, m; | |
| 360 | + int p0, p1, c0; | |
| 361 | + int a[100]; | |
| 362 | + static const int incr = 4; | |
| 363 | + | |
| 364 | + for(j=0; j<nB; j++) a[j] = 1; | |
| 365 | + for(i=0; i<nA; i++){ | |
| 366 | + p0 = i==0 ? 0 : i*incr-1; | |
| 367 | + c0 = i*incr; | |
| 368 | + for(j=0; j<nB; j++){ | |
| 369 | + int m = 999; | |
| 370 | + p1 = a[j]; | |
| 371 | + if( zA[i]==zB[j] ){ | |
| 372 | + m = p0; | |
| 373 | + }else{ | |
| 374 | + m = c0+2; | |
| 375 | + if( m>p1+2 ) m = p1+2; | |
| 376 | + if( m>p0+3 ) m = p0+3; | |
| 377 | + } | |
| 378 | + c0 = a[j]; | |
| 379 | + a[j] = m; | |
| 380 | + p0 = p1; | |
| 381 | + } | |
| 382 | + } | |
| 383 | + m = a[nB-1]; | |
| 384 | + for(j=0; j<nB-1; j++){ | |
| 385 | + if( a[j]+1<m ) m = a[j]+1; | |
| 386 | + } | |
| 387 | + return m; | |
| 388 | +} | |
| 389 | + | |
| 390 | +/* | |
| 391 | +** Fill the pointer array with names of commands that approximately | |
| 392 | +** match the input. Return the number of approximate matches. | |
| 393 | +** | |
| 394 | +** Closest matches appear first. | |
| 395 | +*/ | |
| 396 | +int dispatch_approx_match(const char *zIn, int nArray, const char **azArray){ | |
| 397 | + int i; | |
| 398 | + int bestScore; | |
| 399 | + int m; | |
| 400 | + int n = 0; | |
| 401 | + int mnScore = 0; | |
| 402 | + int mxScore = 99999; | |
| 403 | + int iFirst, iLast; | |
| 404 | + | |
| 405 | + if( zIn[0]=='/' ){ | |
| 406 | + iFirst = 0; | |
| 407 | + iLast = FOSSIL_FIRST_CMD-1; | |
| 408 | + }else{ | |
| 409 | + iFirst = FOSSIL_FIRST_CMD; | |
| 410 | + iLast = MX_COMMAND-1; | |
| 411 | + } | |
| 412 | + | |
| 413 | + while( n<nArray ){ | |
| 414 | + bestScore = mxScore; | |
| 415 | + for(i=iFirst; i<=iLast; i++){ | |
| 416 | + m = edit_distance(zIn, aCommand[i].zName); | |
| 417 | + if( m<mnScore ) continue; | |
| 418 | + if( m==mnScore ){ | |
| 419 | + azArray[n++] = aCommand[i].zName; | |
| 420 | + if( n>=nArray ) return n; | |
| 421 | + }else if( m<bestScore ){ | |
| 422 | + bestScore = m; | |
| 423 | + } | |
| 424 | + } | |
| 425 | + if( bestScore>=mxScore ) break; | |
| 426 | + mnScore = bestScore; | |
| 427 | + } | |
| 428 | + return n; | |
| 429 | +} | |
| 430 | + | |
| 431 | +/* | |
| 432 | +** COMMAND: test-approx-match | |
| 433 | +** | |
| 434 | +** Test the approximate match algorithm | |
| 435 | +*/ | |
| 436 | +void test_approx_match_command(void){ | |
| 437 | + int i, j, n; | |
| 438 | + const char *az[20]; | |
| 439 | + for(i=2; i<g.argc; i++){ | |
| 440 | + fossil_print("%s:\n", g.argv[i]); | |
| 441 | + n = dispatch_approx_match(g.argv[i], 20, az); | |
| 442 | + for(j=0; j<n; j++){ | |
| 443 | + fossil_print(" %s\n", az[j]); | |
| 444 | + } | |
| 445 | + } | |
| 446 | +} | |
| 349 | 447 | |
| 350 | 448 | /* |
| 351 | 449 | ** WEBPAGE: help |
| 352 | 450 | ** URL: /help?name=CMD |
| 353 | 451 | ** |
| @@ -656,21 +754,29 @@ | ||
| 656 | 754 | }else{ |
| 657 | 755 | zCmdOrPage = "command or setting"; |
| 658 | 756 | zCmdOrPagePlural = "commands and settings"; |
| 659 | 757 | } |
| 660 | 758 | rc = dispatch_name_search(g.argv[2], CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); |
| 661 | - if( rc==1 ){ | |
| 662 | - fossil_print("unknown %s: %s\nConsider using:\n", zCmdOrPage, g.argv[2]); | |
| 759 | + if( rc ){ | |
| 760 | + int i, n; | |
| 761 | + const char *az[5]; | |
| 762 | + if( rc==1 ){ | |
| 763 | + fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]); | |
| 764 | + }else{ | |
| 765 | + fossil_print("ambiguous %s prefix: %s\n", | |
| 766 | + zCmdOrPage, g.argv[2]); | |
| 767 | + } | |
| 768 | + fossil_print("Did you mean one of:\n"); | |
| 769 | + n = dispatch_approx_match(g.argv[2], 5, az); | |
| 770 | + for(i=0; i<n; i++){ | |
| 771 | + fossil_print(" * %s\n", az[i]); | |
| 772 | + } | |
| 773 | + fossil_print("Also consider using:\n"); | |
| 663 | 774 | fossil_print(" fossil help -a ;# show all commands\n"); |
| 664 | 775 | fossil_print(" fossil help -w ;# show all web-pages\n"); |
| 665 | 776 | fossil_print(" fossil help -s ;# show all settings\n"); |
| 666 | 777 | fossil_exit(1); |
| 667 | - }else if( rc==2 ){ | |
| 668 | - fossil_print("ambiguous %s prefix: %s\nMatching %s:\n", | |
| 669 | - zCmdOrPage, g.argv[2], zCmdOrPagePlural); | |
| 670 | - command_list(g.argv[2], 0xff); | |
| 671 | - fossil_exit(1); | |
| 672 | 778 | } |
| 673 | 779 | z = pCmd->zHelp; |
| 674 | 780 | if( z==0 ){ |
| 675 | 781 | fossil_fatal("no help available for the %s %s", |
| 676 | 782 | pCmd->zName, zCmdOrPage); |
| 677 | 783 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -344,10 +344,108 @@ | |
| 344 | countCmds( CMDFLAG_WEBPAGE )); |
| 345 | fossil_print("settings: %4d\n", |
| 346 | countCmds( CMDFLAG_SETTING )); |
| 347 | fossil_print("total entries: %4d\n", MX_COMMAND); |
| 348 | } |
| 349 | |
| 350 | /* |
| 351 | ** WEBPAGE: help |
| 352 | ** URL: /help?name=CMD |
| 353 | ** |
| @@ -656,21 +754,29 @@ | |
| 656 | }else{ |
| 657 | zCmdOrPage = "command or setting"; |
| 658 | zCmdOrPagePlural = "commands and settings"; |
| 659 | } |
| 660 | rc = dispatch_name_search(g.argv[2], CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); |
| 661 | if( rc==1 ){ |
| 662 | fossil_print("unknown %s: %s\nConsider using:\n", zCmdOrPage, g.argv[2]); |
| 663 | fossil_print(" fossil help -a ;# show all commands\n"); |
| 664 | fossil_print(" fossil help -w ;# show all web-pages\n"); |
| 665 | fossil_print(" fossil help -s ;# show all settings\n"); |
| 666 | fossil_exit(1); |
| 667 | }else if( rc==2 ){ |
| 668 | fossil_print("ambiguous %s prefix: %s\nMatching %s:\n", |
| 669 | zCmdOrPage, g.argv[2], zCmdOrPagePlural); |
| 670 | command_list(g.argv[2], 0xff); |
| 671 | fossil_exit(1); |
| 672 | } |
| 673 | z = pCmd->zHelp; |
| 674 | if( z==0 ){ |
| 675 | fossil_fatal("no help available for the %s %s", |
| 676 | pCmd->zName, zCmdOrPage); |
| 677 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -344,10 +344,108 @@ | |
| 344 | countCmds( CMDFLAG_WEBPAGE )); |
| 345 | fossil_print("settings: %4d\n", |
| 346 | countCmds( CMDFLAG_SETTING )); |
| 347 | fossil_print("total entries: %4d\n", MX_COMMAND); |
| 348 | } |
| 349 | |
| 350 | /* |
| 351 | ** Compute an estimate of the edit-distance between to input strings. |
| 352 | ** |
| 353 | ** The first string is the input. The second is the pattern. Only the |
| 354 | ** first 100 characters of the pattern are considered. |
| 355 | */ |
| 356 | static int edit_distance(const char *zA, const char *zB){ |
| 357 | int nA = (int)strlen(zA); |
| 358 | int nB = (int)strlen(zB); |
| 359 | int i, j, m; |
| 360 | int p0, p1, c0; |
| 361 | int a[100]; |
| 362 | static const int incr = 4; |
| 363 | |
| 364 | for(j=0; j<nB; j++) a[j] = 1; |
| 365 | for(i=0; i<nA; i++){ |
| 366 | p0 = i==0 ? 0 : i*incr-1; |
| 367 | c0 = i*incr; |
| 368 | for(j=0; j<nB; j++){ |
| 369 | int m = 999; |
| 370 | p1 = a[j]; |
| 371 | if( zA[i]==zB[j] ){ |
| 372 | m = p0; |
| 373 | }else{ |
| 374 | m = c0+2; |
| 375 | if( m>p1+2 ) m = p1+2; |
| 376 | if( m>p0+3 ) m = p0+3; |
| 377 | } |
| 378 | c0 = a[j]; |
| 379 | a[j] = m; |
| 380 | p0 = p1; |
| 381 | } |
| 382 | } |
| 383 | m = a[nB-1]; |
| 384 | for(j=0; j<nB-1; j++){ |
| 385 | if( a[j]+1<m ) m = a[j]+1; |
| 386 | } |
| 387 | return m; |
| 388 | } |
| 389 | |
| 390 | /* |
| 391 | ** Fill the pointer array with names of commands that approximately |
| 392 | ** match the input. Return the number of approximate matches. |
| 393 | ** |
| 394 | ** Closest matches appear first. |
| 395 | */ |
| 396 | int dispatch_approx_match(const char *zIn, int nArray, const char **azArray){ |
| 397 | int i; |
| 398 | int bestScore; |
| 399 | int m; |
| 400 | int n = 0; |
| 401 | int mnScore = 0; |
| 402 | int mxScore = 99999; |
| 403 | int iFirst, iLast; |
| 404 | |
| 405 | if( zIn[0]=='/' ){ |
| 406 | iFirst = 0; |
| 407 | iLast = FOSSIL_FIRST_CMD-1; |
| 408 | }else{ |
| 409 | iFirst = FOSSIL_FIRST_CMD; |
| 410 | iLast = MX_COMMAND-1; |
| 411 | } |
| 412 | |
| 413 | while( n<nArray ){ |
| 414 | bestScore = mxScore; |
| 415 | for(i=iFirst; i<=iLast; i++){ |
| 416 | m = edit_distance(zIn, aCommand[i].zName); |
| 417 | if( m<mnScore ) continue; |
| 418 | if( m==mnScore ){ |
| 419 | azArray[n++] = aCommand[i].zName; |
| 420 | if( n>=nArray ) return n; |
| 421 | }else if( m<bestScore ){ |
| 422 | bestScore = m; |
| 423 | } |
| 424 | } |
| 425 | if( bestScore>=mxScore ) break; |
| 426 | mnScore = bestScore; |
| 427 | } |
| 428 | return n; |
| 429 | } |
| 430 | |
| 431 | /* |
| 432 | ** COMMAND: test-approx-match |
| 433 | ** |
| 434 | ** Test the approximate match algorithm |
| 435 | */ |
| 436 | void test_approx_match_command(void){ |
| 437 | int i, j, n; |
| 438 | const char *az[20]; |
| 439 | for(i=2; i<g.argc; i++){ |
| 440 | fossil_print("%s:\n", g.argv[i]); |
| 441 | n = dispatch_approx_match(g.argv[i], 20, az); |
| 442 | for(j=0; j<n; j++){ |
| 443 | fossil_print(" %s\n", az[j]); |
| 444 | } |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | /* |
| 449 | ** WEBPAGE: help |
| 450 | ** URL: /help?name=CMD |
| 451 | ** |
| @@ -656,21 +754,29 @@ | |
| 754 | }else{ |
| 755 | zCmdOrPage = "command or setting"; |
| 756 | zCmdOrPagePlural = "commands and settings"; |
| 757 | } |
| 758 | rc = dispatch_name_search(g.argv[2], CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); |
| 759 | if( rc ){ |
| 760 | int i, n; |
| 761 | const char *az[5]; |
| 762 | if( rc==1 ){ |
| 763 | fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]); |
| 764 | }else{ |
| 765 | fossil_print("ambiguous %s prefix: %s\n", |
| 766 | zCmdOrPage, g.argv[2]); |
| 767 | } |
| 768 | fossil_print("Did you mean one of:\n"); |
| 769 | n = dispatch_approx_match(g.argv[2], 5, az); |
| 770 | for(i=0; i<n; i++){ |
| 771 | fossil_print(" * %s\n", az[i]); |
| 772 | } |
| 773 | fossil_print("Also consider using:\n"); |
| 774 | fossil_print(" fossil help -a ;# show all commands\n"); |
| 775 | fossil_print(" fossil help -w ;# show all web-pages\n"); |
| 776 | fossil_print(" fossil help -s ;# show all settings\n"); |
| 777 | fossil_exit(1); |
| 778 | } |
| 779 | z = pCmd->zHelp; |
| 780 | if( z==0 ){ |
| 781 | fossil_fatal("no help available for the %s %s", |
| 782 | pCmd->zName, zCmdOrPage); |
| 783 |
+5
| --- src/mkindex.c | ||
| +++ src/mkindex.c | ||
| @@ -390,10 +390,12 @@ | ||
| 390 | 390 | ** Build the binary search table. |
| 391 | 391 | */ |
| 392 | 392 | void build_table(void){ |
| 393 | 393 | int i; |
| 394 | 394 | int nWeb = 0; |
| 395 | + int mxLen = 0; | |
| 396 | + int len; | |
| 395 | 397 | |
| 396 | 398 | qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare); |
| 397 | 399 | |
| 398 | 400 | printf( |
| 399 | 401 | "/* Automatically generated code\n" |
| @@ -435,10 +437,11 @@ | ||
| 435 | 437 | /* Generate the aCommand[] table */ |
| 436 | 438 | printf("static const CmdOrPage aCommand[] = {\n"); |
| 437 | 439 | for(i=0; i<nFixed; i++){ |
| 438 | 440 | const char *z = aEntry[i].zPath; |
| 439 | 441 | int n = strlen(z); |
| 442 | + if( n>mxLen ) mxLen = n; | |
| 440 | 443 | if( aEntry[i].zIf ){ |
| 441 | 444 | printf("%s", aEntry[i].zIf); |
| 442 | 445 | }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){ |
| 443 | 446 | nWeb++; |
| 444 | 447 | } |
| @@ -452,10 +455,12 @@ | ||
| 452 | 455 | ); |
| 453 | 456 | if( aEntry[i].zIf ) printf("#endif\n"); |
| 454 | 457 | } |
| 455 | 458 | printf("};\n"); |
| 456 | 459 | printf("#define FOSSIL_FIRST_CMD %d\n", nWeb); |
| 460 | + printf("#define FOSSIL_MX_CMDNAME %d /* max length of any command name */\n", | |
| 461 | + mxLen); | |
| 457 | 462 | |
| 458 | 463 | /* Generate the aSetting[] table */ |
| 459 | 464 | printf("const Setting aSetting[] = {\n"); |
| 460 | 465 | for(i=0; i<nFixed; i++){ |
| 461 | 466 | const char *z; |
| 462 | 467 |
| --- src/mkindex.c | |
| +++ src/mkindex.c | |
| @@ -390,10 +390,12 @@ | |
| 390 | ** Build the binary search table. |
| 391 | */ |
| 392 | void build_table(void){ |
| 393 | int i; |
| 394 | int nWeb = 0; |
| 395 | |
| 396 | qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare); |
| 397 | |
| 398 | printf( |
| 399 | "/* Automatically generated code\n" |
| @@ -435,10 +437,11 @@ | |
| 435 | /* Generate the aCommand[] table */ |
| 436 | printf("static const CmdOrPage aCommand[] = {\n"); |
| 437 | for(i=0; i<nFixed; i++){ |
| 438 | const char *z = aEntry[i].zPath; |
| 439 | int n = strlen(z); |
| 440 | if( aEntry[i].zIf ){ |
| 441 | printf("%s", aEntry[i].zIf); |
| 442 | }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){ |
| 443 | nWeb++; |
| 444 | } |
| @@ -452,10 +455,12 @@ | |
| 452 | ); |
| 453 | if( aEntry[i].zIf ) printf("#endif\n"); |
| 454 | } |
| 455 | printf("};\n"); |
| 456 | printf("#define FOSSIL_FIRST_CMD %d\n", nWeb); |
| 457 | |
| 458 | /* Generate the aSetting[] table */ |
| 459 | printf("const Setting aSetting[] = {\n"); |
| 460 | for(i=0; i<nFixed; i++){ |
| 461 | const char *z; |
| 462 |
| --- src/mkindex.c | |
| +++ src/mkindex.c | |
| @@ -390,10 +390,12 @@ | |
| 390 | ** Build the binary search table. |
| 391 | */ |
| 392 | void build_table(void){ |
| 393 | int i; |
| 394 | int nWeb = 0; |
| 395 | int mxLen = 0; |
| 396 | int len; |
| 397 | |
| 398 | qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare); |
| 399 | |
| 400 | printf( |
| 401 | "/* Automatically generated code\n" |
| @@ -435,10 +437,11 @@ | |
| 437 | /* Generate the aCommand[] table */ |
| 438 | printf("static const CmdOrPage aCommand[] = {\n"); |
| 439 | for(i=0; i<nFixed; i++){ |
| 440 | const char *z = aEntry[i].zPath; |
| 441 | int n = strlen(z); |
| 442 | if( n>mxLen ) mxLen = n; |
| 443 | if( aEntry[i].zIf ){ |
| 444 | printf("%s", aEntry[i].zIf); |
| 445 | }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){ |
| 446 | nWeb++; |
| 447 | } |
| @@ -452,10 +455,12 @@ | |
| 455 | ); |
| 456 | if( aEntry[i].zIf ) printf("#endif\n"); |
| 457 | } |
| 458 | printf("};\n"); |
| 459 | printf("#define FOSSIL_FIRST_CMD %d\n", nWeb); |
| 460 | printf("#define FOSSIL_MX_CMDNAME %d /* max length of any command name */\n", |
| 461 | mxLen); |
| 462 | |
| 463 | /* Generate the aSetting[] table */ |
| 464 | printf("const Setting aSetting[] = {\n"); |
| 465 | for(i=0; i<nFixed; i++){ |
| 466 | const char *z; |
| 467 |