Fossil SCM
Automatically add HTML formatting to the on-line help when displaying in a web-page. (This is a work-in-progress. No everything displays correctly, yet.)
Commit
f1230cb3f3ace07b43e261c47a15441d86f0b1cb9ad69d5f62eeac2caeac289d
Parent
e1d8cea28ae3952…
1 file changed
+227
-42
+227
-42
| --- src/dispatch.c | ||
| +++ src/dispatch.c | ||
| @@ -216,35 +216,210 @@ | ||
| 216 | 216 | if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){ |
| 217 | 217 | blob_appendf(pList, " %s", aCommand[i].zName); |
| 218 | 218 | } |
| 219 | 219 | } |
| 220 | 220 | } |
| 221 | + | |
| 222 | +/* | |
| 223 | +** Return the index of the first non-space character that follows | |
| 224 | +** a span of two or more spaces. Return 0 if there is not gap. | |
| 225 | +*/ | |
| 226 | +static int hasGap(const char *z, int n){ | |
| 227 | + int i; | |
| 228 | + for(i=3; i<n-1; i++){ | |
| 229 | + if( z[i]==' ' && z[i+1]!=' ' && z[i-1]==' ' ) return i+1; | |
| 230 | + } | |
| 231 | + return 0 ; | |
| 232 | +} | |
| 233 | + | |
| 234 | +/* | |
| 235 | +** Append text to pOut, adding formatting markup. Terms that | |
| 236 | +** have all lower-case letters are within <tt>..</tt>. Terms | |
| 237 | +** that have all upper-case letters are within <i>..</i>. | |
| 238 | +*/ | |
| 239 | +static void appendMixedFont(Blob *pOut, const char *z, int n){ | |
| 240 | + const char *zEnd = ""; | |
| 241 | + int i = 0; | |
| 242 | + int j; | |
| 243 | + while( i<n ){ | |
| 244 | + if( z[i]==' ' ){ | |
| 245 | + for(j=i+1; j<n && z[j]==' '; j++){} | |
| 246 | + blob_append(pOut, z+i, j-i); | |
| 247 | + i = j; | |
| 248 | + }else{ | |
| 249 | + for(j=i; j<n && z[j]!=' ' && !fossil_isalpha(z[j]); j++){} | |
| 250 | + if( j>=n || z[j]==' ' ){ | |
| 251 | + zEnd = ""; | |
| 252 | + }else{ | |
| 253 | + if( fossil_isupper(z[j]) ){ | |
| 254 | + blob_append(pOut, "<i>",3); | |
| 255 | + zEnd = "</i>"; | |
| 256 | + }else{ | |
| 257 | + blob_append(pOut, "<tt>", 4); | |
| 258 | + zEnd = "</tt>"; | |
| 259 | + } | |
| 260 | + } | |
| 261 | + while( j<n && z[j]!=' ' ){ j++; } | |
| 262 | + blob_appendf(pOut, "%#h", j-i, z+i); | |
| 263 | + if( zEnd[0] ) blob_append(pOut, zEnd, -1); | |
| 264 | + i = j; | |
| 265 | + } | |
| 266 | + } | |
| 267 | +} | |
| 221 | 268 | |
| 222 | 269 | /* |
| 223 | 270 | ** Attempt to reformat plain-text help into HTML for display on a webpage. |
| 224 | 271 | ** |
| 225 | 272 | ** The HTML output is appended to Blob pHtml, which should already be |
| 226 | 273 | ** initialized. |
| 274 | +** | |
| 275 | +** Formatting rules: | |
| 276 | +** | |
| 277 | +** * Bullet lists are indented from the surrounding text by | |
| 278 | +** at least one space. Each bullet begins with " * ". | |
| 279 | +** | |
| 280 | +** * Display lists are indented from the surrounding text. | |
| 281 | +** Each tag begins with "-" or occur on a line that is | |
| 282 | +** followed by two spaces and a non-space. <dd> elements can begin | |
| 283 | +** on the same line as long as they are separated by at least | |
| 284 | +** two spaces. | |
| 285 | +** | |
| 286 | +** * Indented text is show verbatim (<pre>...</pre>) | |
| 227 | 287 | */ |
| 228 | 288 | static void help_to_html(const char *zHelp, Blob *pHtml){ |
| 229 | - char *s; | |
| 230 | - char *d; | |
| 231 | - char *z; | |
| 232 | - | |
| 233 | - /* Transform "%fossil" into just "fossil" */ | |
| 234 | - z = s = d = mprintf("%s", zHelp); | |
| 235 | - while( *s ){ | |
| 236 | - if( *s=='%' && strncmp(s, "%fossil", 7)==0 ){ | |
| 237 | - s++; | |
| 238 | - }else{ | |
| 239 | - *d++ = *s++; | |
| 240 | - } | |
| 241 | - } | |
| 242 | - *d = 0; | |
| 243 | - | |
| 244 | - blob_appendf(pHtml, "<pre>\n%h\n</pre>\n", z); | |
| 245 | - fossil_free(z); | |
| 289 | + int i; | |
| 290 | + char c; | |
| 291 | + int nIndent = 0; | |
| 292 | + int wantP = 0; | |
| 293 | + int wantBR = 0; | |
| 294 | + int aIndent[10]; | |
| 295 | + const char *azEnd[10]; | |
| 296 | + int iLevel = 0; | |
| 297 | + int isLI = 0; | |
| 298 | + static const char *zEndDL = "</dl></blockquote>"; | |
| 299 | + static const char *zEndPRE = "</pre></blockquote>"; | |
| 300 | + static const char *zEndUL = "</ul>"; | |
| 301 | + static const char *zEndDD = "</dd>"; | |
| 302 | + | |
| 303 | + aIndent[0] = 0; | |
| 304 | + azEnd[0] = ""; | |
| 305 | + while( zHelp[0] ){ | |
| 306 | + i = 0; | |
| 307 | + while( (c = zHelp[i])!=0 | |
| 308 | + && c!='\n' | |
| 309 | + && (c!='%' || strncmp(zHelp+i,"%fossil",7)!=0) | |
| 310 | + ){ i++; } | |
| 311 | + if( c=='%' ){ | |
| 312 | + if( i ) blob_appendf(pHtml, "%#h", i, zHelp); | |
| 313 | + zHelp += i + 1; | |
| 314 | + i = 0; | |
| 315 | + wantBR = 1; | |
| 316 | + continue; | |
| 317 | + } | |
| 318 | + for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){} | |
| 319 | + if( nIndent==i ){ | |
| 320 | + if( c==0 ) break; | |
| 321 | + blob_append(pHtml, "\n", 1); | |
| 322 | + wantP = 1; | |
| 323 | + wantBR = 0; | |
| 324 | + zHelp += i+1; | |
| 325 | + continue; | |
| 326 | + } | |
| 327 | + if( nIndent+2<i && zHelp[nIndent]=='*' && zHelp[nIndent+1]==' ' ){ | |
| 328 | + nIndent += 2; | |
| 329 | + while( nIndent<i && zHelp[nIndent]==' '){ nIndent++; } | |
| 330 | + isLI = 1; | |
| 331 | + }else{ | |
| 332 | + isLI = 0; | |
| 333 | + } | |
| 334 | + while( iLevel>0 && aIndent[iLevel]>nIndent ){ | |
| 335 | + blob_append(pHtml, azEnd[iLevel--], -1); | |
| 336 | + } | |
| 337 | + if( nIndent>aIndent[iLevel] ){ | |
| 338 | + assert( iLevel<ArraySize(aIndent)-2 ); | |
| 339 | + if( isLI ){ | |
| 340 | + iLevel++; | |
| 341 | + aIndent[iLevel] = nIndent; | |
| 342 | + azEnd[iLevel] = zEndUL; | |
| 343 | + blob_append(pHtml, "<ul>\n", 5); | |
| 344 | + }else if( zHelp[nIndent]=='-' || hasGap(zHelp+nIndent,i-nIndent) ){ | |
| 345 | + iLevel++; | |
| 346 | + aIndent[iLevel] = nIndent; | |
| 347 | + azEnd[iLevel] = zEndDL; | |
| 348 | + blob_append(pHtml, "<blockquote><dl>\n", -1); | |
| 349 | + }else if( wantP && azEnd[iLevel]!=zEndDL ){ | |
| 350 | + iLevel++; | |
| 351 | + aIndent[iLevel] = nIndent; | |
| 352 | + azEnd[iLevel] = zEndPRE; | |
| 353 | + blob_append(pHtml, "<blockquote><pre>", -1); | |
| 354 | + wantP = 0; | |
| 355 | + } | |
| 356 | + } | |
| 357 | + if( isLI ){ | |
| 358 | + blob_append(pHtml, "<li> ", 5); | |
| 359 | + } | |
| 360 | + if( wantP ){ | |
| 361 | + blob_append(pHtml, "<p> ", 4); | |
| 362 | + wantP = 0; | |
| 363 | + } | |
| 364 | + if( azEnd[iLevel]==zEndDL ){ | |
| 365 | + int iDD; | |
| 366 | + blob_append(pHtml, "<dt> ", 5); | |
| 367 | + iDD = hasGap(zHelp+nIndent, i-nIndent); | |
| 368 | + if( iDD ){ | |
| 369 | + int x; | |
| 370 | + assert( iLevel<ArraySize(aIndent)-1 ); | |
| 371 | + iLevel++; | |
| 372 | + aIndent[iLevel] = x = nIndent+iDD; | |
| 373 | + azEnd[iLevel] = zEndDD; | |
| 374 | + appendMixedFont(pHtml, zHelp+nIndent, iDD-2); | |
| 375 | + blob_appendf(pHtml, "</dt><dd>%#h\n", x, zHelp+x); | |
| 376 | + }else{ | |
| 377 | + appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); | |
| 378 | + blob_append(pHtml, "</dt>\n", 6); | |
| 379 | + } | |
| 380 | + }else if( wantBR ){ | |
| 381 | + appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); | |
| 382 | + blob_append(pHtml, "<br>\n", 5); | |
| 383 | + wantBR = 0; | |
| 384 | + }else{ | |
| 385 | + blob_appendf(pHtml, "%#h\n", i-nIndent, zHelp+nIndent); | |
| 386 | + } | |
| 387 | + zHelp += i+1; | |
| 388 | + i = 0; | |
| 389 | + if( c==0 ) break; | |
| 390 | + } | |
| 391 | + while( iLevel>0 ){ | |
| 392 | + blob_appendf(pHtml, "%s\n", azEnd[iLevel--]); | |
| 393 | + } | |
| 394 | +} | |
| 395 | + | |
| 396 | +/* | |
| 397 | +** Format help text for TTY display. | |
| 398 | +*/ | |
| 399 | +static void help_to_text(const char *zHelp, Blob *pText){ | |
| 400 | + int i; | |
| 401 | + char c; | |
| 402 | + for(i=0; (c = zHelp[i])!=0; i++){ | |
| 403 | + if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){ | |
| 404 | + if( i>0 ) blob_append(pText, zHelp, i); | |
| 405 | + blob_append(pText, "fossil", 6); | |
| 406 | + zHelp += i+7; | |
| 407 | + i = -1; | |
| 408 | + continue; | |
| 409 | + } | |
| 410 | + if( c=='\n' && strncmp(zHelp+i+1,"> ",3)==0 ){ | |
| 411 | + if( i>0 ) blob_append(pText, zHelp, i-1); | |
| 412 | + blob_append(pText, " ", 6); | |
| 413 | + zHelp += i+3; | |
| 414 | + i = -1; | |
| 415 | + continue; | |
| 416 | + } | |
| 417 | + } | |
| 418 | + if( i>0 ){ | |
| 419 | + blob_append(pText, zHelp, i); | |
| 420 | + } | |
| 246 | 421 | } |
| 247 | 422 | |
| 248 | 423 | /* |
| 249 | 424 | ** COMMAND: test-all-help |
| 250 | 425 | ** |
| @@ -292,19 +467,24 @@ | ||
| 292 | 467 | }else{ |
| 293 | 468 | fossil_print("---\n"); |
| 294 | 469 | } |
| 295 | 470 | for(i=0; i<MX_COMMAND; i++){ |
| 296 | 471 | if( (aCommand[i].eCmdFlags & mask)==0 ) continue; |
| 297 | - fossil_print("# %s\n", aCommand[i].zName); | |
| 298 | 472 | if( useHtml ){ |
| 299 | 473 | Blob html; |
| 300 | - blob_zero(&html); | |
| 474 | + blob_init(&html, 0, 0); | |
| 301 | 475 | help_to_html(aCommand[i].zHelp, &html); |
| 302 | - fossil_print("%s\n\n", blob_str(&html)); | |
| 476 | + fossil_print("<h1>%h</h1>\n", aCommand[i].zName); | |
| 477 | + fossil_print("%s\n<hr>\n", blob_str(&html)); | |
| 303 | 478 | blob_reset(&html); |
| 304 | 479 | }else{ |
| 305 | - fossil_print("%s\n\n", aCommand[i].zHelp); | |
| 480 | + Blob txt; | |
| 481 | + blob_init(&txt, 0, 0); | |
| 482 | + help_to_text(aCommand[i].zHelp, &txt); | |
| 483 | + fossil_print("# %s\n", aCommand[i].zName); | |
| 484 | + fossil_print("%s\n\n", blob_str(&txt)); | |
| 485 | + blob_reset(&txt); | |
| 306 | 486 | } |
| 307 | 487 | } |
| 308 | 488 | if( useHtml ){ |
| 309 | 489 | fossil_print("<!-- end_all_help -->\n"); |
| 310 | 490 | }else{ |
| @@ -689,31 +869,37 @@ | ||
| 689 | 869 | ; |
| 690 | 870 | |
| 691 | 871 | /* |
| 692 | 872 | ** COMMAND: help |
| 693 | 873 | ** |
| 694 | -** Usage: %fossil help TOPIC | |
| 695 | -** or: %fossil TOPIC --help | |
| 874 | +** Usage: %fossil help [OPTIONS] [TOPIC] | |
| 696 | 875 | ** |
| 697 | 876 | ** Display information on how to use TOPIC, which may be a command, webpage, or |
| 698 | -** setting. Webpage names begin with "/". To display a list of available | |
| 699 | -** topics, use one of: | |
| 877 | +** setting. Webpage names begin with "/". If TOPIC is omitted, a list of | |
| 878 | +** topics is returned. | |
| 879 | +** | |
| 880 | +** The following options can be used when TOPIC is omitted: | |
| 881 | +** | |
| 882 | +** -a|--all List both command and auxiliary commands | |
| 883 | +** -o|--options List command-line options common to all commands | |
| 884 | +** -s|--setting List setting names | |
| 885 | +** -t|--test List unsupported "test" commands | |
| 886 | +** -x|--aux List only auxiliary commands | |
| 887 | +** -w|--www List all web pages | |
| 888 | +** | |
| 889 | +** These options can be used when TOPIC is present: | |
| 700 | 890 | ** |
| 701 | -** %fossil help Show common commands | |
| 702 | -** %fossil help -a|--all Show both common and auxiliary commands | |
| 703 | -** %fossil help -o|--options Show command-line options common to all cmds | |
| 704 | -** %fossil help -s|--setting Show setting names | |
| 705 | -** %fossil help -t|--test Show test commands only | |
| 706 | -** %fossil help -x|--aux Show auxiliary commands only | |
| 707 | -** %fossil help -w|--www Show list of webpages | |
| 891 | +** -h|--html Format output as HTML rather than plain text | |
| 708 | 892 | */ |
| 709 | 893 | void help_cmd(void){ |
| 710 | 894 | int rc; |
| 711 | 895 | int isPage = 0; |
| 712 | 896 | const char *z; |
| 713 | 897 | const char *zCmdOrPage; |
| 714 | 898 | const CmdOrPage *pCmd = 0; |
| 899 | + int useHtml = 0; | |
| 900 | + Blob txt; | |
| 715 | 901 | if( g.argc<3 ){ |
| 716 | 902 | z = g.argv[0]; |
| 717 | 903 | fossil_print( |
| 718 | 904 | "Usage: %s help TOPIC\n" |
| 719 | 905 | "Common commands: (use \"%s help help\" for more options)\n", |
| @@ -744,10 +930,11 @@ | ||
| 744 | 930 | } |
| 745 | 931 | else if( find_option("setting","s",0) ){ |
| 746 | 932 | command_list(0, CMDFLAG_SETTING); |
| 747 | 933 | return; |
| 748 | 934 | } |
| 935 | + useHtml = find_option("html","h",0)!=0; | |
| 749 | 936 | isPage = ('/' == *g.argv[2]) ? 1 : 0; |
| 750 | 937 | if(isPage){ |
| 751 | 938 | zCmdOrPage = "page"; |
| 752 | 939 | }else{ |
| 753 | 940 | zCmdOrPage = "command or setting"; |
| @@ -782,20 +969,18 @@ | ||
| 782 | 969 | fossil_print("Setting: \"%s\"%s\n\n", |
| 783 | 970 | pCmd->zName, |
| 784 | 971 | (pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : "" |
| 785 | 972 | ); |
| 786 | 973 | } |
| 787 | - while( *z ){ | |
| 788 | - if( *z=='%' && strncmp(z, "%fossil", 7)==0 ){ | |
| 789 | - fossil_print("%s", g.argv[0]); | |
| 790 | - z += 7; | |
| 791 | - }else{ | |
| 792 | - putchar(*z); | |
| 793 | - z++; | |
| 794 | - } | |
| 795 | - } | |
| 796 | - putchar('\n'); | |
| 974 | + blob_init(&txt, 0, 0); | |
| 975 | + if( useHtml ){ | |
| 976 | + help_to_html(z, &txt); | |
| 977 | + }else{ | |
| 978 | + help_to_text(z, &txt); | |
| 979 | + } | |
| 980 | + fossil_print("%s\n", blob_str(&txt)); | |
| 981 | + blob_reset(&txt); | |
| 797 | 982 | } |
| 798 | 983 | |
| 799 | 984 | /* |
| 800 | 985 | ** Return a pointer to the setting information array. |
| 801 | 986 | ** |
| 802 | 987 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -216,35 +216,210 @@ | |
| 216 | if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){ |
| 217 | blob_appendf(pList, " %s", aCommand[i].zName); |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | /* |
| 223 | ** Attempt to reformat plain-text help into HTML for display on a webpage. |
| 224 | ** |
| 225 | ** The HTML output is appended to Blob pHtml, which should already be |
| 226 | ** initialized. |
| 227 | */ |
| 228 | static void help_to_html(const char *zHelp, Blob *pHtml){ |
| 229 | char *s; |
| 230 | char *d; |
| 231 | char *z; |
| 232 | |
| 233 | /* Transform "%fossil" into just "fossil" */ |
| 234 | z = s = d = mprintf("%s", zHelp); |
| 235 | while( *s ){ |
| 236 | if( *s=='%' && strncmp(s, "%fossil", 7)==0 ){ |
| 237 | s++; |
| 238 | }else{ |
| 239 | *d++ = *s++; |
| 240 | } |
| 241 | } |
| 242 | *d = 0; |
| 243 | |
| 244 | blob_appendf(pHtml, "<pre>\n%h\n</pre>\n", z); |
| 245 | fossil_free(z); |
| 246 | } |
| 247 | |
| 248 | /* |
| 249 | ** COMMAND: test-all-help |
| 250 | ** |
| @@ -292,19 +467,24 @@ | |
| 292 | }else{ |
| 293 | fossil_print("---\n"); |
| 294 | } |
| 295 | for(i=0; i<MX_COMMAND; i++){ |
| 296 | if( (aCommand[i].eCmdFlags & mask)==0 ) continue; |
| 297 | fossil_print("# %s\n", aCommand[i].zName); |
| 298 | if( useHtml ){ |
| 299 | Blob html; |
| 300 | blob_zero(&html); |
| 301 | help_to_html(aCommand[i].zHelp, &html); |
| 302 | fossil_print("%s\n\n", blob_str(&html)); |
| 303 | blob_reset(&html); |
| 304 | }else{ |
| 305 | fossil_print("%s\n\n", aCommand[i].zHelp); |
| 306 | } |
| 307 | } |
| 308 | if( useHtml ){ |
| 309 | fossil_print("<!-- end_all_help -->\n"); |
| 310 | }else{ |
| @@ -689,31 +869,37 @@ | |
| 689 | ; |
| 690 | |
| 691 | /* |
| 692 | ** COMMAND: help |
| 693 | ** |
| 694 | ** Usage: %fossil help TOPIC |
| 695 | ** or: %fossil TOPIC --help |
| 696 | ** |
| 697 | ** Display information on how to use TOPIC, which may be a command, webpage, or |
| 698 | ** setting. Webpage names begin with "/". To display a list of available |
| 699 | ** topics, use one of: |
| 700 | ** |
| 701 | ** %fossil help Show common commands |
| 702 | ** %fossil help -a|--all Show both common and auxiliary commands |
| 703 | ** %fossil help -o|--options Show command-line options common to all cmds |
| 704 | ** %fossil help -s|--setting Show setting names |
| 705 | ** %fossil help -t|--test Show test commands only |
| 706 | ** %fossil help -x|--aux Show auxiliary commands only |
| 707 | ** %fossil help -w|--www Show list of webpages |
| 708 | */ |
| 709 | void help_cmd(void){ |
| 710 | int rc; |
| 711 | int isPage = 0; |
| 712 | const char *z; |
| 713 | const char *zCmdOrPage; |
| 714 | const CmdOrPage *pCmd = 0; |
| 715 | if( g.argc<3 ){ |
| 716 | z = g.argv[0]; |
| 717 | fossil_print( |
| 718 | "Usage: %s help TOPIC\n" |
| 719 | "Common commands: (use \"%s help help\" for more options)\n", |
| @@ -744,10 +930,11 @@ | |
| 744 | } |
| 745 | else if( find_option("setting","s",0) ){ |
| 746 | command_list(0, CMDFLAG_SETTING); |
| 747 | return; |
| 748 | } |
| 749 | isPage = ('/' == *g.argv[2]) ? 1 : 0; |
| 750 | if(isPage){ |
| 751 | zCmdOrPage = "page"; |
| 752 | }else{ |
| 753 | zCmdOrPage = "command or setting"; |
| @@ -782,20 +969,18 @@ | |
| 782 | fossil_print("Setting: \"%s\"%s\n\n", |
| 783 | pCmd->zName, |
| 784 | (pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : "" |
| 785 | ); |
| 786 | } |
| 787 | while( *z ){ |
| 788 | if( *z=='%' && strncmp(z, "%fossil", 7)==0 ){ |
| 789 | fossil_print("%s", g.argv[0]); |
| 790 | z += 7; |
| 791 | }else{ |
| 792 | putchar(*z); |
| 793 | z++; |
| 794 | } |
| 795 | } |
| 796 | putchar('\n'); |
| 797 | } |
| 798 | |
| 799 | /* |
| 800 | ** Return a pointer to the setting information array. |
| 801 | ** |
| 802 |
| --- src/dispatch.c | |
| +++ src/dispatch.c | |
| @@ -216,35 +216,210 @@ | |
| 216 | if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){ |
| 217 | blob_appendf(pList, " %s", aCommand[i].zName); |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | /* |
| 223 | ** Return the index of the first non-space character that follows |
| 224 | ** a span of two or more spaces. Return 0 if there is not gap. |
| 225 | */ |
| 226 | static int hasGap(const char *z, int n){ |
| 227 | int i; |
| 228 | for(i=3; i<n-1; i++){ |
| 229 | if( z[i]==' ' && z[i+1]!=' ' && z[i-1]==' ' ) return i+1; |
| 230 | } |
| 231 | return 0 ; |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | ** Append text to pOut, adding formatting markup. Terms that |
| 236 | ** have all lower-case letters are within <tt>..</tt>. Terms |
| 237 | ** that have all upper-case letters are within <i>..</i>. |
| 238 | */ |
| 239 | static void appendMixedFont(Blob *pOut, const char *z, int n){ |
| 240 | const char *zEnd = ""; |
| 241 | int i = 0; |
| 242 | int j; |
| 243 | while( i<n ){ |
| 244 | if( z[i]==' ' ){ |
| 245 | for(j=i+1; j<n && z[j]==' '; j++){} |
| 246 | blob_append(pOut, z+i, j-i); |
| 247 | i = j; |
| 248 | }else{ |
| 249 | for(j=i; j<n && z[j]!=' ' && !fossil_isalpha(z[j]); j++){} |
| 250 | if( j>=n || z[j]==' ' ){ |
| 251 | zEnd = ""; |
| 252 | }else{ |
| 253 | if( fossil_isupper(z[j]) ){ |
| 254 | blob_append(pOut, "<i>",3); |
| 255 | zEnd = "</i>"; |
| 256 | }else{ |
| 257 | blob_append(pOut, "<tt>", 4); |
| 258 | zEnd = "</tt>"; |
| 259 | } |
| 260 | } |
| 261 | while( j<n && z[j]!=' ' ){ j++; } |
| 262 | blob_appendf(pOut, "%#h", j-i, z+i); |
| 263 | if( zEnd[0] ) blob_append(pOut, zEnd, -1); |
| 264 | i = j; |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | /* |
| 270 | ** Attempt to reformat plain-text help into HTML for display on a webpage. |
| 271 | ** |
| 272 | ** The HTML output is appended to Blob pHtml, which should already be |
| 273 | ** initialized. |
| 274 | ** |
| 275 | ** Formatting rules: |
| 276 | ** |
| 277 | ** * Bullet lists are indented from the surrounding text by |
| 278 | ** at least one space. Each bullet begins with " * ". |
| 279 | ** |
| 280 | ** * Display lists are indented from the surrounding text. |
| 281 | ** Each tag begins with "-" or occur on a line that is |
| 282 | ** followed by two spaces and a non-space. <dd> elements can begin |
| 283 | ** on the same line as long as they are separated by at least |
| 284 | ** two spaces. |
| 285 | ** |
| 286 | ** * Indented text is show verbatim (<pre>...</pre>) |
| 287 | */ |
| 288 | static void help_to_html(const char *zHelp, Blob *pHtml){ |
| 289 | int i; |
| 290 | char c; |
| 291 | int nIndent = 0; |
| 292 | int wantP = 0; |
| 293 | int wantBR = 0; |
| 294 | int aIndent[10]; |
| 295 | const char *azEnd[10]; |
| 296 | int iLevel = 0; |
| 297 | int isLI = 0; |
| 298 | static const char *zEndDL = "</dl></blockquote>"; |
| 299 | static const char *zEndPRE = "</pre></blockquote>"; |
| 300 | static const char *zEndUL = "</ul>"; |
| 301 | static const char *zEndDD = "</dd>"; |
| 302 | |
| 303 | aIndent[0] = 0; |
| 304 | azEnd[0] = ""; |
| 305 | while( zHelp[0] ){ |
| 306 | i = 0; |
| 307 | while( (c = zHelp[i])!=0 |
| 308 | && c!='\n' |
| 309 | && (c!='%' || strncmp(zHelp+i,"%fossil",7)!=0) |
| 310 | ){ i++; } |
| 311 | if( c=='%' ){ |
| 312 | if( i ) blob_appendf(pHtml, "%#h", i, zHelp); |
| 313 | zHelp += i + 1; |
| 314 | i = 0; |
| 315 | wantBR = 1; |
| 316 | continue; |
| 317 | } |
| 318 | for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){} |
| 319 | if( nIndent==i ){ |
| 320 | if( c==0 ) break; |
| 321 | blob_append(pHtml, "\n", 1); |
| 322 | wantP = 1; |
| 323 | wantBR = 0; |
| 324 | zHelp += i+1; |
| 325 | continue; |
| 326 | } |
| 327 | if( nIndent+2<i && zHelp[nIndent]=='*' && zHelp[nIndent+1]==' ' ){ |
| 328 | nIndent += 2; |
| 329 | while( nIndent<i && zHelp[nIndent]==' '){ nIndent++; } |
| 330 | isLI = 1; |
| 331 | }else{ |
| 332 | isLI = 0; |
| 333 | } |
| 334 | while( iLevel>0 && aIndent[iLevel]>nIndent ){ |
| 335 | blob_append(pHtml, azEnd[iLevel--], -1); |
| 336 | } |
| 337 | if( nIndent>aIndent[iLevel] ){ |
| 338 | assert( iLevel<ArraySize(aIndent)-2 ); |
| 339 | if( isLI ){ |
| 340 | iLevel++; |
| 341 | aIndent[iLevel] = nIndent; |
| 342 | azEnd[iLevel] = zEndUL; |
| 343 | blob_append(pHtml, "<ul>\n", 5); |
| 344 | }else if( zHelp[nIndent]=='-' || hasGap(zHelp+nIndent,i-nIndent) ){ |
| 345 | iLevel++; |
| 346 | aIndent[iLevel] = nIndent; |
| 347 | azEnd[iLevel] = zEndDL; |
| 348 | blob_append(pHtml, "<blockquote><dl>\n", -1); |
| 349 | }else if( wantP && azEnd[iLevel]!=zEndDL ){ |
| 350 | iLevel++; |
| 351 | aIndent[iLevel] = nIndent; |
| 352 | azEnd[iLevel] = zEndPRE; |
| 353 | blob_append(pHtml, "<blockquote><pre>", -1); |
| 354 | wantP = 0; |
| 355 | } |
| 356 | } |
| 357 | if( isLI ){ |
| 358 | blob_append(pHtml, "<li> ", 5); |
| 359 | } |
| 360 | if( wantP ){ |
| 361 | blob_append(pHtml, "<p> ", 4); |
| 362 | wantP = 0; |
| 363 | } |
| 364 | if( azEnd[iLevel]==zEndDL ){ |
| 365 | int iDD; |
| 366 | blob_append(pHtml, "<dt> ", 5); |
| 367 | iDD = hasGap(zHelp+nIndent, i-nIndent); |
| 368 | if( iDD ){ |
| 369 | int x; |
| 370 | assert( iLevel<ArraySize(aIndent)-1 ); |
| 371 | iLevel++; |
| 372 | aIndent[iLevel] = x = nIndent+iDD; |
| 373 | azEnd[iLevel] = zEndDD; |
| 374 | appendMixedFont(pHtml, zHelp+nIndent, iDD-2); |
| 375 | blob_appendf(pHtml, "</dt><dd>%#h\n", x, zHelp+x); |
| 376 | }else{ |
| 377 | appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); |
| 378 | blob_append(pHtml, "</dt>\n", 6); |
| 379 | } |
| 380 | }else if( wantBR ){ |
| 381 | appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); |
| 382 | blob_append(pHtml, "<br>\n", 5); |
| 383 | wantBR = 0; |
| 384 | }else{ |
| 385 | blob_appendf(pHtml, "%#h\n", i-nIndent, zHelp+nIndent); |
| 386 | } |
| 387 | zHelp += i+1; |
| 388 | i = 0; |
| 389 | if( c==0 ) break; |
| 390 | } |
| 391 | while( iLevel>0 ){ |
| 392 | blob_appendf(pHtml, "%s\n", azEnd[iLevel--]); |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | /* |
| 397 | ** Format help text for TTY display. |
| 398 | */ |
| 399 | static void help_to_text(const char *zHelp, Blob *pText){ |
| 400 | int i; |
| 401 | char c; |
| 402 | for(i=0; (c = zHelp[i])!=0; i++){ |
| 403 | if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){ |
| 404 | if( i>0 ) blob_append(pText, zHelp, i); |
| 405 | blob_append(pText, "fossil", 6); |
| 406 | zHelp += i+7; |
| 407 | i = -1; |
| 408 | continue; |
| 409 | } |
| 410 | if( c=='\n' && strncmp(zHelp+i+1,"> ",3)==0 ){ |
| 411 | if( i>0 ) blob_append(pText, zHelp, i-1); |
| 412 | blob_append(pText, " ", 6); |
| 413 | zHelp += i+3; |
| 414 | i = -1; |
| 415 | continue; |
| 416 | } |
| 417 | } |
| 418 | if( i>0 ){ |
| 419 | blob_append(pText, zHelp, i); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | /* |
| 424 | ** COMMAND: test-all-help |
| 425 | ** |
| @@ -292,19 +467,24 @@ | |
| 467 | }else{ |
| 468 | fossil_print("---\n"); |
| 469 | } |
| 470 | for(i=0; i<MX_COMMAND; i++){ |
| 471 | if( (aCommand[i].eCmdFlags & mask)==0 ) continue; |
| 472 | if( useHtml ){ |
| 473 | Blob html; |
| 474 | blob_init(&html, 0, 0); |
| 475 | help_to_html(aCommand[i].zHelp, &html); |
| 476 | fossil_print("<h1>%h</h1>\n", aCommand[i].zName); |
| 477 | fossil_print("%s\n<hr>\n", blob_str(&html)); |
| 478 | blob_reset(&html); |
| 479 | }else{ |
| 480 | Blob txt; |
| 481 | blob_init(&txt, 0, 0); |
| 482 | help_to_text(aCommand[i].zHelp, &txt); |
| 483 | fossil_print("# %s\n", aCommand[i].zName); |
| 484 | fossil_print("%s\n\n", blob_str(&txt)); |
| 485 | blob_reset(&txt); |
| 486 | } |
| 487 | } |
| 488 | if( useHtml ){ |
| 489 | fossil_print("<!-- end_all_help -->\n"); |
| 490 | }else{ |
| @@ -689,31 +869,37 @@ | |
| 869 | ; |
| 870 | |
| 871 | /* |
| 872 | ** COMMAND: help |
| 873 | ** |
| 874 | ** Usage: %fossil help [OPTIONS] [TOPIC] |
| 875 | ** |
| 876 | ** Display information on how to use TOPIC, which may be a command, webpage, or |
| 877 | ** setting. Webpage names begin with "/". If TOPIC is omitted, a list of |
| 878 | ** topics is returned. |
| 879 | ** |
| 880 | ** The following options can be used when TOPIC is omitted: |
| 881 | ** |
| 882 | ** -a|--all List both command and auxiliary commands |
| 883 | ** -o|--options List command-line options common to all commands |
| 884 | ** -s|--setting List setting names |
| 885 | ** -t|--test List unsupported "test" commands |
| 886 | ** -x|--aux List only auxiliary commands |
| 887 | ** -w|--www List all web pages |
| 888 | ** |
| 889 | ** These options can be used when TOPIC is present: |
| 890 | ** |
| 891 | ** -h|--html Format output as HTML rather than plain text |
| 892 | */ |
| 893 | void help_cmd(void){ |
| 894 | int rc; |
| 895 | int isPage = 0; |
| 896 | const char *z; |
| 897 | const char *zCmdOrPage; |
| 898 | const CmdOrPage *pCmd = 0; |
| 899 | int useHtml = 0; |
| 900 | Blob txt; |
| 901 | if( g.argc<3 ){ |
| 902 | z = g.argv[0]; |
| 903 | fossil_print( |
| 904 | "Usage: %s help TOPIC\n" |
| 905 | "Common commands: (use \"%s help help\" for more options)\n", |
| @@ -744,10 +930,11 @@ | |
| 930 | } |
| 931 | else if( find_option("setting","s",0) ){ |
| 932 | command_list(0, CMDFLAG_SETTING); |
| 933 | return; |
| 934 | } |
| 935 | useHtml = find_option("html","h",0)!=0; |
| 936 | isPage = ('/' == *g.argv[2]) ? 1 : 0; |
| 937 | if(isPage){ |
| 938 | zCmdOrPage = "page"; |
| 939 | }else{ |
| 940 | zCmdOrPage = "command or setting"; |
| @@ -782,20 +969,18 @@ | |
| 969 | fossil_print("Setting: \"%s\"%s\n\n", |
| 970 | pCmd->zName, |
| 971 | (pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : "" |
| 972 | ); |
| 973 | } |
| 974 | blob_init(&txt, 0, 0); |
| 975 | if( useHtml ){ |
| 976 | help_to_html(z, &txt); |
| 977 | }else{ |
| 978 | help_to_text(z, &txt); |
| 979 | } |
| 980 | fossil_print("%s\n", blob_str(&txt)); |
| 981 | blob_reset(&txt); |
| 982 | } |
| 983 | |
| 984 | /* |
| 985 | ** Return a pointer to the setting information array. |
| 986 | ** |
| 987 |