diff options
Diffstat (limited to 'tools/mkindex.c')
| -rw-r--r-- | tools/mkindex.c | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/tools/mkindex.c b/tools/mkindex.c new file mode 100644 index 0000000..97a2456 --- /dev/null +++ b/tools/mkindex.c @@ -0,0 +1,607 @@ +/* +** Copyright (c) 2002 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 utility program scans Fossil source text looking for specially +** formatted comments and generates C source code for constant tables +** that define the behavior of commands, webpages, and settings. +** +** USAGE: +** +** mkindex *.c >page_index.h +** +** Run this command with arguments that are all input source files to +** scan. Generated C code appears on standard output. The generated +** C code includes structures that: +** +** * Map command names to the C-language functions that implement +** those command. +** +** * Map webpage names to the C-language functions that implement +** those web pages. +** +** * Map settings into attributes, such as they default value for +** each setting, and the kind of value (boolean, multi-line, etc). +** +** * Provide help text for commands, webpages, settings, and other +** miscellanous help topics. +** +** COMMENT TEXT THAT THIS PROGRAM LOOKS FOR: +** +** The source code is scanned for comment lines of the form: +** +** WEBPAGE: /abc/xyz +** COMMAND: cmdname +** SETTING: access-log +** TOPIC: help-topic +** +** The WEBPAGE and COMMAND comments should be followed by a function that +** implements the webpage or command. The form of this function is: +** +** void function_name(void){ +** +** Command names can divided into three classes: 1st-tier, 2nd-tier, +** and test. 1st-tier commands are the most frequently used and the +** ones that show up with "fossil help". 2nd-tier are seldom-used and/or +** legacy commands. Test commands are unsupported commands used for testing +** and analysis only. +** +** Commands are 1st-tier by default. If the command name begins with +** "test-" or if the command name has a "test" argument, then it becomes +** a test command. If the command name has a "2nd-tier" argument or ends +** with a "*" character, it is second tier. If the command name has an "alias" +** argument or ends with a "#" character, it is an alias: another name +** (a one-to-one replacement) for a command. Examples: +** +** COMMAND: abcde* +** COMMAND: fghij 2nd-tier +** COMMAND: mnopq# +** COMMAND: rstuv alias +** COMMAND: test-xyzzy +** COMMAND: xyzzy test +** +** A SETTING: may be followed by arguments that give additional attributes +** to that setting: +** +** SETTING: clean-blob versionable width=40 block-text +** SETTING: auto-shun boolean default=on +** +** New arguments may be added in future releases that set additional +** bits in the eCmdFlags field. +** +** Additional lines of comment after the COMMAND: or WEBPAGE: or SETTING: +** become the built-in help text for that command or webpage or setting. +** Backslashes must be escaped ("\\" in comment yields "\" in the help text.) +** +** Multiple COMMAND: entries can be attached to the same command, thus +** creating multiple aliases for that command. Similarly, multiple +** WEBPAGE: entries can be attached to the same webpage function, to give +** that page aliases. +** +** For SETTING: entries, the default value for the setting can be specified +** using a default=VALUE argument if the default contains no spaces. If the +** default value does contain spaces, use a separate line like this: +** +** SETTING: pgp-command +** DEFAULT: gpg --clearsign -o +** +** If no default is supplied, the default is assumed to be an empty string +** or "off" in the case of a boolean. +** +** A TOPIC: is followed by help text for the named topic. +** +** OUTPUTS: +** +** The output is C-language text to define and initialize a constant +** array of CmdOrPage objects named "aCommand[]". That array is a global +** variable. The dispatch.c source file defines the CmdOrPage object and +** deals with the aCommand[] global variable. +** +** The output also contains a constant array of Setting objects named +** aSetting[]. The Setting object is defined in db.c. +*/ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +/*************************************************************************** +** These macros must match similar macros in dispatch.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 webpage content */ +#define CMDFLAG_SENSITIVE 0x000400 /* Security-sensitive setting */ +#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 /* Abbreviated subcmd in help text */ +#define CMDFLAG_TOPIC 0x010000 /* A help topic */ +/**************************************************************************/ + +/* +** Each entry looks like this: +*/ +typedef struct Entry { + int eType; /* CMDFLAG_* values */ + char *zIf; /* Enclose in #if */ + char *zFunc; /* Name of implementation */ + char *zPath; /* Webpage or command name */ + char *zHelp; /* Help text */ + char *zDflt; /* Default value for settings */ + char *zVar; /* config.name for settings, if different from zPath */ + int iHelp; /* Index of Help text */ + int iWidth; /* Display width for SETTING: values */ +} Entry; + +/* +** Maximum number of entries +*/ +#define N_ENTRY 5000 + +/* +** Maximum size of a help message +*/ +#define MX_HELP 250000 + +/* +** Table of entries +*/ +Entry aEntry[N_ENTRY]; + +/* +** Current help message accumulator +*/ +char zHelp[MX_HELP]; +int nHelp; + +/* +** Most recently encountered #if +*/ +char zIf[2000]; + +/* +** How many entries are used +*/ +int nUsed; +int nFixed; + +/* +** Current filename and line number +*/ +char *zFile; +int nLine; + +/* +** Number of errors +*/ +int nErr = 0; + +/* +** Duplicate N characters of a string. +*/ +char *string_dup(const char *zSrc, int n){ + char *z; + if( n<0 ) n = strlen(zSrc); + z = malloc( n+1 ); + if( z==0 ){ fprintf(stderr,"Out of memory!\n"); exit(1); } + strncpy(z, zSrc, n); + z[n] = 0; + return z; +} + +/* +** Safe isspace macro. Works with signed characters. +*/ +int fossil_isspace(char c){ + return c==' ' || (c<='\r' && c>='\t'); +} + +/* +** Safe isident macro. Works with signed characters. +*/ +int fossil_isident(char c){ + if( c>='a' && c<='z' ) return 1; + if( c>='A' && c<='Z' ) return 1; + if( c>='0' && c<='9' ) return 1; + if( c=='_' ) return 1; + return 0; +} + +/* +** Scan a line looking for comments containing zLabel. Make +** new entries if found. +*/ +void scan_for_label(const char *zLabel, char *zLine, int eType){ + int i, j; + int len = strlen(zLabel); + if( nUsed>=N_ENTRY ) return; + for(i=0; fossil_isspace(zLine[i]) || zLine[i]=='*'; i++){} + if( zLine[i]!=zLabel[0] ) return; + if( strncmp(&zLine[i],zLabel, len)==0 ){ + i += len; + }else{ + return; + } + while( fossil_isspace(zLine[i]) ){ i++; } + if( zLine[i]=='/' ) i++; + for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} + aEntry[nUsed].eType = eType; + if( eType & CMDFLAG_WEBPAGE ){ + aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1); + aEntry[nUsed].zPath[0] = '/'; + }else{ + aEntry[nUsed].zPath = string_dup(&zLine[i], j); + } + aEntry[nUsed].zFunc = 0; + if( (eType & CMDFLAG_COMMAND)!=0 ){ + if( strncmp(&zLine[i], "test-", 5)==0 ){ + /* Commands that start with "test-" are test-commands */ + aEntry[nUsed].eType |= CMDFLAG_TEST; + }else if( zLine[i+j-1]=='*' ){ + /* If the command name ends in '*', remove the '*' from the name + ** but move the command into the second tier */ + aEntry[nUsed].zPath[j-1] = 0; + aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; + }else if( zLine[i+j-1]=='#' ){ + /* If the command name ends in '#', remove the '#' from the name + ** but move the command into aliases */ + aEntry[nUsed].zPath[j-1] = 0; + aEntry[nUsed].eType |= CMDFLAG_ALIAS; + }else{ + /* Otherwise, this is a first-tier command */ + aEntry[nUsed].eType |= CMDFLAG_1ST_TIER; + } + } + + /* Process additional flags that might follow the command name */ + while( zLine[i+j]!=0 ){ + i += j; + while( fossil_isspace(zLine[i]) ){ i++; } + if( zLine[i]==0 ) break; + for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} + if( j==8 && strncmp(&zLine[i], "1st-tier", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_2ND_TIER|CMDFLAG_TEST|CMDFLAG_ALIAS); + aEntry[nUsed].eType |= CMDFLAG_1ST_TIER; + }else if( j==8 && strncmp(&zLine[i], "2nd-tier", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST|CMDFLAG_ALIAS); + aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; + }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER|CMDFLAG_ALIAS); + aEntry[nUsed].eType |= CMDFLAG_TEST; + }else if( j==5 && strncmp(&zLine[i], "alias", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER|CMDFLAG_TEST); + aEntry[nUsed].eType |= CMDFLAG_ALIAS; + }else if( j==11 && strncmp(&zLine[i], "raw-content", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_RAWCONTENT; + }else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT); + aEntry[nUsed].iWidth = 0; + aEntry[nUsed].eType |= CMDFLAG_BOOLEAN; + }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN); + aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT; + }else if( j==10 && strncmp(&zLine[i], "keep-empty", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_KEEPEMPTY; + }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE; + }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_SENSITIVE; + }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){ + aEntry[nUsed].iWidth = atoi(&zLine[i+6]); + }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){ + aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8); + }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){ + aEntry[nUsed].zVar = string_dup(&zLine[i+9], j-9); + }else if( j==6 && strncmp(&zLine[i], "hidden", 6)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_HIDDEN; + }else if( j==14 && strncmp(&zLine[i], "loadavg-exempt", 14)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_LDAVG_EXEMPT; + }else if( (j==23 && strncmp(&zLine[i], "abbreviated-subcommands", 23)==0) + || (j==12 && strncmp(&zLine[i], "abbrv-subcom", 12)==0) ){ + aEntry[nUsed].eType |= CMDFLAG_ABBREVSUBCMD; + }else{ + fprintf(stderr, "%s:%d: unknown option: '%.*s'\n", + zFile, nLine, j, &zLine[i]); + nErr++; + } + } + + nUsed++; + return; +} + +/* +** Check to see if the current line is an #if and if it is, add it to +** the zIf[] string. If the current line is an #endif or #else or #elif +** then cancel the current zIf[] string. +*/ +void scan_for_if(const char *zLine){ + int i; + int len; + if( zLine[0]!='#' ) return; + for(i=1; fossil_isspace(zLine[i]); i++){} + if( zLine[i]==0 ) return; + len = strlen(&zLine[i]); + if( strncmp(&zLine[i],"if",2)==0 ){ + zIf[0] = '#'; + memcpy(&zIf[1], &zLine[i], len+1); + }else if( zLine[i]=='e' ){ + zIf[0] = 0; + } +} + +/* +** Check to see if the current line is a "** DEFAULT: ..." line for a +** SETTING definition. If so, remember the default value. +*/ +void scan_for_default(const char *zLine){ + int len; + const char *z; + if( nUsed<1 ) return; + if( (aEntry[nUsed-1].eType & CMDFLAG_SETTING)==0 ) return; + if( strncmp(zLine, "** DEFAULT: ", 12)!=0 ) return; + z = zLine + 12; + while( fossil_isspace(z[0]) ) z++; + len = (int)strlen(z); + while( len>0 && fossil_isspace(z[len-1]) ){ len--; } + aEntry[nUsed-1].zDflt = string_dup(z,len); +} + +/* Local strcpy() clone to squelch an unwarranted warning from OpenBSD. */ +static void local_strcpy(char *dest, const char *src){ + while( (*(dest++) = *(src++))!=0 ){} +} + +/* +** Scan a line for a function that implements a web page or command. +*/ +void scan_for_func(char *zLine){ + int i,j,k; + char *z; + int hasFunc; + if( nUsed<=nFixed ) return; + if( strncmp(zLine, "**", 2)==0 + && fossil_isspace(zLine[2]) + && strlen(zLine)<sizeof(zHelp)-nHelp-1 + && nUsed>nFixed + && strncmp(zLine,"** COMMAND:",11)!=0 + && strncmp(zLine,"** WEBPAGE:",11)!=0 + && strncmp(zLine,"** SETTING:",11)!=0 + && strncmp(zLine,"** DEFAULT:",11)!=0 + && strncmp(zLine,"** TOPIC:",9)!=0 + ){ + if( zLine[2]=='\n' ){ + zHelp[nHelp++] = '\n'; + }else{ + if( strncmp(&zLine[3], "Usage: ", 6)==0 ) nHelp = 0; + local_strcpy(&zHelp[nHelp], &zLine[3]); + nHelp += strlen(&zHelp[nHelp]); + } + return; + } + for(i=0; fossil_isspace(zLine[i]); i++){} + if( zLine[i]==0 ) return; + hasFunc = (aEntry[nFixed].eType & (CMDFLAG_SETTING|CMDFLAG_TOPIC))==0; + if( hasFunc ){ + if( strncmp(&zLine[i],"void",4)!=0 ){ + if( zLine[i]!='*' ) goto page_skip; + return; + } + i += 4; + if( !fossil_isspace(zLine[i]) ) goto page_skip; + while( fossil_isspace(zLine[i]) ){ i++; } + for(j=0; fossil_isident(zLine[i+j]); j++){} + if( j==0 ) goto page_skip; + }else{ + j = 0; + } + for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){} + nHelp = k+1; + zHelp[nHelp] = 0; + for(k=0; k<nHelp && fossil_isspace(zHelp[k]); k++){} + if( k<nHelp ){ + z = string_dup(&zHelp[k], nHelp-k); + }else{ + z = ""; + } + for(k=nFixed; k<nUsed; k++){ + aEntry[k].zIf = zIf[0] ? string_dup(zIf, -1) : 0; + aEntry[k].zFunc = hasFunc ? string_dup(&zLine[i], j) : "0"; + aEntry[k].zHelp = z; + z = 0; + aEntry[k].iHelp = nFixed; + } + if( hasFunc ){ + i+=j; + while( fossil_isspace(zLine[i]) ){ i++; } + if( zLine[i]!='(' ) goto page_skip; + } + nFixed = nUsed; + nHelp = 0; + return; + +page_skip: + for(i=nFixed; i<nUsed; i++){ + fprintf(stderr,"%s:%d: skipping page \"%s\"\n", + zFile, nLine, aEntry[i].zPath); + } + nUsed = nFixed; +} + +/* +** Compare two entries +*/ +int e_compare(const void *a, const void *b){ + const Entry *pA = (const Entry*)a; + const Entry *pB = (const Entry*)b; + return strcmp(pA->zPath, pB->zPath); +} + +/* +** Build the binary search table. +*/ +void build_table(void){ + int i; + int nWeb = 0; + int mxLen = 0; + + qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare); + + printf( + "/* Automatically generated code\n" + "** DO NOT EDIT!\n" + "**\n" + "** This file was generated by the mkindex.exe program based on\n" + "** comments in other Fossil source files.\n" + "*/\n" + ); + + /* Output declarations for all the action functions */ + for(i=0; i<nFixed; i++){ + if( aEntry[i].eType & (CMDFLAG_SETTING|CMDFLAG_TOPIC) ) continue; + if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf); + printf("extern void %s(void);\n", aEntry[i].zFunc); + if( aEntry[i].zIf ) printf("#endif\n"); + } + + /* Output strings for all the help text */ + for(i=0; i<nFixed; i++){ + char *z = aEntry[i].zHelp; + if( z==0 ) continue; + if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf); + printf("static const char zHelp%03d[] =\n \"", aEntry[i].iHelp); + while( *z ){ + if( *z=='\n' ){ + printf("\\n\"\n \""); + }else if( *z=='"' ){ + printf("\\\""); + }else{ + putchar(*z); + } + z++; + } + printf("\";\n"); + if( aEntry[i].zIf ) printf("#endif\n"); + } + + /* Generate the aCommand[] table */ + printf("static const CmdOrPage aCommand[] = {\n"); + for(i=0; i<nFixed; i++){ + const char *z = aEntry[i].zPath; + int n = strlen(z); + if( n>mxLen ) mxLen = n; + if( aEntry[i].zIf ){ + printf("%s", aEntry[i].zIf); + }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){ + nWeb++; + } + printf(" { \"%.*s\",%*s%s,%*szHelp%03d, %3d, 0x%05x },\n", + n, z, + 25-n, "", + aEntry[i].zFunc, + (int)(29-strlen(aEntry[i].zFunc)), "", + aEntry[i].iHelp, + aEntry[i].iHelp, + aEntry[i].eType + ); + if( aEntry[i].zIf ) printf("#endif\n"); + } + printf("};\n"); + printf("#define FOSSIL_FIRST_CMD %d\n", nWeb); + printf("#define FOSSIL_MX_CMDNAME %d /* max length of any command name */\n", + mxLen); + printf("#define FOSSIL_MX_CMDIDX %d /* max index for commands */\n", nFixed); + + /* Generate the aSetting[] table */ + printf("const Setting aSetting[] = {\n"); + for(i=0; i<nFixed; i++){ + const char *z; + const char *zVar; + const char *zDef; + if( (aEntry[i].eType & CMDFLAG_SETTING)==0 ) continue; + z = aEntry[i].zPath; + zVar = aEntry[i].zVar; + zDef = aEntry[i].zDflt; + if( zDef==0 ) zDef = ""; + if( aEntry[i].zIf ){ + printf("%s", aEntry[i].zIf); + } + printf(" { \"%s\",%*s", z, (int)(20-strlen(z)), ""); + if( zVar ){ + printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), ""); + }else{ + printf(" 0,%*s", 16, ""); + } + printf(" %3d, %d, %d, %d, \"%s\"%*s },\n", + aEntry[i].iWidth, + (aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0, + (aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0, + (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0, + zDef, (int)(10-strlen(zDef)), "" + ); + if( aEntry[i].zIf ){ + printf("#endif\n"); + } + } + printf("{0,0,0,0,0,0,0}};\n"); + +} + +/* +** Process a single file of input +*/ +void process_file(void){ + FILE *in = fopen(zFile, "r"); + char zLine[2000]; + if( in==0 ){ + fprintf(stderr,"%s: cannot open\n", zFile); + return; + } + nLine = 0; + while( fgets(zLine, sizeof(zLine), in) ){ + nLine++; + scan_for_if(zLine); + scan_for_label("WEBPAGE:",zLine,CMDFLAG_WEBPAGE); + scan_for_label("COMMAND:",zLine,CMDFLAG_COMMAND); + scan_for_func(zLine); + scan_for_label("SETTING:",zLine,CMDFLAG_SETTING); + scan_for_default(zLine); + scan_for_label("TOPIC:",zLine,CMDFLAG_TOPIC); + } + fclose(in); + nUsed = nFixed; +} + +int main(int argc, char **argv){ + int i; + memset(aEntry, 0, sizeof(Entry) * N_ENTRY); + for(i=1; i<argc; i++){ + zFile = argv[i]; + process_file(); + } + build_table(); + return nErr; +} |
