1 /*
2 ** Copyright (c) 2016 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code used to map command names (ex: "help", "commit",
19 ** "diff") or webpage names (ex: "/timeline", "/search") into the functions
20 ** that implement those commands and web pages and their associated help
21 ** text.
22 */
23 #include "config.h"
24 #include <assert.h>
25 #include "dispatch.h"
26 
27 #if INTERFACE
28 /*
29 ** An instance of this object defines everything we need to know about an
30 ** individual command, webpage, or setting.
31 */
32 struct CmdOrPage {
33   const char *zName;       /* Name.  Webpages start with "/". Commands do not */
34   void (*xFunc)(void);     /* Implementation function, or NULL for settings */
35   const char *zHelp;       /* Raw help text */
36   unsigned int eCmdFlags;  /* Flags */
37 };
38 
39 /***************************************************************************
40 ** These macros must match similar macros in mkindex.c
41 ** Allowed values for CmdOrPage.eCmdFlags.
42 */
43 #define CMDFLAG_1ST_TIER    0x0001      /* Most important commands */
44 #define CMDFLAG_2ND_TIER    0x0002      /* Obscure and seldom used commands */
45 #define CMDFLAG_TEST        0x0004      /* Commands for testing only */
46 #define CMDFLAG_WEBPAGE     0x0008      /* Web pages */
47 #define CMDFLAG_COMMAND     0x0010      /* A command */
48 #define CMDFLAG_SETTING     0x0020      /* A setting */
49 #define CMDFLAG_VERSIONABLE 0x0040      /* A versionable setting */
50 #define CMDFLAG_BLOCKTEXT   0x0080      /* Multi-line text setting */
51 #define CMDFLAG_BOOLEAN     0x0100      /* A boolean setting */
52 #define CMDFLAG_RAWCONTENT  0x0200      /* Do not interpret POST content */
53 /* NOTE:                    0x0400 = CMDFLAG_SENSITIVE in mkindex.c! */
54 #define CMDFLAG_HIDDEN      0x0800      /* Elide from most listings */
55 /**************************************************************************/
56 
57 /* Values for the 2nd parameter to dispatch_name_search() */
58 #define CMDFLAG_ANY         0x0038      /* Match anything */
59 #define CMDFLAG_PREFIX      0x0200      /* Prefix match is ok */
60 
61 #endif /* INTERFACE */
62 
63 /*
64 ** The page_index.h file contains the definition for aCommand[] - an array
65 ** of CmdOrPage objects that defines all available commands and webpages
66 ** known to Fossil.
67 **
68 ** The entries in aCommand[] are in sorted order by name.  Since webpage names
69 ** always begin with "/", all webpage names occur first.  The page_index.h file
70 ** also sets the FOSSIL_FIRST_CMD macro to be the *approximate* index
71 ** in aCommand[] of the first command entry.  FOSSIL_FIRST_CMD might be
72 ** slightly too low, and so the range FOSSIL_FIRST_CMD...MX_COMMAND might
73 ** contain a few webpage entries at the beginning.
74 **
75 ** The page_index.h file is generated by the mkindex program which scans all
76 ** source code files looking for header comments on the functions that
77 ** implement command and webpages.
78 */
79 #include "page_index.h"
80 #define MX_COMMAND count(aCommand)
81 
82 /*
83 ** Given a command, webpage, or setting name in zName, find the corresponding
84 ** CmdOrPage object and return a pointer to that object in *ppCmd.
85 **
86 ** The eType field is CMDFLAG_COMMAND to look up commands, CMDFLAG_WEBPAGE to
87 ** look up webpages, CMDFLAG_SETTING to look up settings, or CMDFLAG_ANY to look
88 ** for any.  If the CMDFLAG_PREFIX bit is set, then a prefix match is allowed.
89 **
90 ** Return values:
91 **    0:     Success.  *ppCmd is set to the appropriate CmdOrPage
92 **    1:     Not found.
93 **    2:     Ambiguous.  Two or more entries match.
94 */
dispatch_name_search(const char * zName,unsigned eType,const CmdOrPage ** ppCmd)95 int dispatch_name_search(
96   const char *zName,           /* Look for this name */
97   unsigned eType,              /* CMDFLAGS_* bits */
98   const CmdOrPage **ppCmd      /* Write the matching CmdOrPage object here */
99 ){
100   int upr, lwr, mid;
101   int nName = strlen(zName);
102   lwr = 0;
103   upr = MX_COMMAND - 1;
104   while( lwr<=upr ){
105     int c;
106     mid = (upr+lwr)/2;
107     c = strcmp(zName, aCommand[mid].zName);
108     if( c==0 ){
109       if( (aCommand[mid].eCmdFlags & eType)==0 ) return 1;
110       *ppCmd = &aCommand[mid];
111       return 0;  /* An exact match */
112     }else if( c<0 ){
113       upr = mid - 1;
114     }else{
115       lwr = mid + 1;
116     }
117   }
118   if( (eType & CMDFLAG_PREFIX)!=0
119    && lwr<MX_COMMAND
120    && strncmp(zName, aCommand[lwr].zName, nName)==0
121   ){
122     /* An inexact prefix match was found.  Scan the name table to try to find
123      * exactly one entry with this prefix and the requested type. */
124     for( mid=-1; lwr<MX_COMMAND
125               && strncmp(zName, aCommand[lwr].zName, nName)==0; ++lwr ){
126       if( aCommand[lwr].eCmdFlags & eType ){
127         if( mid<0 ){
128           mid = lwr;  /* Potential ambiguous prefix */
129         }else{
130           return 2;  /* Confirmed ambiguous prefix */
131         }
132       }
133     }
134     if( mid>=0 ){
135       *ppCmd = &aCommand[mid];
136       return 0;  /* Prefix match */
137     }
138   }
139   return 1;  /* Not found */
140 }
141 
142 /*
143 ** zName is the name of a webpage (eType==CMDFLAGS_WEBPAGE) that does not
144 ** exist in the dispatch table.  Check to see if this webpage name exists
145 ** as an alias in the CONFIG table of the repository.  If it is, then make
146 ** appropriate changes to the CGI environment and set *ppCmd to point to the
147 ** aliased command.
148 **
149 ** Return 0 if the command is successfully aliased.  Return 1 if there
150 ** is not alias for zName.  Any kind of error in the alias value causes a
151 ** error to be thrown.
152 **
153 ** Alias entries in the CONFIG table have a "name" value of "walias:NAME"
154 ** where NAME is the input page name.  The value is a string of the form
155 ** "NEWNAME?QUERYPARAMS".  The ?QUERYPARAMS is optional.  If present (and it
156 ** usually is), then all query parameters are added to the CGI environment.
157 ** Except, query parameters of the form "X!" cause any CGI X variable to be
158 ** removed.
159 */
dispatch_alias(const char * zName,const CmdOrPage ** ppCmd)160 int dispatch_alias(const char *zName, const CmdOrPage **ppCmd){
161   char *z;
162   char *zQ;
163   int i;
164 
165   z = db_text(0, "SELECT value FROM config WHERE name='walias:%q'",zName);
166   if( z==0 ) return 1;
167   for(i=0; z[i] && z[i]!='?'; i++){}
168   if( z[i]=='?' ){
169     z[i] = 0;
170     zQ = &z[i+1];
171   }else{
172     zQ = &z[i];
173   }
174   if( dispatch_name_search(z, CMDFLAG_WEBPAGE, ppCmd) ){
175     fossil_fatal("\"%s\" aliased to \"%s\" but \"%s\" does not exist",
176                  zName, z, z);
177   }
178   z = zQ;
179   while( *z ){
180     char *zName = z;
181     char *zValue = 0;
182     while( *z && *z!='=' && *z!='&' && *z!='!' ){ z++; }
183     if( *z=='=' ){
184       *z = 0;
185       z++;
186       zValue = z;
187       while( *z && *z!='&' ){ z++; }
188       if( *z ){
189         *z = 0;
190         z++;
191       }
192       dehttpize(zValue);
193     }else if( *z=='!' ){
194       *(z++) = 0;
195       cgi_delete_query_parameter(zName);
196       zName = "";
197     }else{
198       if( *z ){ *z++ = 0; }
199       zValue = "";
200     }
201     if( fossil_islower(zName[0]) ){
202       cgi_replace_query_parameter(zName, zValue);
203     }else if( fossil_isupper(zName[0]) ){
204       cgi_replace_query_parameter_tolower(zName, zValue);
205     }
206   }
207   return 0;
208 }
209 
210 /*
211 ** Fill Blob with a space-separated list of all command names that
212 ** match the prefix zPrefix.
213 */
dispatch_matching_names(const char * zPrefix,Blob * pList)214 void dispatch_matching_names(const char *zPrefix, Blob *pList){
215   int i;
216   int nPrefix = (int)strlen(zPrefix);
217   for(i=FOSSIL_FIRST_CMD; i<MX_COMMAND; i++){
218     if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){
219       blob_appendf(pList, " %s", aCommand[i].zName);
220     }
221   }
222 }
223 
224 /*
225 ** Return the index of the first non-space character that follows
226 ** a span of two or more spaces.  Return 0 if there is not gap.
227 */
hasGap(const char * z,int n)228 static int hasGap(const char *z, int n){
229   int i;
230   for(i=3; i<n-1; i++){
231     if( z[i]==' ' && z[i+1]!=' ' && z[i-1]==' ' && z[i-2]!='.' ) return i+1;
232   }
233   return 0 ;
234 }
235 
236 /*
237 ** Input string zIn starts with '['.  If the content is a hyperlink of the
238 ** form [[...]] then return the index of the closing ']'.  Otherwise return 0.
239 */
help_is_link(const char * z,int n)240 static int help_is_link(const char *z, int n){
241   int i;
242   char c;
243   if( n<5 ) return 0;
244   if( z[1]!='[' ) return 0;
245   for(i=3; i<n && (c = z[i])!=0; i++){
246     if( c==']' && z[i-1]==']' ) return i;
247   }
248   return 0;
249 }
250 
251 /*
252 ** Append text to pOut with changes:
253 **
254 **    *   Add hyperlink markup for [[...]]
255 **    *   Escape HTML characters: < > & and "
256 **    *   Change "%fossil" to just "fossil"
257 */
appendLinked(Blob * pOut,const char * z,int n)258 static void appendLinked(Blob *pOut, const char *z, int n){
259   int i = 0;
260   int j;
261   while( i<n ){
262     char c = z[i];
263     if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){
264       if( i ) blob_append(pOut, z, i);
265       z += i+2;
266       n -= i+2;
267       blob_appendf(pOut, "<a href='%R/help?cmd=%.*s'>%.*s</a>",
268          j-3, z, j-3, z);
269       z += j-1;
270       n -= j-1;
271       i = 0;
272     }else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){
273       if( i ) blob_append(pOut, z, i);
274       z += i+7;
275       n -= i+7;
276       blob_append(pOut, "fossil", 6);
277       i = 0;
278     }else if( c=='<' ){
279       if( i ) blob_append(pOut, z, i);
280       blob_append(pOut, "&lt;", 4);
281       z += i+1;
282       n -= i+1;
283       i = 0;
284     }else if( c=='>' ){
285       if( i ) blob_append(pOut, z, i);
286       blob_append(pOut, "&gt;", 4);
287       z += i+1;
288       n -= i+1;
289       i = 0;
290     }else if( c=='&' ){
291       if( i ) blob_append(pOut, z, i);
292       blob_append(pOut, "&amp;", 5);
293       z += i+1;
294       n -= i+1;
295       i = 0;
296     }else{
297       i++;
298     }
299   }
300   blob_append(pOut, z, i);
301 }
302 
303 /*
304 ** Append text to pOut, adding formatting markup.  Terms that
305 ** have all lower-case letters are within <tt>..</tt>.  Terms
306 ** that have all upper-case letters are within <i>..</i>.
307 */
appendMixedFont(Blob * pOut,const char * z,int n)308 static void appendMixedFont(Blob *pOut, const char *z, int n){
309   const char *zEnd = "";
310   int i = 0;
311   int j;
312   while( i<n ){
313     if( z[i]==' ' || z[i]=='=' ){
314       for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){}
315       appendLinked(pOut, z+i, j-i);
316       i = j;
317     }else{
318       for(j=i; j<n && z[j]!=' ' && z[j]!='=' && !fossil_isalpha(z[j]); j++){}
319       if( j>=n || z[j]==' ' || z[j]=='=' ){
320         zEnd = "";
321       }else{
322         if( fossil_isupper(z[j]) && z[i]!='-' ){
323           blob_append(pOut, "<i>",3);
324           zEnd = "</i>";
325         }else{
326           blob_append(pOut, "<tt>", 4);
327           zEnd = "</tt>";
328         }
329       }
330       while( j<n && z[j]!=' ' && z[j]!='=' ){ j++; }
331       appendLinked(pOut, z+i, j-i);
332       if( zEnd[0] ) blob_append(pOut, zEnd, -1);
333       i = j;
334     }
335   }
336 }
337 
338 /*
339 ** Attempt to reformat plain-text help into HTML for display on a webpage.
340 **
341 ** The HTML output is appended to Blob pHtml, which should already be
342 ** initialized.
343 **
344 ** Formatting rules:
345 **
346 **   *  Bullet lists are indented from the surrounding text by
347 **      at least one space.  Each bullet begins with " * ".
348 **
349 **   *  Display lists are indented from the surrounding text.
350 **      Each tag begins with "-" or occur on a line that is
351 **      followed by two spaces and a non-space.  <dd> elements can begin
352 **      on the same line as long as they are separated by at least
353 **      two spaces.
354 **
355 **   *  Indented text is show verbatim (<pre>...</pre>)
356 **
357 **   *  Lines that begin with "|" at the left margin are in <pre>...</pre>
358 */
help_to_html(const char * zHelp,Blob * pHtml)359 static void help_to_html(const char *zHelp, Blob *pHtml){
360   int i;
361   char c;
362   int nIndent = 0;
363   int wantP = 0;
364   int wantBR = 0;
365   int aIndent[10];
366   const char *azEnd[10];
367   int iLevel = 0;
368   int isLI = 0;
369   int isDT = 0;
370   int inPRE = 0;
371   static const char *zEndDL = "</dl></blockquote>";
372   static const char *zEndPRE = "</pre></blockquote>";
373   static const char *zEndUL = "</ul>";
374   static const char *zEndDD = "</dd>";
375 
376   aIndent[0] = 0;
377   azEnd[0] = "";
378   while( zHelp[0] ){
379     i = 0;
380     while( (c = zHelp[i])!=0 && c!='\n' ){
381       if( c=='%' && i>2 && zHelp[i-2]==':' && strncmp(zHelp+i,"%fossil",7)==0 ){
382         appendLinked(pHtml, zHelp, i);
383         zHelp += i+1;
384         i = 0;
385         wantBR = 1;
386         continue;
387       }
388       i++;
389     }
390     if( i>2 && (zHelp[0]=='>' || zHelp[0]=='|') && zHelp[1]==' ' ){
391       if( zHelp[0]=='>' ){
392         isDT = 1;
393         for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
394       }else{
395         if( !inPRE ){
396           blob_append(pHtml, "<pre>\n", -1);
397           inPRE = 1;
398         }
399       }
400     }else{
401       if( inPRE ){
402         blob_append(pHtml, "</pre>\n", -1);
403         inPRE = 0;
404       }
405       isDT = 0;
406       for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
407     }
408     if( inPRE ){
409       blob_append(pHtml, zHelp+1, i);
410       zHelp += i + 1;
411       continue;
412     }
413     if( nIndent==i ){
414       if( c==0 ) break;
415       if( iLevel && azEnd[iLevel]==zEndPRE ){
416         /* Skip the newline at the end of a <pre> */
417       }else{
418         blob_append_char(pHtml, '\n');
419       }
420       wantP = 1;
421       wantBR = 0;
422       zHelp += i+1;
423       continue;
424     }
425     if( nIndent+2<i && zHelp[nIndent]=='*' && zHelp[nIndent+1]==' ' ){
426       nIndent += 2;
427       while( nIndent<i && zHelp[nIndent]==' '){ nIndent++; }
428       isLI = 1;
429     }else{
430       isLI = 0;
431     }
432     while( iLevel>0 && aIndent[iLevel]>nIndent ){
433       blob_append(pHtml, azEnd[iLevel--], -1);
434     }
435     if( nIndent>aIndent[iLevel] ){
436       assert( iLevel<ArraySize(aIndent)-2 );
437       if( isLI ){
438         iLevel++;
439         aIndent[iLevel] = nIndent;
440         azEnd[iLevel] = zEndUL;
441         blob_append(pHtml, "<ul>\n", 5);
442       }else if( isDT
443              || zHelp[nIndent]=='-'
444              || hasGap(zHelp+nIndent,i-nIndent) ){
445         iLevel++;
446         aIndent[iLevel] = nIndent;
447         azEnd[iLevel] = zEndDL;
448         blob_append(pHtml, "<blockquote><dl>\n", -1);
449       }else if( azEnd[iLevel]==zEndDL ){
450         iLevel++;
451         aIndent[iLevel] = nIndent;
452         azEnd[iLevel] = zEndDD;
453         blob_append(pHtml, "<dd>", 4);
454       }else if( wantP ){
455         iLevel++;
456         aIndent[iLevel] = nIndent;
457         azEnd[iLevel] = zEndPRE;
458         blob_append(pHtml, "<blockquote><pre>", -1);
459         wantP = 0;
460       }
461     }
462     if( isLI ){
463       blob_append(pHtml, "<li> ", 5);
464     }
465     if( wantP ){
466       blob_append(pHtml, "<p> ", 4);
467       wantP = 0;
468     }
469     if( azEnd[iLevel]==zEndDL ){
470       int iDD;
471       blob_append(pHtml, "<dt> ", 5);
472       iDD = hasGap(zHelp+nIndent, i-nIndent);
473       if( iDD ){
474         int x;
475         assert( iLevel<ArraySize(aIndent)-1 );
476         iLevel++;
477         aIndent[iLevel] = x = nIndent+iDD;
478         azEnd[iLevel] = zEndDD;
479         appendMixedFont(pHtml, zHelp+nIndent, iDD-2);
480         blob_append(pHtml, "</dt><dd>",9);
481         appendLinked(pHtml, zHelp+x, i-x);
482       }else{
483         appendMixedFont(pHtml, zHelp+nIndent, i-nIndent);
484       }
485       blob_append(pHtml, "</dt>\n", 6);
486     }else if( wantBR ){
487       appendMixedFont(pHtml, zHelp+nIndent, i-nIndent);
488       blob_append(pHtml, "<br>\n", 5);
489       wantBR = 0;
490     }else{
491       appendLinked(pHtml, zHelp+nIndent, i-nIndent);
492       blob_append_char(pHtml, '\n');
493     }
494     zHelp += i+1;
495     i = 0;
496     if( c==0 ) break;
497   }
498   while( iLevel>0 ){
499     blob_appendf(pHtml, "%s\n", azEnd[iLevel--]);
500   }
501 }
502 
503 /*
504 ** Format help text for TTY display.
505 */
help_to_text(const char * zHelp,Blob * pText)506 static void help_to_text(const char *zHelp, Blob *pText){
507   int i, x;
508   char c;
509   for(i=0; (c = zHelp[i])!=0; i++){
510     if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){
511       if( i>0 ) blob_append(pText, zHelp, i);
512       blob_append(pText, "fossil", 6);
513       zHelp += i+7;
514       i = -1;
515       continue;
516     }
517     if( c=='\n' && (zHelp[i+1]=='>' || zHelp[i+1]=='|') && zHelp[i+2]==' ' ){
518       blob_append(pText, zHelp, i+1);
519       blob_append(pText, " ", 1);
520       zHelp += i+2;
521       i = -1;
522       continue;
523     }
524     if( c=='[' && (x = help_is_link(zHelp+i, 100000))!=0 ){
525       if( i>0 ) blob_append(pText, zHelp, i);
526       zHelp += i+2;
527       blob_append(pText, zHelp, x-3);
528       zHelp += x-1;
529       i = -1;
530       continue;
531     }
532   }
533   if( i>0 ){
534     blob_append(pText, zHelp, i);
535   }
536 }
537 
538 /*
539 ** Display help for all commands based on provided flags.
540 */
display_all_help(int mask,int useHtml,int rawOut)541 static void display_all_help(int mask, int useHtml, int rawOut){
542   int i;
543   if( useHtml ) fossil_print("<!--\n");
544   fossil_print("Help text for:\n");
545   if( mask & CMDFLAG_1ST_TIER ) fossil_print(" * Commands\n");
546   if( mask & CMDFLAG_2ND_TIER ) fossil_print(" * Auxiliary commands\n");
547   if( mask & CMDFLAG_TEST )     fossil_print(" * Test commands\n");
548   if( mask & CMDFLAG_WEBPAGE )  fossil_print(" * Web pages\n");
549   if( mask & CMDFLAG_SETTING )  fossil_print(" * Settings\n");
550   if( useHtml ){
551     fossil_print("-->\n");
552     fossil_print("<!-- start_all_help -->\n");
553   }else{
554     fossil_print("---\n");
555   }
556   for(i=0; i<MX_COMMAND; i++){
557     if( (aCommand[i].eCmdFlags & mask)==0 ) continue;
558     else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
559     if( useHtml ){
560       Blob html;
561       blob_init(&html, 0, 0);
562       help_to_html(aCommand[i].zHelp, &html);
563       fossil_print("<h1>%h</h1>\n", aCommand[i].zName);
564       fossil_print("%s\n<hr>\n", blob_str(&html));
565       blob_reset(&html);
566     }else if( rawOut ){
567       fossil_print("# %s\n", aCommand[i].zName);
568       fossil_print("%s\n\n", aCommand[i].zHelp);
569     }else{
570       Blob txt;
571       blob_init(&txt, 0, 0);
572       help_to_text(aCommand[i].zHelp, &txt);
573       fossil_print("# %s%s\n", aCommand[i].zName,
574         (aCommand[i].eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ?
575         " (versionable)" : "");
576       fossil_print("%s\n\n", blob_str(&txt));
577       blob_reset(&txt);
578     }
579   }
580   if( useHtml ){
581     fossil_print("<!-- end_all_help -->\n");
582   }else{
583     fossil_print("---\n");
584   }
585   version_cmd();
586 }
587 
588 /*
589 ** COMMAND: test-all-help
590 **
591 ** Usage: %fossil test-all-help ?OPTIONS?
592 **
593 ** Show help text for commands and pages.  Useful for proof-reading.
594 ** Defaults to just the CLI commands.  Specify --www to see only the
595 ** web pages, or --everything to see both commands and pages.
596 **
597 ** Options:
598 **    -e|--everything   Show all commands and pages.
599 **    -t|--test         Include test- commands
600 **    -w|--www          Show WWW pages.
601 **    -s|--settings     Show settings.
602 **    -h|--html         Transform output to HTML.
603 **    -r|--raw          No output formatting.
604 */
test_all_help_cmd(void)605 void test_all_help_cmd(void){
606   int mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER;
607   int useHtml = find_option("html","h",0)!=0;
608   int rawOut = find_option("raw","r",0)!=0;
609 
610   if( find_option("www","w",0) ){
611     mask = CMDFLAG_WEBPAGE;
612   }
613   if( find_option("everything","e",0) ){
614     mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE |
615               CMDFLAG_SETTING | CMDFLAG_TEST;
616   }
617   if( find_option("settings","s",0) ){
618     mask = CMDFLAG_SETTING;
619   }
620   if( find_option("test","t",0) ){
621     mask |= CMDFLAG_TEST;
622   }
623   display_all_help(mask, useHtml, rawOut);
624 }
625 
626 /*
627 ** Count the number of entries in the aCommand[] table that match
628 ** the given flag.
629 */
countCmds(unsigned int eFlg)630 static int countCmds(unsigned int eFlg){
631   int n = 0;
632   int i;
633   for(i=0; i<MX_COMMAND; i++){
634     if( (aCommand[i].eCmdFlags & eFlg)!=0 ) n++;
635   }
636   return n;
637 }
638 
639 /*
640 ** COMMAND: test-command-stats
641 **
642 ** Print statistics about the built-in command dispatch table.
643 */
test_command_stats_cmd(void)644 void test_command_stats_cmd(void){
645   fossil_print("commands:       %4d\n",
646      countCmds( CMDFLAG_COMMAND ));
647   fossil_print("  1st tier         %4d\n",
648      countCmds( CMDFLAG_1ST_TIER ));
649   fossil_print("  2nd tier         %4d\n",
650      countCmds( CMDFLAG_2ND_TIER ));
651   fossil_print("  test             %4d\n",
652      countCmds( CMDFLAG_TEST ));
653   fossil_print("web-pages:      %4d\n",
654      countCmds( CMDFLAG_WEBPAGE ));
655   fossil_print("settings:       %4d\n",
656      countCmds( CMDFLAG_SETTING ));
657   fossil_print("total entries:  %4d\n", MX_COMMAND);
658 }
659 
660 /*
661 ** Compute an estimate of the edit-distance between to input strings.
662 **
663 ** The first string is the input.  The second is the pattern.  Only the
664 ** first 100 characters of the pattern are considered.
665 */
edit_distance(const char * zA,const char * zB)666 static int edit_distance(const char *zA, const char *zB){
667   int nA = (int)strlen(zA);
668   int nB = (int)strlen(zB);
669   int i, j, m;
670   int p0, p1, c0;
671   int a[100];
672   static const int incr = 4;
673 
674   for(j=0; j<nB; j++) a[j] = 1;
675   for(i=0; i<nA; i++){
676     p0 = i==0 ? 0 : i*incr-1;
677     c0 = i*incr;
678     for(j=0; j<nB; j++){
679       int m = 999;
680       p1 = a[j];
681       if( zA[i]==zB[j] ){
682         m = p0;
683       }else{
684         m = c0+2;
685         if( m>p1+2 ) m = p1+2;
686         if( m>p0+3 ) m = p0+3;
687       }
688       c0 = a[j];
689       a[j] = m;
690       p0 = p1;
691     }
692   }
693   m = a[nB-1];
694   for(j=0; j<nB-1; j++){
695     if( a[j]+1<m ) m = a[j]+1;
696   }
697   return m;
698 }
699 
700 /*
701 ** Fill the pointer array with names of commands that approximately
702 ** match the input.  Return the number of approximate matches.
703 **
704 ** Closest matches appear first.
705 */
dispatch_approx_match(const char * zIn,int nArray,const char ** azArray)706 int dispatch_approx_match(const char *zIn, int nArray, const char **azArray){
707   int i;
708   int bestScore;
709   int m;
710   int n = 0;
711   int mnScore = 0;
712   int mxScore = 99999;
713   int iFirst, iLast;
714 
715   if( zIn[0]=='/' ){
716     iFirst = 0;
717     iLast = FOSSIL_FIRST_CMD-1;
718   }else{
719     iFirst = FOSSIL_FIRST_CMD;
720     iLast = MX_COMMAND-1;
721   }
722 
723   while( n<nArray ){
724     bestScore = mxScore;
725     for(i=iFirst; i<=iLast; i++){
726       m = edit_distance(zIn, aCommand[i].zName);
727       if( m<mnScore ) continue;
728       if( m==mnScore ){
729         azArray[n++] = aCommand[i].zName;
730         if( n>=nArray ) return n;
731        }else if( m<bestScore ){
732         bestScore = m;
733       }
734     }
735     if( bestScore>=mxScore ) break;
736     mnScore = bestScore;
737   }
738   return n;
739 }
740 
741 /*
742 ** COMMAND: test-approx-match
743 **
744 ** Test the approximate match algorithm
745 */
test_approx_match_command(void)746 void test_approx_match_command(void){
747   int i, j, n;
748   const char *az[20];
749   for(i=2; i<g.argc; i++){
750     fossil_print("%s:\n", g.argv[i]);
751     n = dispatch_approx_match(g.argv[i], 20, az);
752     for(j=0; j<n; j++){
753       fossil_print("   %s\n", az[j]);
754     }
755   }
756 }
757 
758 /*
759 ** WEBPAGE: help
760 ** URL: /help?name=CMD
761 **
762 ** Show the built-in help text for CMD.  CMD can be a command-line interface
763 ** command or a page name from the web interface or a setting.
764 ** Query parameters:
765 **
766 **    name=CMD        Show help for CMD where CMD is a command name or
767 **                    webpage name or setting name.
768 **
769 **    plaintext       Show the help within <pre>...</pre>, as if it were
770 **                    displayed using the "fossil help" command.
771 **
772 **    raw             Show the raw help text without any formatting.
773 **                    (Used for debugging.)
774 */
help_page(void)775 void help_page(void){
776   const char *zCmd = P("cmd");
777 
778   if( zCmd==0 ) zCmd = P("name");
779   if( zCmd && *zCmd ){
780     int rc;
781     const CmdOrPage *pCmd = 0;
782 
783   style_set_current_feature("tkt");
784     style_header("Help: %s", zCmd);
785 
786     style_submenu_element("Command-List", "%R/help");
787     rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd);
788     if( *zCmd=='/' ){
789       /* Some of the webpages require query parameters in order to work.
790       ** @ <h1>The "<a href='%R%s(zCmd)'>%s(zCmd)</a>" page:</h1> */
791       @ <h1>The "%h(zCmd)" page:</h1>
792     }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_SETTING)!=0 ){
793       @ <h1>The "%h(pCmd->zName)" setting:</h1>
794     }else{
795       @ <h1>The "%h(zCmd)" command:</h1>
796     }
797     if( rc==1 ){
798       @ unknown command: %h(zCmd)
799     }else if( rc==2 ){
800       @ ambiguous command prefix: %h(zCmd)
801     }else{
802       if( pCmd->zHelp[0]==0 ){
803         @ No help available for "%h(pCmd->zName)"
804       }else if( P("plaintext") ){
805         Blob txt;
806         blob_init(&txt, 0, 0);
807         help_to_text(pCmd->zHelp, &txt);
808         @ <pre class="helpPage">
809         @ %h(blob_str(&txt))
810         @ </pre>
811         blob_reset(&txt);
812       }else if( P("raw") ){
813         @ <pre class="helpPage">
814         @ %h(pCmd->zHelp)
815         @ </pre>
816       }else{
817         @ <div class="helpPage">
818         help_to_html(pCmd->zHelp, cgi_output_blob());
819         @ </div>
820       }
821     }
822   }else{
823     int i;
824 
825     style_header("Help");
826 
827     @ <a name='commands'></a>
828     @ <h1>Available commands:</h1>
829     @ <div class="columns" style="column-width: 12ex;">
830     @ <ul>
831     for(i=0; i<MX_COMMAND; i++){
832       const char *z = aCommand[i].zName;
833       const char *zBoldOn  = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"<b>" :"";
834       const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":"";
835       if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
836       if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue;
837       else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
838       @ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a></li>
839     }
840     @ </ul></div>
841 
842     @ <a name='webpages'></a>
843     @ <h1>Available web UI pages:</h1>
844     @ <div class="columns" style="column-width: 18ex;">
845     @ <ul>
846     for(i=0; i<MX_COMMAND; i++){
847       const char *z = aCommand[i].zName;
848       if( '/'!=*z ) continue;
849       else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
850       if( aCommand[i].zHelp[0] ){
851         @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
852       }else{
853         @ <li>%s(z+1)</li>
854       }
855     }
856     @ </ul></div>
857 
858     @ <a name='unsupported'></a>
859     @ <h1>Unsupported commands:</h1>
860     @ <div class="columns" style="column-width: 20ex;">
861     @ <ul>
862     for(i=0; i<MX_COMMAND; i++){
863       const char *z = aCommand[i].zName;
864       if( strncmp(z,"test",4)!=0 ) continue;
865       else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
866       if( aCommand[i].zHelp[0] ){
867         @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
868       }else{
869         @ <li>%s(z)</li>
870       }
871     }
872     @ </ul></div>
873 
874     @ <a name='settings'></a>
875     @ <h1>Settings:</h1>
876     @ <div class="columns" style="column-width: 20ex;">
877     @ <ul>
878     for(i=0; i<MX_COMMAND; i++){
879       const char *z = aCommand[i].zName;
880       if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;
881       else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
882       if( aCommand[i].zHelp[0] ){
883         @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
884       }else{
885         @ <li>%s(z)</li>
886       }
887     }
888     @ </ul></div>
889 
890   }
891   style_finish_page();
892 }
893 
894 /*
895 ** WEBPAGE: test-all-help
896 **
897 ** Show all help text on a single page.  Useful for proof-reading.
898 */
899 void test_all_help_page(void){
900   int i;
901   Blob buf;
902   blob_init(&buf,0,0);
903   style_set_current_feature("test");
904   style_header("All Help Text");
905   @ <dl>
906   for(i=0; i<MX_COMMAND; i++){
907     const char *zDesc;
908     unsigned int e = aCommand[i].eCmdFlags;
909     if( e & CMDFLAG_1ST_TIER ){
910       zDesc = "1st tier command";
911     }else if( e & CMDFLAG_2ND_TIER ){
912       zDesc = "2nd tier command";
913     }else if( e & CMDFLAG_TEST ){
914       zDesc = "test command";
915     }else if( e & CMDFLAG_WEBPAGE ){
916       if( e & CMDFLAG_RAWCONTENT ){
917         zDesc = "raw-content web page";
918       }else{
919         zDesc = "web page";
920       }
921     }else{
922       blob_reset(&buf);
923       if( e & CMDFLAG_VERSIONABLE ){
924         blob_appendf(&buf, "versionable ");
925       }
926       if( e & CMDFLAG_BLOCKTEXT ){
927         blob_appendf(&buf, "block-text ");
928       }
929       if( e & CMDFLAG_BOOLEAN ){
930         blob_appendf(&buf, "boolean ");
931       }
932       blob_appendf(&buf,"setting");
933       zDesc = blob_str(&buf);
934     }
935     if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
936     @ <dt><big><b>%s(aCommand[i].zName)</b></big> (%s(zDesc))</dt>
937     @ <dd>
938     help_to_html(aCommand[i].zHelp, cgi_output_blob());
939     @ </dd>
940   }
941   @ </dl>
942   blob_reset(&buf);
943   style_finish_page();
944 }
945 
946 static void multi_column_list(const char **azWord, int nWord){
947   int i, j, len;
948   int mxLen = 0;
949   int nCol;
950   int nRow;
951   for(i=0; i<nWord; i++){
952     len = strlen(azWord[i]);
953     if( len>mxLen ) mxLen = len;
954   }
955   nCol = 80/(mxLen+2);
956   if( nCol==0 ) nCol = 1;
957   nRow = (nWord + nCol - 1)/nCol;
958   for(i=0; i<nRow; i++){
959     const char *zSpacer = "";
960     for(j=i; j<nWord; j+=nRow){
961       fossil_print("%s%-*s", zSpacer, mxLen, azWord[j]);
962       zSpacer = "  ";
963     }
964     fossil_print("\n");
965   }
966 }
967 
968 /*
969 ** COMMAND: test-list-webpage
970 **
971 ** List all web pages.
972 */
973 void cmd_test_webpage_list(void){
974   int i, nCmd;
975   const char *aCmd[MX_COMMAND];
976   for(i=nCmd=0; i<MX_COMMAND; i++){
977     if(CMDFLAG_WEBPAGE & aCommand[i].eCmdFlags){
978       aCmd[nCmd++] = aCommand[i].zName;
979     }
980   }
981   assert(nCmd && "page list is empty?");
982   multi_column_list(aCmd, nCmd);
983 }
984 
985 
986 /*
987 ** List of commands starting with zPrefix, or all commands if zPrefix is NULL.
988 */
989 static void command_list(int cmdMask, int verboseFlag, int useHtml){
990   if( verboseFlag ){
991     display_all_help(cmdMask, useHtml, 0);
992   }else{
993     int i, nCmd;
994     const char *aCmd[MX_COMMAND];
995     for(i=nCmd=0; i<MX_COMMAND; i++){
996       if( (aCommand[i].eCmdFlags & cmdMask)==0 ) continue;
997       else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
998       aCmd[nCmd++] = aCommand[i].zName;
999     }
1000     multi_column_list(aCmd, nCmd);
1001   }
1002 }
1003 
1004 /*
1005 ** Documentation on universal command-line options.
1006 */
1007 /* @-comment: # */
1008 static const char zOptions[] =
1009 @ Command-line options common to all commands:
1010 @
1011 @   --args FILENAME         Read additional arguments and options from FILENAME
1012 @   --cgitrace              Active CGI tracing
1013 @   --comfmtflags VALUE     Set comment formatting flags to VALUE
1014 @   --comment-format VALUE  Alias for --comfmtflags
1015 @   --errorlog FILENAME     Log errors to FILENAME
1016 @   --help                  Show help on the command rather than running it
1017 @   --httptrace             Trace outbound HTTP requests
1018 @   --localtime             Display times using the local timezone
1019 @   --no-th-hook            Do not run TH1 hooks
1020 @   --quiet                 Reduce the amount of output
1021 @   --sqlstats              Show SQL usage statistics when done
1022 @   --sqltrace              Trace all SQL commands
1023 @   --sshtrace              Trace SSH activity
1024 @   --ssl-identity NAME     Set the SSL identity to NAME
1025 @   --systemtrace           Trace calls to system()
1026 @   -U|--user USER          Make the default user be USER
1027 @   --utc                   Display times using UTC
1028 @   --vfs NAME              Cause SQLite to use the NAME VFS
1029 ;
1030 
1031 /*
1032 ** COMMAND: help
1033 **
1034 ** Usage: %fossil help [OPTIONS] [TOPIC]
1035 **
1036 ** Display information on how to use TOPIC, which may be a command, webpage, or
1037 ** setting.  Webpage names begin with "/".  If TOPIC is omitted, a list of
1038 ** topics is returned.
1039 **
1040 ** The following options can be used when TOPIC is omitted:
1041 **
1042 **    -a|--all          List both common and auxiliary commands
1043 **    -o|--options      List command-line options common to all commands
1044 **    -s|--setting      List setting names
1045 **    -t|--test         List unsupported "test" commands
1046 **    -v|--verbose      List both names and help text
1047 **    -x|--aux          List only auxiliary commands
1048 **    -w|--www          List all web pages
1049 **    -f|--full         List full set of commands (including auxiliary
1050 **                      and unsupported "test" commands), options,
1051 **                      settings, and web pages
1052 **    -e|--everything   List all help on all topics
1053 **
1054 ** These options can be used when TOPIC is present:
1055 **
1056 **    -h|--html         Format output as HTML rather than plain text
1057 **    -c|--commands     Restrict TOPIC search to commands
1058 */
1059 void help_cmd(void){
1060   int rc;
1061   int mask = CMDFLAG_ANY;
1062   int isPage = 0;
1063   int verboseFlag = 0;
1064   int commandsFlag = 0;
1065   const char *z;
1066   const char *zCmdOrPage;
1067   const CmdOrPage *pCmd = 0;
1068   int useHtml = 0;
1069   const char *zTopic;
1070   Blob txt;
1071   verboseFlag = find_option("verbose","v",0)!=0;
1072   commandsFlag = find_option("commands","c",0)!=0;
1073   useHtml = find_option("html","h",0)!=0;
1074   if( find_option("options","o",0) ){
1075     fossil_print("%s", zOptions);
1076     return;
1077   }
1078   else if( find_option("all","a",0) ){
1079     command_list(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER, verboseFlag, useHtml);
1080     return;
1081   }
1082   else if( find_option("www","w",0) ){
1083     command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml);
1084     return;
1085   }
1086   else if( find_option("aux","x",0) ){
1087     command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml);
1088     return;
1089   }
1090   else if( find_option("test","t",0) ){
1091     command_list(CMDFLAG_TEST, verboseFlag, useHtml);
1092     return;
1093   }
1094   else if( find_option("setting","s",0) ){
1095     command_list(CMDFLAG_SETTING, verboseFlag, useHtml);
1096     return;
1097   }
1098   else if( find_option("full","f",0) ){
1099     fossil_print("fossil commands:\n\n");
1100     command_list(CMDFLAG_1ST_TIER, verboseFlag, useHtml);
1101     fossil_print("\nfossil auxiliary commands:\n\n");
1102     command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml);
1103     fossil_print("\n%s", zOptions);
1104     fossil_print("\nfossil settings:\n\n");
1105     command_list(CMDFLAG_SETTING, verboseFlag, useHtml);
1106     fossil_print("\nfossil web pages:\n\n");
1107     command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml);
1108     fossil_print("\nfossil test commands (unsupported):\n\n");
1109     command_list(CMDFLAG_TEST, verboseFlag, useHtml);
1110     fossil_print("\n");
1111     version_cmd();
1112     return;
1113   }
1114   else if( find_option("everything","e",0) ){
1115     display_all_help(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE |
1116                      CMDFLAG_SETTING | CMDFLAG_TEST, useHtml, 0);
1117     return;
1118   }
1119   verify_all_options();
1120   if( g.argc<3 ){
1121     z = g.argv[0];
1122     fossil_print(
1123       "Usage: %s help TOPIC\n"
1124       "Try \"%s help help\" or \"%s help -a\" for more options\n"
1125       "Frequently used commands:\n",
1126       z, z, z);
1127     command_list(CMDFLAG_1ST_TIER,verboseFlag,useHtml);
1128     if( !verboseFlag ) version_cmd();
1129     return;
1130   }
1131   zTopic = g.argv[2];
1132   isPage = ('/' == zTopic[0]) ? 1 : 0;
1133   if(isPage){
1134     zCmdOrPage = "page";
1135   }else if( commandsFlag ){
1136     mask = CMDFLAG_COMMAND;
1137     zCmdOrPage = "command";
1138   }else{
1139     zCmdOrPage = "command or setting";
1140   }
1141   rc = dispatch_name_search(g.argv[2], mask|CMDFLAG_PREFIX, &pCmd);
1142   if( rc ){
1143     int i, n;
1144     const char *az[5];
1145     if( rc==1 ){
1146       fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]);
1147     }else{
1148       fossil_print("ambiguous %s prefix: %s\n",
1149                  zCmdOrPage, g.argv[2]);
1150     }
1151     fossil_print("Did you mean one of these TOPICs:\n");
1152     n = dispatch_approx_match(g.argv[2], 5, az);
1153     for(i=0; i<n; i++){
1154       fossil_print("  *  %s\n", az[i]);
1155     }
1156     fossil_print("Also consider using:\n");
1157     fossil_print("   fossil help TOPIC     ;# show help on TOPIC\n");
1158     fossil_print("   fossil help -a        ;# show all commands\n");
1159     fossil_print("   fossil help -w        ;# show all web-pages\n");
1160     fossil_print("   fossil help -s        ;# show all settings\n");
1161     fossil_exit(1);
1162   }
1163   z = pCmd->zHelp;
1164   if( z==0 ){
1165     fossil_fatal("no help available for the %s %s",
1166                  pCmd->zName, zCmdOrPage);
1167   }
1168   if( pCmd->eCmdFlags & CMDFLAG_SETTING ){
1169     fossil_print("Setting: \"%s\"%s\n\n",
1170          pCmd->zName,
1171          (pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : ""
1172     );
1173   }
1174   blob_init(&txt, 0, 0);
1175   if( useHtml ){
1176     help_to_html(z, &txt);
1177   }else{
1178     help_to_text(z, &txt);
1179   }
1180   fossil_print("%s\n", blob_str(&txt));
1181   blob_reset(&txt);
1182 }
1183 
1184 /*
1185 ** Return a pointer to the setting information array.
1186 **
1187 ** This routine provides access to the aSetting2[] array which is created
1188 ** by the mkindex utility program and included with <page_index.h>.
1189 */
1190 const Setting *setting_info(int *pnCount){
1191   if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
1192   return aSetting;
1193 }
1194 
1195 /*****************************************************************************
1196 ** A virtual table for accessing the information in aCommand[], and
1197 ** especially the help-text
1198 */
1199 
1200 /* helptextVtab_vtab is a subclass of sqlite3_vtab which is
1201 ** underlying representation of the virtual table
1202 */
1203 typedef struct helptextVtab_vtab helptextVtab_vtab;
1204 struct helptextVtab_vtab {
1205   sqlite3_vtab base;  /* Base class - must be first */
1206   /* Add new fields here, as necessary */
1207 };
1208 
1209 /* helptextVtab_cursor is a subclass of sqlite3_vtab_cursor which will
1210 ** serve as the underlying representation of a cursor that scans
1211 ** over rows of the result
1212 */
1213 typedef struct helptextVtab_cursor helptextVtab_cursor;
1214 struct helptextVtab_cursor {
1215   sqlite3_vtab_cursor base;  /* Base class - must be first */
1216   /* Insert new fields here.  For this helptextVtab we only keep track
1217   ** of the rowid */
1218   sqlite3_int64 iRowid;      /* The rowid */
1219 };
1220 
1221 /*
1222 ** The helptextVtabConnect() method is invoked to create a new
1223 ** helptext virtual table.
1224 **
1225 ** Think of this routine as the constructor for helptextVtab_vtab objects.
1226 **
1227 ** All this routine needs to do is:
1228 **
1229 **    (1) Allocate the helptextVtab_vtab object and initialize all fields.
1230 **
1231 **    (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
1232 **        result set of queries against the virtual table will look like.
1233 */
1234 static int helptextVtabConnect(
1235   sqlite3 *db,
1236   void *pAux,
1237   int argc, const char *const*argv,
1238   sqlite3_vtab **ppVtab,
1239   char **pzErr
1240 ){
1241   helptextVtab_vtab *pNew;
1242   int rc;
1243 
1244   rc = sqlite3_declare_vtab(db,
1245            "CREATE TABLE x(name,type,flags,helptext,formatted,html)"
1246        );
1247   if( rc==SQLITE_OK ){
1248     pNew = sqlite3_malloc( sizeof(*pNew) );
1249     *ppVtab = (sqlite3_vtab*)pNew;
1250     if( pNew==0 ) return SQLITE_NOMEM;
1251     memset(pNew, 0, sizeof(*pNew));
1252   }
1253   return rc;
1254 }
1255 
1256 /*
1257 ** This method is the destructor for helptextVtab_vtab objects.
1258 */
1259 static int helptextVtabDisconnect(sqlite3_vtab *pVtab){
1260   helptextVtab_vtab *p = (helptextVtab_vtab*)pVtab;
1261   sqlite3_free(p);
1262   return SQLITE_OK;
1263 }
1264 
1265 /*
1266 ** Constructor for a new helptextVtab_cursor object.
1267 */
1268 static int helptextVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
1269   helptextVtab_cursor *pCur;
1270   pCur = sqlite3_malloc( sizeof(*pCur) );
1271   if( pCur==0 ) return SQLITE_NOMEM;
1272   memset(pCur, 0, sizeof(*pCur));
1273   *ppCursor = &pCur->base;
1274   return SQLITE_OK;
1275 }
1276 
1277 /*
1278 ** Destructor for a helptextVtab_cursor.
1279 */
1280 static int helptextVtabClose(sqlite3_vtab_cursor *cur){
1281   helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1282   sqlite3_free(pCur);
1283   return SQLITE_OK;
1284 }
1285 
1286 
1287 /*
1288 ** Advance a helptextVtab_cursor to its next row of output.
1289 */
1290 static int helptextVtabNext(sqlite3_vtab_cursor *cur){
1291   helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1292   pCur->iRowid++;
1293   return SQLITE_OK;
1294 }
1295 
1296 /*
1297 ** Return values of columns for the row at which the helptextVtab_cursor
1298 ** is currently pointing.
1299 */
1300 static int helptextVtabColumn(
1301   sqlite3_vtab_cursor *cur,   /* The cursor */
1302   sqlite3_context *ctx,       /* First argument to sqlite3_result_...() */
1303   int i                       /* Which column to return */
1304 ){
1305   helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1306   const CmdOrPage *pPage = aCommand + pCur->iRowid;
1307   switch( i ){
1308     case 0:  /* name */
1309       sqlite3_result_text(ctx, pPage->zName, -1, SQLITE_STATIC);
1310       break;
1311     case 1: { /* type */
1312       const char *zType = 0;
1313       if( pPage->eCmdFlags & CMDFLAG_COMMAND ){
1314         zType = "command";
1315       }else if( pPage->eCmdFlags & CMDFLAG_WEBPAGE ){
1316         zType = "webpage";
1317       }else if( pPage->eCmdFlags & CMDFLAG_SETTING ){
1318         zType = "setting";
1319       }
1320       sqlite3_result_text(ctx, zType, -1, SQLITE_STATIC);
1321       break;
1322     }
1323     case 2:  /* flags */
1324       sqlite3_result_int(ctx, pPage->eCmdFlags);
1325       break;
1326     case 3:  /* helptext */
1327       sqlite3_result_text(ctx, pPage->zHelp, -1, SQLITE_STATIC);
1328       break;
1329     case 4: { /* formatted */
1330       Blob txt;
1331       blob_init(&txt, 0, 0);
1332       help_to_text(pPage->zHelp, &txt);
1333       sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free);
1334       break;
1335     }
1336     case 5: { /* formatted */
1337       Blob txt;
1338       blob_init(&txt, 0, 0);
1339       help_to_html(pPage->zHelp, &txt);
1340       sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free);
1341       break;
1342     }
1343   }
1344   return SQLITE_OK;
1345 }
1346 
1347 /*
1348 ** Return the rowid for the current row.  In this implementation, the
1349 ** rowid is the same as the output value.
1350 */
1351 static int helptextVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
1352   helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1353   *pRowid = pCur->iRowid;
1354   return SQLITE_OK;
1355 }
1356 
1357 /*
1358 ** Return TRUE if the cursor has been moved off of the last
1359 ** row of output.
1360 */
1361 static int helptextVtabEof(sqlite3_vtab_cursor *cur){
1362   helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1363   return pCur->iRowid>=MX_COMMAND;
1364 }
1365 
1366 /*
1367 ** This method is called to "rewind" the helptextVtab_cursor object back
1368 ** to the first row of output.  This method is always called at least
1369 ** once prior to any call to helptextVtabColumn() or helptextVtabRowid() or
1370 ** helptextVtabEof().
1371 */
1372 static int helptextVtabFilter(
1373   sqlite3_vtab_cursor *pVtabCursor,
1374   int idxNum, const char *idxStr,
1375   int argc, sqlite3_value **argv
1376 ){
1377   helptextVtab_cursor *pCur = (helptextVtab_cursor *)pVtabCursor;
1378   pCur->iRowid = 1;
1379   return SQLITE_OK;
1380 }
1381 
1382 /*
1383 ** SQLite will invoke this method one or more times while planning a query
1384 ** that uses the virtual table.  This routine needs to create
1385 ** a query plan for each invocation and compute an estimated cost for that
1386 ** plan.
1387 */
1388 static int helptextVtabBestIndex(
1389   sqlite3_vtab *tab,
1390   sqlite3_index_info *pIdxInfo
1391 ){
1392   pIdxInfo->estimatedCost = (double)MX_COMMAND;
1393   pIdxInfo->estimatedRows = MX_COMMAND;
1394   return SQLITE_OK;
1395 }
1396 
1397 /*
1398 ** This following structure defines all the methods for the
1399 ** virtual table.
1400 */
1401 static sqlite3_module helptextVtabModule = {
1402   /* iVersion    */ 0,
1403   /* xCreate     */ 0,  /* Helptext is eponymous and read-only */
1404   /* xConnect    */ helptextVtabConnect,
1405   /* xBestIndex  */ helptextVtabBestIndex,
1406   /* xDisconnect */ helptextVtabDisconnect,
1407   /* xDestroy    */ 0,
1408   /* xOpen       */ helptextVtabOpen,
1409   /* xClose      */ helptextVtabClose,
1410   /* xFilter     */ helptextVtabFilter,
1411   /* xNext       */ helptextVtabNext,
1412   /* xEof        */ helptextVtabEof,
1413   /* xColumn     */ helptextVtabColumn,
1414   /* xRowid      */ helptextVtabRowid,
1415   /* xUpdate     */ 0,
1416   /* xBegin      */ 0,
1417   /* xSync       */ 0,
1418   /* xCommit     */ 0,
1419   /* xRollback   */ 0,
1420   /* xFindMethod */ 0,
1421   /* xRename     */ 0,
1422   /* xSavepoint  */ 0,
1423   /* xRelease    */ 0,
1424   /* xRollbackTo */ 0,
1425   /* xShadowName */ 0
1426 };
1427 
1428 
1429 /*
1430 ** Register the helptext virtual table
1431 */
1432 int helptext_vtab_register(sqlite3 *db){
1433   int rc = sqlite3_create_module(db, "helptext", &helptextVtabModule, 0);
1434   return rc;
1435 }
1436 /* End of the helptext virtual table
1437 ******************************************************************************/
1438