diff options
Diffstat (limited to 'src/dispatch.c')
| -rw-r--r-- | src/dispatch.c | 1971 |
1 files changed, 1971 insertions, 0 deletions
diff --git a/src/dispatch.c b/src/dispatch.c new file mode 100644 index 0000000..9a464a4 --- /dev/null +++ b/src/dispatch.c @@ -0,0 +1,1971 @@ +/* +** Copyright (c) 2016 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to map command names (ex: "help", "commit", +** "diff") or webpage names (ex: "/timeline", "/search") into the functions +** that implement those commands and web pages and their associated help +** text. +*/ +#include "config.h" +#include <assert.h> +#include "dispatch.h" + +#if INTERFACE +/* +** An instance of this object defines everything we need to know about an +** individual command, webpage, setting, or help topic. +*/ +struct CmdOrPage { + const char *zName; /* Name. Webpages start with "/". Commands do not */ + void (*xFunc)(void); /* Implementation function, or NULL for settings */ + const char *zHelp; /* Raw help text */ + int iHelp; /* Index of help variable */ + unsigned int eCmdFlags; /* Flags */ +}; + +/*************************************************************************** +** These macros must match similar macros in mkindex.c +** Allowed values for CmdOrPage.eCmdFlags. +*/ +#define CMDFLAG_1ST_TIER 0x000001 /* Most important commands */ +#define CMDFLAG_2ND_TIER 0x000002 /* Obscure and seldom used commands */ +#define CMDFLAG_TEST 0x000004 /* Commands for testing only */ +#define CMDFLAG_WEBPAGE 0x000008 /* Web pages */ +#define CMDFLAG_COMMAND 0x000010 /* A command */ +#define CMDFLAG_SETTING 0x000020 /* A setting */ +#define CMDFLAG_VERSIONABLE 0x000040 /* A versionable setting */ +#define CMDFLAG_BLOCKTEXT 0x000080 /* Multi-line text setting */ +#define CMDFLAG_BOOLEAN 0x000100 /* A boolean setting */ +#define CMDFLAG_RAWCONTENT 0x000200 /* Do not interpret POST content */ +/* NOTE: 0x000400 = CMDFLAG_SENSITIVE in mkindex.c! */ +#define CMDFLAG_HIDDEN 0x000800 /* Elide from most listings */ +#define CMDFLAG_LDAVG_EXEMPT 0x001000 /* Exempt from load_control() */ +#define CMDFLAG_ALIAS 0x002000 /* Command aliases */ +#define CMDFLAG_KEEPEMPTY 0x004000 /* Do not unset empty settings */ +#define CMDFLAG_ABBREVSUBCMD 0x008000 /* Help text abbreviates subcommands */ +#define CMDFLAG_TOPIC 0x010000 /* A help topic */ +/**************************************************************************/ + +/* Values for the 2nd parameter to dispatch_name_search() */ +#define CMDFLAG_ANY 0x010038 /* Match anything */ +#define CMDFLAG_PREFIX 0x000200 /* Prefix match is OK */ + +#endif /* INTERFACE */ + +/* +** The page_index.h file contains the definition for aCommand[] - an array +** of CmdOrPage objects that defines all available commands and webpages +** known to Fossil. +** +** The entries in aCommand[] are in sorted order by name. Since webpage names +** always begin with "/", all webpage names occur first. The page_index.h file +** also sets the FOSSIL_FIRST_CMD macro to be the *approximate* index +** in aCommand[] of the first command entry. FOSSIL_FIRST_CMD might be +** slightly too low, and so the range FOSSIL_FIRST_CMD...MX_COMMAND might +** contain a few webpage entries at the beginning. +** +** The page_index.h file is generated by the mkindex program which scans all +** source code files looking for header comments on the functions that +** implement command and webpages. +*/ +#include "page_index.h" +#define MX_COMMAND count(aCommand) +#define MX_HELP_DUP 5 /* Upper bound estimate on help string duplication */ + +/* +** Given a command, webpage, or setting name in zName, find the corresponding +** CmdOrPage object and return a pointer to that object in *ppCmd. +** +** The eType field is CMDFLAG_COMMAND to look up commands, CMDFLAG_WEBPAGE to +** look up webpages, CMDFLAG_SETTING to look up settings, or CMDFLAG_ANY to look +** for any. If the CMDFLAG_PREFIX bit is set, then a prefix match is allowed. +** +** Return values: +** 0: Success. *ppCmd is set to the appropriate CmdOrPage +** 1: Not found. +** 2: Ambiguous. Two or more entries match. +*/ +int dispatch_name_search( + const char *zName, /* Look for this name */ + unsigned eType, /* CMDFLAGS_* bits */ + const CmdOrPage **ppCmd /* Write the matching CmdOrPage object here */ +){ + int upr, lwr, mid; + int nName = strlen(zName); + lwr = 0; + upr = MX_COMMAND - 1; + while( lwr<=upr ){ + int c; + mid = (upr+lwr)/2; + c = strcmp(zName, aCommand[mid].zName); + if( c==0 ){ + if( (aCommand[mid].eCmdFlags & eType)==0 ) return 1; + *ppCmd = &aCommand[mid]; + return 0; /* An exact match */ + }else if( c<0 ){ + upr = mid - 1; + }else{ + lwr = mid + 1; + } + } + if( (eType & CMDFLAG_PREFIX)!=0 + && lwr<MX_COMMAND + && strncmp(zName, aCommand[lwr].zName, nName)==0 + ){ + /* An inexact prefix match was found. Scan the name table to try to find + * exactly one entry with this prefix and the requested type. */ + for( mid=-1; lwr<MX_COMMAND + && strncmp(zName, aCommand[lwr].zName, nName)==0; ++lwr ){ + if( aCommand[lwr].eCmdFlags & eType ){ + if( mid<0 ){ + mid = lwr; /* Potential ambiguous prefix */ + }else{ + if( aCommand[lwr].xFunc != aCommand[mid].xFunc ){ + return 2; /* Confirmed ambiguous prefix */ + } + } + } + } + if( mid>=0 ){ + *ppCmd = &aCommand[mid]; + return 0; /* Prefix match */ + } + } + return 1; /* Not found */ +} + +/* +** zName is the name of a webpage (eType==CMDFLAGS_WEBPAGE) that does not +** exist in the dispatch table. Check to see if this webpage name exists +** as an alias in the CONFIG table of the repository. If it is, then make +** appropriate changes to the CGI environment and set *ppCmd to point to the +** aliased command. +** +** Return 0 if the command is successfully aliased. Return 1 if there +** is not alias for zName. Any kind of error in the alias value causes a +** error to be thrown. +** +** Alias entries in the CONFIG table have a "name" value of "walias:NAME" +** where NAME is the input page name. The value is a string of the form +** "NEWNAME?QUERYPARAMS". The ?QUERYPARAMS is optional. If present (and it +** usually is), then all query parameters are added to the CGI environment. +** Except, query parameters of the form "X!" cause any CGI X variable to be +** removed. +*/ +int dispatch_alias(const char *zName, const CmdOrPage **ppCmd){ + char *z; + char *zQ; + int i; + + z = db_text(0, "SELECT value FROM config WHERE name='walias:%q'",zName); + if( z==0 ) return 1; + for(i=0; z[i] && z[i]!='?'; i++){} + if( z[i]=='?' ){ + z[i] = 0; + zQ = &z[i+1]; + }else{ + zQ = &z[i]; + } + if( dispatch_name_search(z, CMDFLAG_WEBPAGE, ppCmd) ){ + fossil_fatal("\"%s\" aliased to \"%s\" but \"%s\" does not exist", + zName, z, z); + } + z = zQ; + while( *z ){ + char *zName = z; + char *zValue = 0; + while( *z && *z!='=' && *z!='&' && *z!='!' ){ z++; } + if( *z=='=' ){ + *z = 0; + z++; + zValue = z; + while( *z && *z!='&' ){ z++; } + if( *z ){ + *z = 0; + z++; + } + dehttpize(zValue); + }else if( *z=='!' ){ + *(z++) = 0; + cgi_delete_query_parameter(zName); + zName = ""; + }else{ + if( *z ){ *z++ = 0; } + zValue = ""; + } + if( fossil_islower(zName[0]) ){ + cgi_replace_query_parameter(zName, zValue); + }else if( fossil_isupper(zName[0]) ){ + cgi_replace_query_parameter_tolower(zName, zValue); + } + } + return 0; +} + +/* +** Fill Blob with a space-separated list of all command names that +** match the prefix zPrefix and the eType CMDFLAGS_ bits. +*/ +void dispatch_matching_names( + const char *zPrefix, /* name prefix */ + unsigned eType, /* CMDFLAG_ bits */ + Blob *pList /* space-separated list of command names */ +){ + int i; + int nPrefix = (int)strlen(zPrefix); + for(i=FOSSIL_FIRST_CMD; i<MX_COMMAND; i++){ + if( (aCommand[i].eCmdFlags & eType)==0 ) continue; + if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){ + blob_appendf(pList, " %s", aCommand[i].zName); + } + } +} + +/* +** Return the index of the first non-space character that follows +** a span of two or more spaces. Return 0 if there is not gap. +*/ +static int hasGap(const char *z, int n){ + int i; + for(i=3; i<n-1; i++){ + if( z[i]==' ' && z[i+1]!=' ' && z[i-1]==' ' && z[i-2]!='.' ) return i+1; + } + return 0 ; +} + +/* +** Input string zIn starts with '['. If the content is a hyperlink of the +** form [[...]] then return the index of the closing ']'. Otherwise return 0. +*/ +static int help_is_link(const char *z, int n){ + int i; + char c; + if( n<5 ) return 0; + if( z[1]!='[' ) return 0; + for(i=3; i<n && (c = z[i])!=0; i++){ + if( c==']' && z[i-1]==']' ) return i; + } + return 0; +} + +/* +** Append text to pOut with changes: +** +** * Add hyperlink markup for [[...]] +** * Escape HTML characters: < > & and " +** * Change "%fossil" to just "fossil" +*/ +static void appendLinked(Blob *pOut, const char *z, int n){ + int i = 0; + int j; + while( i<n ){ + char c = z[i]; + if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){ + if( i ) blob_append(pOut, z, i); + z += i+2; + n -= i+2; + blob_appendf(pOut, "<a href='%R/help/%.*s'>%.*s</a>", + j-3, z, j-3, z); + z += j-1; + n -= j-1; + i = 0; + }else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){ + if( i ) blob_append(pOut, z, i); + z += i+7; + n -= i+7; + blob_append(pOut, "fossil", 6); + i = 0; + }else if( c=='<' ){ + if( i ) blob_append(pOut, z, i); + blob_append(pOut, "<", 4); + z += i+1; + n -= i+1; + i = 0; + }else if( c=='>' ){ + if( i ) blob_append(pOut, z, i); + blob_append(pOut, ">", 4); + z += i+1; + n -= i+1; + i = 0; + }else if( c=='&' ){ + if( i ) blob_append(pOut, z, i); + blob_append(pOut, "&", 5); + z += i+1; + n -= i+1; + i = 0; + }else{ + i++; + } + } + blob_append(pOut, z, i); +} + +/* +** Append text to pOut, adding formatting markup. Terms that +** have all lower-case letters are within <tt>..</tt>. Terms +** that have all upper-case letters are within <i>..</i>. +*/ +static void appendMixedFont(Blob *pOut, const char *z, int n){ + const char *zEnd = ""; + int i = 0; + int j; + while( i<n ){ + if( z[i]==' ' || z[i]=='=' ){ + for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){} + appendLinked(pOut, z+i, j-i); + i = j; + }else{ + for(j=i; j<n && z[j]!=' ' && z[j]!='=' && !fossil_isalpha(z[j]); j++){} + if( j>=n || z[j]==' ' || z[j]=='=' ){ + zEnd = ""; + }else{ + if( fossil_isupper(z[j]) && z[i]!='-' ){ + blob_append(pOut, "<i>",3); + zEnd = "</i>"; + }else{ + blob_append(pOut, "<tt>", 4); + zEnd = "</tt>"; + } + } + while( j<n && z[j]!=' ' && z[j]!='=' ){ j++; } + appendLinked(pOut, z+i, j-i); + if( zEnd[0] ) blob_append(pOut, zEnd, -1); + i = j; + } + } +} + +/* +** Attempt to reformat plain-text help into HTML for display on a webpage. +** +** The HTML output is appended to Blob pHtml, which should already be +** initialized. +** +** Formatting rules: +** +** * Bullet lists are indented from the surrounding text by +** at least one space. Each bullet begins with " * ". +** +** * Display lists are indented from the surrounding text. +** Each tag begins with "-" or occur on a line that is +** followed by two spaces and a non-space. <dd> elements can begin +** on the same line as long as they are separated by at least +** two spaces. +** +** * Indented text is show verbatim (<pre>...</pre>) +** +** * Lines that begin with "|" at the left margin are in <pre>...</pre> +*/ +static void help_to_html(const char *zHelp, Blob *pHtml){ + int i; + char c; + int nIndent = 0; + int wantP = 0; + int wantBR = 0; + int aIndent[10]; + const char *azEnd[10]; + int iLevel = 0; + int isLI = 0; + int isDT = 0; + int inPRE = 0; + static const char *zEndDL = "</dl></blockquote>"; + static const char *zEndPRE = "</pre></blockquote>"; + static const char *zEndUL = "</ul>"; + static const char *zEndDD = "</dd>"; + + aIndent[0] = 0; + azEnd[0] = ""; + while( zHelp[0] ){ + i = 0; + while( (c = zHelp[i])!=0 && c!='\n' ){ + if( c=='%' && i>2 && zHelp[i-2]==':' && strncmp(zHelp+i,"%fossil",7)==0 ){ + appendLinked(pHtml, zHelp, i); + zHelp += i+1; + i = 0; + wantBR = 1; + continue; + } + i++; + } + if( i>2 && (zHelp[0]=='>' || zHelp[0]=='|') && zHelp[1]==' ' ){ + if( zHelp[0]=='>' ){ + isDT = 1; + for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){} + }else{ + if( !inPRE ){ + blob_append(pHtml, "<pre>\n", -1); + inPRE = 1; + } + } + }else{ + if( inPRE ){ + blob_append(pHtml, "</pre>\n", -1); + inPRE = 0; + } + isDT = 0; + for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){} + } + if( inPRE ){ + blob_append(pHtml, zHelp+1, i); + zHelp += i + 1; + continue; + } + if( nIndent==i ){ + if( c==0 ) break; + if( iLevel && azEnd[iLevel]==zEndPRE ){ + /* Skip the newline at the end of a <pre> */ + }else{ + blob_append_char(pHtml, '\n'); + } + wantP = 1; + wantBR = 0; + zHelp += i+1; + continue; + } + if( nIndent+2<i && zHelp[nIndent]=='*' && zHelp[nIndent+1]==' ' ){ + nIndent += 2; + while( nIndent<i && zHelp[nIndent]==' '){ nIndent++; } + isLI = 1; + }else{ + isLI = 0; + } + while( iLevel>0 && aIndent[iLevel]>nIndent ){ + blob_append(pHtml, azEnd[iLevel--], -1); + } + if( nIndent>aIndent[iLevel] ){ + assert( iLevel<ArraySize(aIndent)-2 ); + if( isLI ){ + iLevel++; + aIndent[iLevel] = nIndent; + azEnd[iLevel] = zEndUL; + if( wantP ){ + blob_append(pHtml,"<p>", 3); + wantP = 0; + } + blob_append(pHtml, "<ul>\n", 5); + }else if( isDT + || zHelp[nIndent]=='-' + || hasGap(zHelp+nIndent,i-nIndent) ){ + iLevel++; + aIndent[iLevel] = nIndent; + azEnd[iLevel] = zEndDL; + wantP = 0; + if( isDT ){ + blob_append(pHtml, "<blockquote><dl>\n", -1); + }else{ + blob_append(pHtml, "<blockquote><dl class=\"helpOptions\">\n", -1); + } + }else if( azEnd[iLevel]==zEndDL ){ + iLevel++; + aIndent[iLevel] = nIndent; + azEnd[iLevel] = zEndDD; + if( wantP ){ + blob_append(pHtml,"<p>", 3); + wantP = 0; + } + blob_append(pHtml, "<dd>", 4); + }else if( wantP ){ + iLevel++; + aIndent[iLevel] = nIndent; + azEnd[iLevel] = zEndPRE; + blob_append(pHtml, "<blockquote><pre>", -1); + wantP = 0; + } + } + if( isLI ){ + blob_append(pHtml, "<li> ", 5); + } + if( wantP ){ + blob_append(pHtml, "<p> ", 4); + wantP = 0; + } + if( azEnd[iLevel]==zEndDL ){ + int iDD; + blob_append(pHtml, "<dt> ", 5); + iDD = hasGap(zHelp+nIndent, i-nIndent); + if( iDD ){ + int x; + assert( iLevel<ArraySize(aIndent)-1 ); + iLevel++; + aIndent[iLevel] = x = nIndent+iDD; + azEnd[iLevel] = zEndDD; + appendMixedFont(pHtml, zHelp+nIndent, iDD-2); + blob_append(pHtml, "</dt><dd>",9); + appendLinked(pHtml, zHelp+x, i-x); + }else{ + appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); + } + blob_append(pHtml, "</dt>\n", 6); + }else if( wantBR ){ + appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); + blob_append(pHtml, "<br>\n", 5); + wantBR = 0; + }else{ + appendLinked(pHtml, zHelp+nIndent, i-nIndent); + blob_append_char(pHtml, '\n'); + } + zHelp += i+1; + i = 0; + if( c==0 ) break; + } + while( iLevel>0 ){ + blob_appendf(pHtml, "%s\n", azEnd[iLevel--]); + } +} + +/* +** Format help text for TTY display. +*/ +static void help_to_text(const char *zHelp, Blob *pText, int bUsage){ + int i, x; + char c; + if( zHelp[0]=='>' ){ + if( !bUsage ){ + blob_appendf(pText, "Usage:"); + }else{ + blob_append_char(pText, ' '); + } + zHelp++; + } + for(i=0; (c = zHelp[i])!=0; i++){ + if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){ + if( i>0 ) blob_append(pText, zHelp, i); + blob_append(pText, "fossil", 6); + zHelp += i+7; + i = -1; + continue; + } + if( c=='\n' && (zHelp[i+1]=='>' || zHelp[i+1]=='|') && zHelp[i+2]==' ' ){ + blob_append(pText, zHelp, i+1); + blob_append(pText, " ", 1); + zHelp += i+2; + i = -1; + continue; + } + if( c=='[' && (x = help_is_link(zHelp+i, 100000))!=0 ){ + if( i>0 ) blob_append(pText, zHelp, i); + zHelp += i+2; + blob_append(pText, zHelp, x-3); + zHelp += x-1; + i = -1; + continue; + } + } + if( i>0 ){ + blob_append(pText, zHelp, i); + } +} + +/* +** Display help for all commands based on provided flags. +*/ +static void display_all_help(int mask, int useHtml, int rawOut){ + int i; + unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0}; /* Help string occurrences */ + int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help strings->commands*/ + if( useHtml ) fossil_print("<!--\n"); + fossil_print("Help text for:\n"); + if( mask & CMDFLAG_1ST_TIER ) fossil_print(" * Commands\n"); + if( mask & CMDFLAG_2ND_TIER ) fossil_print(" * Auxiliary commands\n"); + if( mask & CMDFLAG_ALIAS ) fossil_print(" * Aliases\n"); + if( mask & CMDFLAG_TEST ) fossil_print(" * Test commands\n"); + if( mask & CMDFLAG_WEBPAGE ) fossil_print(" * Web pages\n"); + if( mask & CMDFLAG_SETTING ) fossil_print(" * Settings\n"); + if( mask & CMDFLAG_TOPIC ) fossil_print(" * Help Topic\n"); + if( useHtml ){ + fossil_print("-->\n"); + fossil_print("<!-- start_all_help -->\n"); + }else{ + fossil_print("---\n"); + } + /* Fill in help string buckets */ + for(i=0; i<MX_COMMAND; i++){ + if( (aCommand[i].eCmdFlags & mask)==0 ) continue; + else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue; + bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i; + } + for(i=0; i<MX_COMMAND; i++){ + if( (aCommand[i].eCmdFlags & mask)==0 ) continue; + else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue; + if( occHelp[aCommand[i].iHelp] > 0 ){ + int j; + if( useHtml ){ + Blob html; + blob_init(&html, 0, 0); + help_to_html(aCommand[i].zHelp, &html); + for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ + fossil_print("<h1>%h</h1>\n", + aCommand[bktHelp[aCommand[i].iHelp][j]].zName); + } + fossil_print("%s\n<hr>\n", blob_str(&html)); + blob_reset(&html); + }else if( rawOut ){ + for(j=0; j<occHelp[aCommand[i].iHelp]; j++) + fossil_print("# %s\n", aCommand[bktHelp[aCommand[i].iHelp][j]].zName); + fossil_print("%s\n\n", aCommand[i].zHelp); + }else{ + Blob txt; + blob_init(&txt, 0, 0); + help_to_text(aCommand[i].zHelp, &txt, 0); + for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ + fossil_print("# %s%s\n", + aCommand[bktHelp[aCommand[i].iHelp][j]].zName, + (aCommand[i].eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? + " (versionable)" : ""); + } + fossil_print("%s\n\n", blob_str(&txt)); + blob_reset(&txt); + } + occHelp[aCommand[i].iHelp] = 0; + } + } + if( useHtml ){ + fossil_print("<!-- end_all_help -->\n"); + }else{ + fossil_print("---\n"); + } + version_cmd(); +} + +/* +** COMMAND: test-all-help +** +** Usage: %fossil test-all-help ?OPTIONS? +** +** Show help text for commands and pages. Useful for proof-reading. +** Defaults to just the CLI commands. Specify --www to see only the +** web pages, or --everything to see both commands and pages. +** +** Options: +** -a|--aliases Show aliases +** -c|--topics Show help topics +** -e|--everything Show all commands and pages. Omit aliases to +** avoid duplicates. +** -h|--html Transform output to HTML +** -o|--options Show global options +** -r|--raw No output formatting +** -s|--settings Show settings +** -t|--test Include test- commands +** -w|--www Show WWW pages +*/ +void test_all_help_cmd(void){ + int mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER; + int useHtml = find_option("html","h",0)!=0; + int rawOut = find_option("raw","r",0)!=0; + + if( find_option("www","w",0) ){ + mask = CMDFLAG_WEBPAGE; + } + if( find_option("everything","e",0) ){ + mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE | + CMDFLAG_ALIAS | CMDFLAG_SETTING | CMDFLAG_TEST | CMDFLAG_TOPIC; + } + if( find_option("settings","s",0) ){ + mask = CMDFLAG_SETTING; + } + if( find_option("aliases","a",0) ){ + mask = CMDFLAG_ALIAS; + } + if( find_option("test","t",0) ){ + mask |= CMDFLAG_TEST; + } + if( find_option("topics","c",0) ){ + mask |= CMDFLAG_TOPIC; + } + display_all_help(mask, useHtml, rawOut); +} + +/* +** Count the number of entries in the aCommand[] table that match +** the given flag. +*/ +static int countCmds(unsigned int eFlg){ + int n = 0; + int i; + for(i=0; i<MX_COMMAND; i++){ + if( (aCommand[i].eCmdFlags & eFlg)!=0 ) n++; + } + return n; +} + +/* +** COMMAND: test-command-stats +** +** Print statistics about the built-in command dispatch table. +*/ +void test_command_stats_cmd(void){ + fossil_print("commands: %4d\n", + countCmds( CMDFLAG_COMMAND )); + fossil_print(" 1st tier %4d\n", + countCmds( CMDFLAG_1ST_TIER )); + fossil_print(" 2nd tier %4d\n", + countCmds( CMDFLAG_2ND_TIER )); + fossil_print(" alias %4d\n", + countCmds( CMDFLAG_ALIAS )); + fossil_print(" test %4d\n", + countCmds( CMDFLAG_TEST )); + fossil_print("web-pages: %4d\n", + countCmds( CMDFLAG_WEBPAGE )); + fossil_print("settings: %4d\n", + countCmds( CMDFLAG_SETTING )); + fossil_print("help-topics: %4d\n", + countCmds( CMDFLAG_TOPIC )); + fossil_print("total entries: %4d\n", MX_COMMAND); +} + +/* +** Compute an estimate of the edit-distance between to input strings. +** +** The first string is the input. The second is the pattern. Only the +** first 100 characters of the pattern are considered. +*/ +static int edit_distance(const char *zA, const char *zB){ + int nA = (int)strlen(zA); + int nB = (int)strlen(zB); + int i, j, m; + int p0, p1, c0; + int a[100] = {0}; + static const int incr = 4; + + for(j=0; j<nB; j++) a[j] = 1; + for(i=0; i<nA; i++){ + p0 = i==0 ? 0 : i*incr-1; + c0 = i*incr; + for(j=0; j<nB; j++){ + int m = 999; + p1 = a[j]; + if( zA[i]==zB[j] ){ + m = p0; + }else{ + m = c0+2; + if( m>p1+2 ) m = p1+2; + if( m>p0+3 ) m = p0+3; + } + c0 = a[j]; + a[j] = m; + p0 = p1; + } + } + m = a[nB-1]; + for(j=0; j<nB-1; j++){ + if( a[j]+1<m ) m = a[j]+1; + } + return m; +} + +/* +** Fill the pointer array with names of commands that approximately +** match the input. Return the number of approximate matches. +** +** Closest matches appear first. +*/ +int dispatch_approx_match(const char *zIn, int nArray, const char **azArray){ + int i; + int bestScore; + int m; + int n = 0; + int mnScore = 0; + int mxScore = 99999; + int iFirst, iLast; + + if( zIn[0]=='/' ){ + iFirst = 0; + iLast = FOSSIL_FIRST_CMD-1; + }else{ + iFirst = FOSSIL_FIRST_CMD; + iLast = MX_COMMAND-1; + } + + while( n<nArray ){ + bestScore = mxScore; + for(i=iFirst; i<=iLast; i++){ + m = edit_distance(zIn, aCommand[i].zName); + if( m<mnScore ) continue; + if( m==mnScore ){ + azArray[n++] = aCommand[i].zName; + if( n>=nArray ) return n; + }else if( m<bestScore ){ + bestScore = m; + } + } + if( bestScore>=mxScore ) break; + mnScore = bestScore; + } + return n; +} + +/* +** COMMAND: test-approx-match +** +** Test the approximate match algorithm +*/ +void test_approx_match_command(void){ + int i, j, n; + const char *az[20]; + for(i=2; i<g.argc; i++){ + fossil_print("%s:\n", g.argv[i]); + n = dispatch_approx_match(g.argv[i], 20, az); + for(j=0; j<n; j++){ + fossil_print(" %s\n", az[j]); + } + } +} + + +/* +** Returns 1 if the command or page name zName is known to be a +** command/page which is only available in certain builds/platforms, +** else returns 0. +*/ +static int help_is_platform_command(const char *zName){ + const char *aList[] = { + /* List of commands/pages which are known to only be available in + ** certain builds/platforms. */ + "winsrv", + "json", "/json", + NULL /* end-of-list sentinel */ + }; + int i = 0; + const char *z; + for( z = aList[0]; z ; z = aList[++i] ){ + if( 0==fossil_strcmp(zName, z) ) return 1; + } + return 0; +} + +/* +** WEBPAGE: help +** URL: /help/CMD or /help/www/PAGE +** +** Show the built-in help text for CMD or PAGE. CMD can be a command-line +** interface command or a setting name. PAGE is the name of a +** web interface. /help//PAGE also works if the double-/ makes it through +** the main web server. +** +** Query parameters: +** +** name=CMD Show help for CMD where CMD is a command name or +** or setting name. If CMD beings with "/" it is +** interpreted as a PAGE name. +** +** name=www/PAGE Show help for web page PAGE. +** +** name=/PAGE The initial "www/" on web-page help can be abbreviated as +** just "/" +** +** plaintext Show the help within <pre>...</pre>, as if it were +** displayed using the "fossil help" command. +** +** raw Show the raw help text without any formatting. +** (Used for debugging.) +*/ +void help_page(void){ + const char *zCmd = P("cmd"); + + if( zCmd==0 ) zCmd = P("name"); + cgi_check_for_malice(); + if( zCmd && *zCmd ){ + int rc; + const CmdOrPage *pCmd = 0; + + style_set_current_feature("tkt"); + style_submenu_element("Topic-List", "%R/help"); + if( search_restrict(SRCH_HELP)!=0 ){ + style_submenu_element("Search","%R/search?y=h"); + } + if( strncmp(zCmd,"www/",4)==0 && zCmd[4]!=0 ){ + /* Use https://domain/fossil/help/www/timeline or similar with the "www" + ** intermediate tag to view web-page documentation. */ + zCmd += 3; + } + rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd); + if( pCmd ){ + style_header("Help: %s", pCmd->zName); + }else{ + style_header("Help"); + } + if( pCmd==0 ){ + /* No <h1> line in this case */ + }else if( *zCmd=='/' ){ + /* Some of the webpages require query parameters in order to work. + ** @ <h1>The "<a href='%R%s(zCmd)'>%s(zCmd)</a>" page:</h1> */ + @ <h1>The "%h(pCmd->zName)" page:</h1> + }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_SETTING)!=0 ){ + @ <h1>The "%h(pCmd->zName)" setting:</h1> + }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_TOPIC)!=0 ){ + @ <h1>The "%h(pCmd->zName)" help topic:</h1> + }else{ + @ <h1>The "%h(pCmd->zName)" command:</h1> + } + if( rc==1 || (rc==2 && zCmd[0]=='/') ){ + if( zCmd && help_is_platform_command(zCmd) ){ + @ Not available in this build: "%h(zCmd)" + }else{ + @ Unknown topic: "%h(zCmd)" + } + }else if( rc==2 ){ + @ Ambiguous prefix: "%h(zCmd)" + }else{ + if( pCmd->zHelp[0]==0 ){ + @ No help available for "%h(pCmd->zName)" + }else if( P("plaintext") ){ + Blob txt; + blob_init(&txt, 0, 0); + help_to_text(pCmd->zHelp, &txt, 0); + @ <pre class="helpPage"> + @ %h(blob_str(&txt)) + @ </pre> + blob_reset(&txt); + }else if( P("raw") ){ + @ <pre class="helpPage"> + @ %h(pCmd->zHelp) + @ </pre> + }else{ + @ <div class="helpPage"> + help_to_html(pCmd->zHelp, cgi_output_blob()); + @ </div> + } + } + }else{ + int i; + const char *zWidth = "28ex"; + unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0}; /* Help str occurrences */ + int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help str->commands */ + style_header("Help"); + search_screen(SRCH_HELP, 0x02); + + @ <a name='commands'></a> + @ <h1>Commands:</h1> + @ <div class="columns" style="column-width: %s(zWidth);"> + @ <ul> + /* Fill in help string buckets */ + for(i=0; i<MX_COMMAND; i++){ + if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue; + bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i; + } + for(i=0; i<MX_COMMAND; i++){ + const char *z = aCommand[i].zName; + const char *zBoldOn = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"<b>" :""; + const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":""; + if( '/'==*z || strncmp(z,"test",4)==0 ) continue; + if( (aCommand[i].eCmdFlags & (CMDFLAG_SETTING|CMDFLAG_TOPIC))!=0 ){ + continue; + } + else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; + else if( (aCommand[i].eCmdFlags & CMDFLAG_ALIAS)!=0 ) continue; + @ <li><a href="%R/help/%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a> + /* Output aliases */ + if( occHelp[aCommand[i].iHelp] > 1 ){ + int j; + int aliases[MX_HELP_DUP], nAliases=0; + for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ + if( bktHelp[aCommand[i].iHelp][j] != i ){ + if( aCommand[bktHelp[aCommand[i].iHelp][j]].eCmdFlags + & CMDFLAG_ALIAS ){ + aliases[nAliases++] = bktHelp[aCommand[i].iHelp][j]; + } + } + } + if( nAliases>0 ){ + int k; + @(\ + for(k=0; k<nAliases; k++){ + @<a href="%R/help/%s(aCommand[aliases[k]].zName)">\ + @%s(aCommand[aliases[k]].zName)</a>%s((k<nAliases-1)?", ":"")\ + } + @)\ + } + } + @ </li> + } + + @ </ul></div> + + @ <a name='webpages'></a> + @ <h1>Web pages:</h1> + @ <div class="columns" style="column-width: %s(zWidth);"> + @ <ul> + for(i=0; i<MX_COMMAND; i++){ + const char *z = aCommand[i].zName; + if( '/'!=*z ) continue; + else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; + if( aCommand[i].zHelp[0] ){ + @ <li><a href="%R/help/www%s(z)">%s(z+1)</a></li> + }else{ + @ <li>%s(z+1)</li> + } + } + @ </ul></div> + + @ <a name='settings'></a> + @ <h1>Settings:</h1> + @ <div class="columns" style="column-width: %s(zWidth);"> + @ <ul> + for(i=0; i<MX_COMMAND; i++){ + const char *z = aCommand[i].zName; + if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue; + else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; + if( aCommand[i].zHelp[0] ){ + @ <li><a href="%R/help/%s(z)">%s(z)</a></li> + }else{ + @ <li>%s(z)</li> + } + } + @ </ul></div> + + @ <a name='topics'></a> + @ <h1>Other Miscellaneous Help Topics:</h1> + @ <div class="columns" style="column-width: %s(zWidth);"> + @ <ul> + for(i=0; i<MX_COMMAND; i++){ + const char *z = aCommand[i].zName; + if( (aCommand[i].eCmdFlags & CMDFLAG_TOPIC)==0 ) continue; + if( aCommand[i].zHelp[0] ){ + @ <li><a href="%R/help/%s(z)">%s(z)</a></li> + }else{ + @ <li>%s(z)</li> + } + } + @ </ul></div> + + @ <a name='unsupported'></a> + @ <h1>Unsupported and Testing Commands:</h1> + @ <div class="columns" style="column-width: %s(zWidth);"> + @ <ul> + for(i=0; i<MX_COMMAND; i++){ + const char *z = aCommand[i].zName; + if( strncmp(z,"test",4)!=0 ) continue; + else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue; + if( aCommand[i].zHelp[0] ){ + @ <li><a href="%R/help/%s(z)">%s(z)</a></li> + }else{ + @ <li>%s(z)</li> + } + } + @ </ul></div> + + } + style_finish_page(); +} + +/* +** WEBPAGE: test-all-help +** +** Show all help text on a single page. Useful for proof-reading. +*/ +void test_all_help_page(void){ + int i; + unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0}; /* Help string occurrences */ + int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help strings->commands*/ + Blob buf; + blob_init(&buf,0,0); + style_set_current_feature("test"); + style_header("All Help Text"); + @ <dl> + /* Fill in help string buckets */ + for(i=0; i<MX_COMMAND; i++){ + if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue; + bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i; + } + for(i=0; i<MX_COMMAND; i++){ + const char *zDesc; + unsigned int e = aCommand[i].eCmdFlags; + if( e & CMDFLAG_1ST_TIER ){ + zDesc = "1st tier command"; + }else if( e & CMDFLAG_2ND_TIER ){ + zDesc = "2nd tier command"; + }else if( e & CMDFLAG_ALIAS ){ + zDesc = "alias"; + }else if( e & CMDFLAG_TEST ){ + zDesc = "test command"; + }else if( e & CMDFLAG_TOPIC ){ + zDesc = "help-topic"; + }else if( e & CMDFLAG_WEBPAGE ){ + if( e & CMDFLAG_RAWCONTENT ){ + zDesc = "raw-content web page"; + }else{ + zDesc = "web page"; + } + }else{ + blob_reset(&buf); + if( e & CMDFLAG_VERSIONABLE ){ + blob_appendf(&buf, "versionable "); + } + if( e & CMDFLAG_BLOCKTEXT ){ + blob_appendf(&buf, "block-text "); + } + if( e & CMDFLAG_BOOLEAN ){ + blob_appendf(&buf, "boolean "); + } + blob_appendf(&buf,"setting"); + zDesc = blob_str(&buf); + } + if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue; + if( occHelp[aCommand[i].iHelp] > 0 ){ + int j; + for(j=0; j<occHelp[aCommand[i].iHelp]; j++){ + unsigned int e = aCommand[bktHelp[aCommand[i].iHelp][j]].eCmdFlags; + if( e & CMDFLAG_1ST_TIER ){ + zDesc = "1st tier command"; + }else if( e & CMDFLAG_2ND_TIER ){ + zDesc = "2nd tier command"; + }else if( e & CMDFLAG_ALIAS ){ + zDesc = "alias"; + }else if( e & CMDFLAG_TEST ){ + zDesc = "test command"; + }else if( e & CMDFLAG_WEBPAGE ){ + if( e & CMDFLAG_RAWCONTENT ){ + zDesc = "raw-content web page"; + }else{ + zDesc = "web page"; + } + } + + @ <dt><big><b>%s(aCommand[bktHelp[aCommand[i].iHelp][j]].zName)</b> + @</big> (%s(zDesc))</dt> + } + @ <p><dd> + help_to_html(aCommand[i].zHelp, cgi_output_blob()); + @ </dd><p> + occHelp[aCommand[i].iHelp] = 0; + } + } + @ </dl> + blob_reset(&buf); + style_finish_page(); +} + +/* +** Analyze p and return one of three values: +** +** 0 p is the continuation of a prior subcommand. +** +** 1 p is text past the end of a prior subcommand. +** +** 2 p is the start of a new subcommand. +*/ +static int is_subcommand(Blob *p, int bAbbrevSubcmd){ + int i, sz; + const unsigned char *z = (const unsigned char*)blob_buffer(p); + sz = blob_size(p); + if( sz>6 ) sz = 6; + for(i=0; i<sz && fossil_isspace(z[i]); i++){} + if( i>=sz ) return 0; + + if( bAbbrevSubcmd==0 ){ + if( i>1 ) return 0; + return z[0]=='>' ? 2 : 1; + }else{ + return (i==3 && fossil_isalpha(z[3])) ? 2 : 1; + } +} + +/* +** Input z[] is help text for zTopic. If zTopic has sub-command zSub, +** then cut out all portions of the original help text that do not +** directly pertain to zSub and write the zSub-relevant parts into +** pOut. +** +** Return the number of lines of z[] written into pOut. A return of +** zero means no simplification occurred. +*/ +static int simplify_to_subtopic( + const char *z, /* Full original help text */ + Blob *pOut, /* Write simplified help text here */ + const char *zTopic, /* TOPIC */ + const char *zSubtopic, /* SUBTOPIC */ + int bAbbrevSubcmd /* True if z[] contains abbreviated subcommands */ +){ + Blob in, line;/*, subsection;*/ + int n = 0; + char *zQTop = re_quote(zTopic); + char *zQSub = re_quote(zSubtopic); + char *zPattern; + ReCompiled *pRe = 0; + + if( bAbbrevSubcmd ){ + zPattern = mprintf(" ([a-z]+ ?\\| ?)*%s\\b", zQSub); + }else{ + zPattern = mprintf("> ?fossil [-a-z]+ .*\\b%s\\b", zQSub); + } + fossil_free(zQTop); + fossil_free(zQSub); + fossil_re_compile(&pRe, zPattern, 0); + fossil_free(zPattern); + blob_init(&in, z, -1); + while( blob_line(&in, &line) ){ + if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_size(&line)) ){ + int atStart = 1; + blob_appendb(pOut, &line); + n++; + while( blob_line(&in, &line) ){ + if( re_match(pRe,(unsigned char*)blob_buffer(&line),blob_size(&line)) ){ + blob_appendb(pOut, &line); + n++; + atStart = 1; + }else{ + int x = is_subcommand(&line,bAbbrevSubcmd); + if( x==2 ){ + if( atStart ){ + blob_appendb(pOut, &line); + n++; + }else{ + break; + } + }else if( x==1 ){ + break; + }else{ + blob_appendb(pOut, &line); + n++; + atStart = 0; + } + } + } + } + } + blob_reset(&line); + re_free(pRe); + if( n ){ + blob_trim(pOut); + blob_reset(&in); + } + return n; +} + +/* +** Input p is a "Usage:" line or a subcommand line. Simplify this line +** for the --usage option and write it into pOut. +*/ +static void simplify_usage_line( + Blob *p, + Blob *pOut, + int bAbbrevSubcmd, + const char *zCmd +){ + const char *z = blob_buffer(p); + int sz = blob_size(p); + int i = 0; + if( sz>6 && z[0]=='U' ){ + for(i=1; i<sz && !fossil_isspace(z[i]); i++){} + }else if( sz>0 && z[0]=='>' ){ + i = 1; + }else if( sz>4 && bAbbrevSubcmd + && memcmp(z," ",3)==0 && !fossil_isspace(z[3]) ){ + int j; + for(j=3; j<sz-1 && (z[j]!=' ' || z[j+1]!=' '); j++){} + blob_appendf(pOut, "fossil %s %.*s\n", zCmd, j-3, &z[3]); + return; + }else{ + while( i<sz && fossil_isspace(z[i]) ) i++; + if( i+2<sz && (z[i]=='o' || z[i]=='O') && z[i+1]=='r' ){ + while( i<sz && !fossil_isspace(z[i]) ) i++; + } + } + while( i<sz && fossil_isspace(z[i]) ) i++; + blob_append(pOut, &z[i], sz-i); +} + +/* +** Input z[] is help text for a command zTopic. Write into pOut all lines of +** z[] that show the command-line syntax for that command. Lines written +** to pOut are lines that begin with out of: +** +** Usage: +** or: +** > fossil TOPIC +** +** Return the number of lines written into pOut. +*/ +static int simplify_to_usage( + const char *z, /* Full original help text */ + Blob *pOut, /* Write simplified help text here */ + const char *zTopic, /* The command for which z[] is full help text */ + int bAbbrevSubcmd /* z[] uses abbreviated subcommands */ +){ + ReCompiled *pRe = 0; + Blob in, line; + int n = 0; + + if( bAbbrevSubcmd ){ + fossil_re_compile(&pRe, "^(Usage: | [a-z][-a-z|]+ .*)", 0); + }else{ + fossil_re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |> ?fossil )", 0); + } + blob_init(&in, z, -1); + while( blob_line(&in, &line) ){ + if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_strlen(&line)) ){ + simplify_usage_line(&line, pOut, bAbbrevSubcmd, zTopic); + n++; + } + } + re_free(pRe); + if( n ) blob_trim(pOut); + return n; +} + +/* +** Input z[] is help text. Write into pOut all lines of z[] that show +** command-line options. Return the number of lines written. +*/ +static int simplify_to_options( + const char *z, /* Full original help text */ + Blob *pOut, /* Write simplified help text here */ + int bAbbrevSubcmd, /* z[] uses abbreviated subcommands */ + const char *zCmd /* Name of the command that z[] describes */ +){ + ReCompiled *pRe = 0; + Blob txt, line, subsection; + int n = 0; + int bSubsectionSeen = 0; + + blob_init(&txt, z, -1); + blob_init(&subsection, 0, 0); + fossil_re_compile(&pRe, "^ +-.* ", 0); + while( blob_line(&txt, &line) ){ + int len = blob_size(&line); + unsigned char *zLine = (unsigned char *)blob_buffer(&line); + if( re_match(pRe, zLine, len) ){ + if( blob_size(&subsection) ){ + simplify_usage_line(&subsection, pOut, bAbbrevSubcmd, zCmd); + blob_reset(&subsection); + } + blob_appendb(pOut, &line); + }else if( len>7 && !fossil_isspace(zLine[0]) && bSubsectionSeen + && sqlite3_strlike("%options:%",blob_str(&line),0)==0 ){ + subsection = line; + }else if( !bAbbrevSubcmd && len>9 + && (memcmp(zLine,"> fossil ",9)==0 + || memcmp(zLine,"> fossil",9)==0) ){ + subsection = line; + bSubsectionSeen = 1; + }else if( bAbbrevSubcmd && len>5 && memcmp(zLine," ",3)==0 + && fossil_isalpha(zLine[3]) ){ + subsection = line; + bSubsectionSeen = 1; + }else if( len>1 && !fossil_isspace(zLine[0]) && bSubsectionSeen ){ + blob_reset(&subsection); + } + } + re_free(pRe); + blob_trim(pOut); + blob_reset(&subsection); + return n; +} + + + +static void multi_column_list(const char **azWord, int nWord){ + int i, j, len; + int mxLen = 0; + int nCol; + int nRow; + for(i=0; i<nWord; i++){ + len = strlen(azWord[i]); + if( len>mxLen ) mxLen = len; + } + nCol = 80/(mxLen+2); + if( nCol==0 ) nCol = 1; + nRow = (nWord + nCol - 1)/nCol; + for(i=0; i<nRow; i++){ + const char *zSpacer = ""; + for(j=i; j<nWord; j+=nRow){ + fossil_print("%s%-*s", zSpacer, mxLen, azWord[j]); + zSpacer = " "; + } + fossil_print("\n"); + } +} + +/* +** COMMAND: test-list-webpage +** +** List all web pages. +*/ +void cmd_test_webpage_list(void){ + int i, nCmd; + const char *aCmd[MX_COMMAND]; + for(i=nCmd=0; i<MX_COMMAND; i++){ + if(CMDFLAG_WEBPAGE & aCommand[i].eCmdFlags){ + aCmd[nCmd++] = aCommand[i].zName; + } + } + assert(nCmd && "page list is empty?"); + multi_column_list(aCmd, nCmd); +} + + +/* +** List of commands starting with zPrefix, or all commands if zPrefix is NULL. +*/ +static void command_list(int cmdMask, int verboseFlag, int useHtml){ + if( verboseFlag ){ + display_all_help(cmdMask, useHtml, 0); + }else{ + int i, nCmd; + const char *aCmd[MX_COMMAND]; + for(i=nCmd=0; i<MX_COMMAND; i++){ + if( (aCommand[i].eCmdFlags & cmdMask)==0 ) continue; + else if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue; + aCmd[nCmd++] = aCommand[i].zName; + } + multi_column_list(aCmd, nCmd); + } +} + +/* +** TOPIC: options +** +** Command-line options common to all commands: +** +** --args FILENAME Read additional arguments and options from FILENAME +** --case-sensitive BOOL Set case sensitivity for file names +** --cgitrace Active CGI tracing +** --chdir PATH Change to PATH before performing any operations +** --errorlog FILENAME Log errors to FILENAME +** -?|--help Show help on the command rather than running it +** --httptrace Trace outbound HTTP requests +** --localtime Display times using the local timezone +** --nocgi Do not act as CGI +** --no-th-hook Do not run TH1 hooks +** -q|--quiet Reduce the amount of output +** --sqlstats Show SQL usage statistics when done +** --sqltrace Trace all SQL commands +** --sshtrace Trace SSH activity +** --ssl-identity NAME Set the SSL identity to NAME +** --systemtrace Trace calls to system() +** -U|--user USER Make the default user be USER +** --utc Display times using UTC +** --vfs NAME Cause SQLite to use the NAME VFS +** +** Additional options available on most commands that use network I/O: +** +** --accept-any-cert Disable server SSL cdert validation. Accept any SSL +** cert that the server provides. WARNING: Unsafe! +** Testing and debugging use only! +** --ipv4 Use only IPv4. Disable IPv6 support. +** --ipv6 Use only IPv6. Disable IPv4 support. +** --nosync Disable autosync for the current command. +** --proxy URL Specify the HTTP proxy to use. URL can be "off". +*/ + +/* +** COMMAND: help +** +** Usage: %fossil help [OPTIONS] [TOPIC] [SUBCOMMAND] +** +** Display information on how to use TOPIC, which may be a command, webpage, or +** setting. Webpage names begin with "/". If TOPIC is omitted, a list of +** topics is returned. If there is an extra argument after TOPIC, it is +** the name of a subcommand, in which case only the help text for that one +** subcommand is shown. +** +** The following options can be used when TOPIC is omitted: +** +** -a|--all List both common and auxiliary commands +** -e|--everything List all help on all topics +** -f|--full List full set of commands (including auxiliary +** and unsupported "test" commands), options, +** settings, and web pages +** -o|--options List command-line options common to all commands +** -s|--setting List setting names +** -t|--test List unsupported "test" commands +** -v|--verbose List both names and help text +** -x|--aux List only auxiliary commands +** -w|--www List all web pages +** +** These options can be used when TOPIC is present: +** +** -c|--commands Restrict TOPIC search to commands +** -h|--html Format output as HTML rather than plain text +** -o|--options Show command-line options for TOPIC +** --raw Output raw, unformatted help text +** -u|--usage Show a succinct usage summary, not full help text +** +** See also: [[usage]], [[options]], [[search]] with the -h option +*/ +void help_cmd(void){ + int rc; + int mask = CMDFLAG_ANY; /* Mask of help topic types */ + int isPage = 0; /* True if TOPIC is a page */ + int verboseFlag = 0; /* -v option */ + int commandsFlag = 0; /* -c option */ + const char *z; /* Original, untranslated help text */ + const char *zCmdOrPage; /* "command" or "page" or "setting" */ + const CmdOrPage *pCmd = 0; /* ptr to aCommand[] entry for TOPIC */ + int useHtml = 0; /* -h option */ + int bUsage; /* --usage */ + int bRaw; /* --raw option */ + int bOptions; /* --options */ + const char *zTopic; /* TOPIC argument */ + const char *zSubtopic = 0; /* SUBTOPIC argument */ + Blob subtext1, subtext2, s3; /* Subsets of z[] containing subtopic/usage */ + Blob txt; /* Text after rendering */ + int bAbbrevSubcmd = 0; /* Help text uses abbreviated subcommands */ + + verboseFlag = find_option("verbose","v",0)!=0; + commandsFlag = find_option("commands","c",0)!=0; + useHtml = find_option("html","h",0)!=0; + bRaw = find_option("raw",0,0)!=0; + bOptions = find_option("options","o",0)!=0; + bUsage = find_option("usage","u",0)!=0; + if( find_option("all","a",0) ){ + command_list(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER, verboseFlag, useHtml); + return; + } + else if( find_option("www","w",0) ){ + command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml); + return; + } + else if( find_option("aux","x",0) ){ + command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml); + return; + } + else if( find_option("test","t",0) ){ + command_list(CMDFLAG_TEST, verboseFlag, useHtml); + return; + } + else if( find_option("setting","s",0) ){ + command_list(CMDFLAG_SETTING, verboseFlag, useHtml); + return; + } + else if( find_option("topic","c",0) ){ + command_list(CMDFLAG_TOPIC, verboseFlag, useHtml); + return; + } + else if( find_option("full","f",0) ){ + fossil_print("fossil commands:\n\n"); + command_list(CMDFLAG_1ST_TIER, verboseFlag, useHtml); + fossil_print("\nfossil auxiliary commands:\n\n"); + command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml); + fossil_print("\nfossil settings:\n\n"); + command_list(CMDFLAG_SETTING, verboseFlag, useHtml); + fossil_print("\nfossil web pages:\n\n"); + command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml); + fossil_print("\nfossil miscellaneous help topics:\n\n"); + command_list(CMDFLAG_TOPIC, verboseFlag, useHtml); + fossil_print("\nfossil test commands (unsupported):\n\n"); + command_list(CMDFLAG_TEST, verboseFlag, useHtml); + if ( !verboseFlag ) { + fossil_print("\n"); + version_cmd(); + } + return; + } + else if( find_option("everything","e",0) ){ + display_all_help(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE | + CMDFLAG_SETTING | CMDFLAG_TEST | CMDFLAG_TOPIC, + useHtml, 0); + return; + } + verify_all_options(); + zCmdOrPage = "help topic"; + if( g.argc<3 ){ + if( bOptions ){ + zTopic = "options"; + zSubtopic = 0; + mask = CMDFLAG_TOPIC; + bOptions = 0; + goto find_and_show_help; + } + z = g.argv[0]; + fossil_print( + "Usage: %s help TOPIC\n" + "Things to try:\n\n" + " %s help help\n" + " %s help -o\n" + " %s help -a\n" + " %s search -h TOPIC\n\n" + "Other common values for TOPIC:\n\n", + z, z, z, z, z); + command_list(CMDFLAG_1ST_TIER,verboseFlag,useHtml); + if( !verboseFlag ) version_cmd(); + return; + } + zTopic = g.argv[2]; + zSubtopic = g.argc>=4 ? g.argv[3] : 0; + isPage = ('/' == zTopic[0]) ? 1 : 0; + if( isPage ){ + zCmdOrPage = "page"; + }else if( commandsFlag ){ + mask = CMDFLAG_COMMAND; + zCmdOrPage = "command"; + } +find_and_show_help: + rc = dispatch_name_search(zTopic, mask|CMDFLAG_PREFIX, &pCmd); + if( rc ){ + int i, n; + const char *az[5]; + if( rc==1 ){ + if( help_is_platform_command(g.argv[2]) ){ + fossil_print("Not available in this build: %s\n", g.argv[2]); + return; + } + fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]); + }else{ + fossil_print("ambiguous %s prefix: %s\n", + zCmdOrPage, g.argv[2]); + } + fossil_print("Did you mean one of these TOPICs:\n"); + n = dispatch_approx_match(g.argv[2], 5, az); + for(i=0; i<n; i++){ + fossil_print(" * %s\n", az[i]); + } + fossil_print("Other commands to try:\n"); + fossil_print(" fossil search -h PATTERN ;# search all help text\n"); + fossil_print(" fossil help -a ;# show all commands\n"); + fossil_print(" fossil help -w ;# show all web-pages\n"); + fossil_print(" fossil help -s ;# show all settings\n"); + fossil_print(" fossil help -o ;# show global options\n"); + return; + } + bAbbrevSubcmd = (pCmd->eCmdFlags & CMDFLAG_ABBREVSUBCMD)!=0; + z = pCmd->zHelp; + if( z==0 ){ + fossil_fatal("no help available for the %s %s", + pCmd->zName, zCmdOrPage); + } + blob_init(&subtext1, 0, 0); + blob_init(&subtext2, 0, 0); + blob_init(&s3, 0, 0); + if( zSubtopic!=0 ){ + if( simplify_to_subtopic(z, &subtext1, zTopic, zSubtopic, bAbbrevSubcmd) ){ + z = blob_str(&subtext1); + }else{ + fossil_print("No subtopic \"%s\" for \"%s\".\n", zSubtopic, zTopic); + bUsage = 1; + zSubtopic = 0; + } + } + if( bUsage ){ + if( simplify_to_usage(z, &subtext2, zTopic, bAbbrevSubcmd) ){ + z = blob_str(&subtext2); + }else{ + bUsage = 0; + } + } + if( bOptions ){ + simplify_to_options(z, &s3, bAbbrevSubcmd, zTopic); + z = blob_str(&s3); + } + if( pCmd && pCmd->eCmdFlags & CMDFLAG_SETTING ){ + const Setting *pSetting = db_find_setting(pCmd->zName, 0); + char *zDflt = 0; + if( pSetting!=0 && pSetting->def!=0 && *pSetting->def!=0 ){ + zDflt = mprintf(" (default: %s)", pSetting->def); + } + fossil_print("Setting: \"%s\"%s%s\n\n", + pCmd->zName, zDflt!=0 ? zDflt : "", + (pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : "" + ); + fossil_free(zDflt); + } + blob_init(&txt, 0, 0); + if( bRaw ){ + blob_append(&txt, z, -1); + }else if( useHtml ){ + help_to_html(z, &txt); + }else{ + help_to_text(z, &txt, bUsage || zSubtopic!=0); + } + if( blob_strlen(&txt)>0 ) fossil_print("%s\n", blob_str(&txt)); + blob_reset(&txt); + blob_reset(&subtext1); + blob_reset(&subtext2); +} + +/* +** Return a pointer to the setting information array. +** +** This routine provides access to the aSetting[] array which is created +** by the mkindex utility program and included with <page_index.h>. +*/ +const Setting *setting_info(int *pnCount){ + if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1; + return aSetting; +} + +/* +** Return a pointer to a specific Setting entry for the setting named +** in the argument. Or return NULL if no such setting exists. +** +** The pointer returned points into the middle of the global aSetting[] +** array that is generated by mkindex. Use setting_info() to fetch the +** whole array. Use this routine to fetch a specific entry. +*/ +const Setting *setting_find(const char *zName){ + int iFirst = 0; + int iLast = ArraySize(aSetting)-1; + while( iFirst<=iLast ){ + int iCur = (iFirst+iLast)/2; + int c = strcmp(aSetting[iCur].name, zName); + if( c<0 ){ + iFirst = iCur+1; + }else if( c>0 ){ + iLast = iCur-1; + }else{ + return &aSetting[iCur]; + } + } + return 0; +} + +/***************************************************************************** +** A virtual table for accessing the information in aCommand[], and +** especially the help-text +*/ + +/* helptextVtab_vtab is a subclass of sqlite3_vtab which is +** underlying representation of the virtual table +*/ +typedef struct helptextVtab_vtab helptextVtab_vtab; +struct helptextVtab_vtab { + sqlite3_vtab base; /* Base class - must be first */ + /* Add new fields here, as necessary */ +}; + +/* helptextVtab_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct helptextVtab_cursor helptextVtab_cursor; +struct helptextVtab_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + /* Insert new fields here. For this helptextVtab we only keep track + ** of the rowid */ + sqlite3_int64 iRowid; /* The rowid */ +}; + +/* +** The helptextVtabConnect() method is invoked to create a new +** helptext virtual table. +** +** Think of this routine as the constructor for helptextVtab_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the helptextVtab_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against the virtual table will look like. +*/ +static int helptextVtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + helptextVtab_vtab *pNew; + int rc; + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(name,type,flags,helptext,formatted,html)" + ); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + } + return rc; +} + +/* +** This method is the destructor for helptextVtab_vtab objects. +*/ +static int helptextVtabDisconnect(sqlite3_vtab *pVtab){ + helptextVtab_vtab *p = (helptextVtab_vtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new helptextVtab_cursor object. +*/ +static int helptextVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + helptextVtab_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a helptextVtab_cursor. +*/ +static int helptextVtabClose(sqlite3_vtab_cursor *cur){ + helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur; + sqlite3_free(pCur); + return SQLITE_OK; +} + + +/* +** Advance a helptextVtab_cursor to its next row of output. +*/ +static int helptextVtabNext(sqlite3_vtab_cursor *cur){ + helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur; + pCur->iRowid++; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the helptextVtab_cursor +** is currently pointing. +*/ +static int helptextVtabColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur; + const CmdOrPage *pPage = aCommand + pCur->iRowid; + switch( i ){ + case 0: /* name */ + sqlite3_result_text(ctx, pPage->zName, -1, SQLITE_STATIC); + break; + case 1: { /* type */ + const char *zType = 0; + if( pPage->eCmdFlags & CMDFLAG_COMMAND ){ + zType = "command"; + }else if( pPage->eCmdFlags & CMDFLAG_WEBPAGE ){ + zType = "webpage"; + }else if( pPage->eCmdFlags & CMDFLAG_SETTING ){ + zType = "setting"; + }else if( pPage->eCmdFlags & CMDFLAG_TOPIC ){ + zType = "help-topic"; + } + sqlite3_result_text(ctx, zType, -1, SQLITE_STATIC); + break; + } + case 2: /* flags */ + sqlite3_result_int(ctx, pPage->eCmdFlags); + break; + case 3: /* helptext */ + sqlite3_result_text(ctx, pPage->zHelp, -1, SQLITE_STATIC); + break; + case 4: { /* formatted */ + Blob txt; + blob_init(&txt, 0, 0); + help_to_text(pPage->zHelp, &txt, 0); + sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free); + break; + } + case 5: { /* formatted */ + Blob txt; + blob_init(&txt, 0, 0); + help_to_html(pPage->zHelp, &txt); + sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free); + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int helptextVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int helptextVtabEof(sqlite3_vtab_cursor *cur){ + helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur; + return pCur->iRowid>=MX_COMMAND; +} + +/* +** This method is called to "rewind" the helptextVtab_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to helptextVtabColumn() or helptextVtabRowid() or +** helptextVtabEof(). +*/ +static int helptextVtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + helptextVtab_cursor *pCur = (helptextVtab_cursor *)pVtabCursor; + pCur->iRowid = 1; + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +*/ +static int helptextVtabBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + pIdxInfo->estimatedCost = (double)MX_COMMAND; + pIdxInfo->estimatedRows = MX_COMMAND; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** virtual table. +*/ +static sqlite3_module helptextVtabModule = { + /* iVersion */ 0, + /* xCreate */ 0, /* Helptext is eponymous and read-only */ + /* xConnect */ helptextVtabConnect, + /* xBestIndex */ helptextVtabBestIndex, + /* xDisconnect */ helptextVtabDisconnect, + /* xDestroy */ 0, + /* xOpen */ helptextVtabOpen, + /* xClose */ helptextVtabClose, + /* xFilter */ helptextVtabFilter, + /* xNext */ helptextVtabNext, + /* xEof */ helptextVtabEof, + /* xColumn */ helptextVtabColumn, + /* xRowid */ helptextVtabRowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindMethod */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0, + /* xIntegrity */ 0 +}; + + +/* +** Register the helptext virtual table +*/ +int helptext_vtab_register(sqlite3 *db){ + int rc = sqlite3_create_module(db, "helptext", &helptextVtabModule, 0); + return rc; +} +/* End of the helptext virtual table +******************************************************************************/ |
