[25] | 1 | /* |
---|
| 2 | * man2tcl.c -- |
---|
| 3 | * |
---|
| 4 | * This file contains a program that turns a man page of the form used |
---|
| 5 | * for Tcl and Tk into a Tcl script that invokes a Tcl command for each |
---|
| 6 | * construct in the man page. The script can then be eval'ed to translate |
---|
| 7 | * the manual entry into some other format such as MIF or HTML. |
---|
| 8 | * |
---|
| 9 | * Usage: |
---|
| 10 | * |
---|
| 11 | * man2tcl ?fileName? |
---|
| 12 | * |
---|
| 13 | * Copyright (c) 1995 Sun Microsystems, Inc. |
---|
| 14 | * |
---|
| 15 | * See the file "license.terms" for information on usage and redistribution of |
---|
| 16 | * this file, and for a DISCLAIMER OF ALL WARRANTIES. |
---|
| 17 | * |
---|
| 18 | * RCS: @(#) $Id: man2tcl.c,v 1.13 2007/12/13 15:28:40 dgp Exp $ |
---|
| 19 | */ |
---|
| 20 | |
---|
| 21 | static char sccsid[] = "@(#) man2tcl.c 1.3 95/08/12 17:34:08"; |
---|
| 22 | |
---|
| 23 | #include <stdio.h> |
---|
| 24 | #include <stdlib.h> |
---|
| 25 | #include <string.h> |
---|
| 26 | #include <ctype.h> |
---|
| 27 | #include <errno.h> |
---|
| 28 | |
---|
| 29 | /* |
---|
| 30 | * Imported things that aren't defined in header files: |
---|
| 31 | */ |
---|
| 32 | |
---|
| 33 | /* |
---|
| 34 | * Some <errno.h> define errno to be something complex and thread-aware; in |
---|
| 35 | * that case we definitely do not want to declare errno ourselves! |
---|
| 36 | */ |
---|
| 37 | |
---|
| 38 | #ifndef errno |
---|
| 39 | extern int errno; |
---|
| 40 | #endif |
---|
| 41 | |
---|
| 42 | /* |
---|
| 43 | * Current line number, used for error messages. |
---|
| 44 | */ |
---|
| 45 | |
---|
| 46 | static int lineNumber; |
---|
| 47 | |
---|
| 48 | /* |
---|
| 49 | * The variable below is set to 1 if an error occurs anywhere while reading in |
---|
| 50 | * the file. |
---|
| 51 | */ |
---|
| 52 | |
---|
| 53 | static int status; |
---|
| 54 | |
---|
| 55 | /* |
---|
| 56 | * The variable below is set to 1 if output should be generated. If it's 0, it |
---|
| 57 | * means we're doing a pre-pass to make sure that the file can be properly |
---|
| 58 | * parsed. |
---|
| 59 | */ |
---|
| 60 | |
---|
| 61 | static int writeOutput; |
---|
| 62 | |
---|
| 63 | #define PRINT(args) if (writeOutput) { printf args; } |
---|
| 64 | #define PRINTC(chr) if (writeOutput) { putc((chr), stdout); } |
---|
| 65 | |
---|
| 66 | /* |
---|
| 67 | * Prototypes for functions defined in this file: |
---|
| 68 | */ |
---|
| 69 | |
---|
| 70 | static void DoMacro(char *line); |
---|
| 71 | static void DoText(char *line); |
---|
| 72 | static void QuoteText(char *string, int count); |
---|
| 73 | |
---|
| 74 | /* |
---|
| 75 | *---------------------------------------------------------------------- |
---|
| 76 | * |
---|
| 77 | * main -- |
---|
| 78 | * |
---|
| 79 | * This function is the main program, which does all of the work of the |
---|
| 80 | * program. |
---|
| 81 | * |
---|
| 82 | * Results: |
---|
| 83 | * None: exits with a 0 return status to indicate success, or 1 to |
---|
| 84 | * indicate that there were problems in the translation. |
---|
| 85 | * |
---|
| 86 | * Side effects: |
---|
| 87 | * A Tcl script is output to standard output. Error messages may be |
---|
| 88 | * output on standard error. |
---|
| 89 | * |
---|
| 90 | *---------------------------------------------------------------------- |
---|
| 91 | */ |
---|
| 92 | |
---|
| 93 | int |
---|
| 94 | main( |
---|
| 95 | int argc, /* Number of command-line arguments. */ |
---|
| 96 | char **argv) /* Values of command-line arguments. */ |
---|
| 97 | { |
---|
| 98 | FILE *f; |
---|
| 99 | #define MAX_LINE_SIZE 1000 |
---|
| 100 | char line[MAX_LINE_SIZE]; |
---|
| 101 | char *p; |
---|
| 102 | |
---|
| 103 | /* |
---|
| 104 | * Find the file to read, and open it if it isn't stdin. |
---|
| 105 | */ |
---|
| 106 | |
---|
| 107 | if (argc == 1) { |
---|
| 108 | f = stdin; |
---|
| 109 | } else if (argc == 2) { |
---|
| 110 | f = fopen(argv[1], "r"); |
---|
| 111 | if (f == NULL) { |
---|
| 112 | fprintf(stderr, "Couldn't read \"%s\": %s\n", argv[1], |
---|
| 113 | strerror(errno)); |
---|
| 114 | exit(1); |
---|
| 115 | } |
---|
| 116 | } else { |
---|
| 117 | fprintf(stderr, "Usage: %s ?fileName?\n", argv[0]); |
---|
| 118 | } |
---|
| 119 | |
---|
| 120 | /* |
---|
| 121 | * Make two passes over the file. In the first pass, just check to make |
---|
| 122 | * sure we can handle everything. If there are problems, generate output |
---|
| 123 | * and stop. If everything is OK, make a second pass to actually generate |
---|
| 124 | * output. |
---|
| 125 | */ |
---|
| 126 | |
---|
| 127 | for (writeOutput = 0; writeOutput < 2; writeOutput++) { |
---|
| 128 | lineNumber = 0; |
---|
| 129 | status = 0; |
---|
| 130 | while (fgets(line, MAX_LINE_SIZE, f) != NULL) { |
---|
| 131 | for (p = line; *p != 0; p++) { |
---|
| 132 | if (*p == '\n') { |
---|
| 133 | *p = 0; |
---|
| 134 | break; |
---|
| 135 | } |
---|
| 136 | } |
---|
| 137 | lineNumber++; |
---|
| 138 | |
---|
| 139 | if (((line[0] == '.') || (line[0] == '\'')) && (line[1] == '\\') && (line[2] == '\"')) { |
---|
| 140 | /* |
---|
| 141 | * This line is a comment. Ignore it. |
---|
| 142 | */ |
---|
| 143 | |
---|
| 144 | continue; |
---|
| 145 | } |
---|
| 146 | |
---|
| 147 | if (strlen(line) >= MAX_LINE_SIZE -1) { |
---|
| 148 | fprintf(stderr, "Too long line. Max is %d chars.\n", |
---|
| 149 | MAX_LINE_SIZE - 1); |
---|
| 150 | exit(1); |
---|
| 151 | } |
---|
| 152 | |
---|
| 153 | if ((line[0] == '.') || (line[0] == '\'')) { |
---|
| 154 | /* |
---|
| 155 | * This line is a macro invocation. |
---|
| 156 | */ |
---|
| 157 | |
---|
| 158 | DoMacro(line); |
---|
| 159 | } else { |
---|
| 160 | /* |
---|
| 161 | * This line is text, possibly with formatting characters |
---|
| 162 | * embedded in it. |
---|
| 163 | */ |
---|
| 164 | |
---|
| 165 | DoText(line); |
---|
| 166 | } |
---|
| 167 | } |
---|
| 168 | if (status != 0) { |
---|
| 169 | break; |
---|
| 170 | } |
---|
| 171 | fseek(f, 0, SEEK_SET); |
---|
| 172 | } |
---|
| 173 | exit(status); |
---|
| 174 | } |
---|
| 175 | |
---|
| 176 | /* |
---|
| 177 | *---------------------------------------------------------------------- |
---|
| 178 | * |
---|
| 179 | * DoMacro -- |
---|
| 180 | * |
---|
| 181 | * This function is called to handle a macro invocation. It parses the |
---|
| 182 | * arguments to the macro and generates a Tcl command to handle the |
---|
| 183 | * invocation. |
---|
| 184 | * |
---|
| 185 | * Results: |
---|
| 186 | * None. |
---|
| 187 | * |
---|
| 188 | * Side effects: |
---|
| 189 | * A Tcl command is written to stdout. |
---|
| 190 | * |
---|
| 191 | *---------------------------------------------------------------------- |
---|
| 192 | */ |
---|
| 193 | |
---|
| 194 | static void |
---|
| 195 | DoMacro( |
---|
| 196 | char *line) /* The line of text that contains the macro |
---|
| 197 | * invocation. */ |
---|
| 198 | { |
---|
| 199 | char *p, *end; |
---|
| 200 | |
---|
| 201 | /* |
---|
| 202 | * If there is no macro name, then just skip the whole line. |
---|
| 203 | */ |
---|
| 204 | |
---|
| 205 | if ((line[1] == 0) || (isspace(line[1]))) { |
---|
| 206 | return; |
---|
| 207 | } |
---|
| 208 | |
---|
| 209 | PRINT(("macro")); |
---|
| 210 | if (*line != '.') { |
---|
| 211 | PRINT(("2")); |
---|
| 212 | } |
---|
| 213 | |
---|
| 214 | /* |
---|
| 215 | * Parse the arguments to the macro (including the name), in order. |
---|
| 216 | */ |
---|
| 217 | |
---|
| 218 | p = line+1; |
---|
| 219 | while (1) { |
---|
| 220 | PRINTC(' '); |
---|
| 221 | if (*p == '"') { |
---|
| 222 | /* |
---|
| 223 | * The argument is delimited by quotes. |
---|
| 224 | */ |
---|
| 225 | |
---|
| 226 | for (end = p+1; *end != '"'; end++) { |
---|
| 227 | if (*end == 0) { |
---|
| 228 | fprintf(stderr, |
---|
| 229 | "Unclosed quote in macro call on line %d.\n", |
---|
| 230 | lineNumber); |
---|
| 231 | status = 1; |
---|
| 232 | break; |
---|
| 233 | } |
---|
| 234 | } |
---|
| 235 | QuoteText(p+1, (end-(p+1))); |
---|
| 236 | } else { |
---|
| 237 | for (end = p+1; (*end != 0) && !isspace(*end); end++) { |
---|
| 238 | /* Empty loop body. */ |
---|
| 239 | } |
---|
| 240 | QuoteText(p, end-p); |
---|
| 241 | } |
---|
| 242 | if (*end == 0) { |
---|
| 243 | break; |
---|
| 244 | } |
---|
| 245 | p = end+1; |
---|
| 246 | while (isspace(*p)) { |
---|
| 247 | /* |
---|
| 248 | * Skip empty space before next argument. |
---|
| 249 | */ |
---|
| 250 | |
---|
| 251 | p++; |
---|
| 252 | } |
---|
| 253 | if (*p == 0) { |
---|
| 254 | break; |
---|
| 255 | } |
---|
| 256 | } |
---|
| 257 | PRINTC('\n'); |
---|
| 258 | } |
---|
| 259 | |
---|
| 260 | /* |
---|
| 261 | *---------------------------------------------------------------------- |
---|
| 262 | * |
---|
| 263 | * DoText -- |
---|
| 264 | * |
---|
| 265 | * This function is called to handle a line of troff text. It parses the |
---|
| 266 | * text, generating Tcl commands for text and for formatting stuff such |
---|
| 267 | * as font changes. |
---|
| 268 | * |
---|
| 269 | * Results: |
---|
| 270 | * None. |
---|
| 271 | * |
---|
| 272 | * Side effects: |
---|
| 273 | * Tcl commands are written to stdout. |
---|
| 274 | * |
---|
| 275 | *---------------------------------------------------------------------- |
---|
| 276 | */ |
---|
| 277 | |
---|
| 278 | static void |
---|
| 279 | DoText( |
---|
| 280 | char *line) /* The line of text. */ |
---|
| 281 | { |
---|
| 282 | char *p, *end; |
---|
| 283 | |
---|
| 284 | /* |
---|
| 285 | * Divide the line up into pieces consisting of backslash sequences, tabs, |
---|
| 286 | * and other text. |
---|
| 287 | */ |
---|
| 288 | |
---|
| 289 | p = line; |
---|
| 290 | while (*p != 0) { |
---|
| 291 | if (*p == '\t') { |
---|
| 292 | PRINT(("tab\n")); |
---|
| 293 | p++; |
---|
| 294 | } else if (*p != '\\') { |
---|
| 295 | /* |
---|
| 296 | * Ordinary text. |
---|
| 297 | */ |
---|
| 298 | |
---|
| 299 | for (end = p+1; (*end != '\\') && (*end != 0); end++) { |
---|
| 300 | /* Empty loop body. */ |
---|
| 301 | } |
---|
| 302 | PRINT(("text ")); |
---|
| 303 | QuoteText(p, end-p); |
---|
| 304 | PRINTC('\n'); |
---|
| 305 | p = end; |
---|
| 306 | } else { |
---|
| 307 | /* |
---|
| 308 | * A backslash sequence. There are particular ones that we |
---|
| 309 | * understand; output an error message for anything else and just |
---|
| 310 | * ignore the backslash. |
---|
| 311 | */ |
---|
| 312 | |
---|
| 313 | p++; |
---|
| 314 | if (*p == 'f') { |
---|
| 315 | /* |
---|
| 316 | * Font change. |
---|
| 317 | */ |
---|
| 318 | |
---|
| 319 | PRINT(("font %c\n", p[1])); |
---|
| 320 | p += 2; |
---|
| 321 | } else if (*p == '-') { |
---|
| 322 | PRINT(("dash\n")); |
---|
| 323 | p++; |
---|
| 324 | } else if (*p == 'e') { |
---|
| 325 | PRINT(("text \\\\\n")); |
---|
| 326 | p++; |
---|
| 327 | } else if (*p == '.') { |
---|
| 328 | PRINT(("text .\n")); |
---|
| 329 | p++; |
---|
| 330 | } else if (*p == '&') { |
---|
| 331 | p++; |
---|
| 332 | } else if (*p == '0') { |
---|
| 333 | PRINT(("text { }\n")); |
---|
| 334 | p++; |
---|
| 335 | } else if (*p == '(') { |
---|
| 336 | if ((p[1] == 0) || (p[2] == 0)) { |
---|
| 337 | fprintf(stderr, "Bad \\( sequence on line %d.\n", |
---|
| 338 | lineNumber); |
---|
| 339 | status = 1; |
---|
| 340 | } else { |
---|
| 341 | PRINT(("char {\\(%c%c}\n", p[1], p[2])); |
---|
| 342 | p += 3; |
---|
| 343 | } |
---|
| 344 | } else if (*p == 'N' && *(p+1) == '\'') { |
---|
| 345 | int ch; |
---|
| 346 | |
---|
| 347 | p += 2; |
---|
| 348 | sscanf(p,"%d",&ch); |
---|
| 349 | PRINT(("text \\u%04x", ch)); |
---|
| 350 | while(*p&&*p!='\'') p++; |
---|
| 351 | } else if (*p != 0) { |
---|
| 352 | PRINT(("char {\\%c}\n", *p)); |
---|
| 353 | p++; |
---|
| 354 | } |
---|
| 355 | } |
---|
| 356 | } |
---|
| 357 | PRINT(("newline\n")); |
---|
| 358 | } |
---|
| 359 | |
---|
| 360 | /* |
---|
| 361 | *---------------------------------------------------------------------- |
---|
| 362 | * |
---|
| 363 | * QuoteText -- |
---|
| 364 | * |
---|
| 365 | * Copy the "string" argument to stdout, adding quote characters around |
---|
| 366 | * any special Tcl characters so that they'll just be treated as ordinary |
---|
| 367 | * text. |
---|
| 368 | * |
---|
| 369 | * Results: |
---|
| 370 | * None. |
---|
| 371 | * |
---|
| 372 | * Side effects: |
---|
| 373 | * Text is written to stdout. |
---|
| 374 | * |
---|
| 375 | *---------------------------------------------------------------------- |
---|
| 376 | */ |
---|
| 377 | |
---|
| 378 | static void |
---|
| 379 | QuoteText( |
---|
| 380 | char *string, /* The line of text. */ |
---|
| 381 | int count) /* Number of characters to write from |
---|
| 382 | * string. */ |
---|
| 383 | { |
---|
| 384 | if (count == 0) { |
---|
| 385 | PRINT(("{}")); |
---|
| 386 | return; |
---|
| 387 | } |
---|
| 388 | for ( ; count > 0; string++, count--) { |
---|
| 389 | switch (*string) { |
---|
| 390 | case '\\': |
---|
| 391 | if (*(string+1) == 'N' && *(string+2) == '\'') { |
---|
| 392 | int ch; |
---|
| 393 | |
---|
| 394 | string += 3; |
---|
| 395 | count -= 3; |
---|
| 396 | sscanf(string,"%d",&ch); |
---|
| 397 | PRINT(("\\u%04x", ch)); |
---|
| 398 | while(count>0&&*string!='\'') {string++;count--;} |
---|
| 399 | continue; |
---|
| 400 | } else if (*(string+1) == '0') { |
---|
| 401 | PRINT(("\\ ")); |
---|
| 402 | string++; |
---|
| 403 | count--; |
---|
| 404 | continue; |
---|
| 405 | } |
---|
| 406 | case '$': case '[': case '{': case ' ': case ';': |
---|
| 407 | case '"': case '\t': |
---|
| 408 | PRINTC('\\'); |
---|
| 409 | default: |
---|
| 410 | PRINTC(*string); |
---|
| 411 | } |
---|
| 412 | } |
---|
| 413 | } |
---|
| 414 | |
---|
| 415 | /* |
---|
| 416 | * Local Variables: |
---|
| 417 | * mode: c |
---|
| 418 | * c-basic-offset: 4 |
---|
| 419 | * fill-column: 78 |
---|
| 420 | * End: |
---|
| 421 | */ |
---|