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, "<", 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, ">", 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, "&", 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