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