Fossil SCM

The "fossil test" command offers suggestions if the help requested is not recognized.

drh 2020-06-19 20:13 trunk
Commit a99d9173bf898d68028716cd3cc6149de1390695dbcd34aec7facfd81a78fdba
2 files changed +113 -7 +5
+113 -7
--- src/dispatch.c
+++ src/dispatch.c
@@ -344,10 +344,108 @@
344344
countCmds( CMDFLAG_WEBPAGE ));
345345
fossil_print("settings: %4d\n",
346346
countCmds( CMDFLAG_SETTING ));
347347
fossil_print("total entries: %4d\n", MX_COMMAND);
348348
}
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
+}
349447
350448
/*
351449
** WEBPAGE: help
352450
** URL: /help?name=CMD
353451
**
@@ -656,21 +754,29 @@
656754
}else{
657755
zCmdOrPage = "command or setting";
658756
zCmdOrPagePlural = "commands and settings";
659757
}
660758
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");
663774
fossil_print(" fossil help -a ;# show all commands\n");
664775
fossil_print(" fossil help -w ;# show all web-pages\n");
665776
fossil_print(" fossil help -s ;# show all settings\n");
666777
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);
672778
}
673779
z = pCmd->zHelp;
674780
if( z==0 ){
675781
fossil_fatal("no help available for the %s %s",
676782
pCmd->zName, zCmdOrPage);
677783
--- 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
--- src/mkindex.c
+++ src/mkindex.c
@@ -390,10 +390,12 @@
390390
** Build the binary search table.
391391
*/
392392
void build_table(void){
393393
int i;
394394
int nWeb = 0;
395
+ int mxLen = 0;
396
+ int len;
395397
396398
qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare);
397399
398400
printf(
399401
"/* Automatically generated code\n"
@@ -435,10 +437,11 @@
435437
/* Generate the aCommand[] table */
436438
printf("static const CmdOrPage aCommand[] = {\n");
437439
for(i=0; i<nFixed; i++){
438440
const char *z = aEntry[i].zPath;
439441
int n = strlen(z);
442
+ if( n>mxLen ) mxLen = n;
440443
if( aEntry[i].zIf ){
441444
printf("%s", aEntry[i].zIf);
442445
}else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){
443446
nWeb++;
444447
}
@@ -452,10 +455,12 @@
452455
);
453456
if( aEntry[i].zIf ) printf("#endif\n");
454457
}
455458
printf("};\n");
456459
printf("#define FOSSIL_FIRST_CMD %d\n", nWeb);
460
+ printf("#define FOSSIL_MX_CMDNAME %d /* max length of any command name */\n",
461
+ mxLen);
457462
458463
/* Generate the aSetting[] table */
459464
printf("const Setting aSetting[] = {\n");
460465
for(i=0; i<nFixed; i++){
461466
const char *z;
462467
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button