/*
** 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 */
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 */
/**************************************************************************/
/* 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)
/*
** 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{
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);
}
}
return 0;
}
/*
** Fill Blob with a space-separated list of all command names that
** match the prefix zPrefix.
*/
void dispatch_matching_names(const char *zPrefix, Blob *pList){
int i;
int nPrefix = (int)strlen(zPrefix);
for(i=FOSSIL_FIRST_CMD; i<MX_COMMAND; i++){
if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){
blob_appendf(pList, " %s", aCommand[i].zName);
}
}
}
/*
** 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.
*/
static void help_to_html(const char *zHelp, Blob *pHtml){
char *s;
char *d;
char *z;
/* Transform "%fossil" into just "fossil" */
z = s = d = mprintf("%s", zHelp);
while( *s ){
if( *s=='%' && strncmp(s, "%fossil", 7)==0 ){
s++;
}else{
*d++ = *s++;
}
}
*d = 0;
blob_appendf(pHtml, "<pre>\n%h\n</pre>\n", z);
fossil_free(z);
}
/*
** 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:
** -e|--everything Show all commands and pages.
** -t|--test Include test- commands
** -w|--www Show WWW pages.
** -s|--settings Show settings.
** -h|--html Transform output to HTML.
*/
void test_all_help_cmd(void){
int i;
int mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER;
int useHtml = find_option("html","h",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_SETTING | CMDFLAG_TEST;
}
if( find_option("settings","s",0) ){
mask = CMDFLAG_SETTING;
}
if( find_option("test","t",0) ){
mask |= CMDFLAG_TEST;
}
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_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");
}
for(i=0; i<MX_COMMAND; i++){
if( (aCommand[i].eCmdFlags & mask)==0 ) continue;
fossil_print("# %s\n", aCommand[i].zName);
if( useHtml ){
Blob html;
blob_zero(&html);
help_to_html(aCommand[i].zHelp, &html);
fossil_print("%s\n\n", blob_str(&html));
blob_reset(&html);
}else{
fossil_print("%s\n\n", aCommand[i].zHelp);
}
}
if( useHtml ){
fossil_print("<!-- end_all_help -->\n");
}else{
fossil_print("---\n");
}
version_cmd();
}
/*
** WEBPAGE: help
** URL: /help?name=CMD
**
** Show the built-in help text for CMD. CMD can be a command-line interface
** command or a page name from the web interface or a setting.
*/
void help_page(void){
const char *zCmd = P("cmd");
if( zCmd==0 ) zCmd = P("name");
if( zCmd && *zCmd ){
int rc;
const CmdOrPage *pCmd = 0;
style_header("Help: %s", zCmd);
style_submenu_element("Command-List", "%s/help", g.zTop);
rc = dispatch_name_search(zCmd, CMDFLAG_ANY, &pCmd);
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(zCmd)" page:</h1>
}else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_SETTING)!=0 ){
@ <h1>The "%h(pCmd->zName)" setting:</h1>
}else{
@ <h1>The "%h(zCmd)" command:</h1>
}
if( rc==1 ){
@ unknown command: %h(zCmd)
}else if( rc==2 ){
@ ambiguous command prefix: %h(zCmd)
}else{
if( pCmd->zHelp[0]==0 ){
@ No help available for "%h(pCmd->zName)"
}else{
@ <blockquote>
help_to_html(pCmd->zHelp, cgi_output_blob());
@ </blockquote>
}
}
}else{
int i;
style_header("Help");
@ <a name='commands'></a>
@ <h1>Available commands:</h1>
@ <div class="columns" style="column-width: 12ex;">
@ <ul>
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;
@ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a></li>
}
@ </ul></div>
@ <a name='webpages'></a>
@ <h1>Available web UI pages:</h1>
@ <div class="columns" style="column-width: 18ex;">
@ <ul>
for(i=0; i<MX_COMMAND; i++){
const char *z = aCommand[i].zName;
if( '/'!=*z ) continue;
if( aCommand[i].zHelp[0] ){
@ <li><a href="%R/help?cmd=%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: 20ex;">
@ <ul>
for(i=0; i<MX_COMMAND; i++){
const char *z = aCommand[i].zName;
if( strncmp(z,"test",4)!=0 ) continue;
if( aCommand[i].zHelp[0] ){
@ <li><a href="%R/help?cmd=%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: 20ex;">
@ <ul>
for(i=0; i<MX_COMMAND; i++){
const char *z = aCommand[i].zName;
if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;
if( aCommand[i].zHelp[0] ){
@ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
}else{
@ <li>%s(z)</li>
}
}
@ </ul></div>
}
style_footer();
}
/*
** WEBPAGE: test-all-help
**
** Show all help text on a single page. Useful for proof-reading.
*/
void test_all_help_page(void){
int i;
style_header("All Help Text");
for(i=0; i<MX_COMMAND; i++){
if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
@ <h2>%s(aCommand[i].zName):</h2>
@ <blockquote>
help_to_html(aCommand[i].zHelp, cgi_output_blob());
@ </blockquote>
}
style_footer();
}
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(const char *zPrefix, int cmdMask){
int i, nCmd;
int nPrefix = zPrefix ? strlen(zPrefix) : 0;
const char *aCmd[MX_COMMAND];
for(i=nCmd=0; i<MX_COMMAND; i++){
const char *z = aCommand[i].zName;
if( (aCommand[i].eCmdFlags & cmdMask)==0 ) continue;
if( zPrefix && memcmp(zPrefix, z, nPrefix)!=0 ) 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
@ --cgitrace Active CGI tracing
@ --comfmtflags VALUE Set comment formatting flags to VALUE
@ --comment-format VALUE Alias for --comfmtflags
@ --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
@ --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()
@ --user|-U 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 TOPIC
** or: %fossil TOPIC --help
**
** Display information on how to use TOPIC, which may be a command, webpage, or
** setting. Webpage names begin with "/". To display a list of available
** topics, use one of:
**
** %fossil help Show common commands
** %fossil help -a|--all Show both common and auxiliary commands
** %fossil help -o|--options Show command-line options common to all cmds
** %fossil help -s|--setting Show setting names
** %fossil help -t|--test Show test commands only
** %fossil help -x|--aux Show auxiliary commands only
** %fossil help -w|--www Show list of webpages
*/
void help_cmd(void){
int rc;
int isPage = 0;
const char *z;
const char *zCmdOrPage;
const char *zCmdOrPagePlural;
const CmdOrPage *pCmd = 0;
if( g.argc<3 ){
z = g.argv[0];
fossil_print(
"Usage: %s help TOPIC\n"
"Common commands: (use \"%s help help\" for more options)\n",
z, z);
command_list(0, CMDFLAG_1ST_TIER);
version_cmd();
return;
}
if( find_option("options","o",0) ){
fossil_print("%s", zOptions);
return;
}
if( find_option("all","a",0) ){
command_list(0, CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER);
return;
}
else if( find_option("www","w",0) ){
command_list(0, CMDFLAG_WEBPAGE);
return;
}
else if( find_option("aux","x",0) ){
command_list(0, CMDFLAG_2ND_TIER);
return;
}
else if( find_option("test","t",0) ){
command_list(0, CMDFLAG_TEST);
return;
}
else if( find_option("setting","s",0) ){
command_list(0, CMDFLAG_SETTING);
return;
}
isPage = ('/' == *g.argv[2]) ? 1 : 0;
if(isPage){
zCmdOrPage = "page";
zCmdOrPagePlural = "pages";
}else{
zCmdOrPage = "command or setting";
zCmdOrPagePlural = "commands and settings";
}
rc = dispatch_name_search(g.argv[2], CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd);
if( rc==1 ){
fossil_print("unknown %s: %s\nConsider using:\n", zCmdOrPage, g.argv[2]);
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_exit(1);
}else if( rc==2 ){
fossil_print("ambiguous %s prefix: %s\nMatching %s:\n",
zCmdOrPage, g.argv[2], zCmdOrPagePlural);
command_list(g.argv[2], 0xff);
fossil_exit(1);
}
z = pCmd->zHelp;
if( z==0 ){
fossil_fatal("no help available for the %s %s",
pCmd->zName, zCmdOrPage);
}
if( pCmd->eCmdFlags & CMDFLAG_SETTING ){
fossil_print("Setting: \"%s\"%s\n\n",
pCmd->zName,
(pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : ""
);
}
while( *z ){
if( *z=='%' && strncmp(z, "%fossil", 7)==0 ){
fossil_print("%s", g.argv[0]);
z += 7;
}else{
putchar(*z);
z++;
}
}
putchar('\n');
}
/*
** Return a pointer to the setting information array.
**
** This routine provides access to the aSetting2[] 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;
}