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