From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- tools/mkindex.c | 607 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 607 insertions(+) create mode 100644 tools/mkindex.c (limited to 'tools/mkindex.c') 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 +#include +#include +#include + +/*************************************************************************** +** 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)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; kzPath, 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; imxLen ) 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