From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/dispatch.c | 1971 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1971 insertions(+) create mode 100644 src/dispatch.c (limited to 'src/dispatch.c') 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 +#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=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 & and " +** * Change "%fossil" to just "fossil" +*/ +static void appendLinked(Blob *pOut, const char *z, int n){ + int i = 0; + int j; + while( i0 ){ + if( i ) blob_append(pOut, z, i); + z += i+2; + n -= i+2; + blob_appendf(pOut, "%.*s", + 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 ... Terms +** that have all upper-case letters are within ... +*/ +static void appendMixedFont(Blob *pOut, const char *z, int n){ + const char *zEnd = ""; + int i = 0; + int j; + while( i=n || z[j]==' ' || z[j]=='=' ){ + zEnd = ""; + }else{ + if( fossil_isupper(z[j]) && z[i]!='-' ){ + blob_append(pOut, "",3); + zEnd = ""; + }else{ + blob_append(pOut, "", 4); + zEnd = ""; + } + } + while( j elements can begin +** on the same line as long as they are separated by at least +** two spaces. +** +** * Indented text is show verbatim (
...
) +** +** * Lines that begin with "|" at the left margin are in
...
+*/ +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 = ""; + static const char *zEndPRE = ""; + static const char *zEndUL = ""; + static const char *zEndDD = ""; + + 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\n", -1); + inPRE = 1; + } + } + }else{ + if( inPRE ){ + blob_append(pHtml, "\n", -1); + inPRE = 0; + } + isDT = 0; + for(nIndent=0; nIndent */ + }else{ + blob_append_char(pHtml, '\n'); + } + wantP = 1; + wantBR = 0; + zHelp += i+1; + continue; + } + if( nIndent+20 && aIndent[iLevel]>nIndent ){ + blob_append(pHtml, azEnd[iLevel--], -1); + } + if( nIndent>aIndent[iLevel] ){ + assert( iLevel", 3); + wantP = 0; + } + blob_append(pHtml, "
    \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, "
    \n", -1); + }else{ + blob_append(pHtml, "
    \n", -1); + } + }else if( azEnd[iLevel]==zEndDL ){ + iLevel++; + aIndent[iLevel] = nIndent; + azEnd[iLevel] = zEndDD; + if( wantP ){ + blob_append(pHtml,"

    ", 3); + wantP = 0; + } + blob_append(pHtml, "

    ", 4); + }else if( wantP ){ + iLevel++; + aIndent[iLevel] = nIndent; + azEnd[iLevel] = zEndPRE; + blob_append(pHtml, "
    ", -1);
    +        wantP = 0;
    +      }
    +    }
    +    if( isLI ){
    +      blob_append(pHtml, "
  • ", 5); + } + if( wantP ){ + blob_append(pHtml, "

    ", 4); + wantP = 0; + } + if( azEnd[iLevel]==zEndDL ){ + int iDD; + blob_append(pHtml, "

    ", 5); + iDD = hasGap(zHelp+nIndent, i-nIndent); + if( iDD ){ + int x; + assert( iLevel
    ",9); + appendLinked(pHtml, zHelp+x, i-x); + }else{ + appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); + } + blob_append(pHtml, "\n", 6); + }else if( wantBR ){ + appendMixedFont(pHtml, zHelp+nIndent, i-nIndent); + blob_append(pHtml, "
    \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("\n"); + }else{ + fossil_print("---\n"); + } + /* Fill in help string buckets */ + for(i=0; i 0 ){ + int j; + if( useHtml ){ + Blob html; + blob_init(&html, 0, 0); + help_to_html(aCommand[i].zHelp, &html); + for(j=0; j%h\n", + aCommand[bktHelp[aCommand[i].iHelp][j]].zName); + } + fossil_print("%s\n
    \n", blob_str(&html)); + blob_reset(&html); + }else if( rawOut ){ + for(j=0; j\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; ip1+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=nArray ) return n; + }else if( m=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...
  • , 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

    line in this case */ + }else if( *zCmd=='/' ){ + /* Some of the webpages require query parameters in order to work. + ** @

    The "%s(zCmd)" page:

    */ + @

    The "%h(pCmd->zName)" page:

    + }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_SETTING)!=0 ){ + @

    The "%h(pCmd->zName)" setting:

    + }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_TOPIC)!=0 ){ + @

    The "%h(pCmd->zName)" help topic:

    + }else{ + @

    The "%h(pCmd->zName)" command:

    + } + 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); + @
    +        @ %h(blob_str(&txt))
    +        @ 
    + blob_reset(&txt); + }else if( P("raw") ){ + @
    +        @ %h(pCmd->zHelp)
    +        @ 
    + }else{ + @
    + help_to_html(pCmd->zHelp, cgi_output_blob()); + @
    + } + } + }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); + + @ + @

    Commands:

    + @
    + @
      + /* Fill in help string buckets */ + for(i=0; i" :""; + const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"":""; + 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; + @
    • %s(zBoldOn)%s(z)%s(zBoldOff) + /* Output aliases */ + if( occHelp[aCommand[i].iHelp] > 1 ){ + int j; + int aliases[MX_HELP_DUP], nAliases=0; + for(j=0; j0 ){ + int k; + @(\ + for(k=0; k\ + @%s(aCommand[aliases[k]].zName)%s((k + } + + @
    + + @ + @

    Web pages:

    + @
    + @
      + for(i=0; i%s(z+1) + }else{ + @
    • %s(z+1)
    • + } + } + @
    + + @ + @

    Settings:

    + @
    + @
      + for(i=0; i%s(z) + }else{ + @
    • %s(z)
    • + } + } + @
    + + @ + @

    Other Miscellaneous Help Topics:

    + @
    + @
      + for(i=0; i%s(z) + }else{ + @
    • %s(z)
    • + } + } + @
    + + @ + @

    Unsupported and Testing Commands:

    + @
    + @
      + for(i=0; i%s(z) + }else{ + @
    • %s(z)
    • + } + } + @
    + + } + 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"); + @
    + /* Fill in help string buckets */ + for(i=0; i 0 ){ + int j; + for(j=0; j%s(aCommand[bktHelp[aCommand[i].iHelp][j]].zName) + @ (%s(zDesc)) + } + @

    + help_to_html(aCommand[i].zHelp, cgi_output_blob()); + @

    + occHelp[aCommand[i].iHelp] = 0; + } + } + @

    + 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 ) 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; i0 && z[0]=='>' ){ + i = 1; + }else if( sz>4 && bAbbrevSubcmd + && memcmp(z," ",3)==0 && !fossil_isspace(z[3]) ){ + int j; + for(j=3; j 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; imxLen ) mxLen = len; + } + nCol = 80/(mxLen+2); + if( nCol==0 ) nCol = 1; + nRow = (nWord + nCol - 1)/nCol; + for(i=0; i=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; ieCmdFlags & 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 . +*/ +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 +******************************************************************************/ -- cgit v1.3.1