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
main(int argc,char ** argv)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
basename(const char * s)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
handle_unknown_desc_command(const char * command,const char * arg,const char * filename,int lineno)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
print_commands()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
run_commands()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
possible_command()373 possible_command::possible_command()
374 : name(0), argv(0)
375 {
376 }
377
~possible_command()378 possible_command::~possible_command()
379 {
380 a_delete name;
381 a_delete argv;
382 }
383
set_name(const char * s)384 void possible_command::set_name(const char *s)
385 {
386 a_delete name;
387 name = strsave(s);
388 }
389
set_name(const char * s1,const char * s2)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
get_name()398 const char *possible_command::get_name()
399 {
400 return name;
401 }
402
clear_args()403 void possible_command::clear_args()
404 {
405 args.clear();
406 }
407
append_arg(const char * s,const char * t)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
build_argv()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
print(int is_last,FILE * fp)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
append_arg_to_string(const char * arg,string & str)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
get_argv()515 char **possible_command::get_argv()
516 {
517 build_argv();
518 return argv;
519 }
520
synopsis()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
help()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
usage()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
c_error(const char * format,const char * arg1,const char * arg2,const char * arg3)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
c_fatal(const char * format,const char * arg1,const char * arg2,const char * arg3)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