Fossil

dispatch.c at [8af686481e]
Login

dispatch.c at [8af686481e]

File src/dispatch.c artifact 586d74b991 part of check-in 8af686481e


/*
** 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, or setting.
*/
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     0x0001     /* Most important commands */
#define CMDFLAG_2ND_TIER     0x0002     /* Obscure and seldom used commands */
#define CMDFLAG_TEST         0x0004     /* Commands for testing only */
#define CMDFLAG_WEBPAGE      0x0008     /* Web pages */
#define CMDFLAG_COMMAND      0x0010     /* A command */
#define CMDFLAG_SETTING      0x0020     /* A setting */
#define CMDFLAG_VERSIONABLE  0x0040     /* A versionable setting */
#define CMDFLAG_BLOCKTEXT    0x0080     /* Multi-line text setting */
#define CMDFLAG_BOOLEAN      0x0100     /* A boolean setting */
#define CMDFLAG_RAWCONTENT   0x0200     /* Do not interpret POST content */
/* NOTE:                     0x0400 = CMDFLAG_SENSITIVE in mkindex.c! */
#define CMDFLAG_HIDDEN       0x0800     /* Elide from most listings */
#define CMDFLAG_LDAVG_EXEMPT 0x1000     /* Exempt from load_control() */
#define CMDFLAG_ALIAS        0x2000     /* Command aliases */
#define CMDFLAG_KEEPEMPTY    0x4000     /* Do not unset empty settings */
#define CMDFLAG_ABBREVSUBCMD 0x8000     /* Help text abbreviates subcommands */
/**************************************************************************/

/* Values for the 2nd parameter to dispatch_name_search() */
#define CMDFLAG_ANY         0x0038      /* Match anything */
#define CMDFLAG_PREFIX      0x0200      /* 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, "&lt;", 4);
      z += i+1;
      n -= i+1;
      i = 0;
    }else if( c=='>' ){
      if( i ) blob_append(pOut, z, i);
      blob_append(pOut, "&gt;", 4);
      z += i+1;
      n -= i+1;
      i = 0;
    }else if( c=='&' ){
      if( i ) blob_append(pOut, z, i);
      blob_append(pOut, "&amp;", 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;
        blob_append(pHtml, "<blockquote><dl>\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( 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
**    -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;
  }
  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;
  }
  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("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{
      @ <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>Available 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)!=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>Available web UI 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='unsupported'></a>
    @ <h1>Unsupported 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>

    @ <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>

  }
  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_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);
  }
}

/*
** Documentation on universal command-line options.
*/
/* @-comment: # */
static const char zOptions[] =
@ 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
@   --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
;

/*
** 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("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("\n%s", zOptions);
    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 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, useHtml, 0);
    return;
  }
  verify_all_options();
  if( g.argc<3 ){
    if( bOptions ){
      fossil_print("%s", zOptions);
      return;
    }
    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";
  }else{
    zCmdOrPage = "command or setting";
  }
  rc = dispatch_name_search(g.argv[2], 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";
      }
      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
******************************************************************************/