groff before CVS: release 1.15
[s-roff.git] / groff / groff.cc
blobc5f55e02d6fe1cbf63a428e478d7f87ea509fbf1
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
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.
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.
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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21 // A front end for groff.
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <signal.h>
27 #include <errno.h>
29 #include "lib.h"
30 #include "assert.h"
31 #include "errarg.h"
32 #include "error.h"
33 #include "stringclass.h"
34 #include "cset.h"
35 #include "font.h"
36 #include "device.h"
37 #include "pipeline.h"
38 #include "defs.h"
40 #define BSHELL "/bin/sh"
41 #define GXDITVIEW "gxditview"
43 // troff will be passed an argument of -rXREG=1 if the -X option is
44 // specified
45 #define XREG ".X"
47 #ifndef STDLIB_H_DECLARES_PUTENV
48 extern "C" {
49 int putenv(const char *);
51 #endif /* not STDLIB_H_DECLARES_PUTENV */
53 const int SOELIM_INDEX = 0;
54 const int REFER_INDEX = SOELIM_INDEX + 1;
55 const int PIC_INDEX = REFER_INDEX + 1;
56 const int TBL_INDEX = PIC_INDEX + 1;
57 const int EQN_INDEX = TBL_INDEX + 1;
58 const int TROFF_INDEX = EQN_INDEX + 1;
59 const int POST_INDEX = TROFF_INDEX + 1;
60 const int SPOOL_INDEX = POST_INDEX + 1;
62 const int NCOMMANDS = SPOOL_INDEX + 1;
64 class possible_command {
65 char *name;
66 string args;
67 char **argv;
69 void build_argv();
70 public:
71 possible_command();
72 ~possible_command();
73 void set_name(const char *);
74 void set_name(const char *, const char *);
75 const char *get_name();
76 void append_arg(const char *, const char * = 0);
77 void insert_arg(const char *);
78 void clear_args();
79 char **get_argv();
80 void print(int is_last, FILE *fp);
83 int lflag = 0;
84 char *spooler = 0;
85 char *driver = 0;
87 possible_command commands[NCOMMANDS];
89 int run_commands();
90 void print_commands();
91 void append_arg_to_string(const char *arg, string &str);
92 void handle_unknown_desc_command(const char *command, const char *arg,
93 const char *filename, int lineno);
94 const char *xbasename(const char *);
96 void usage();
97 void help();
99 int main(int argc, char **argv)
101 program_name = argv[0];
102 static char stderr_buf[BUFSIZ];
103 setbuf(stderr, stderr_buf);
104 assert(NCOMMANDS <= MAX_COMMANDS);
105 string Pargs, Largs, Fargs;
106 int Vflag = 0;
107 int zflag = 0;
108 int iflag = 0;
109 int Xflag = 0;
110 int safer_flag = 1;
111 int opt;
112 const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
113 if (!command_prefix)
114 command_prefix = PROG_PREFIX;
115 commands[TROFF_INDEX].set_name(command_prefix, "troff");
116 while ((opt = getopt(argc, argv,
117 "abCd:eEf:F:hiI:lL:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ"))
118 != EOF) {
119 char buf[3];
120 buf[0] = '-';
121 buf[1] = opt;
122 buf[2] = '\0';
123 switch (opt) {
124 case 'i':
125 iflag = 1;
126 break;
127 case 'I':
128 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
129 commands[SOELIM_INDEX].append_arg(buf, optarg);
130 break;
131 case 't':
132 commands[TBL_INDEX].set_name(command_prefix, "tbl");
133 break;
134 case 'p':
135 commands[PIC_INDEX].set_name(command_prefix, "pic");
136 break;
137 case 'e':
138 commands[EQN_INDEX].set_name(command_prefix, "eqn");
139 break;
140 case 's':
141 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
142 break;
143 case 'R':
144 commands[REFER_INDEX].set_name(command_prefix, "refer");
145 break;
146 case 'z':
147 case 'a':
148 commands[TROFF_INDEX].append_arg(buf);
149 // fall through
150 case 'Z':
151 zflag++;
152 break;
153 case 'l':
154 lflag++;
155 break;
156 case 'V':
157 Vflag++;
158 break;
159 case 'v':
160 case 'C':
161 commands[SOELIM_INDEX].append_arg(buf);
162 commands[PIC_INDEX].append_arg(buf);
163 commands[TBL_INDEX].append_arg(buf);
164 commands[EQN_INDEX].append_arg(buf);
165 commands[TROFF_INDEX].append_arg(buf);
166 break;
167 case 'N':
168 commands[EQN_INDEX].append_arg(buf);
169 break;
170 case 'h':
171 help();
172 break;
173 case 'E':
174 case 'b':
175 commands[TROFF_INDEX].append_arg(buf);
176 break;
177 case 'S':
178 safer_flag = 1;
179 break;
180 case 'U':
181 safer_flag = 0;
182 break;
183 case 'T':
184 if (strcmp(optarg, "Xps") == 0) {
185 warning("-TXps option is obsolete: use -X -Tps instead");
186 device = "ps";
187 Xflag++;
189 else
190 device = optarg;
191 break;
192 case 'F':
193 font::command_line_font_dir(optarg);
194 if (Fargs.length() > 0) {
195 Fargs += ':';
196 Fargs += optarg;
198 else
199 Fargs = optarg;
200 break;
201 case 'f':
202 case 'o':
203 case 'm':
204 case 'r':
205 case 'd':
206 case 'n':
207 case 'w':
208 case 'W':
209 commands[TROFF_INDEX].append_arg(buf, optarg);
210 break;
211 case 'M':
212 commands[EQN_INDEX].append_arg(buf, optarg);
213 commands[TROFF_INDEX].append_arg(buf, optarg);
214 break;
215 case 'P':
216 Pargs += optarg;
217 Pargs += '\0';
218 break;
219 case 'L':
220 append_arg_to_string(optarg, Largs);
221 break;
222 case 'X':
223 Xflag++;
224 break;
225 case '?':
226 usage();
227 break;
228 default:
229 assert(0);
230 break;
233 if (safer_flag) {
234 commands[PIC_INDEX].append_arg("-S");
235 commands[TROFF_INDEX].insert_arg("-msafer");
236 } else {
237 commands[TROFF_INDEX].insert_arg("-U");
239 font::set_unknown_desc_command_handler(handle_unknown_desc_command);
240 if (!font::load_desc())
241 fatal("invalid device `%1'", device);
242 if (!driver)
243 fatal("no `postpro' command in DESC file for device `%1'", device);
244 const char *real_driver = 0;
245 if (Xflag) {
246 real_driver = driver;
247 driver = GXDITVIEW;
248 commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
250 if (driver)
251 commands[POST_INDEX].set_name(driver);
252 int gxditview_flag = driver && strcmp(xbasename(driver), GXDITVIEW) == 0;
253 if (gxditview_flag && argc - optind == 1) {
254 commands[POST_INDEX].append_arg("-title");
255 commands[POST_INDEX].append_arg(argv[optind]);
256 commands[POST_INDEX].append_arg("-xrm");
257 commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
258 string filename_string("|");
259 append_arg_to_string(argv[0], filename_string);
260 append_arg_to_string("-Z", filename_string);
261 for (int i = 1; i < argc; i++)
262 append_arg_to_string(argv[i], filename_string);
263 filename_string += '\0';
264 commands[POST_INDEX].append_arg("-filename");
265 commands[POST_INDEX].append_arg(filename_string.contents());
267 if (gxditview_flag && Xflag) {
268 string print_string(real_driver);
269 if (spooler) {
270 print_string += " | ";
271 print_string += spooler;
272 print_string += Largs;
274 print_string += '\0';
275 commands[POST_INDEX].append_arg("-printCommand");
276 commands[POST_INDEX].append_arg(print_string.contents());
278 const char *p = Pargs.contents();
279 const char *end = p + Pargs.length();
280 while (p < end) {
281 commands[POST_INDEX].append_arg(p);
282 p = strchr(p, '\0') + 1;
284 if (gxditview_flag)
285 commands[POST_INDEX].append_arg("-");
286 if (lflag && !Xflag && spooler) {
287 commands[SPOOL_INDEX].set_name(BSHELL);
288 commands[SPOOL_INDEX].append_arg("-c");
289 Largs += '\0';
290 Largs = spooler + Largs;
291 commands[SPOOL_INDEX].append_arg(Largs.contents());
293 if (zflag) {
294 commands[POST_INDEX].set_name(0);
295 commands[SPOOL_INDEX].set_name(0);
297 commands[TROFF_INDEX].append_arg("-T", device);
298 commands[EQN_INDEX].append_arg("-T", device);
300 int first_index;
301 for (first_index = 0; first_index < TROFF_INDEX; first_index++)
302 if (commands[first_index].get_name() != 0)
303 break;
304 if (optind < argc) {
305 if (argv[optind][0] == '-' && argv[optind][1] != '\0')
306 commands[first_index].append_arg("--");
307 for (int i = optind; i < argc; i++)
308 commands[first_index].append_arg(argv[i]);
309 if (iflag)
310 commands[first_index].append_arg("-");
312 if (Fargs.length() > 0) {
313 string e = "GROFF_FONT_PATH";
314 e += '=';
315 e += Fargs;
316 char *fontpath = getenv("GROFF_FONT_PATH");
317 if (fontpath && *fontpath) {
318 e += ':';
319 e += fontpath;
321 e += '\0';
322 if (putenv(strsave(e.contents())))
323 fatal("putenv failed");
325 if (Vflag) {
326 print_commands();
327 exit(0);
329 return run_commands();
332 const char *xbasename(const char *s)
334 if (!s)
335 return 0;
336 const char *p = strrchr(s, '/');
337 return p ? p + 1 : s;
340 void handle_unknown_desc_command(const char *command, const char *arg,
341 const char *filename, int lineno)
343 if (strcmp(command, "print") == 0) {
344 if (arg == 0)
345 error_with_file_and_line(filename, lineno,
346 "`print' command requires an argument");
347 else
348 spooler = strsave(arg);
350 if (strcmp(command, "postpro") == 0) {
351 if (arg == 0)
352 error_with_file_and_line(filename, lineno,
353 "`postpro' command requires an argument");
354 else {
355 for (const char *p = arg; *p; p++)
356 if (csspace(*p)) {
357 error_with_file_and_line(filename, lineno,
358 "invalid `postpro' argument `%1'"
359 ": program name required", arg);
360 return;
362 driver = strsave(arg);
367 void print_commands()
369 int last;
370 for (last = SPOOL_INDEX; last >= 0; last--)
371 if (commands[last].get_name() != 0)
372 break;
373 for (int i = 0; i <= last; i++)
374 if (commands[i].get_name() != 0)
375 commands[i].print(i == last, stdout);
378 // Run the commands. Return the code with which to exit.
380 int run_commands()
382 char **v[NCOMMANDS];
383 int j = 0;
384 for (int i = 0; i < NCOMMANDS; i++)
385 if (commands[i].get_name() != 0)
386 v[j++] = commands[i].get_argv();
387 return run_pipeline(j, v);
390 possible_command::possible_command()
391 : name(0), argv(0)
395 possible_command::~possible_command()
397 a_delete name;
398 a_delete argv;
401 void possible_command::set_name(const char *s)
403 a_delete name;
404 name = strsave(s);
407 void possible_command::set_name(const char *s1, const char *s2)
409 a_delete name;
410 name = new char[strlen(s1) + strlen(s2) + 1];
411 strcpy(name, s1);
412 strcat(name, s2);
415 const char *possible_command::get_name()
417 return name;
420 void possible_command::clear_args()
422 args.clear();
425 void possible_command::append_arg(const char *s, const char *t)
427 args += s;
428 if (t)
429 args += t;
430 args += '\0';
433 void possible_command::insert_arg(const char *s)
435 string str(s);
436 str += '\0';
437 str += args;
438 args = str;
441 void possible_command::build_argv()
443 if (argv)
444 return;
445 // Count the number of arguments.
446 int len = args.length();
447 int argc = 1;
448 char *p = 0;
449 if (len > 0) {
450 p = &args[0];
451 for (int i = 0; i < len; i++)
452 if (p[i] == '\0')
453 argc++;
455 // Build an argument vector.
456 argv = new char *[argc + 1];
457 argv[0] = name;
458 for (int i = 1; i < argc; i++) {
459 argv[i] = p;
460 p = strchr(p, '\0') + 1;
462 argv[argc] = 0;
465 void possible_command::print(int is_last, FILE *fp)
467 build_argv();
468 if (argv[0] != 0 && strcmp(argv[0], BSHELL) == 0
469 && argv[1] != 0 && strcmp(argv[1], "-c") == 0
470 && argv[2] != 0 && argv[3] == 0)
471 fputs(argv[2], fp);
472 else {
473 fputs(argv[0], fp);
474 string str;
475 for (int i = 1; argv[i] != 0; i++) {
476 str.clear();
477 append_arg_to_string(argv[i], str);
478 put_string(str, fp);
481 if (is_last)
482 putc('\n', fp);
483 else
484 fputs(" | ", fp);
487 void append_arg_to_string(const char *arg, string &str)
489 str += ' ';
490 int needs_quoting = 0;
491 int contains_single_quote = 0;
492 const char*p;
493 for (p = arg; *p != '\0'; p++)
494 switch (*p) {
495 case ';':
496 case '&':
497 case '(':
498 case ')':
499 case '|':
500 case '^':
501 case '<':
502 case '>':
503 case '\n':
504 case ' ':
505 case '\t':
506 case '\\':
507 case '"':
508 case '$':
509 case '?':
510 case '*':
511 needs_quoting = 1;
512 break;
513 case '\'':
514 contains_single_quote = 1;
515 break;
517 if (contains_single_quote || arg[0] == '\0') {
518 str += '"';
519 for (p = arg; *p != '\0'; p++)
520 switch (*p) {
521 case '"':
522 case '\\':
523 case '$':
524 str += '\\';
525 // fall through
526 default:
527 str += *p;
528 break;
530 str += '"';
532 else if (needs_quoting) {
533 str += '\'';
534 str += arg;
535 str += '\'';
537 else
538 str += arg;
541 char **possible_command::get_argv()
543 build_argv();
544 return argv;
547 void synopsis()
549 fprintf(stderr,
550 "usage: %s [-abehilpstvzCENRSUVXZ] [-Fdir] [-mname] [-Tdev] [-ffam] [-wname]\n"
551 " [-Wname] [-Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg] [-Larg]\n"
552 " [files...]\n",
553 program_name);
556 void help()
558 synopsis();
559 fputs("\n"
560 "-h\tprint this message\n"
561 "-t\tpreprocess with tbl\n"
562 "-p\tpreprocess with pic\n"
563 "-e\tpreprocess with eqn\n"
564 "-s\tpreprocess with soelim\n"
565 "-R\tpreprocess with refer\n"
566 "-Tdev\tuse device dev\n"
567 "-X\tuse X11 previewer rather than usual postprocessor\n"
568 "-mname\tread macros tmac.name\n"
569 "-dcs\tdefine a string c as s\n"
570 "-rcn\tdefine a number register c as n\n"
571 "-nnum\tnumber first page n\n"
572 "-olist\toutput only pages in list\n"
573 "-ffam\tuse fam as the default font family\n"
574 "-Fdir\tsearch directory dir for device directories\n"
575 "-Mdir\tsearch dir for macro files\n"
576 "-v\tprint version number\n"
577 "-z\tsuppress formatted output\n"
578 "-Z\tdon't postprocess\n"
579 "-a\tproduce ASCII description of output\n"
580 "-i\tread standard input after named input files\n"
581 "-wname\tenable warning name\n"
582 "-Wname\tinhibit warning name\n"
583 "-E\tinhibit all errors\n"
584 "-b\tprint backtraces with errors or warnings\n"
585 "-l\tspool the output\n"
586 "-C\tenable compatibility mode\n"
587 "-V\tprint commands on stdout instead of running them\n"
588 "-Parg\tpass arg to the postprocessor\n"
589 "-Larg\tpass arg to the spooler\n"
590 "-N\tdon't allow newlines within eqn delimiters\n"
591 "-S\tenable safer mode (the default)\n"
592 "-U\tenable unsafe mode\n"
593 "\n",
594 stderr);
595 exit(0);
598 void usage()
600 synopsis();
601 fprintf(stderr, "%s -h gives more help\n", program_name);
602 exit(1);
605 extern "C" {
607 void c_error(const char *format, const char *arg1, const char *arg2,
608 const char *arg3)
610 error(format, arg1, arg2, arg3);
613 void c_fatal(const char *format, const char *arg1, const char *arg2,
614 const char *arg3)
616 fatal(format, arg1, arg2, arg3);