| | @@ -22,49 +22,68 @@ |
| 22 | 22 | ** to those entry points. |
| 23 | 23 | ** |
| 24 | 24 | ** The source code is scanned for comment lines of the form: |
| 25 | 25 | ** |
| 26 | 26 | ** WEBPAGE: /abc/xyz |
| 27 | +** COMMAND: cmdname |
| 27 | 28 | ** |
| 28 | | -** This comment should be followed by a function definition of the |
| 29 | +** These comment should be followed by a function definition of the |
| 29 | 30 | ** form: |
| 30 | 31 | ** |
| 31 | 32 | ** void function_name(void){ |
| 32 | 33 | ** |
| 33 | 34 | ** This routine creates C source code for a constant table that maps |
| 34 | | -** webpage name into pointers to the function. |
| 35 | | -** |
| 36 | | -** We also scan for comments lines of this form: |
| 37 | | -** |
| 38 | | -** COMMAND: cmdname |
| 39 | | -** |
| 40 | | -** These entries build a constant table used to map command names into |
| 41 | | -** functions. If cmdname ends with "*" then the command is a second-tier |
| 42 | | -** command that is not displayed by the "fossil help" command. The |
| 43 | | -** final "*" is not considered to be part of the command name. |
| 44 | | -** |
| 45 | | -** Comment text following COMMAND: through the end of the comment is |
| 46 | | -** understood to be help text for the command specified. This help |
| 47 | | -** text is accumulated and a table containing the text for each command |
| 48 | | -** is generated. That table is used implement the "fossil help" command |
| 49 | | -** and the "/help" HTTP method. |
| 50 | | -** |
| 51 | | -** Multiple occurrences of WEBPAGE: or COMMAND: (but not both) can appear |
| 52 | | -** before each function name. In this way, webpages and commands can |
| 53 | | -** have aliases. |
| 35 | +** command and webpage name into pointers to the function. |
| 36 | +** |
| 37 | +** Command names can divided into three classes: 1st-tier, 2nd-tier, |
| 38 | +** and test. 1st-tier commands are the most frequently used and the |
| 39 | +** ones that show up with "fossil help". 2nd-tier are seldom-used and/or |
| 40 | +** legacy command. Test commands are unsupported commands used for testing |
| 41 | +** and analysis only. |
| 42 | +** |
| 43 | +** Commands are 1st-tier by default. If the command name begins with |
| 44 | +** "test-" or if the command name as a "test" argument, then it becomes |
| 45 | +** a test command. If the command name has a "2nd-tier" argument or ends |
| 46 | +** with a "*" character, it is second tier. Examples: |
| 47 | +** |
| 48 | +** COMMAND: abcde* |
| 49 | +** COMMAND: fghij 2nd-tier |
| 50 | +** COMMAND: test-xyzzy |
| 51 | +** COMMAND: xyzzy test |
| 52 | +** |
| 53 | +** New arguments may be added in future releases that set additional |
| 54 | +** bits in the eCmdFlags field. |
| 55 | +** |
| 56 | +** Additional lines of comment after the COMMAND: or WEBPAGE: become |
| 57 | +** the built-in help text for that command or webpage. |
| 58 | +** |
| 59 | +** Multiple COMMAND: entries can be attached to the same command, thus |
| 60 | +** creating multiple aliases for that command. Similarly, multiple |
| 61 | +** WEBPAGE: entries can be attached to the same webpage function, to give |
| 62 | +** that page aliases. |
| 54 | 63 | */ |
| 55 | 64 | #include <stdio.h> |
| 56 | 65 | #include <stdlib.h> |
| 57 | | -#include <ctype.h> |
| 58 | 66 | #include <assert.h> |
| 59 | 67 | #include <string.h> |
| 68 | + |
| 69 | +/*************************************************************************** |
| 70 | +** These macros must match similar macros in dispatch.c. |
| 71 | +** |
| 72 | +** Allowed values for CmdOrPage.eCmdFlags. */ |
| 73 | +#define CMDFLAG_1ST_TIER 0x0001 /* Most important commands */ |
| 74 | +#define CMDFLAG_2ND_TIER 0x0002 /* Obscure and seldom used commands */ |
| 75 | +#define CMDFLAG_TEST 0x0004 /* Commands for testing only */ |
| 76 | +#define CMDFLAG_WEBPAGE 0x0008 /* Web pages */ |
| 77 | +#define CMDFLAG_COMMAND 0x0010 /* A command */ |
| 78 | +/**************************************************************************/ |
| 60 | 79 | |
| 61 | 80 | /* |
| 62 | 81 | ** Each entry looks like this: |
| 63 | 82 | */ |
| 64 | 83 | typedef struct Entry { |
| 65 | | - int eType; /* 0: webpage, 1: command */ |
| 84 | + int eType; /* CMDFLAG_* values */ |
| 66 | 85 | char *zIf; /* Enclose in #if */ |
| 67 | 86 | char *zFunc; /* Name of implementation */ |
| 68 | 87 | char *zPath; /* Webpage or command name */ |
| 69 | 88 | char *zHelp; /* Help text */ |
| 70 | 89 | int iHelp; /* Index of Help text */ |
| | @@ -106,10 +125,15 @@ |
| 106 | 125 | ** Current filename and line number |
| 107 | 126 | */ |
| 108 | 127 | char *zFile; |
| 109 | 128 | int nLine; |
| 110 | 129 | |
| 130 | +/* |
| 131 | +** Number of errors |
| 132 | +*/ |
| 133 | +int nErr = 0; |
| 134 | + |
| 111 | 135 | /* |
| 112 | 136 | ** Duplicate N characters of a string. |
| 113 | 137 | */ |
| 114 | 138 | char *string_dup(const char *zSrc, int n){ |
| 115 | 139 | char *z; |
| | @@ -118,32 +142,92 @@ |
| 118 | 142 | if( z==0 ){ fprintf(stderr,"Out of memory!\n"); exit(1); } |
| 119 | 143 | strncpy(z, zSrc, n); |
| 120 | 144 | z[n] = 0; |
| 121 | 145 | return z; |
| 122 | 146 | } |
| 147 | + |
| 148 | +/* |
| 149 | +** Safe isspace macro. Works with signed characters. |
| 150 | +*/ |
| 151 | +int fossil_isspace(char c){ |
| 152 | + return c==' ' || (c<='\r' && c>='\t'); |
| 153 | +} |
| 154 | + |
| 155 | +/* |
| 156 | +** Safe isident macro. Works with signed characters. |
| 157 | +*/ |
| 158 | +int fossil_isident(char c){ |
| 159 | + if( c>='a' && c<='z' ) return 1; |
| 160 | + if( c>='A' && c<='Z' ) return 1; |
| 161 | + if( c>='0' && c<='9' ) return 1; |
| 162 | + if( c=='_' ) return 1; |
| 163 | + return 0; |
| 164 | +} |
| 123 | 165 | |
| 124 | 166 | /* |
| 125 | 167 | ** Scan a line looking for comments containing zLabel. Make |
| 126 | 168 | ** new entries if found. |
| 127 | 169 | */ |
| 128 | 170 | void scan_for_label(const char *zLabel, char *zLine, int eType){ |
| 129 | 171 | int i, j; |
| 130 | 172 | int len = strlen(zLabel); |
| 131 | 173 | if( nUsed>=N_ENTRY ) return; |
| 132 | | - for(i=0; isspace(zLine[i]) || zLine[i]=='*'; i++){} |
| 174 | + for(i=0; fossil_isspace(zLine[i]) || zLine[i]=='*'; i++){} |
| 133 | 175 | if( zLine[i]!=zLabel[0] ) return; |
| 134 | 176 | if( strncmp(&zLine[i],zLabel, len)==0 ){ |
| 135 | 177 | i += len; |
| 136 | 178 | }else{ |
| 137 | 179 | return; |
| 138 | 180 | } |
| 139 | | - while( isspace(zLine[i]) ){ i++; } |
| 181 | + while( fossil_isspace(zLine[i]) ){ i++; } |
| 140 | 182 | if( zLine[i]=='/' ) i++; |
| 141 | | - for(j=0; zLine[i+j] && !isspace(zLine[i+j]); j++){} |
| 183 | + for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} |
| 142 | 184 | aEntry[nUsed].eType = eType; |
| 143 | | - aEntry[nUsed].zPath = string_dup(&zLine[i], j); |
| 185 | + if( eType & CMDFLAG_WEBPAGE ){ |
| 186 | + aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1); |
| 187 | + aEntry[nUsed].zPath[0] = '/'; |
| 188 | + }else{ |
| 189 | + aEntry[nUsed].zPath = string_dup(&zLine[i], j); |
| 190 | + } |
| 144 | 191 | aEntry[nUsed].zFunc = 0; |
| 192 | + if( (eType & CMDFLAG_COMMAND)!=0 ){ |
| 193 | + if( strncmp(&zLine[i], "test-", 5)==0 ){ |
| 194 | + /* Commands that start with "test-" are test-commands */ |
| 195 | + aEntry[nUsed].eType |= CMDFLAG_TEST; |
| 196 | + }else if( zLine[i+j-1]=='*' ){ |
| 197 | + /* If the command name ends in '*', remove the '*' from the name |
| 198 | + ** but move the command into the second tier */ |
| 199 | + aEntry[nUsed].zPath[j-1] = 0; |
| 200 | + aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; |
| 201 | + }else{ |
| 202 | + /* Otherwise, this is a first-tier command */ |
| 203 | + aEntry[nUsed].eType |= CMDFLAG_1ST_TIER; |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + /* Processing additional flags that might following the command name */ |
| 208 | + while( zLine[i+j]!=0 ){ |
| 209 | + i += j; |
| 210 | + while( fossil_isspace(zLine[i]) ){ i++; } |
| 211 | + if( zLine[i]==0 ) break; |
| 212 | + for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} |
| 213 | + if( j==8 && strncmp(&zLine[i], "1st-tier", j)==0 ){ |
| 214 | + aEntry[nUsed].eType &= ~(CMDFLAG_2ND_TIER|CMDFLAG_TEST); |
| 215 | + aEntry[nUsed].eType |= CMDFLAG_1ST_TIER; |
| 216 | + }else if( j==8 && strncmp(&zLine[i], "2nd-tier", j)==0 ){ |
| 217 | + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST); |
| 218 | + aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; |
| 219 | + }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){ |
| 220 | + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER); |
| 221 | + aEntry[nUsed].eType |= CMDFLAG_TEST; |
| 222 | + }else{ |
| 223 | + fprintf(stderr, "%s:%d: unknown option: '%.*s'\n", |
| 224 | + zFile, nLine, j, &zLine[i]); |
| 225 | + nErr++; |
| 226 | + } |
| 227 | + } |
| 228 | + |
| 145 | 229 | nUsed++; |
| 146 | 230 | } |
| 147 | 231 | |
| 148 | 232 | /* |
| 149 | 233 | ** Check to see if the current line is an #if and if it is, add it to |
| | @@ -152,11 +236,11 @@ |
| 152 | 236 | */ |
| 153 | 237 | void scan_for_if(const char *zLine){ |
| 154 | 238 | int i; |
| 155 | 239 | int len; |
| 156 | 240 | if( zLine[0]!='#' ) return; |
| 157 | | - for(i=1; isspace(zLine[i]); i++){} |
| 241 | + for(i=1; fossil_isspace(zLine[i]); i++){} |
| 158 | 242 | if( zLine[i]==0 ) return; |
| 159 | 243 | len = strlen(&zLine[i]); |
| 160 | 244 | if( strncmp(&zLine[i],"if",2)==0 ){ |
| 161 | 245 | zIf[0] = '#'; |
| 162 | 246 | memcpy(&zIf[1], &zLine[i], len+1); |
| | @@ -171,11 +255,11 @@ |
| 171 | 255 | void scan_for_func(char *zLine){ |
| 172 | 256 | int i,j,k; |
| 173 | 257 | char *z; |
| 174 | 258 | if( nUsed<=nFixed ) return; |
| 175 | 259 | if( strncmp(zLine, "**", 2)==0 |
| 176 | | - && isspace(zLine[2]) |
| 260 | + && fossil_isspace(zLine[2]) |
| 177 | 261 | && strlen(zLine)<sizeof(zHelp)-nHelp-1 |
| 178 | 262 | && nUsed>nFixed |
| 179 | 263 | && strncmp(zLine,"** COMMAND:",11)!=0 |
| 180 | 264 | && strncmp(zLine,"** WEBPAGE:",11)!=0 |
| 181 | 265 | ){ |
| | @@ -186,25 +270,25 @@ |
| 186 | 270 | strcpy(&zHelp[nHelp], &zLine[3]); |
| 187 | 271 | nHelp += strlen(&zHelp[nHelp]); |
| 188 | 272 | } |
| 189 | 273 | return; |
| 190 | 274 | } |
| 191 | | - for(i=0; isspace(zLine[i]); i++){} |
| 275 | + for(i=0; fossil_isspace(zLine[i]); i++){} |
| 192 | 276 | if( zLine[i]==0 ) return; |
| 193 | 277 | if( strncmp(&zLine[i],"void",4)!=0 ){ |
| 194 | 278 | if( zLine[i]!='*' ) goto page_skip; |
| 195 | 279 | return; |
| 196 | 280 | } |
| 197 | 281 | i += 4; |
| 198 | | - if( !isspace(zLine[i]) ) goto page_skip; |
| 199 | | - while( isspace(zLine[i]) ){ i++; } |
| 200 | | - for(j=0; isalnum(zLine[i+j]) || zLine[i+j]=='_'; j++){} |
| 282 | + if( !fossil_isspace(zLine[i]) ) goto page_skip; |
| 283 | + while( fossil_isspace(zLine[i]) ){ i++; } |
| 284 | + for(j=0; fossil_isident(zLine[i+j]); j++){} |
| 201 | 285 | if( j==0 ) goto page_skip; |
| 202 | | - for(k=nHelp-1; k>=0 && isspace(zHelp[k]); k--){} |
| 286 | + for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){} |
| 203 | 287 | nHelp = k+1; |
| 204 | 288 | zHelp[nHelp] = 0; |
| 205 | | - for(k=0; k<nHelp && isspace(zHelp[k]); k++){} |
| 289 | + for(k=0; k<nHelp && fossil_isspace(zHelp[k]); k++){} |
| 206 | 290 | if( k<nHelp ){ |
| 207 | 291 | z = string_dup(&zHelp[k], nHelp-k); |
| 208 | 292 | }else{ |
| 209 | 293 | z = ""; |
| 210 | 294 | } |
| | @@ -214,11 +298,11 @@ |
| 214 | 298 | aEntry[k].zHelp = z; |
| 215 | 299 | z = 0; |
| 216 | 300 | aEntry[k].iHelp = nFixed; |
| 217 | 301 | } |
| 218 | 302 | i+=j; |
| 219 | | - while( isspace(zLine[i]) ){ i++; } |
| 303 | + while( fossil_isspace(zLine[i]) ){ i++; } |
| 220 | 304 | if( zLine[i]!='(' ) goto page_skip; |
| 221 | 305 | nFixed = nUsed; |
| 222 | 306 | nHelp = 0; |
| 223 | 307 | return; |
| 224 | 308 | |
| | @@ -234,15 +318,11 @@ |
| 234 | 318 | ** Compare two entries |
| 235 | 319 | */ |
| 236 | 320 | int e_compare(const void *a, const void *b){ |
| 237 | 321 | const Entry *pA = (const Entry*)a; |
| 238 | 322 | const Entry *pB = (const Entry*)b; |
| 239 | | - int x = pA->eType - pB->eType; |
| 240 | | - if( x==0 ){ |
| 241 | | - x = strcmp(pA->zPath, pB->zPath); |
| 242 | | - } |
| 243 | | - return x; |
| 323 | + return strcmp(pA->zPath, pB->zPath); |
| 244 | 324 | } |
| 245 | 325 | |
| 246 | 326 | /* |
| 247 | 327 | ** Build the binary search table. |
| 248 | 328 | */ |
| | @@ -292,31 +372,22 @@ |
| 292 | 372 | /* Generate the aCommand[] table */ |
| 293 | 373 | printf("static const CmdOrPage aCommand[] = {\n"); |
| 294 | 374 | for(i=0; i<nFixed; i++){ |
| 295 | 375 | const char *z = aEntry[i].zPath; |
| 296 | 376 | int n = strlen(z); |
| 297 | | - int cmdFlags = (1==aEntry[i].eType) ? 0x01 : 0x08; |
| 298 | | - if( 0x01==cmdFlags ){ |
| 299 | | - if( z[n-1]=='*' ){ |
| 300 | | - n--; |
| 301 | | - cmdFlags = 0x02; |
| 302 | | - }else if( strncmp(z, "test-", 5)==0 ){ |
| 303 | | - cmdFlags = 0x04; |
| 304 | | - } |
| 305 | | - } |
| 306 | 377 | if( aEntry[i].zIf ){ |
| 307 | 378 | printf("%s", aEntry[i].zIf); |
| 308 | | - }else if( aEntry[i].eType==0 ){ |
| 379 | + }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){ |
| 309 | 380 | nWeb++; |
| 310 | 381 | } |
| 311 | | - printf(" { \"%s%.*s\",%*s%s,%*szHelp%03d, %d },\n", |
| 312 | | - (0x08 & cmdFlags) ? "/" : "", n, z, |
| 313 | | - 25-n-((0x8&cmdFlags)!=0), "", |
| 382 | + printf(" { \"%.*s\",%*s%s,%*szHelp%03d, 0x%02x },\n", |
| 383 | + n, z, |
| 384 | + 25-n, "", |
| 314 | 385 | aEntry[i].zFunc, |
| 315 | 386 | (int)(30-strlen(aEntry[i].zFunc)), "", |
| 316 | 387 | aEntry[i].iHelp, |
| 317 | | - cmdFlags |
| 388 | + aEntry[i].eType |
| 318 | 389 | ); |
| 319 | 390 | if( aEntry[i].zIf ) printf("#endif\n"); |
| 320 | 391 | } |
| 321 | 392 | printf("};\n"); |
| 322 | 393 | printf("#define FOSSIL_FIRST_CMD %d\n", nWeb); |
| | @@ -334,12 +405,12 @@ |
| 334 | 405 | } |
| 335 | 406 | nLine = 0; |
| 336 | 407 | while( fgets(zLine, sizeof(zLine), in) ){ |
| 337 | 408 | nLine++; |
| 338 | 409 | scan_for_if(zLine); |
| 339 | | - scan_for_label("WEBPAGE:",zLine,0); |
| 340 | | - scan_for_label("COMMAND:",zLine,1); |
| 410 | + scan_for_label("WEBPAGE:",zLine,CMDFLAG_WEBPAGE); |
| 411 | + scan_for_label("COMMAND:",zLine,CMDFLAG_COMMAND); |
| 341 | 412 | scan_for_func(zLine); |
| 342 | 413 | } |
| 343 | 414 | fclose(in); |
| 344 | 415 | nUsed = nFixed; |
| 345 | 416 | } |
| | @@ -349,7 +420,7 @@ |
| 349 | 420 | for(i=1; i<argc; i++){ |
| 350 | 421 | zFile = argv[i]; |
| 351 | 422 | process_file(); |
| 352 | 423 | } |
| 353 | 424 | build_table(); |
| 354 | | - return 0; |
| 425 | + return nErr; |
| 355 | 426 | } |
| 356 | 427 | |