Fossil SCM

fossil-scm / src / dispatch.c
Blame History Raw 1972 lines
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
** [email protected]
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, setting, or help topic.
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
int iHelp; /* Index of help variable */
37
unsigned int eCmdFlags; /* Flags */
38
};
39
40
/***************************************************************************
41
** These macros must match similar macros in mkindex.c
42
** Allowed values for CmdOrPage.eCmdFlags.
43
*/
44
#define CMDFLAG_1ST_TIER 0x000001 /* Most important commands */
45
#define CMDFLAG_2ND_TIER 0x000002 /* Obscure and seldom used commands */
46
#define CMDFLAG_TEST 0x000004 /* Commands for testing only */
47
#define CMDFLAG_WEBPAGE 0x000008 /* Web pages */
48
#define CMDFLAG_COMMAND 0x000010 /* A command */
49
#define CMDFLAG_SETTING 0x000020 /* A setting */
50
#define CMDFLAG_VERSIONABLE 0x000040 /* A versionable setting */
51
#define CMDFLAG_BLOCKTEXT 0x000080 /* Multi-line text setting */
52
#define CMDFLAG_BOOLEAN 0x000100 /* A boolean setting */
53
#define CMDFLAG_RAWCONTENT 0x000200 /* Do not interpret POST content */
54
/* NOTE: 0x000400 = CMDFLAG_SENSITIVE in mkindex.c! */
55
#define CMDFLAG_HIDDEN 0x000800 /* Elide from most listings */
56
#define CMDFLAG_LDAVG_EXEMPT 0x001000 /* Exempt from load_control() */
57
#define CMDFLAG_ALIAS 0x002000 /* Command aliases */
58
#define CMDFLAG_KEEPEMPTY 0x004000 /* Do not unset empty settings */
59
#define CMDFLAG_ABBREVSUBCMD 0x008000 /* Help text abbreviates subcommands */
60
#define CMDFLAG_TOPIC 0x010000 /* A help topic */
61
/**************************************************************************/
62
63
/* Values for the 2nd parameter to dispatch_name_search() */
64
#define CMDFLAG_ANY 0x010038 /* Match anything */
65
#define CMDFLAG_PREFIX 0x000200 /* Prefix match is OK */
66
67
#endif /* INTERFACE */
68
69
/*
70
** The page_index.h file contains the definition for aCommand[] - an array
71
** of CmdOrPage objects that defines all available commands and webpages
72
** known to Fossil.
73
**
74
** The entries in aCommand[] are in sorted order by name. Since webpage names
75
** always begin with "/", all webpage names occur first. The page_index.h file
76
** also sets the FOSSIL_FIRST_CMD macro to be the *approximate* index
77
** in aCommand[] of the first command entry. FOSSIL_FIRST_CMD might be
78
** slightly too low, and so the range FOSSIL_FIRST_CMD...MX_COMMAND might
79
** contain a few webpage entries at the beginning.
80
**
81
** The page_index.h file is generated by the mkindex program which scans all
82
** source code files looking for header comments on the functions that
83
** implement command and webpages.
84
*/
85
#include "page_index.h"
86
#define MX_COMMAND count(aCommand)
87
#define MX_HELP_DUP 5 /* Upper bound estimate on help string duplication */
88
89
/*
90
** Given a command, webpage, or setting name in zName, find the corresponding
91
** CmdOrPage object and return a pointer to that object in *ppCmd.
92
**
93
** The eType field is CMDFLAG_COMMAND to look up commands, CMDFLAG_WEBPAGE to
94
** look up webpages, CMDFLAG_SETTING to look up settings, or CMDFLAG_ANY to look
95
** for any. If the CMDFLAG_PREFIX bit is set, then a prefix match is allowed.
96
**
97
** Return values:
98
** 0: Success. *ppCmd is set to the appropriate CmdOrPage
99
** 1: Not found.
100
** 2: Ambiguous. Two or more entries match.
101
*/
102
int dispatch_name_search(
103
const char *zName, /* Look for this name */
104
unsigned eType, /* CMDFLAGS_* bits */
105
const CmdOrPage **ppCmd /* Write the matching CmdOrPage object here */
106
){
107
int upr, lwr, mid;
108
int nName = strlen(zName);
109
lwr = 0;
110
upr = MX_COMMAND - 1;
111
while( lwr<=upr ){
112
int c;
113
mid = (upr+lwr)/2;
114
c = strcmp(zName, aCommand[mid].zName);
115
if( c==0 ){
116
if( (aCommand[mid].eCmdFlags & eType)==0 ) return 1;
117
*ppCmd = &aCommand[mid];
118
return 0; /* An exact match */
119
}else if( c<0 ){
120
upr = mid - 1;
121
}else{
122
lwr = mid + 1;
123
}
124
}
125
if( (eType & CMDFLAG_PREFIX)!=0
126
&& lwr<MX_COMMAND
127
&& strncmp(zName, aCommand[lwr].zName, nName)==0
128
){
129
/* An inexact prefix match was found. Scan the name table to try to find
130
* exactly one entry with this prefix and the requested type. */
131
for( mid=-1; lwr<MX_COMMAND
132
&& strncmp(zName, aCommand[lwr].zName, nName)==0; ++lwr ){
133
if( aCommand[lwr].eCmdFlags & eType ){
134
if( mid<0 ){
135
mid = lwr; /* Potential ambiguous prefix */
136
}else{
137
if( aCommand[lwr].xFunc != aCommand[mid].xFunc ){
138
return 2; /* Confirmed ambiguous prefix */
139
}
140
}
141
}
142
}
143
if( mid>=0 ){
144
*ppCmd = &aCommand[mid];
145
return 0; /* Prefix match */
146
}
147
}
148
return 1; /* Not found */
149
}
150
151
/*
152
** zName is the name of a webpage (eType==CMDFLAGS_WEBPAGE) that does not
153
** exist in the dispatch table. Check to see if this webpage name exists
154
** as an alias in the CONFIG table of the repository. If it is, then make
155
** appropriate changes to the CGI environment and set *ppCmd to point to the
156
** aliased command.
157
**
158
** Return 0 if the command is successfully aliased. Return 1 if there
159
** is not alias for zName. Any kind of error in the alias value causes a
160
** error to be thrown.
161
**
162
** Alias entries in the CONFIG table have a "name" value of "walias:NAME"
163
** where NAME is the input page name. The value is a string of the form
164
** "NEWNAME?QUERYPARAMS". The ?QUERYPARAMS is optional. If present (and it
165
** usually is), then all query parameters are added to the CGI environment.
166
** Except, query parameters of the form "X!" cause any CGI X variable to be
167
** removed.
168
*/
169
int dispatch_alias(const char *zName, const CmdOrPage **ppCmd){
170
char *z;
171
char *zQ;
172
int i;
173
174
z = db_text(0, "SELECT value FROM config WHERE name='walias:%q'",zName);
175
if( z==0 ) return 1;
176
for(i=0; z[i] && z[i]!='?'; i++){}
177
if( z[i]=='?' ){
178
z[i] = 0;
179
zQ = &z[i+1];
180
}else{
181
zQ = &z[i];
182
}
183
if( dispatch_name_search(z, CMDFLAG_WEBPAGE, ppCmd) ){
184
fossil_fatal("\"%s\" aliased to \"%s\" but \"%s\" does not exist",
185
zName, z, z);
186
}
187
z = zQ;
188
while( *z ){
189
char *zName = z;
190
char *zValue = 0;
191
while( *z && *z!='=' && *z!='&' && *z!='!' ){ z++; }
192
if( *z=='=' ){
193
*z = 0;
194
z++;
195
zValue = z;
196
while( *z && *z!='&' ){ z++; }
197
if( *z ){
198
*z = 0;
199
z++;
200
}
201
dehttpize(zValue);
202
}else if( *z=='!' ){
203
*(z++) = 0;
204
cgi_delete_query_parameter(zName);
205
zName = "";
206
}else{
207
if( *z ){ *z++ = 0; }
208
zValue = "";
209
}
210
if( fossil_islower(zName[0]) ){
211
cgi_replace_query_parameter(zName, zValue);
212
}else if( fossil_isupper(zName[0]) ){
213
cgi_replace_query_parameter_tolower(zName, zValue);
214
}
215
}
216
return 0;
217
}
218
219
/*
220
** Fill Blob with a space-separated list of all command names that
221
** match the prefix zPrefix and the eType CMDFLAGS_ bits.
222
*/
223
void dispatch_matching_names(
224
const char *zPrefix, /* name prefix */
225
unsigned eType, /* CMDFLAG_ bits */
226
Blob *pList /* space-separated list of command names */
227
){
228
int i;
229
int nPrefix = (int)strlen(zPrefix);
230
for(i=FOSSIL_FIRST_CMD; i<MX_COMMAND; i++){
231
if( (aCommand[i].eCmdFlags & eType)==0 ) continue;
232
if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){
233
blob_appendf(pList, " %s", aCommand[i].zName);
234
}
235
}
236
}
237
238
/*
239
** Return the index of the first non-space character that follows
240
** a span of two or more spaces. Return 0 if there is not gap.
241
*/
242
static int hasGap(const char *z, int n){
243
int i;
244
for(i=3; i<n-1; i++){
245
if( z[i]==' ' && z[i+1]!=' ' && z[i-1]==' ' && z[i-2]!='.' ) return i+1;
246
}
247
return 0 ;
248
}
249
250
/*
251
** Input string zIn starts with '['. If the content is a hyperlink of the
252
** form [[...]] then return the index of the closing ']'. Otherwise return 0.
253
*/
254
static int help_is_link(const char *z, int n){
255
int i;
256
char c;
257
if( n<5 ) return 0;
258
if( z[1]!='[' ) return 0;
259
for(i=3; i<n && (c = z[i])!=0; i++){
260
if( c==']' && z[i-1]==']' ) return i;
261
}
262
return 0;
263
}
264
265
/*
266
** Append text to pOut with changes:
267
**
268
** * Add hyperlink markup for [[...]]
269
** * Escape HTML characters: < > & and "
270
** * Change "%fossil" to just "fossil"
271
*/
272
static void appendLinked(Blob *pOut, const char *z, int n){
273
int i = 0;
274
int j;
275
while( i<n ){
276
char c = z[i];
277
if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){
278
if( i ) blob_append(pOut, z, i);
279
z += i+2;
280
n -= i+2;
281
blob_appendf(pOut, "<a href='%R/help/%.*s'>%.*s</a>",
282
j-3, z, j-3, z);
283
z += j-1;
284
n -= j-1;
285
i = 0;
286
}else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){
287
if( i ) blob_append(pOut, z, i);
288
z += i+7;
289
n -= i+7;
290
blob_append(pOut, "fossil", 6);
291
i = 0;
292
}else if( c=='<' ){
293
if( i ) blob_append(pOut, z, i);
294
blob_append(pOut, "&lt;", 4);
295
z += i+1;
296
n -= i+1;
297
i = 0;
298
}else if( c=='>' ){
299
if( i ) blob_append(pOut, z, i);
300
blob_append(pOut, "&gt;", 4);
301
z += i+1;
302
n -= i+1;
303
i = 0;
304
}else if( c=='&' ){
305
if( i ) blob_append(pOut, z, i);
306
blob_append(pOut, "&amp;", 5);
307
z += i+1;
308
n -= i+1;
309
i = 0;
310
}else{
311
i++;
312
}
313
}
314
blob_append(pOut, z, i);
315
}
316
317
/*
318
** Append text to pOut, adding formatting markup. Terms that
319
** have all lower-case letters are within <tt>..</tt>. Terms
320
** that have all upper-case letters are within <i>..</i>.
321
*/
322
static void appendMixedFont(Blob *pOut, const char *z, int n){
323
const char *zEnd = "";
324
int i = 0;
325
int j;
326
while( i<n ){
327
if( z[i]==' ' || z[i]=='=' ){
328
for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){}
329
appendLinked(pOut, z+i, j-i);
330
i = j;
331
}else{
332
for(j=i; j<n && z[j]!=' ' && z[j]!='=' && !fossil_isalpha(z[j]); j++){}
333
if( j>=n || z[j]==' ' || z[j]=='=' ){
334
zEnd = "";
335
}else{
336
if( fossil_isupper(z[j]) && z[i]!='-' ){
337
blob_append(pOut, "<i>",3);
338
zEnd = "</i>";
339
}else{
340
blob_append(pOut, "<tt>", 4);
341
zEnd = "</tt>";
342
}
343
}
344
while( j<n && z[j]!=' ' && z[j]!='=' ){ j++; }
345
appendLinked(pOut, z+i, j-i);
346
if( zEnd[0] ) blob_append(pOut, zEnd, -1);
347
i = j;
348
}
349
}
350
}
351
352
/*
353
** Attempt to reformat plain-text help into HTML for display on a webpage.
354
**
355
** The HTML output is appended to Blob pHtml, which should already be
356
** initialized.
357
**
358
** Formatting rules:
359
**
360
** * Bullet lists are indented from the surrounding text by
361
** at least one space. Each bullet begins with " * ".
362
**
363
** * Display lists are indented from the surrounding text.
364
** Each tag begins with "-" or occur on a line that is
365
** followed by two spaces and a non-space. <dd> elements can begin
366
** on the same line as long as they are separated by at least
367
** two spaces.
368
**
369
** * Indented text is show verbatim (<pre>...</pre>)
370
**
371
** * Lines that begin with "|" at the left margin are in <pre>...</pre>
372
*/
373
static void help_to_html(const char *zHelp, Blob *pHtml){
374
int i;
375
char c;
376
int nIndent = 0;
377
int wantP = 0;
378
int wantBR = 0;
379
int aIndent[10];
380
const char *azEnd[10];
381
int iLevel = 0;
382
int isLI = 0;
383
int isDT = 0;
384
int inPRE = 0;
385
static const char *zEndDL = "</dl></blockquote>";
386
static const char *zEndPRE = "</pre></blockquote>";
387
static const char *zEndUL = "</ul>";
388
static const char *zEndDD = "</dd>";
389
390
aIndent[0] = 0;
391
azEnd[0] = "";
392
while( zHelp[0] ){
393
i = 0;
394
while( (c = zHelp[i])!=0 && c!='\n' ){
395
if( c=='%' && i>2 && zHelp[i-2]==':' && strncmp(zHelp+i,"%fossil",7)==0 ){
396
appendLinked(pHtml, zHelp, i);
397
zHelp += i+1;
398
i = 0;
399
wantBR = 1;
400
continue;
401
}
402
i++;
403
}
404
if( i>2 && (zHelp[0]=='>' || zHelp[0]=='|') && zHelp[1]==' ' ){
405
if( zHelp[0]=='>' ){
406
isDT = 1;
407
for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
408
}else{
409
if( !inPRE ){
410
blob_append(pHtml, "<pre>\n", -1);
411
inPRE = 1;
412
}
413
}
414
}else{
415
if( inPRE ){
416
blob_append(pHtml, "</pre>\n", -1);
417
inPRE = 0;
418
}
419
isDT = 0;
420
for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
421
}
422
if( inPRE ){
423
blob_append(pHtml, zHelp+1, i);
424
zHelp += i + 1;
425
continue;
426
}
427
if( nIndent==i ){
428
if( c==0 ) break;
429
if( iLevel && azEnd[iLevel]==zEndPRE ){
430
/* Skip the newline at the end of a <pre> */
431
}else{
432
blob_append_char(pHtml, '\n');
433
}
434
wantP = 1;
435
wantBR = 0;
436
zHelp += i+1;
437
continue;
438
}
439
if( nIndent+2<i && zHelp[nIndent]=='*' && zHelp[nIndent+1]==' ' ){
440
nIndent += 2;
441
while( nIndent<i && zHelp[nIndent]==' '){ nIndent++; }
442
isLI = 1;
443
}else{
444
isLI = 0;
445
}
446
while( iLevel>0 && aIndent[iLevel]>nIndent ){
447
blob_append(pHtml, azEnd[iLevel--], -1);
448
}
449
if( nIndent>aIndent[iLevel] ){
450
assert( iLevel<ArraySize(aIndent)-2 );
451
if( isLI ){
452
iLevel++;
453
aIndent[iLevel] = nIndent;
454
azEnd[iLevel] = zEndUL;
455
if( wantP ){
456
blob_append(pHtml,"<p>", 3);
457
wantP = 0;
458
}
459
blob_append(pHtml, "<ul>\n", 5);
460
}else if( isDT
461
|| zHelp[nIndent]=='-'
462
|| hasGap(zHelp+nIndent,i-nIndent) ){
463
iLevel++;
464
aIndent[iLevel] = nIndent;
465
azEnd[iLevel] = zEndDL;
466
wantP = 0;
467
if( isDT ){
468
blob_append(pHtml, "<blockquote><dl>\n", -1);
469
}else{
470
blob_append(pHtml, "<blockquote><dl class=\"helpOptions\">\n", -1);
471
}
472
}else if( azEnd[iLevel]==zEndDL ){
473
iLevel++;
474
aIndent[iLevel] = nIndent;
475
azEnd[iLevel] = zEndDD;
476
if( wantP ){
477
blob_append(pHtml,"<p>", 3);
478
wantP = 0;
479
}
480
blob_append(pHtml, "<dd>", 4);
481
}else if( wantP ){
482
iLevel++;
483
aIndent[iLevel] = nIndent;
484
azEnd[iLevel] = zEndPRE;
485
blob_append(pHtml, "<blockquote><pre>", -1);
486
wantP = 0;
487
}
488
}
489
if( isLI ){
490
blob_append(pHtml, "<li> ", 5);
491
}
492
if( wantP ){
493
blob_append(pHtml, "<p> ", 4);
494
wantP = 0;
495
}
496
if( azEnd[iLevel]==zEndDL ){
497
int iDD;
498
blob_append(pHtml, "<dt> ", 5);
499
iDD = hasGap(zHelp+nIndent, i-nIndent);
500
if( iDD ){
501
int x;
502
assert( iLevel<ArraySize(aIndent)-1 );
503
iLevel++;
504
aIndent[iLevel] = x = nIndent+iDD;
505
azEnd[iLevel] = zEndDD;
506
appendMixedFont(pHtml, zHelp+nIndent, iDD-2);
507
blob_append(pHtml, "</dt><dd>",9);
508
appendLinked(pHtml, zHelp+x, i-x);
509
}else{
510
appendMixedFont(pHtml, zHelp+nIndent, i-nIndent);
511
}
512
blob_append(pHtml, "</dt>\n", 6);
513
}else if( wantBR ){
514
appendMixedFont(pHtml, zHelp+nIndent, i-nIndent);
515
blob_append(pHtml, "<br>\n", 5);
516
wantBR = 0;
517
}else{
518
appendLinked(pHtml, zHelp+nIndent, i-nIndent);
519
blob_append_char(pHtml, '\n');
520
}
521
zHelp += i+1;
522
i = 0;
523
if( c==0 ) break;
524
}
525
while( iLevel>0 ){
526
blob_appendf(pHtml, "%s\n", azEnd[iLevel--]);
527
}
528
}
529
530
/*
531
** Format help text for TTY display.
532
*/
533
static void help_to_text(const char *zHelp, Blob *pText, int bUsage){
534
int i, x;
535
char c;
536
if( zHelp[0]=='>' ){
537
if( !bUsage ){
538
blob_appendf(pText, "Usage:");
539
}else{
540
blob_append_char(pText, ' ');
541
}
542
zHelp++;
543
}
544
for(i=0; (c = zHelp[i])!=0; i++){
545
if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){
546
if( i>0 ) blob_append(pText, zHelp, i);
547
blob_append(pText, "fossil", 6);
548
zHelp += i+7;
549
i = -1;
550
continue;
551
}
552
if( c=='\n' && (zHelp[i+1]=='>' || zHelp[i+1]=='|') && zHelp[i+2]==' ' ){
553
blob_append(pText, zHelp, i+1);
554
blob_append(pText, " ", 1);
555
zHelp += i+2;
556
i = -1;
557
continue;
558
}
559
if( c=='[' && (x = help_is_link(zHelp+i, 100000))!=0 ){
560
if( i>0 ) blob_append(pText, zHelp, i);
561
zHelp += i+2;
562
blob_append(pText, zHelp, x-3);
563
zHelp += x-1;
564
i = -1;
565
continue;
566
}
567
}
568
if( i>0 ){
569
blob_append(pText, zHelp, i);
570
}
571
}
572
573
/*
574
** Display help for all commands based on provided flags.
575
*/
576
static void display_all_help(int mask, int useHtml, int rawOut){
577
int i;
578
unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0}; /* Help string occurrences */
579
int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help strings->commands*/
580
if( useHtml ) fossil_print("<!--\n");
581
fossil_print("Help text for:\n");
582
if( mask & CMDFLAG_1ST_TIER ) fossil_print(" * Commands\n");
583
if( mask & CMDFLAG_2ND_TIER ) fossil_print(" * Auxiliary commands\n");
584
if( mask & CMDFLAG_ALIAS ) fossil_print(" * Aliases\n");
585
if( mask & CMDFLAG_TEST ) fossil_print(" * Test commands\n");
586
if( mask & CMDFLAG_WEBPAGE ) fossil_print(" * Web pages\n");
587
if( mask & CMDFLAG_SETTING ) fossil_print(" * Settings\n");
588
if( mask & CMDFLAG_TOPIC ) fossil_print(" * Help Topic\n");
589
if( useHtml ){
590
fossil_print("-->\n");
591
fossil_print("<!-- start_all_help -->\n");
592
}else{
593
fossil_print("---\n");
594
}
595
/* Fill in help string buckets */
596
for(i=0; i<MX_COMMAND; i++){
597
if( (aCommand[i].eCmdFlags & mask)==0 ) continue;
598
else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
599
bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i;
600
}
601
for(i=0; i<MX_COMMAND; i++){
602
if( (aCommand[i].eCmdFlags & mask)==0 ) continue;
603
else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
604
if( occHelp[aCommand[i].iHelp] > 0 ){
605
int j;
606
if( useHtml ){
607
Blob html;
608
blob_init(&html, 0, 0);
609
help_to_html(aCommand[i].zHelp, &html);
610
for(j=0; j<occHelp[aCommand[i].iHelp]; j++){
611
fossil_print("<h1>%h</h1>\n",
612
aCommand[bktHelp[aCommand[i].iHelp][j]].zName);
613
}
614
fossil_print("%s\n<hr>\n", blob_str(&html));
615
blob_reset(&html);
616
}else if( rawOut ){
617
for(j=0; j<occHelp[aCommand[i].iHelp]; j++)
618
fossil_print("# %s\n", aCommand[bktHelp[aCommand[i].iHelp][j]].zName);
619
fossil_print("%s\n\n", aCommand[i].zHelp);
620
}else{
621
Blob txt;
622
blob_init(&txt, 0, 0);
623
help_to_text(aCommand[i].zHelp, &txt, 0);
624
for(j=0; j<occHelp[aCommand[i].iHelp]; j++){
625
fossil_print("# %s%s\n",
626
aCommand[bktHelp[aCommand[i].iHelp][j]].zName,
627
(aCommand[i].eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ?
628
" (versionable)" : "");
629
}
630
fossil_print("%s\n\n", blob_str(&txt));
631
blob_reset(&txt);
632
}
633
occHelp[aCommand[i].iHelp] = 0;
634
}
635
}
636
if( useHtml ){
637
fossil_print("<!-- end_all_help -->\n");
638
}else{
639
fossil_print("---\n");
640
}
641
version_cmd();
642
}
643
644
/*
645
** COMMAND: test-all-help
646
**
647
** Usage: %fossil test-all-help ?OPTIONS?
648
**
649
** Show help text for commands and pages. Useful for proof-reading.
650
** Defaults to just the CLI commands. Specify --www to see only the
651
** web pages, or --everything to see both commands and pages.
652
**
653
** Options:
654
** -a|--aliases Show aliases
655
** -c|--topics Show help topics
656
** -e|--everything Show all commands and pages. Omit aliases to
657
** avoid duplicates.
658
** -h|--html Transform output to HTML
659
** -o|--options Show global options
660
** -r|--raw No output formatting
661
** -s|--settings Show settings
662
** -t|--test Include test- commands
663
** -w|--www Show WWW pages
664
*/
665
void test_all_help_cmd(void){
666
int mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER;
667
int useHtml = find_option("html","h",0)!=0;
668
int rawOut = find_option("raw","r",0)!=0;
669
670
if( find_option("www","w",0) ){
671
mask = CMDFLAG_WEBPAGE;
672
}
673
if( find_option("everything","e",0) ){
674
mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE |
675
CMDFLAG_ALIAS | CMDFLAG_SETTING | CMDFLAG_TEST | CMDFLAG_TOPIC;
676
}
677
if( find_option("settings","s",0) ){
678
mask = CMDFLAG_SETTING;
679
}
680
if( find_option("aliases","a",0) ){
681
mask = CMDFLAG_ALIAS;
682
}
683
if( find_option("test","t",0) ){
684
mask |= CMDFLAG_TEST;
685
}
686
if( find_option("topics","c",0) ){
687
mask |= CMDFLAG_TOPIC;
688
}
689
display_all_help(mask, useHtml, rawOut);
690
}
691
692
/*
693
** Count the number of entries in the aCommand[] table that match
694
** the given flag.
695
*/
696
static int countCmds(unsigned int eFlg){
697
int n = 0;
698
int i;
699
for(i=0; i<MX_COMMAND; i++){
700
if( (aCommand[i].eCmdFlags & eFlg)!=0 ) n++;
701
}
702
return n;
703
}
704
705
/*
706
** COMMAND: test-command-stats
707
**
708
** Print statistics about the built-in command dispatch table.
709
*/
710
void test_command_stats_cmd(void){
711
fossil_print("commands: %4d\n",
712
countCmds( CMDFLAG_COMMAND ));
713
fossil_print(" 1st tier %4d\n",
714
countCmds( CMDFLAG_1ST_TIER ));
715
fossil_print(" 2nd tier %4d\n",
716
countCmds( CMDFLAG_2ND_TIER ));
717
fossil_print(" alias %4d\n",
718
countCmds( CMDFLAG_ALIAS ));
719
fossil_print(" test %4d\n",
720
countCmds( CMDFLAG_TEST ));
721
fossil_print("web-pages: %4d\n",
722
countCmds( CMDFLAG_WEBPAGE ));
723
fossil_print("settings: %4d\n",
724
countCmds( CMDFLAG_SETTING ));
725
fossil_print("help-topics: %4d\n",
726
countCmds( CMDFLAG_TOPIC ));
727
fossil_print("total entries: %4d\n", MX_COMMAND);
728
}
729
730
/*
731
** Compute an estimate of the edit-distance between to input strings.
732
**
733
** The first string is the input. The second is the pattern. Only the
734
** first 100 characters of the pattern are considered.
735
*/
736
static int edit_distance(const char *zA, const char *zB){
737
int nA = (int)strlen(zA);
738
int nB = (int)strlen(zB);
739
int i, j, m;
740
int p0, p1, c0;
741
int a[100] = {0};
742
static const int incr = 4;
743
744
for(j=0; j<nB; j++) a[j] = 1;
745
for(i=0; i<nA; i++){
746
p0 = i==0 ? 0 : i*incr-1;
747
c0 = i*incr;
748
for(j=0; j<nB; j++){
749
int m = 999;
750
p1 = a[j];
751
if( zA[i]==zB[j] ){
752
m = p0;
753
}else{
754
m = c0+2;
755
if( m>p1+2 ) m = p1+2;
756
if( m>p0+3 ) m = p0+3;
757
}
758
c0 = a[j];
759
a[j] = m;
760
p0 = p1;
761
}
762
}
763
m = a[nB-1];
764
for(j=0; j<nB-1; j++){
765
if( a[j]+1<m ) m = a[j]+1;
766
}
767
return m;
768
}
769
770
/*
771
** Fill the pointer array with names of commands that approximately
772
** match the input. Return the number of approximate matches.
773
**
774
** Closest matches appear first.
775
*/
776
int dispatch_approx_match(const char *zIn, int nArray, const char **azArray){
777
int i;
778
int bestScore;
779
int m;
780
int n = 0;
781
int mnScore = 0;
782
int mxScore = 99999;
783
int iFirst, iLast;
784
785
if( zIn[0]=='/' ){
786
iFirst = 0;
787
iLast = FOSSIL_FIRST_CMD-1;
788
}else{
789
iFirst = FOSSIL_FIRST_CMD;
790
iLast = MX_COMMAND-1;
791
}
792
793
while( n<nArray ){
794
bestScore = mxScore;
795
for(i=iFirst; i<=iLast; i++){
796
m = edit_distance(zIn, aCommand[i].zName);
797
if( m<mnScore ) continue;
798
if( m==mnScore ){
799
azArray[n++] = aCommand[i].zName;
800
if( n>=nArray ) return n;
801
}else if( m<bestScore ){
802
bestScore = m;
803
}
804
}
805
if( bestScore>=mxScore ) break;
806
mnScore = bestScore;
807
}
808
return n;
809
}
810
811
/*
812
** COMMAND: test-approx-match
813
**
814
** Test the approximate match algorithm
815
*/
816
void test_approx_match_command(void){
817
int i, j, n;
818
const char *az[20];
819
for(i=2; i<g.argc; i++){
820
fossil_print("%s:\n", g.argv[i]);
821
n = dispatch_approx_match(g.argv[i], 20, az);
822
for(j=0; j<n; j++){
823
fossil_print(" %s\n", az[j]);
824
}
825
}
826
}
827
828
829
/*
830
** Returns 1 if the command or page name zName is known to be a
831
** command/page which is only available in certain builds/platforms,
832
** else returns 0.
833
*/
834
static int help_is_platform_command(const char *zName){
835
const char *aList[] = {
836
/* List of commands/pages which are known to only be available in
837
** certain builds/platforms. */
838
"winsrv",
839
"json", "/json",
840
NULL /* end-of-list sentinel */
841
};
842
int i = 0;
843
const char *z;
844
for( z = aList[0]; z ; z = aList[++i] ){
845
if( 0==fossil_strcmp(zName, z) ) return 1;
846
}
847
return 0;
848
}
849
850
/*
851
** WEBPAGE: help
852
** URL: /help/CMD or /help/www/PAGE
853
**
854
** Show the built-in help text for CMD or PAGE. CMD can be a command-line
855
** interface command or a setting name. PAGE is the name of a
856
** web interface. /help//PAGE also works if the double-/ makes it through
857
** the main web server.
858
**
859
** Query parameters:
860
**
861
** name=CMD Show help for CMD where CMD is a command name or
862
** or setting name. If CMD beings with "/" it is
863
** interpreted as a PAGE name.
864
**
865
** name=www/PAGE Show help for web page PAGE.
866
**
867
** name=/PAGE The initial "www/" on web-page help can be abbreviated as
868
** just "/"
869
**
870
** plaintext Show the help within <pre>...</pre>, as if it were
871
** displayed using the "fossil help" command.
872
**
873
** raw Show the raw help text without any formatting.
874
** (Used for debugging.)
875
*/
876
void help_page(void){
877
const char *zCmd = P("cmd");
878
879
if( zCmd==0 ) zCmd = P("name");
880
cgi_check_for_malice();
881
if( zCmd && *zCmd ){
882
int rc;
883
const CmdOrPage *pCmd = 0;
884
885
style_set_current_feature("tkt");
886
style_submenu_element("Topic-List", "%R/help");
887
if( search_restrict(SRCH_HELP)!=0 ){
888
style_submenu_element("Search","%R/search?y=h");
889
}
890
if( strncmp(zCmd,"www/",4)==0 && zCmd[4]!=0 ){
891
/* Use https://domain/fossil/help/www/timeline or similar with the "www"
892
** intermediate tag to view web-page documentation. */
893
zCmd += 3;
894
}
895
rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd);
896
if( pCmd ){
897
style_header("Help: %s", pCmd->zName);
898
}else{
899
style_header("Help");
900
}
901
if( pCmd==0 ){
902
/* No <h1> line in this case */
903
}else if( *zCmd=='/' ){
904
/* Some of the webpages require query parameters in order to work.
905
** @ <h1>The "<a href='%R%s(zCmd)'>%s(zCmd)</a>" page:</h1> */
906
@ <h1>The "%h(pCmd->zName)" page:</h1>
907
}else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_SETTING)!=0 ){
908
@ <h1>The "%h(pCmd->zName)" setting:</h1>
909
}else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_TOPIC)!=0 ){
910
@ <h1>The "%h(pCmd->zName)" help topic:</h1>
911
}else{
912
@ <h1>The "%h(pCmd->zName)" command:</h1>
913
}
914
if( rc==1 || (rc==2 && zCmd[0]=='/') ){
915
if( zCmd && help_is_platform_command(zCmd) ){
916
@ Not available in this build: "%h(zCmd)"
917
}else{
918
@ Unknown topic: "%h(zCmd)"
919
}
920
}else if( rc==2 ){
921
@ Ambiguous prefix: "%h(zCmd)"
922
}else{
923
if( pCmd->zHelp[0]==0 ){
924
@ No help available for "%h(pCmd->zName)"
925
}else if( P("plaintext") ){
926
Blob txt;
927
blob_init(&txt, 0, 0);
928
help_to_text(pCmd->zHelp, &txt, 0);
929
@ <pre class="helpPage">
930
@ %h(blob_str(&txt))
931
@ </pre>
932
blob_reset(&txt);
933
}else if( P("raw") ){
934
@ <pre class="helpPage">
935
@ %h(pCmd->zHelp)
936
@ </pre>
937
}else{
938
@ <div class="helpPage">
939
help_to_html(pCmd->zHelp, cgi_output_blob());
940
@ </div>
941
}
942
}
943
}else{
944
int i;
945
const char *zWidth = "28ex";
946
unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0}; /* Help str occurrences */
947
int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help str->commands */
948
style_header("Help");
949
search_screen(SRCH_HELP, 0x02);
950
951
@ <a name='commands'></a>
952
@ <h1>Commands:</h1>
953
@ <div class="columns" style="column-width: %s(zWidth);">
954
@ <ul>
955
/* Fill in help string buckets */
956
for(i=0; i<MX_COMMAND; i++){
957
if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
958
bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i;
959
}
960
for(i=0; i<MX_COMMAND; i++){
961
const char *z = aCommand[i].zName;
962
const char *zBoldOn = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"<b>" :"";
963
const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":"";
964
if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
965
if( (aCommand[i].eCmdFlags & (CMDFLAG_SETTING|CMDFLAG_TOPIC))!=0 ){
966
continue;
967
}
968
else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
969
else if( (aCommand[i].eCmdFlags & CMDFLAG_ALIAS)!=0 ) continue;
970
@ <li><a href="%R/help/%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a>
971
/* Output aliases */
972
if( occHelp[aCommand[i].iHelp] > 1 ){
973
int j;
974
int aliases[MX_HELP_DUP], nAliases=0;
975
for(j=0; j<occHelp[aCommand[i].iHelp]; j++){
976
if( bktHelp[aCommand[i].iHelp][j] != i ){
977
if( aCommand[bktHelp[aCommand[i].iHelp][j]].eCmdFlags
978
& CMDFLAG_ALIAS ){
979
aliases[nAliases++] = bktHelp[aCommand[i].iHelp][j];
980
}
981
}
982
}
983
if( nAliases>0 ){
984
int k;
985
@(\
986
for(k=0; k<nAliases; k++){
987
@<a href="%R/help/%s(aCommand[aliases[k]].zName)">\
988
@%s(aCommand[aliases[k]].zName)</a>%s((k<nAliases-1)?", ":"")\
989
}
990
@)\
991
}
992
}
993
@ </li>
994
}
995
996
@ </ul></div>
997
998
@ <a name='webpages'></a>
999
@ <h1>Web pages:</h1>
1000
@ <div class="columns" style="column-width: %s(zWidth);">
1001
@ <ul>
1002
for(i=0; i<MX_COMMAND; i++){
1003
const char *z = aCommand[i].zName;
1004
if( '/'!=*z ) continue;
1005
else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
1006
if( aCommand[i].zHelp[0] ){
1007
@ <li><a href="%R/help/www%s(z)">%s(z+1)</a></li>
1008
}else{
1009
@ <li>%s(z+1)</li>
1010
}
1011
}
1012
@ </ul></div>
1013
1014
@ <a name='settings'></a>
1015
@ <h1>Settings:</h1>
1016
@ <div class="columns" style="column-width: %s(zWidth);">
1017
@ <ul>
1018
for(i=0; i<MX_COMMAND; i++){
1019
const char *z = aCommand[i].zName;
1020
if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;
1021
else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
1022
if( aCommand[i].zHelp[0] ){
1023
@ <li><a href="%R/help/%s(z)">%s(z)</a></li>
1024
}else{
1025
@ <li>%s(z)</li>
1026
}
1027
}
1028
@ </ul></div>
1029
1030
@ <a name='topics'></a>
1031
@ <h1>Other Miscellaneous Help Topics:</h1>
1032
@ <div class="columns" style="column-width: %s(zWidth);">
1033
@ <ul>
1034
for(i=0; i<MX_COMMAND; i++){
1035
const char *z = aCommand[i].zName;
1036
if( (aCommand[i].eCmdFlags & CMDFLAG_TOPIC)==0 ) continue;
1037
if( aCommand[i].zHelp[0] ){
1038
@ <li><a href="%R/help/%s(z)">%s(z)</a></li>
1039
}else{
1040
@ <li>%s(z)</li>
1041
}
1042
}
1043
@ </ul></div>
1044
1045
@ <a name='unsupported'></a>
1046
@ <h1>Unsupported and Testing Commands:</h1>
1047
@ <div class="columns" style="column-width: %s(zWidth);">
1048
@ <ul>
1049
for(i=0; i<MX_COMMAND; i++){
1050
const char *z = aCommand[i].zName;
1051
if( strncmp(z,"test",4)!=0 ) continue;
1052
else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
1053
if( aCommand[i].zHelp[0] ){
1054
@ <li><a href="%R/help/%s(z)">%s(z)</a></li>
1055
}else{
1056
@ <li>%s(z)</li>
1057
}
1058
}
1059
@ </ul></div>
1060
1061
}
1062
style_finish_page();
1063
}
1064
1065
/*
1066
** WEBPAGE: test-all-help
1067
**
1068
** Show all help text on a single page. Useful for proof-reading.
1069
*/
1070
void test_all_help_page(void){
1071
int i;
1072
unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0}; /* Help string occurrences */
1073
int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help strings->commands*/
1074
Blob buf;
1075
blob_init(&buf,0,0);
1076
style_set_current_feature("test");
1077
style_header("All Help Text");
1078
@ <dl>
1079
/* Fill in help string buckets */
1080
for(i=0; i<MX_COMMAND; i++){
1081
if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
1082
bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i;
1083
}
1084
for(i=0; i<MX_COMMAND; i++){
1085
const char *zDesc;
1086
unsigned int e = aCommand[i].eCmdFlags;
1087
if( e & CMDFLAG_1ST_TIER ){
1088
zDesc = "1st tier command";
1089
}else if( e & CMDFLAG_2ND_TIER ){
1090
zDesc = "2nd tier command";
1091
}else if( e & CMDFLAG_ALIAS ){
1092
zDesc = "alias";
1093
}else if( e & CMDFLAG_TEST ){
1094
zDesc = "test command";
1095
}else if( e & CMDFLAG_TOPIC ){
1096
zDesc = "help-topic";
1097
}else if( e & CMDFLAG_WEBPAGE ){
1098
if( e & CMDFLAG_RAWCONTENT ){
1099
zDesc = "raw-content web page";
1100
}else{
1101
zDesc = "web page";
1102
}
1103
}else{
1104
blob_reset(&buf);
1105
if( e & CMDFLAG_VERSIONABLE ){
1106
blob_appendf(&buf, "versionable ");
1107
}
1108
if( e & CMDFLAG_BLOCKTEXT ){
1109
blob_appendf(&buf, "block-text ");
1110
}
1111
if( e & CMDFLAG_BOOLEAN ){
1112
blob_appendf(&buf, "boolean ");
1113
}
1114
blob_appendf(&buf,"setting");
1115
zDesc = blob_str(&buf);
1116
}
1117
if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
1118
if( occHelp[aCommand[i].iHelp] > 0 ){
1119
int j;
1120
for(j=0; j<occHelp[aCommand[i].iHelp]; j++){
1121
unsigned int e = aCommand[bktHelp[aCommand[i].iHelp][j]].eCmdFlags;
1122
if( e & CMDFLAG_1ST_TIER ){
1123
zDesc = "1st tier command";
1124
}else if( e & CMDFLAG_2ND_TIER ){
1125
zDesc = "2nd tier command";
1126
}else if( e & CMDFLAG_ALIAS ){
1127
zDesc = "alias";
1128
}else if( e & CMDFLAG_TEST ){
1129
zDesc = "test command";
1130
}else if( e & CMDFLAG_WEBPAGE ){
1131
if( e & CMDFLAG_RAWCONTENT ){
1132
zDesc = "raw-content web page";
1133
}else{
1134
zDesc = "web page";
1135
}
1136
}
1137
1138
@ <dt><big><b>%s(aCommand[bktHelp[aCommand[i].iHelp][j]].zName)</b>
1139
@</big> (%s(zDesc))</dt>
1140
}
1141
@ <p><dd>
1142
help_to_html(aCommand[i].zHelp, cgi_output_blob());
1143
@ </dd><p>
1144
occHelp[aCommand[i].iHelp] = 0;
1145
}
1146
}
1147
@ </dl>
1148
blob_reset(&buf);
1149
style_finish_page();
1150
}
1151
1152
/*
1153
** Analyze p and return one of three values:
1154
**
1155
** 0 p is the continuation of a prior subcommand.
1156
**
1157
** 1 p is text past the end of a prior subcommand.
1158
**
1159
** 2 p is the start of a new subcommand.
1160
*/
1161
static int is_subcommand(Blob *p, int bAbbrevSubcmd){
1162
int i, sz;
1163
const unsigned char *z = (const unsigned char*)blob_buffer(p);
1164
sz = blob_size(p);
1165
if( sz>6 ) sz = 6;
1166
for(i=0; i<sz && fossil_isspace(z[i]); i++){}
1167
if( i>=sz ) return 0;
1168
1169
if( bAbbrevSubcmd==0 ){
1170
if( i>1 ) return 0;
1171
return z[0]=='>' ? 2 : 1;
1172
}else{
1173
return (i==3 && fossil_isalpha(z[3])) ? 2 : 1;
1174
}
1175
}
1176
1177
/*
1178
** Input z[] is help text for zTopic. If zTopic has sub-command zSub,
1179
** then cut out all portions of the original help text that do not
1180
** directly pertain to zSub and write the zSub-relevant parts into
1181
** pOut.
1182
**
1183
** Return the number of lines of z[] written into pOut. A return of
1184
** zero means no simplification occurred.
1185
*/
1186
static int simplify_to_subtopic(
1187
const char *z, /* Full original help text */
1188
Blob *pOut, /* Write simplified help text here */
1189
const char *zTopic, /* TOPIC */
1190
const char *zSubtopic, /* SUBTOPIC */
1191
int bAbbrevSubcmd /* True if z[] contains abbreviated subcommands */
1192
){
1193
Blob in, line;/*, subsection;*/
1194
int n = 0;
1195
char *zQTop = re_quote(zTopic);
1196
char *zQSub = re_quote(zSubtopic);
1197
char *zPattern;
1198
ReCompiled *pRe = 0;
1199
1200
if( bAbbrevSubcmd ){
1201
zPattern = mprintf(" ([a-z]+ ?\\| ?)*%s\\b", zQSub);
1202
}else{
1203
zPattern = mprintf("> ?fossil [-a-z]+ .*\\b%s\\b", zQSub);
1204
}
1205
fossil_free(zQTop);
1206
fossil_free(zQSub);
1207
fossil_re_compile(&pRe, zPattern, 0);
1208
fossil_free(zPattern);
1209
blob_init(&in, z, -1);
1210
while( blob_line(&in, &line) ){
1211
if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_size(&line)) ){
1212
int atStart = 1;
1213
blob_appendb(pOut, &line);
1214
n++;
1215
while( blob_line(&in, &line) ){
1216
if( re_match(pRe,(unsigned char*)blob_buffer(&line),blob_size(&line)) ){
1217
blob_appendb(pOut, &line);
1218
n++;
1219
atStart = 1;
1220
}else{
1221
int x = is_subcommand(&line,bAbbrevSubcmd);
1222
if( x==2 ){
1223
if( atStart ){
1224
blob_appendb(pOut, &line);
1225
n++;
1226
}else{
1227
break;
1228
}
1229
}else if( x==1 ){
1230
break;
1231
}else{
1232
blob_appendb(pOut, &line);
1233
n++;
1234
atStart = 0;
1235
}
1236
}
1237
}
1238
}
1239
}
1240
blob_reset(&line);
1241
re_free(pRe);
1242
if( n ){
1243
blob_trim(pOut);
1244
blob_reset(&in);
1245
}
1246
return n;
1247
}
1248
1249
/*
1250
** Input p is a "Usage:" line or a subcommand line. Simplify this line
1251
** for the --usage option and write it into pOut.
1252
*/
1253
static void simplify_usage_line(
1254
Blob *p,
1255
Blob *pOut,
1256
int bAbbrevSubcmd,
1257
const char *zCmd
1258
){
1259
const char *z = blob_buffer(p);
1260
int sz = blob_size(p);
1261
int i = 0;
1262
if( sz>6 && z[0]=='U' ){
1263
for(i=1; i<sz && !fossil_isspace(z[i]); i++){}
1264
}else if( sz>0 && z[0]=='>' ){
1265
i = 1;
1266
}else if( sz>4 && bAbbrevSubcmd
1267
&& memcmp(z," ",3)==0 && !fossil_isspace(z[3]) ){
1268
int j;
1269
for(j=3; j<sz-1 && (z[j]!=' ' || z[j+1]!=' '); j++){}
1270
blob_appendf(pOut, "fossil %s %.*s\n", zCmd, j-3, &z[3]);
1271
return;
1272
}else{
1273
while( i<sz && fossil_isspace(z[i]) ) i++;
1274
if( i+2<sz && (z[i]=='o' || z[i]=='O') && z[i+1]=='r' ){
1275
while( i<sz && !fossil_isspace(z[i]) ) i++;
1276
}
1277
}
1278
while( i<sz && fossil_isspace(z[i]) ) i++;
1279
blob_append(pOut, &z[i], sz-i);
1280
}
1281
1282
/*
1283
** Input z[] is help text for a command zTopic. Write into pOut all lines of
1284
** z[] that show the command-line syntax for that command. Lines written
1285
** to pOut are lines that begin with out of:
1286
**
1287
** Usage:
1288
** or:
1289
** > fossil TOPIC
1290
**
1291
** Return the number of lines written into pOut.
1292
*/
1293
static int simplify_to_usage(
1294
const char *z, /* Full original help text */
1295
Blob *pOut, /* Write simplified help text here */
1296
const char *zTopic, /* The command for which z[] is full help text */
1297
int bAbbrevSubcmd /* z[] uses abbreviated subcommands */
1298
){
1299
ReCompiled *pRe = 0;
1300
Blob in, line;
1301
int n = 0;
1302
1303
if( bAbbrevSubcmd ){
1304
fossil_re_compile(&pRe, "^(Usage: | [a-z][-a-z|]+ .*)", 0);
1305
}else{
1306
fossil_re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |> ?fossil )", 0);
1307
}
1308
blob_init(&in, z, -1);
1309
while( blob_line(&in, &line) ){
1310
if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_strlen(&line)) ){
1311
simplify_usage_line(&line, pOut, bAbbrevSubcmd, zTopic);
1312
n++;
1313
}
1314
}
1315
re_free(pRe);
1316
if( n ) blob_trim(pOut);
1317
return n;
1318
}
1319
1320
/*
1321
** Input z[] is help text. Write into pOut all lines of z[] that show
1322
** command-line options. Return the number of lines written.
1323
*/
1324
static int simplify_to_options(
1325
const char *z, /* Full original help text */
1326
Blob *pOut, /* Write simplified help text here */
1327
int bAbbrevSubcmd, /* z[] uses abbreviated subcommands */
1328
const char *zCmd /* Name of the command that z[] describes */
1329
){
1330
ReCompiled *pRe = 0;
1331
Blob txt, line, subsection;
1332
int n = 0;
1333
int bSubsectionSeen = 0;
1334
1335
blob_init(&txt, z, -1);
1336
blob_init(&subsection, 0, 0);
1337
fossil_re_compile(&pRe, "^ +-.* ", 0);
1338
while( blob_line(&txt, &line) ){
1339
int len = blob_size(&line);
1340
unsigned char *zLine = (unsigned char *)blob_buffer(&line);
1341
if( re_match(pRe, zLine, len) ){
1342
if( blob_size(&subsection) ){
1343
simplify_usage_line(&subsection, pOut, bAbbrevSubcmd, zCmd);
1344
blob_reset(&subsection);
1345
}
1346
blob_appendb(pOut, &line);
1347
}else if( len>7 && !fossil_isspace(zLine[0]) && bSubsectionSeen
1348
&& sqlite3_strlike("%options:%",blob_str(&line),0)==0 ){
1349
subsection = line;
1350
}else if( !bAbbrevSubcmd && len>9
1351
&& (memcmp(zLine,"> fossil ",9)==0
1352
|| memcmp(zLine,"> fossil",9)==0) ){
1353
subsection = line;
1354
bSubsectionSeen = 1;
1355
}else if( bAbbrevSubcmd && len>5 && memcmp(zLine," ",3)==0
1356
&& fossil_isalpha(zLine[3]) ){
1357
subsection = line;
1358
bSubsectionSeen = 1;
1359
}else if( len>1 && !fossil_isspace(zLine[0]) && bSubsectionSeen ){
1360
blob_reset(&subsection);
1361
}
1362
}
1363
re_free(pRe);
1364
blob_trim(pOut);
1365
blob_reset(&subsection);
1366
return n;
1367
}
1368
1369
1370
1371
static void multi_column_list(const char **azWord, int nWord){
1372
int i, j, len;
1373
int mxLen = 0;
1374
int nCol;
1375
int nRow;
1376
for(i=0; i<nWord; i++){
1377
len = strlen(azWord[i]);
1378
if( len>mxLen ) mxLen = len;
1379
}
1380
nCol = 80/(mxLen+2);
1381
if( nCol==0 ) nCol = 1;
1382
nRow = (nWord + nCol - 1)/nCol;
1383
for(i=0; i<nRow; i++){
1384
const char *zSpacer = "";
1385
for(j=i; j<nWord; j+=nRow){
1386
fossil_print("%s%-*s", zSpacer, mxLen, azWord[j]);
1387
zSpacer = " ";
1388
}
1389
fossil_print("\n");
1390
}
1391
}
1392
1393
/*
1394
** COMMAND: test-list-webpage
1395
**
1396
** List all web pages.
1397
*/
1398
void cmd_test_webpage_list(void){
1399
int i, nCmd;
1400
const char *aCmd[MX_COMMAND];
1401
for(i=nCmd=0; i<MX_COMMAND; i++){
1402
if(CMDFLAG_WEBPAGE & aCommand[i].eCmdFlags){
1403
aCmd[nCmd++] = aCommand[i].zName;
1404
}
1405
}
1406
assert(nCmd && "page list is empty?");
1407
multi_column_list(aCmd, nCmd);
1408
}
1409
1410
1411
/*
1412
** List of commands starting with zPrefix, or all commands if zPrefix is NULL.
1413
*/
1414
static void command_list(int cmdMask, int verboseFlag, int useHtml){
1415
if( verboseFlag ){
1416
display_all_help(cmdMask, useHtml, 0);
1417
}else{
1418
int i, nCmd;
1419
const char *aCmd[MX_COMMAND];
1420
for(i=nCmd=0; i<MX_COMMAND; i++){
1421
if( (aCommand[i].eCmdFlags & cmdMask)==0 ) continue;
1422
else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
1423
aCmd[nCmd++] = aCommand[i].zName;
1424
}
1425
multi_column_list(aCmd, nCmd);
1426
}
1427
}
1428
1429
/*
1430
** TOPIC: options
1431
**
1432
** Command-line options common to all commands:
1433
**
1434
** --args FILENAME Read additional arguments and options from FILENAME
1435
** --case-sensitive BOOL Set case sensitivity for file names
1436
** --cgitrace Active CGI tracing
1437
** --chdir PATH Change to PATH before performing any operations
1438
** --errorlog FILENAME Log errors to FILENAME
1439
** -?|--help Show help on the command rather than running it
1440
** --httptrace Trace outbound HTTP requests
1441
** --localtime Display times using the local timezone
1442
** --nocgi Do not act as CGI
1443
** --no-th-hook Do not run TH1 hooks
1444
** -q|--quiet Reduce the amount of output
1445
** --sqlstats Show SQL usage statistics when done
1446
** --sqltrace Trace all SQL commands
1447
** --sshtrace Trace SSH activity
1448
** --ssl-identity NAME Set the SSL identity to NAME
1449
** --systemtrace Trace calls to system()
1450
** -U|--user USER Make the default user be USER
1451
** --utc Display times using UTC
1452
** --vfs NAME Cause SQLite to use the NAME VFS
1453
**
1454
** Additional options available on most commands that use network I/O:
1455
**
1456
** --accept-any-cert Disable server SSL cdert validation. Accept any SSL
1457
** cert that the server provides. WARNING: Unsafe!
1458
** Testing and debugging use only!
1459
** --ipv4 Use only IPv4. Disable IPv6 support.
1460
** --ipv6 Use only IPv6. Disable IPv4 support.
1461
** --nosync Disable autosync for the current command.
1462
** --proxy URL Specify the HTTP proxy to use. URL can be "off".
1463
*/
1464
1465
/*
1466
** COMMAND: help
1467
**
1468
** Usage: %fossil help [OPTIONS] [TOPIC] [SUBCOMMAND]
1469
**
1470
** Display information on how to use TOPIC, which may be a command, webpage, or
1471
** setting. Webpage names begin with "/". If TOPIC is omitted, a list of
1472
** topics is returned. If there is an extra argument after TOPIC, it is
1473
** the name of a subcommand, in which case only the help text for that one
1474
** subcommand is shown.
1475
**
1476
** The following options can be used when TOPIC is omitted:
1477
**
1478
** -a|--all List both common and auxiliary commands
1479
** -e|--everything List all help on all topics
1480
** -f|--full List full set of commands (including auxiliary
1481
** and unsupported "test" commands), options,
1482
** settings, and web pages
1483
** -o|--options List command-line options common to all commands
1484
** -s|--setting List setting names
1485
** -t|--test List unsupported "test" commands
1486
** -v|--verbose List both names and help text
1487
** -x|--aux List only auxiliary commands
1488
** -w|--www List all web pages
1489
**
1490
** These options can be used when TOPIC is present:
1491
**
1492
** -c|--commands Restrict TOPIC search to commands
1493
** -h|--html Format output as HTML rather than plain text
1494
** -o|--options Show command-line options for TOPIC
1495
** --raw Output raw, unformatted help text
1496
** -u|--usage Show a succinct usage summary, not full help text
1497
**
1498
** See also: [[usage]], [[options]], [[search]] with the -h option
1499
*/
1500
void help_cmd(void){
1501
int rc;
1502
int mask = CMDFLAG_ANY; /* Mask of help topic types */
1503
int isPage = 0; /* True if TOPIC is a page */
1504
int verboseFlag = 0; /* -v option */
1505
int commandsFlag = 0; /* -c option */
1506
const char *z; /* Original, untranslated help text */
1507
const char *zCmdOrPage; /* "command" or "page" or "setting" */
1508
const CmdOrPage *pCmd = 0; /* ptr to aCommand[] entry for TOPIC */
1509
int useHtml = 0; /* -h option */
1510
int bUsage; /* --usage */
1511
int bRaw; /* --raw option */
1512
int bOptions; /* --options */
1513
const char *zTopic; /* TOPIC argument */
1514
const char *zSubtopic = 0; /* SUBTOPIC argument */
1515
Blob subtext1, subtext2, s3; /* Subsets of z[] containing subtopic/usage */
1516
Blob txt; /* Text after rendering */
1517
int bAbbrevSubcmd = 0; /* Help text uses abbreviated subcommands */
1518
1519
verboseFlag = find_option("verbose","v",0)!=0;
1520
commandsFlag = find_option("commands","c",0)!=0;
1521
useHtml = find_option("html","h",0)!=0;
1522
bRaw = find_option("raw",0,0)!=0;
1523
bOptions = find_option("options","o",0)!=0;
1524
bUsage = find_option("usage","u",0)!=0;
1525
if( find_option("all","a",0) ){
1526
command_list(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER, verboseFlag, useHtml);
1527
return;
1528
}
1529
else if( find_option("www","w",0) ){
1530
command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml);
1531
return;
1532
}
1533
else if( find_option("aux","x",0) ){
1534
command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml);
1535
return;
1536
}
1537
else if( find_option("test","t",0) ){
1538
command_list(CMDFLAG_TEST, verboseFlag, useHtml);
1539
return;
1540
}
1541
else if( find_option("setting","s",0) ){
1542
command_list(CMDFLAG_SETTING, verboseFlag, useHtml);
1543
return;
1544
}
1545
else if( find_option("topic","c",0) ){
1546
command_list(CMDFLAG_TOPIC, verboseFlag, useHtml);
1547
return;
1548
}
1549
else if( find_option("full","f",0) ){
1550
fossil_print("fossil commands:\n\n");
1551
command_list(CMDFLAG_1ST_TIER, verboseFlag, useHtml);
1552
fossil_print("\nfossil auxiliary commands:\n\n");
1553
command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml);
1554
fossil_print("\nfossil settings:\n\n");
1555
command_list(CMDFLAG_SETTING, verboseFlag, useHtml);
1556
fossil_print("\nfossil web pages:\n\n");
1557
command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml);
1558
fossil_print("\nfossil miscellaneous help topics:\n\n");
1559
command_list(CMDFLAG_TOPIC, verboseFlag, useHtml);
1560
fossil_print("\nfossil test commands (unsupported):\n\n");
1561
command_list(CMDFLAG_TEST, verboseFlag, useHtml);
1562
if ( !verboseFlag ) {
1563
fossil_print("\n");
1564
version_cmd();
1565
}
1566
return;
1567
}
1568
else if( find_option("everything","e",0) ){
1569
display_all_help(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE |
1570
CMDFLAG_SETTING | CMDFLAG_TEST | CMDFLAG_TOPIC,
1571
useHtml, 0);
1572
return;
1573
}
1574
verify_all_options();
1575
zCmdOrPage = "help topic";
1576
if( g.argc<3 ){
1577
if( bOptions ){
1578
zTopic = "options";
1579
zSubtopic = 0;
1580
mask = CMDFLAG_TOPIC;
1581
bOptions = 0;
1582
goto find_and_show_help;
1583
}
1584
z = g.argv[0];
1585
fossil_print(
1586
"Usage: %s help TOPIC\n"
1587
"Things to try:\n\n"
1588
" %s help help\n"
1589
" %s help -o\n"
1590
" %s help -a\n"
1591
" %s search -h TOPIC\n\n"
1592
"Other common values for TOPIC:\n\n",
1593
z, z, z, z, z);
1594
command_list(CMDFLAG_1ST_TIER,verboseFlag,useHtml);
1595
if( !verboseFlag ) version_cmd();
1596
return;
1597
}
1598
zTopic = g.argv[2];
1599
zSubtopic = g.argc>=4 ? g.argv[3] : 0;
1600
isPage = ('/' == zTopic[0]) ? 1 : 0;
1601
if( isPage ){
1602
zCmdOrPage = "page";
1603
}else if( commandsFlag ){
1604
mask = CMDFLAG_COMMAND;
1605
zCmdOrPage = "command";
1606
}
1607
find_and_show_help:
1608
rc = dispatch_name_search(zTopic, mask|CMDFLAG_PREFIX, &pCmd);
1609
if( rc ){
1610
int i, n;
1611
const char *az[5];
1612
if( rc==1 ){
1613
if( help_is_platform_command(g.argv[2]) ){
1614
fossil_print("Not available in this build: %s\n", g.argv[2]);
1615
return;
1616
}
1617
fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]);
1618
}else{
1619
fossil_print("ambiguous %s prefix: %s\n",
1620
zCmdOrPage, g.argv[2]);
1621
}
1622
fossil_print("Did you mean one of these TOPICs:\n");
1623
n = dispatch_approx_match(g.argv[2], 5, az);
1624
for(i=0; i<n; i++){
1625
fossil_print(" * %s\n", az[i]);
1626
}
1627
fossil_print("Other commands to try:\n");
1628
fossil_print(" fossil search -h PATTERN ;# search all help text\n");
1629
fossil_print(" fossil help -a ;# show all commands\n");
1630
fossil_print(" fossil help -w ;# show all web-pages\n");
1631
fossil_print(" fossil help -s ;# show all settings\n");
1632
fossil_print(" fossil help -o ;# show global options\n");
1633
return;
1634
}
1635
bAbbrevSubcmd = (pCmd->eCmdFlags & CMDFLAG_ABBREVSUBCMD)!=0;
1636
z = pCmd->zHelp;
1637
if( z==0 ){
1638
fossil_fatal("no help available for the %s %s",
1639
pCmd->zName, zCmdOrPage);
1640
}
1641
blob_init(&subtext1, 0, 0);
1642
blob_init(&subtext2, 0, 0);
1643
blob_init(&s3, 0, 0);
1644
if( zSubtopic!=0 ){
1645
if( simplify_to_subtopic(z, &subtext1, zTopic, zSubtopic, bAbbrevSubcmd) ){
1646
z = blob_str(&subtext1);
1647
}else{
1648
fossil_print("No subtopic \"%s\" for \"%s\".\n", zSubtopic, zTopic);
1649
bUsage = 1;
1650
zSubtopic = 0;
1651
}
1652
}
1653
if( bUsage ){
1654
if( simplify_to_usage(z, &subtext2, zTopic, bAbbrevSubcmd) ){
1655
z = blob_str(&subtext2);
1656
}else{
1657
bUsage = 0;
1658
}
1659
}
1660
if( bOptions ){
1661
simplify_to_options(z, &s3, bAbbrevSubcmd, zTopic);
1662
z = blob_str(&s3);
1663
}
1664
if( pCmd && pCmd->eCmdFlags & CMDFLAG_SETTING ){
1665
const Setting *pSetting = db_find_setting(pCmd->zName, 0);
1666
char *zDflt = 0;
1667
if( pSetting!=0 && pSetting->def!=0 && *pSetting->def!=0 ){
1668
zDflt = mprintf(" (default: %s)", pSetting->def);
1669
}
1670
fossil_print("Setting: \"%s\"%s%s\n\n",
1671
pCmd->zName, zDflt!=0 ? zDflt : "",
1672
(pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : ""
1673
);
1674
fossil_free(zDflt);
1675
}
1676
blob_init(&txt, 0, 0);
1677
if( bRaw ){
1678
blob_append(&txt, z, -1);
1679
}else if( useHtml ){
1680
help_to_html(z, &txt);
1681
}else{
1682
help_to_text(z, &txt, bUsage || zSubtopic!=0);
1683
}
1684
if( blob_strlen(&txt)>0 ) fossil_print("%s\n", blob_str(&txt));
1685
blob_reset(&txt);
1686
blob_reset(&subtext1);
1687
blob_reset(&subtext2);
1688
}
1689
1690
/*
1691
** Return a pointer to the setting information array.
1692
**
1693
** This routine provides access to the aSetting[] array which is created
1694
** by the mkindex utility program and included with <page_index.h>.
1695
*/
1696
const Setting *setting_info(int *pnCount){
1697
if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
1698
return aSetting;
1699
}
1700
1701
/*
1702
** Return a pointer to a specific Setting entry for the setting named
1703
** in the argument. Or return NULL if no such setting exists.
1704
**
1705
** The pointer returned points into the middle of the global aSetting[]
1706
** array that is generated by mkindex. Use setting_info() to fetch the
1707
** whole array. Use this routine to fetch a specific entry.
1708
*/
1709
const Setting *setting_find(const char *zName){
1710
int iFirst = 0;
1711
int iLast = ArraySize(aSetting)-1;
1712
while( iFirst<=iLast ){
1713
int iCur = (iFirst+iLast)/2;
1714
int c = strcmp(aSetting[iCur].name, zName);
1715
if( c<0 ){
1716
iFirst = iCur+1;
1717
}else if( c>0 ){
1718
iLast = iCur-1;
1719
}else{
1720
return &aSetting[iCur];
1721
}
1722
}
1723
return 0;
1724
}
1725
1726
/*****************************************************************************
1727
** A virtual table for accessing the information in aCommand[], and
1728
** especially the help-text
1729
*/
1730
1731
/* helptextVtab_vtab is a subclass of sqlite3_vtab which is
1732
** underlying representation of the virtual table
1733
*/
1734
typedef struct helptextVtab_vtab helptextVtab_vtab;
1735
struct helptextVtab_vtab {
1736
sqlite3_vtab base; /* Base class - must be first */
1737
/* Add new fields here, as necessary */
1738
};
1739
1740
/* helptextVtab_cursor is a subclass of sqlite3_vtab_cursor which will
1741
** serve as the underlying representation of a cursor that scans
1742
** over rows of the result
1743
*/
1744
typedef struct helptextVtab_cursor helptextVtab_cursor;
1745
struct helptextVtab_cursor {
1746
sqlite3_vtab_cursor base; /* Base class - must be first */
1747
/* Insert new fields here. For this helptextVtab we only keep track
1748
** of the rowid */
1749
sqlite3_int64 iRowid; /* The rowid */
1750
};
1751
1752
/*
1753
** The helptextVtabConnect() method is invoked to create a new
1754
** helptext virtual table.
1755
**
1756
** Think of this routine as the constructor for helptextVtab_vtab objects.
1757
**
1758
** All this routine needs to do is:
1759
**
1760
** (1) Allocate the helptextVtab_vtab object and initialize all fields.
1761
**
1762
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
1763
** result set of queries against the virtual table will look like.
1764
*/
1765
static int helptextVtabConnect(
1766
sqlite3 *db,
1767
void *pAux,
1768
int argc, const char *const*argv,
1769
sqlite3_vtab **ppVtab,
1770
char **pzErr
1771
){
1772
helptextVtab_vtab *pNew;
1773
int rc;
1774
1775
rc = sqlite3_declare_vtab(db,
1776
"CREATE TABLE x(name,type,flags,helptext,formatted,html)"
1777
);
1778
if( rc==SQLITE_OK ){
1779
pNew = sqlite3_malloc( sizeof(*pNew) );
1780
*ppVtab = (sqlite3_vtab*)pNew;
1781
if( pNew==0 ) return SQLITE_NOMEM;
1782
memset(pNew, 0, sizeof(*pNew));
1783
}
1784
return rc;
1785
}
1786
1787
/*
1788
** This method is the destructor for helptextVtab_vtab objects.
1789
*/
1790
static int helptextVtabDisconnect(sqlite3_vtab *pVtab){
1791
helptextVtab_vtab *p = (helptextVtab_vtab*)pVtab;
1792
sqlite3_free(p);
1793
return SQLITE_OK;
1794
}
1795
1796
/*
1797
** Constructor for a new helptextVtab_cursor object.
1798
*/
1799
static int helptextVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
1800
helptextVtab_cursor *pCur;
1801
pCur = sqlite3_malloc( sizeof(*pCur) );
1802
if( pCur==0 ) return SQLITE_NOMEM;
1803
memset(pCur, 0, sizeof(*pCur));
1804
*ppCursor = &pCur->base;
1805
return SQLITE_OK;
1806
}
1807
1808
/*
1809
** Destructor for a helptextVtab_cursor.
1810
*/
1811
static int helptextVtabClose(sqlite3_vtab_cursor *cur){
1812
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1813
sqlite3_free(pCur);
1814
return SQLITE_OK;
1815
}
1816
1817
1818
/*
1819
** Advance a helptextVtab_cursor to its next row of output.
1820
*/
1821
static int helptextVtabNext(sqlite3_vtab_cursor *cur){
1822
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1823
pCur->iRowid++;
1824
return SQLITE_OK;
1825
}
1826
1827
/*
1828
** Return values of columns for the row at which the helptextVtab_cursor
1829
** is currently pointing.
1830
*/
1831
static int helptextVtabColumn(
1832
sqlite3_vtab_cursor *cur, /* The cursor */
1833
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
1834
int i /* Which column to return */
1835
){
1836
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1837
const CmdOrPage *pPage = aCommand + pCur->iRowid;
1838
switch( i ){
1839
case 0: /* name */
1840
sqlite3_result_text(ctx, pPage->zName, -1, SQLITE_STATIC);
1841
break;
1842
case 1: { /* type */
1843
const char *zType = 0;
1844
if( pPage->eCmdFlags & CMDFLAG_COMMAND ){
1845
zType = "command";
1846
}else if( pPage->eCmdFlags & CMDFLAG_WEBPAGE ){
1847
zType = "webpage";
1848
}else if( pPage->eCmdFlags & CMDFLAG_SETTING ){
1849
zType = "setting";
1850
}else if( pPage->eCmdFlags & CMDFLAG_TOPIC ){
1851
zType = "help-topic";
1852
}
1853
sqlite3_result_text(ctx, zType, -1, SQLITE_STATIC);
1854
break;
1855
}
1856
case 2: /* flags */
1857
sqlite3_result_int(ctx, pPage->eCmdFlags);
1858
break;
1859
case 3: /* helptext */
1860
sqlite3_result_text(ctx, pPage->zHelp, -1, SQLITE_STATIC);
1861
break;
1862
case 4: { /* formatted */
1863
Blob txt;
1864
blob_init(&txt, 0, 0);
1865
help_to_text(pPage->zHelp, &txt, 0);
1866
sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free);
1867
break;
1868
}
1869
case 5: { /* formatted */
1870
Blob txt;
1871
blob_init(&txt, 0, 0);
1872
help_to_html(pPage->zHelp, &txt);
1873
sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free);
1874
break;
1875
}
1876
}
1877
return SQLITE_OK;
1878
}
1879
1880
/*
1881
** Return the rowid for the current row. In this implementation, the
1882
** rowid is the same as the output value.
1883
*/
1884
static int helptextVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
1885
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1886
*pRowid = pCur->iRowid;
1887
return SQLITE_OK;
1888
}
1889
1890
/*
1891
** Return TRUE if the cursor has been moved off of the last
1892
** row of output.
1893
*/
1894
static int helptextVtabEof(sqlite3_vtab_cursor *cur){
1895
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
1896
return pCur->iRowid>=MX_COMMAND;
1897
}
1898
1899
/*
1900
** This method is called to "rewind" the helptextVtab_cursor object back
1901
** to the first row of output. This method is always called at least
1902
** once prior to any call to helptextVtabColumn() or helptextVtabRowid() or
1903
** helptextVtabEof().
1904
*/
1905
static int helptextVtabFilter(
1906
sqlite3_vtab_cursor *pVtabCursor,
1907
int idxNum, const char *idxStr,
1908
int argc, sqlite3_value **argv
1909
){
1910
helptextVtab_cursor *pCur = (helptextVtab_cursor *)pVtabCursor;
1911
pCur->iRowid = 1;
1912
return SQLITE_OK;
1913
}
1914
1915
/*
1916
** SQLite will invoke this method one or more times while planning a query
1917
** that uses the virtual table. This routine needs to create
1918
** a query plan for each invocation and compute an estimated cost for that
1919
** plan.
1920
*/
1921
static int helptextVtabBestIndex(
1922
sqlite3_vtab *tab,
1923
sqlite3_index_info *pIdxInfo
1924
){
1925
pIdxInfo->estimatedCost = (double)MX_COMMAND;
1926
pIdxInfo->estimatedRows = MX_COMMAND;
1927
return SQLITE_OK;
1928
}
1929
1930
/*
1931
** This following structure defines all the methods for the
1932
** virtual table.
1933
*/
1934
static sqlite3_module helptextVtabModule = {
1935
/* iVersion */ 0,
1936
/* xCreate */ 0, /* Helptext is eponymous and read-only */
1937
/* xConnect */ helptextVtabConnect,
1938
/* xBestIndex */ helptextVtabBestIndex,
1939
/* xDisconnect */ helptextVtabDisconnect,
1940
/* xDestroy */ 0,
1941
/* xOpen */ helptextVtabOpen,
1942
/* xClose */ helptextVtabClose,
1943
/* xFilter */ helptextVtabFilter,
1944
/* xNext */ helptextVtabNext,
1945
/* xEof */ helptextVtabEof,
1946
/* xColumn */ helptextVtabColumn,
1947
/* xRowid */ helptextVtabRowid,
1948
/* xUpdate */ 0,
1949
/* xBegin */ 0,
1950
/* xSync */ 0,
1951
/* xCommit */ 0,
1952
/* xRollback */ 0,
1953
/* xFindMethod */ 0,
1954
/* xRename */ 0,
1955
/* xSavepoint */ 0,
1956
/* xRelease */ 0,
1957
/* xRollbackTo */ 0,
1958
/* xShadowName */ 0,
1959
/* xIntegrity */ 0
1960
};
1961
1962
1963
/*
1964
** Register the helptext virtual table
1965
*/
1966
int helptext_vtab_register(sqlite3 *db){
1967
int rc = sqlite3_create_module(db, "helptext", &helptextVtabModule, 0);
1968
return rc;
1969
}
1970
/* End of the helptext virtual table
1971
******************************************************************************/
1972

Keyboard Shortcuts

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