Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / roff / groff / groff.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2000, 2001, 2002, 2003, 2004
3    Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22 // A front end for groff.
23
24 #include "lib.h"
25
26 #include <stdlib.h>
27 #include <signal.h>
28 #include <errno.h>
29
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 "nonposix.h"
39 #include "defs.h"
40
41 #define GXDITVIEW "gxditview"
42
43 // troff will be passed an argument of -rXREG=1 if the -X option is
44 // specified
45 #define XREG ".X"
46
47 #ifdef NEED_DECLARATION_PUTENV
48 extern "C" {
49   int putenv(const char *);
50 }
51 #endif /* NEED_DECLARATION_PUTENV */
52
53 // The number of commands must be in sync with MAX_COMMANDS in pipeline.h
54 const int SOELIM_INDEX = 0;
55 const int REFER_INDEX = SOELIM_INDEX + 1;
56 const int GRAP_INDEX = REFER_INDEX + 1;
57 const int PIC_INDEX = GRAP_INDEX + 1;
58 const int TBL_INDEX = PIC_INDEX + 1;
59 const int GRN_INDEX = TBL_INDEX + 1;
60 const int EQN_INDEX = GRN_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 insert_arg(const char *);
81   void insert_args(string s);
82   void clear_args();
83   char **get_argv();
84   void print(int is_last, FILE *fp);
85 };
86
87 extern "C" const char *Version_string;
88
89 int lflag = 0;
90 char *spooler = 0;
91 char *postdriver = 0;
92 char *predriver = 0;
93
94 possible_command commands[NCOMMANDS];
95
96 int run_commands(int no_pipe);
97 void print_commands(FILE *);
98 void append_arg_to_string(const char *arg, string &str);
99 void handle_unknown_desc_command(const char *command, const char *arg,
100                                  const char *filename, int lineno);
101 const char *xbasename(const char *);
102
103 void usage(FILE *stream);
104 void help();
105
106 int main(int argc, char **argv)
107 {
108   program_name = argv[0];
109   static char stderr_buf[BUFSIZ];
110   setbuf(stderr, stderr_buf);
111   assert(NCOMMANDS <= MAX_COMMANDS);
112   string Pargs, Largs, Fargs;
113   int vflag = 0;
114   int Vflag = 0;
115   int zflag = 0;
116   int iflag = 0;
117   int Xflag = 0;
118   int oflag = 0;
119   int safer_flag = 1;
120   int opt;
121   const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
122   if (!command_prefix)
123     command_prefix = PROG_PREFIX;
124   commands[TROFF_INDEX].set_name(command_prefix, "troff");
125   static const struct option long_options[] = {
126     { "help", no_argument, 0, 'h' },
127     { "version", no_argument, 0, 'v' },
128     { NULL, 0, 0, 0 }
129   };
130   while ((opt = getopt_long(argc, argv,
131                             "abcCd:eEf:F:gGhiI:lL:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ",
132                             long_options, NULL))
133          != EOF) {
134     char buf[3];
135     buf[0] = '-';
136     buf[1] = opt;
137     buf[2] = '\0';
138     switch (opt) {
139     case 'i':
140       iflag = 1;
141       break;
142     case 'I':
143       commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
144       commands[SOELIM_INDEX].append_arg(buf, optarg);
145       // .psbb may need to search for files
146       commands[TROFF_INDEX].append_arg(buf, optarg);
147       // \X'ps:import' may need to search for files
148       Pargs += buf;
149       Pargs += optarg;
150       Pargs += '\0';
151       break;
152     case 't':
153       commands[TBL_INDEX].set_name(command_prefix, "tbl");
154       break;
155     case 'p':
156       commands[PIC_INDEX].set_name(command_prefix, "pic");
157       break;
158     case 'g':
159       commands[GRN_INDEX].set_name(command_prefix, "grn");
160       break;
161     case 'G':
162       commands[GRAP_INDEX].set_name(command_prefix, "grap");
163       break;
164     case 'e':
165       commands[EQN_INDEX].set_name(command_prefix, "eqn");
166       break;
167     case 's':
168       commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
169       break;
170     case 'R':
171       commands[REFER_INDEX].set_name(command_prefix, "refer");
172       break;
173     case 'z':
174     case 'a':
175       commands[TROFF_INDEX].append_arg(buf);
176       // fall through
177     case 'Z':
178       zflag++;
179       break;
180     case 'l':
181       lflag++;
182       break;
183     case 'V':
184       Vflag++;
185       break;
186     case 'v':
187       vflag = 1;
188       {
189         printf("GNU groff version %s\n", Version_string);
190         printf("Copyright (C) 2004 Free Software Foundation, Inc.\n"
191                "GNU groff comes with ABSOLUTELY NO WARRANTY.\n"
192                "You may redistribute copies of groff and its subprograms\n"
193                "under the terms of the GNU General Public License.\n"
194                "For more information about these matters, see the file named COPYING.\n");
195         printf("\ncalled subprograms:\n\n");
196         fflush(stdout);
197       }
198       commands[POST_INDEX].append_arg(buf);
199       // fall through
200     case 'C':
201       commands[SOELIM_INDEX].append_arg(buf);
202       commands[REFER_INDEX].append_arg(buf);
203       commands[PIC_INDEX].append_arg(buf);
204       commands[GRAP_INDEX].append_arg(buf);
205       commands[TBL_INDEX].append_arg(buf);
206       commands[GRN_INDEX].append_arg(buf);
207       commands[EQN_INDEX].append_arg(buf);
208       commands[TROFF_INDEX].append_arg(buf);
209       break;
210     case 'N':
211       commands[EQN_INDEX].append_arg(buf);
212       break;
213     case 'h':
214       help();
215       break;
216     case 'E':
217     case 'b':
218       commands[TROFF_INDEX].append_arg(buf);
219       break;
220     case 'c':
221       commands[TROFF_INDEX].append_arg(buf);
222       break;
223     case 'S':
224       safer_flag = 1;
225       break;
226     case 'U':
227       safer_flag = 0;
228       break;
229     case 'T':
230       if (strcmp(optarg, "html") == 0) {
231         // force soelim to aid the html preprocessor
232         commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
233       }
234       if (strcmp(optarg, "Xps") == 0) {
235         warning("-TXps option is obsolete: use -X -Tps instead");
236         device = "ps";
237         Xflag++;
238       }
239       else
240         device = optarg;
241       break;
242     case 'F':
243       font::command_line_font_dir(optarg);
244       if (Fargs.length() > 0) {
245         Fargs += PATH_SEP_CHAR;
246         Fargs += optarg;
247       }
248       else
249         Fargs = optarg;
250       break;
251     case 'o':
252       oflag = 1;
253     case 'f':
254     case 'm':
255     case 'r':
256     case 'd':
257     case 'n':
258     case 'w':
259     case 'W':
260       commands[TROFF_INDEX].append_arg(buf, optarg);
261       break;
262     case 'M':
263       commands[EQN_INDEX].append_arg(buf, optarg);
264       commands[GRAP_INDEX].append_arg(buf, optarg);
265       commands[GRN_INDEX].append_arg(buf, optarg);
266       commands[TROFF_INDEX].append_arg(buf, optarg);
267       break;
268     case 'P':
269       Pargs += optarg;
270       Pargs += '\0';
271       break;
272     case 'L':
273       append_arg_to_string(optarg, Largs);
274       break;
275     case 'X':
276       Xflag++;
277       break;
278     case '?':
279       usage(stderr);
280       exit(1);
281       break;
282     default:
283       assert(0);
284       break;
285     }
286   }
287   if (safer_flag)
288     commands[PIC_INDEX].append_arg("-S");
289   else
290     commands[TROFF_INDEX].insert_arg("-U");
291   font::set_unknown_desc_command_handler(handle_unknown_desc_command);
292   if (!font::load_desc())
293     fatal("invalid device `%1'", device);
294   if (!postdriver)
295     fatal("no `postpro' command in DESC file for device `%1'", device);
296   if (predriver && !zflag) {
297     commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
298     commands[TROFF_INDEX].set_name(predriver);
299     // pass the device arguments to the predrivers as well
300     commands[TROFF_INDEX].insert_args(Pargs);
301     if (vflag)
302       commands[TROFF_INDEX].insert_arg("-v");
303   }
304   const char *real_driver = 0;
305   if (Xflag) {
306     real_driver = postdriver;
307     postdriver = (char *)GXDITVIEW;
308     commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
309   }
310   if (postdriver)
311     commands[POST_INDEX].set_name(postdriver);
312   int gxditview_flag = postdriver && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
313   if (gxditview_flag && argc - optind == 1) {
314     commands[POST_INDEX].append_arg("-title");
315     commands[POST_INDEX].append_arg(argv[optind]);
316     commands[POST_INDEX].append_arg("-xrm");
317     commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
318     string filename_string("|");
319     append_arg_to_string(argv[0], filename_string);
320     append_arg_to_string("-Z", filename_string);
321     for (int i = 1; i < argc; i++)
322       append_arg_to_string(argv[i], filename_string);
323     filename_string += '\0';
324     commands[POST_INDEX].append_arg("-filename");
325     commands[POST_INDEX].append_arg(filename_string.contents());
326   }
327   if (gxditview_flag && Xflag) {
328     string print_string(real_driver);
329     if (spooler) {
330       print_string += " | ";
331       print_string += spooler;
332       print_string += Largs;
333     }
334     print_string += '\0';
335     commands[POST_INDEX].append_arg("-printCommand");
336     commands[POST_INDEX].append_arg(print_string.contents());
337   }
338   const char *p = Pargs.contents();
339   const char *end = p + Pargs.length();
340   while (p < end) {
341     commands[POST_INDEX].append_arg(p);
342     p = strchr(p, '\0') + 1;
343   }
344   if (gxditview_flag)
345     commands[POST_INDEX].append_arg("-");
346   if (lflag && !vflag && !Xflag && spooler) {
347     commands[SPOOL_INDEX].set_name(BSHELL);
348     commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
349     Largs += '\0';
350     Largs = spooler + Largs;
351     commands[SPOOL_INDEX].append_arg(Largs.contents());
352   }
353   if (zflag) {
354     commands[POST_INDEX].set_name(0);
355     commands[SPOOL_INDEX].set_name(0);
356   }
357   commands[TROFF_INDEX].append_arg("-T", device);
358   // html renders equations as images via ps
359   if (strcmp(device, "html") == 0) {
360     if (oflag)
361       fatal("`-o' option is invalid with device `html'");
362     commands[EQN_INDEX].append_arg("-Tps:html");
363   }
364   else
365     commands[EQN_INDEX].append_arg("-T", device);
366
367   commands[GRN_INDEX].append_arg("-T", device);
368
369   int first_index;
370   for (first_index = 0; first_index < TROFF_INDEX; first_index++)
371     if (commands[first_index].get_name() != 0)
372       break;
373   if (optind < argc) {
374     if (argv[optind][0] == '-' && argv[optind][1] != '\0')
375       commands[first_index].append_arg("--");
376     for (int i = optind; i < argc; i++)
377       commands[first_index].append_arg(argv[i]);
378     if (iflag)
379       commands[first_index].append_arg("-");
380   }
381   if (Fargs.length() > 0) {
382     string e = "GROFF_FONT_PATH";
383     e += '=';
384     e += Fargs;
385     char *fontpath = getenv("GROFF_FONT_PATH");
386     if (fontpath && *fontpath) {
387       e += PATH_SEP_CHAR;
388       e += fontpath;
389     }
390     e += '\0';
391     if (putenv(strsave(e.contents())))
392       fatal("putenv failed");
393   }
394   {
395     // we save the original path in GROFF_PATH__ and put it into the
396     // environment -- troff will pick it up later.
397     char *path = getenv("PATH");
398     string e = "GROFF_PATH__";
399     e += '=';
400     if (path && *path)
401       e += path;
402     e += '\0';
403     if (putenv(strsave(e.contents())))
404       fatal("putenv failed");
405     char *binpath = getenv("GROFF_BIN_PATH");
406     string f = "PATH";
407     f += '=';
408     if (binpath && *binpath)
409       f += binpath;
410     else
411       f += BINPATH;
412     if (path && *path) {
413       f += PATH_SEP_CHAR;
414       f += path;
415     }
416     f += '\0';
417     if (putenv(strsave(f.contents())))
418       fatal("putenv failed");
419   }
420   if (Vflag)
421     print_commands(Vflag == 1 ? stdout : stderr);
422   if (Vflag == 1)
423     exit(0);
424   return run_commands(vflag);
425 }
426
427 const char *xbasename(const char *s)
428 {
429   if (!s)
430     return 0;
431   // DIR_SEPS[] are possible directory separator characters, see nonposix.h
432   // We want the rightmost separator of all possible ones.
433   // Example: d:/foo\\bar.
434   const char *p = strrchr(s, DIR_SEPS[0]), *p1;
435   const char *sep = &DIR_SEPS[1];
436
437   while (*sep)
438     {
439       p1 = strrchr(s, *sep);
440       if (p1 && (!p || p1 > p))
441         p = p1;
442       sep++;
443     }
444   return p ? p + 1 : s;
445 }
446
447 void handle_unknown_desc_command(const char *command, const char *arg,
448                                  const char *filename, int lineno)
449 {
450   if (strcmp(command, "print") == 0) {
451     if (arg == 0)
452       error_with_file_and_line(filename, lineno,
453                                "`print' command requires an argument");
454     else
455       spooler = strsave(arg);
456   }
457   if (strcmp(command, "prepro") == 0) {
458     if (arg == 0)
459       error_with_file_and_line(filename, lineno,
460                                "`prepro' command requires an argument");
461     else {
462       for (const char *p = arg; *p; p++)
463         if (csspace(*p)) {
464           error_with_file_and_line(filename, lineno,
465                                    "invalid `prepro' argument `%1'"
466                                    ": program name required", arg);
467           return;
468         }
469       predriver = strsave(arg);
470     }
471   }
472   if (strcmp(command, "postpro") == 0) {
473     if (arg == 0)
474       error_with_file_and_line(filename, lineno,
475                                "`postpro' command requires an argument");
476     else {
477       for (const char *p = arg; *p; p++)
478         if (csspace(*p)) {
479           error_with_file_and_line(filename, lineno,
480                                    "invalid `postpro' argument `%1'"
481                                    ": program name required", arg);
482           return;
483         }
484       postdriver = strsave(arg);
485     }
486   }
487 }
488
489 void print_commands(FILE *fp)
490 {
491   int last;
492   for (last = SPOOL_INDEX; last >= 0; last--)
493     if (commands[last].get_name() != 0)
494       break;
495   for (int i = 0; i <= last; i++)
496     if (commands[i].get_name() != 0)
497       commands[i].print(i == last, fp);
498 }
499
500 // Run the commands. Return the code with which to exit.
501
502 int run_commands(int no_pipe)
503 {
504   char **v[NCOMMANDS];
505   int j = 0;
506   for (int i = 0; i < NCOMMANDS; i++)
507     if (commands[i].get_name() != 0)
508       v[j++] = commands[i].get_argv();
509   return run_pipeline(j, v, no_pipe);
510 }
511
512 possible_command::possible_command()
513 : name(0), argv(0)
514 {
515 }
516
517 possible_command::~possible_command()
518 {
519   a_delete name;
520   a_delete argv;
521 }
522
523 void possible_command::set_name(const char *s)
524 {
525   a_delete name;
526   name = strsave(s);
527 }
528
529 void possible_command::set_name(const char *s1, const char *s2)
530 {
531   a_delete name;
532   name = new char[strlen(s1) + strlen(s2) + 1];
533   strcpy(name, s1);
534   strcat(name, s2);
535 }
536
537 const char *possible_command::get_name()
538 {
539   return name;
540 }
541
542 void possible_command::clear_args()
543 {
544   args.clear();
545 }
546
547 void possible_command::append_arg(const char *s, const char *t)
548 {
549   args += s;
550   if (t)
551     args += t;
552   args += '\0';
553 }
554
555 void possible_command::insert_arg(const char *s)
556 {
557   string str(s);
558   str += '\0';
559   str += args;
560   args = str;
561 }
562
563 void possible_command::insert_args(string s)
564 {
565   const char *p = s.contents();
566   const char *end = p + s.length();
567   int l = 0;
568   if (p >= end)
569     return;
570   // find the total number of arguments in our string
571   do {
572     l++;
573     p = strchr(p, '\0') + 1;
574   } while (p < end);
575   // now insert each argument preserving the order
576   for (int i = l - 1; i >= 0; i--) {
577     p = s.contents();
578     for (int j = 0; j < i; j++)
579       p = strchr(p, '\0') + 1;
580     insert_arg(p);
581   }
582 }
583
584 void possible_command::build_argv()
585 {
586   if (argv)
587     return;
588   // Count the number of arguments.
589   int len = args.length();
590   int argc = 1;
591   char *p = 0;
592   if (len > 0) {
593     p = &args[0];
594     for (int i = 0; i < len; i++)
595       if (p[i] == '\0')
596         argc++;
597   }
598   // Build an argument vector.
599   argv = new char *[argc + 1];
600   argv[0] = name;
601   for (int i = 1; i < argc; i++) {
602     argv[i] = p;
603     p = strchr(p, '\0') + 1;
604   }
605   argv[argc] = 0;
606 }
607
608 void possible_command::print(int is_last, FILE *fp)
609 {
610   build_argv();
611   if (IS_BSHELL(argv[0])
612       && argv[1] != 0 && strcmp(argv[1], BSHELL_DASH_C) == 0
613       && argv[2] != 0 && argv[3] == 0)
614     fputs(argv[2], fp);
615   else {
616     fputs(argv[0], fp);
617     string str;
618     for (int i = 1; argv[i] != 0; i++) {
619       str.clear();
620       append_arg_to_string(argv[i], str);
621       put_string(str, fp);
622     }
623   }
624   if (is_last)
625     putc('\n', fp);
626   else
627     fputs(" | ", fp);
628 }
629
630 void append_arg_to_string(const char *arg, string &str)
631 {
632   str += ' ';
633   int needs_quoting = 0;
634   int contains_single_quote = 0;
635   const char*p;
636   for (p = arg; *p != '\0'; p++)
637     switch (*p) {
638     case ';':
639     case '&':
640     case '(':
641     case ')':
642     case '|':
643     case '^':
644     case '<':
645     case '>':
646     case '\n':
647     case ' ':
648     case '\t':
649     case '\\':
650     case '"':
651     case '$':
652     case '?':
653     case '*':
654       needs_quoting = 1;
655       break;
656     case '\'':
657       contains_single_quote = 1;
658       break;
659     }
660   if (contains_single_quote || arg[0] == '\0') {
661     str += '"';
662     for (p = arg; *p != '\0'; p++)
663       switch (*p) {
664       case '"':
665       case '\\':
666       case '$':
667         str += '\\';
668         // fall through
669       default:
670         str += *p;
671         break;
672       }
673     str += '"';
674   }
675   else if (needs_quoting) {
676     str += '\'';
677     str += arg;
678     str += '\'';
679   }
680   else
681     str += arg;
682 }
683
684 char **possible_command::get_argv()
685 {
686   build_argv();
687   return argv;
688 }
689
690 void synopsis(FILE *stream)
691 {
692   fprintf(stream,
693 "usage: %s [-abceghilpstvzCENRSUVXZ] [-Fdir] [-mname] [-Tdev] [-ffam]\n"
694 "       [-wname] [-Wname] [-Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg]\n"
695 "       [-Larg] [-Idir] [files...]\n",
696           program_name);
697 }
698
699 void help()
700 {
701   synopsis(stdout);
702   fputs("\n"
703 "-h\tprint this message\n"
704 "-t\tpreprocess with tbl\n"
705 "-p\tpreprocess with pic\n"
706 "-e\tpreprocess with eqn\n"
707 "-g\tpreprocess with grn\n"
708 "-G\tpreprocess with grap\n"
709 "-s\tpreprocess with soelim\n"
710 "-R\tpreprocess with refer\n"
711 "-Tdev\tuse device dev\n"
712 "-X\tuse X11 previewer rather than usual postprocessor\n"
713 "-mname\tread macros tmac.name\n"
714 "-dcs\tdefine a string c as s\n"
715 "-rcn\tdefine a number register c as n\n"
716 "-nnum\tnumber first page n\n"
717 "-olist\toutput only pages in list\n"
718 "-ffam\tuse fam as the default font family\n"
719 "-Fdir\tsearch dir for device directories\n"
720 "-Mdir\tsearch dir for macro files\n"
721 "-v\tprint version number\n"
722 "-z\tsuppress formatted output\n"
723 "-Z\tdon't postprocess\n"
724 "-a\tproduce ASCII description of output\n"
725 "-i\tread standard input after named input files\n"
726 "-wname\tenable warning name\n"
727 "-Wname\tinhibit warning name\n"
728 "-E\tinhibit all errors\n"
729 "-b\tprint backtraces with errors or warnings\n"
730 "-l\tspool the output\n"
731 "-c\tdisable color output\n"
732 "-C\tenable compatibility mode\n"
733 "-V\tprint commands on stdout instead of running them\n"
734 "-Parg\tpass arg to the postprocessor\n"
735 "-Larg\tpass arg to the spooler\n"
736 "-N\tdon't allow newlines within eqn delimiters\n"
737 "-S\tenable safer mode (the default)\n"
738 "-U\tenable unsafe mode\n"
739 "-Idir\tsearch dir for soelim, troff, and grops.  Implies -s\n"
740 "\n",
741         stdout);
742   exit(0);
743 }
744
745 void usage(FILE *stream)
746 {
747   synopsis(stream);
748   fprintf(stream, "%s -h gives more help\n", program_name);
749 }
750
751 extern "C" {
752
753 void c_error(const char *format, const char *arg1, const char *arg2,
754              const char *arg3)
755 {
756   error(format, arg1, arg2, arg3);
757 }
758
759 void c_fatal(const char *format, const char *arg1, const char *arg2,
760              const char *arg3)
761 {
762   fatal(format, arg1, arg2, arg3);
763 }
764
765 }