1 // -*- C++ -*- 2 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. 3 Written by James Clark (jjc@jclark.com) 4 5 This file is part of groff. 6 7 groff is free software; you can redistribute it and/or modify it under 8 the terms of the GNU General Public License as published by the Free 9 Software Foundation; either version 2, or (at your option) any later 10 version. 11 12 groff is distributed in the hope that it will be useful, but WITHOUT ANY 13 WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 for more details. 16 17 You should have received a copy of the GNU General Public License along 18 with groff; see the file COPYING. If not, write to the Free Software 19 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ 20 21 // A front end for groff. 22 23 #include <stdio.h> 24 #include <string.h> 25 #include <unistd.h> 26 #include <stdlib.h> 27 #include <signal.h> 28 #include <errno.h> 29 30 #include "lib.h" 31 #include "assert.h" 32 #include "errarg.h" 33 #include "error.h" 34 #include "stringclass.h" 35 #include "cset.h" 36 #include "font.h" 37 #include "device.h" 38 #include "pipeline.h" 39 #include "defs.h" 40 41 #define BSHELL "/bin/sh" 42 #define GXDITVIEW "gxditview" 43 44 // troff will be passed an argument of -rXREG=1 if the -X option is 45 // specified 46 #define XREG ".X" 47 48 #ifndef STDLIB_H_DECLARES_PUTENV 49 extern "C" { 50 int putenv(const char *); 51 } 52 #endif /* not STDLIB_H_DECLARES_PUTENV */ 53 54 const char *strsignal(int); 55 56 const int SOELIM_INDEX = 0; 57 const int REFER_INDEX = SOELIM_INDEX + 1; 58 const int PIC_INDEX = REFER_INDEX + 1; 59 const int TBL_INDEX = PIC_INDEX + 1; 60 const int EQN_INDEX = TBL_INDEX + 1; 61 const int TROFF_INDEX = EQN_INDEX + 1; 62 const int POST_INDEX = TROFF_INDEX + 1; 63 const int SPOOL_INDEX = POST_INDEX + 1; 64 65 const int NCOMMANDS = SPOOL_INDEX + 1; 66 67 class possible_command { 68 char *name; 69 string args; 70 char **argv; 71 72 void build_argv(); 73 public: 74 possible_command(); 75 ~possible_command(); 76 void set_name(const char *); 77 void set_name(const char *, const char *); 78 const char *get_name(); 79 void append_arg(const char *, const char * = 0); 80 void clear_args(); 81 char **get_argv(); 82 void print(int is_last, FILE *fp); 83 }; 84 85 int lflag = 0; 86 char *spooler = 0; 87 char *driver = 0; 88 89 possible_command commands[NCOMMANDS]; 90 91 int run_commands(); 92 void print_commands(); 93 void append_arg_to_string(const char *arg, string &str); 94 void handle_unknown_desc_command(const char *command, const char *arg, 95 const char *filename, int lineno); 96 const char *basename(const char *); 97 98 void usage(); 99 void help(); 100 101 int main(int argc, char **argv) 102 { 103 program_name = argv[0]; 104 static char stderr_buf[BUFSIZ]; 105 setbuf(stderr, stderr_buf); 106 assert(NCOMMANDS <= MAX_COMMANDS); 107 string Pargs, Largs, Fargs; 108 int Vflag = 0; 109 int zflag = 0; 110 int iflag = 0; 111 int Xflag = 0; 112 int opt; 113 const char *command_prefix = getenv("GROFF_COMMAND_PREFIX"); 114 if (!command_prefix) 115 command_prefix = PROG_PREFIX; 116 commands[TROFF_INDEX].set_name(command_prefix, "troff"); 117 while ((opt = getopt(argc, argv, 118 "itpeRszavVhblCENXZF:m:T:f:w:W:M:d:r:n:o:P:L:")) 119 != EOF) { 120 char buf[3]; 121 buf[0] = '-'; 122 buf[1] = opt; 123 buf[2] = '\0'; 124 switch (opt) { 125 case 'i': 126 iflag = 1; 127 break; 128 case 't': 129 commands[TBL_INDEX].set_name(command_prefix, "tbl"); 130 break; 131 case 'p': 132 commands[PIC_INDEX].set_name(command_prefix, "pic"); 133 break; 134 case 'e': 135 commands[EQN_INDEX].set_name(command_prefix, "eqn"); 136 break; 137 case 's': 138 commands[SOELIM_INDEX].set_name(command_prefix, "soelim"); 139 break; 140 case 'R': 141 commands[REFER_INDEX].set_name(command_prefix, "refer"); 142 break; 143 case 'z': 144 case 'a': 145 commands[TROFF_INDEX].append_arg(buf); 146 // fall through 147 case 'Z': 148 zflag++; 149 break; 150 case 'l': 151 lflag++; 152 break; 153 case 'V': 154 Vflag++; 155 break; 156 case 'v': 157 case 'C': 158 commands[SOELIM_INDEX].append_arg(buf); 159 commands[PIC_INDEX].append_arg(buf); 160 commands[TBL_INDEX].append_arg(buf); 161 commands[EQN_INDEX].append_arg(buf); 162 commands[TROFF_INDEX].append_arg(buf); 163 break; 164 case 'N': 165 commands[EQN_INDEX].append_arg(buf); 166 break; 167 case 'h': 168 help(); 169 break; 170 case 'E': 171 case 'b': 172 commands[TROFF_INDEX].append_arg(buf); 173 break; 174 case 'T': 175 if (strcmp(optarg, "Xps") == 0) { 176 warning("-TXps option is obsolete: use -X -Tps instead"); 177 device = "ps"; 178 Xflag++; 179 } 180 else 181 device = optarg; 182 break; 183 case 'F': 184 font::command_line_font_dir(optarg); 185 if (Fargs.length() > 0) { 186 Fargs += ':'; 187 Fargs += optarg; 188 } 189 else 190 Fargs = optarg; 191 break; 192 case 'f': 193 case 'o': 194 case 'm': 195 case 'r': 196 case 'd': 197 case 'n': 198 case 'w': 199 case 'W': 200 commands[TROFF_INDEX].append_arg(buf, optarg); 201 break; 202 case 'M': 203 commands[EQN_INDEX].append_arg(buf, optarg); 204 commands[TROFF_INDEX].append_arg(buf, optarg); 205 break; 206 case 'P': 207 Pargs += optarg; 208 Pargs += '\0'; 209 break; 210 case 'L': 211 append_arg_to_string(optarg, Largs); 212 break; 213 case 'X': 214 Xflag++; 215 break; 216 case '?': 217 usage(); 218 break; 219 default: 220 assert(0); 221 break; 222 } 223 } 224 font::set_unknown_desc_command_handler(handle_unknown_desc_command); 225 if (!font::load_desc()) 226 fatal("invalid device `%1'", device); 227 if (!driver) 228 fatal("no `postpro' command in DESC file for device `%1'", device); 229 const char *real_driver = 0; 230 if (Xflag) { 231 real_driver = driver; 232 driver = GXDITVIEW; 233 commands[TROFF_INDEX].append_arg("-r" XREG "=", "1"); 234 } 235 if (driver) 236 commands[POST_INDEX].set_name(driver); 237 int gxditview_flag = driver && strcmp(basename(driver), GXDITVIEW) == 0; 238 if (gxditview_flag && argc - optind == 1) { 239 commands[POST_INDEX].append_arg("-title"); 240 commands[POST_INDEX].append_arg(argv[optind]); 241 commands[POST_INDEX].append_arg("-xrm"); 242 commands[POST_INDEX].append_arg("*iconName:", argv[optind]); 243 string filename_string("|"); 244 append_arg_to_string(argv[0], filename_string); 245 append_arg_to_string("-Z", filename_string); 246 for (int i = 1; i < argc; i++) 247 append_arg_to_string(argv[i], filename_string); 248 filename_string += '\0'; 249 commands[POST_INDEX].append_arg("-filename"); 250 commands[POST_INDEX].append_arg(filename_string.contents()); 251 } 252 if (gxditview_flag && Xflag) { 253 string print_string(real_driver); 254 if (spooler) { 255 print_string += " | "; 256 print_string += spooler; 257 print_string += Largs; 258 } 259 print_string += '\0'; 260 commands[POST_INDEX].append_arg("-printCommand"); 261 commands[POST_INDEX].append_arg(print_string.contents()); 262 } 263 const char *p = Pargs.contents(); 264 const char *end = p + Pargs.length(); 265 while (p < end) { 266 commands[POST_INDEX].append_arg(p); 267 p = strchr(p, '\0') + 1; 268 } 269 if (gxditview_flag) 270 commands[POST_INDEX].append_arg("-"); 271 if (lflag && !Xflag && spooler) { 272 commands[SPOOL_INDEX].set_name(BSHELL); 273 commands[SPOOL_INDEX].append_arg("-c"); 274 Largs += '\0'; 275 Largs = spooler + Largs; 276 commands[SPOOL_INDEX].append_arg(Largs.contents()); 277 } 278 if (zflag) { 279 commands[POST_INDEX].set_name(0); 280 commands[SPOOL_INDEX].set_name(0); 281 } 282 commands[TROFF_INDEX].append_arg("-T", device); 283 commands[EQN_INDEX].append_arg("-T", device); 284 285 for (int first_index = 0; first_index < TROFF_INDEX; first_index++) 286 if (commands[first_index].get_name() != 0) 287 break; 288 if (optind < argc) { 289 if (argv[optind][0] == '-' && argv[optind][1] != '\0') 290 commands[first_index].append_arg("--"); 291 for (int i = optind; i < argc; i++) 292 commands[first_index].append_arg(argv[i]); 293 if (iflag) 294 commands[first_index].append_arg("-"); 295 } 296 if (Fargs.length() > 0) { 297 string e = "GROFF_FONT_PATH"; 298 e += '='; 299 e += Fargs; 300 char *fontpath = getenv("GROFF_FONT_PATH"); 301 if (fontpath && *fontpath) { 302 e += ':'; 303 e += fontpath; 304 } 305 e += '\0'; 306 if (putenv(strsave(e.contents()))) 307 fatal("putenv failed"); 308 } 309 if (Vflag) { 310 print_commands(); 311 exit(0); 312 } 313 exit(run_commands()); 314 } 315 316 const char *basename(const char *s) 317 { 318 if (!s) 319 return 0; 320 const char *p = strrchr(s, '/'); 321 return p ? p + 1 : s; 322 } 323 324 void handle_unknown_desc_command(const char *command, const char *arg, 325 const char *filename, int lineno) 326 { 327 if (strcmp(command, "print") == 0) { 328 if (arg == 0) 329 error_with_file_and_line(filename, lineno, 330 "`print' command requires an argument"); 331 else 332 spooler = strsave(arg); 333 } 334 if (strcmp(command, "postpro") == 0) { 335 if (arg == 0) 336 error_with_file_and_line(filename, lineno, 337 "`postpro' command requires an argument"); 338 else { 339 for (const char *p = arg; *p; p++) 340 if (csspace(*p)) { 341 error_with_file_and_line(filename, lineno, 342 "invalid `postpro' argument `%1'" 343 ": program name required", arg); 344 return; 345 } 346 driver = strsave(arg); 347 } 348 } 349 } 350 351 void print_commands() 352 { 353 for (int last = SPOOL_INDEX; last >= 0; last--) 354 if (commands[last].get_name() != 0) 355 break; 356 for (int i = 0; i <= last; i++) 357 if (commands[i].get_name() != 0) 358 commands[i].print(i == last, stdout); 359 } 360 361 // Run the commands. Return the code with which to exit. 362 363 int run_commands() 364 { 365 char **v[NCOMMANDS]; 366 int j = 0; 367 for (int i = 0; i < NCOMMANDS; i++) 368 if (commands[i].get_name() != 0) 369 v[j++] = commands[i].get_argv(); 370 return run_pipeline(j, v); 371 } 372 373 possible_command::possible_command() 374 : name(0), argv(0) 375 { 376 } 377 378 possible_command::~possible_command() 379 { 380 a_delete name; 381 a_delete argv; 382 } 383 384 void possible_command::set_name(const char *s) 385 { 386 a_delete name; 387 name = strsave(s); 388 } 389 390 void possible_command::set_name(const char *s1, const char *s2) 391 { 392 a_delete name; 393 name = new char[strlen(s1) + strlen(s2) + 1]; 394 strcpy(name, s1); 395 strcat(name, s2); 396 } 397 398 const char *possible_command::get_name() 399 { 400 return name; 401 } 402 403 void possible_command::clear_args() 404 { 405 args.clear(); 406 } 407 408 void possible_command::append_arg(const char *s, const char *t) 409 { 410 args += s; 411 if (t) 412 args += t; 413 args += '\0'; 414 } 415 416 void possible_command::build_argv() 417 { 418 if (argv) 419 return; 420 // Count the number of arguments. 421 int len = args.length(); 422 int argc = 1; 423 char *p = 0; 424 if (len > 0) { 425 p = &args[0]; 426 for (int i = 0; i < len; i++) 427 if (p[i] == '\0') 428 argc++; 429 } 430 // Build an argument vector. 431 argv = new char *[argc + 1]; 432 argv[0] = name; 433 for (int i = 1; i < argc; i++) { 434 argv[i] = p; 435 p = strchr(p, '\0') + 1; 436 } 437 argv[argc] = 0; 438 } 439 440 void possible_command::print(int is_last, FILE *fp) 441 { 442 build_argv(); 443 if (argv[0] != 0 && strcmp(argv[0], BSHELL) == 0 444 && argv[1] != 0 && strcmp(argv[1], "-c") == 0 445 && argv[2] != 0 && argv[3] == 0) 446 fputs(argv[2], fp); 447 else { 448 fputs(argv[0], fp); 449 string str; 450 for (int i = 1; argv[i] != 0; i++) { 451 str.clear(); 452 append_arg_to_string(argv[i], str); 453 put_string(str, fp); 454 } 455 } 456 if (is_last) 457 putc('\n', fp); 458 else 459 fputs(" | ", fp); 460 } 461 462 void append_arg_to_string(const char *arg, string &str) 463 { 464 str += ' '; 465 int needs_quoting = 0; 466 int contains_single_quote = 0; 467 for (const char *p = arg; *p != '\0'; p++) 468 switch (*p) { 469 case ';': 470 case '&': 471 case '(': 472 case ')': 473 case '|': 474 case '^': 475 case '<': 476 case '>': 477 case '\n': 478 case ' ': 479 case '\t': 480 case '\\': 481 case '"': 482 case '$': 483 case '?': 484 case '*': 485 needs_quoting = 1; 486 break; 487 case '\'': 488 contains_single_quote = 1; 489 break; 490 } 491 if (contains_single_quote || arg[0] == '\0') { 492 str += '"'; 493 for (p = arg; *p != '\0'; p++) 494 switch (*p) { 495 case '"': 496 case '\\': 497 case '$': 498 str += '\\'; 499 // fall through 500 default: 501 str += *p; 502 break; 503 } 504 str += '"'; 505 } 506 else if (needs_quoting) { 507 str += '\''; 508 str += arg; 509 str += '\''; 510 } 511 else 512 str += arg; 513 } 514 515 char **possible_command::get_argv() 516 { 517 build_argv(); 518 return argv; 519 } 520 521 void synopsis() 522 { 523 fprintf(stderr, 524 "usage: %s [-abehilpstvzCENRVXZ] [-Fdir] [-mname] [-Tdev] [-ffam] [-wname]\n" 525 " [-Wname] [ -Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg] [-Larg]\n" 526 " [files...]\n", 527 program_name); 528 } 529 530 void help() 531 { 532 synopsis(); 533 fputs("\n" 534 "-h\tprint this message\n" 535 "-t\tpreprocess with tbl\n" 536 "-p\tpreprocess with pic\n" 537 "-e\tpreprocess with eqn\n" 538 "-s\tpreprocess with soelim\n" 539 "-R\tpreprocess with refer\n" 540 "-Tdev\tuse device dev\n" 541 "-X\tuse X11 previewer rather than usual postprocessor\n" 542 "-mname\tread macros tmac.name\n" 543 "-dcs\tdefine a string c as s\n" 544 "-rcn\tdefine a number register c as n\n" 545 "-nnum\tnumber first page n\n" 546 "-olist\toutput only pages in list\n" 547 "-ffam\tuse fam as the default font family\n" 548 "-Fdir\tsearch directory dir for device directories\n" 549 "-Mdir\tsearch dir for macro files\n" 550 "-v\tprint version number\n" 551 "-z\tsuppress formatted output\n" 552 "-Z\tdon't postprocess\n" 553 "-a\tproduce ASCII description of output\n" 554 "-i\tread standard input after named input files\n" 555 "-wname\tenable warning name\n" 556 "-Wname\tinhibit warning name\n" 557 "-E\tinhibit all errors\n" 558 "-b\tprint backtraces with errors or warnings\n" 559 "-l\tspool the output\n" 560 "-C\tenable compatibility mode\n" 561 "-V\tprint commands on stdout instead of running them\n" 562 "-Parg\tpass arg to the postprocessor\n" 563 "-Larg\tpass arg to the spooler\n" 564 "-N\tdon't allow newlines within eqn delimiters\n" 565 "\n", 566 stderr); 567 exit(0); 568 } 569 570 void usage() 571 { 572 synopsis(); 573 fprintf(stderr, "%s -h gives more help\n", program_name); 574 exit(1); 575 } 576 577 extern "C" { 578 579 void c_error(const char *format, const char *arg1, const char *arg2, 580 const char *arg3) 581 { 582 error(format, arg1, arg2, arg3); 583 } 584 585 void c_fatal(const char *format, const char *arg1, const char *arg2, 586 const char *arg3) 587 { 588 fatal(format, arg1, arg2, arg3); 589 } 590 591 } 592