2 /* Copyright (C) 1989-2018 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 3 of the License, or
10 (at your option) any later 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
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 // A front end for groff.
31 #include "stringclass.h"
40 #define GXDITVIEW "gxditview"
42 // troff will be passed an argument of -rXREG=1 if the -X option is
46 #ifdef NEED_DECLARATION_PUTENV
48 int putenv(const char *);
50 #endif /* NEED_DECLARATION_PUTENV */
52 // The number of commands must be in sync with MAX_COMMANDS in pipeline.h
54 // grap, chem, and ideal must come before pic;
55 // tbl must come before eqn
56 const int PRECONV_INDEX = 0;
57 const int SOELIM_INDEX = PRECONV_INDEX + 1;
58 const int REFER_INDEX = SOELIM_INDEX + 1;
59 const int GRAP_INDEX = REFER_INDEX + 1;
60 const int CHEM_INDEX = GRAP_INDEX + 1;
61 const int IDEAL_INDEX = CHEM_INDEX + 1;
62 const int PIC_INDEX = IDEAL_INDEX + 1;
63 const int TBL_INDEX = PIC_INDEX + 1;
64 const int GRN_INDEX = TBL_INDEX + 1;
65 const int EQN_INDEX = GRN_INDEX + 1;
66 const int TROFF_INDEX = EQN_INDEX + 1;
67 const int POST_INDEX = TROFF_INDEX + 1;
68 const int SPOOL_INDEX = POST_INDEX + 1;
70 const int NCOMMANDS = SPOOL_INDEX + 1;
72 class possible_command {
82 void set_name(const char *);
83 void set_name(const char *, const char *);
84 const char *get_name();
85 void append_arg(const char *, const char * = 0);
86 void insert_arg(const char *);
87 void insert_args(string s);
90 void print(int is_last, FILE *fp);
93 extern "C" const char *Version_string;
100 possible_command commands[NCOMMANDS];
102 int run_commands(int no_pipe);
103 void print_commands(FILE *);
104 void append_arg_to_string(const char *arg, string &str);
105 void handle_unknown_desc_command(const char *command, const char *arg,
106 const char *filename, int lineno);
107 const char *xbasename(const char *);
109 void usage(FILE *stream);
112 int main(int argc, char **argv)
114 program_name = argv[0];
115 static char stderr_buf[BUFSIZ];
116 setbuf(stderr, stderr_buf);
117 assert(NCOMMANDS <= MAX_COMMANDS);
118 string Pargs, Largs, Fargs;
131 const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
132 const char *encoding = getenv("GROFF_ENCODING");
134 command_prefix = PROG_PREFIX;
135 commands[TROFF_INDEX].set_name(command_prefix, "troff");
136 static const struct option long_options[] = {
137 { "help", no_argument, 0, 'h' },
138 { "version", no_argument, 0, 'v' },
141 while ((opt = getopt_long(
143 "abcCd:D:eEf:F:gGhiI:jJkK:lL:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ",
155 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
156 commands[SOELIM_INDEX].append_arg(buf, optarg);
157 // .psbb may need to search for files
158 commands[TROFF_INDEX].append_arg(buf, optarg);
159 // \X'ps:import' may need to search for files
165 commands[PRECONV_INDEX].set_name("preconv");
166 commands[PRECONV_INDEX].append_arg("-D", optarg);
169 commands[PRECONV_INDEX].append_arg("-e", optarg);
173 commands[PRECONV_INDEX].set_name("preconv");
176 commands[TBL_INDEX].set_name(command_prefix, "tbl");
179 commands[IDEAL_INDEX].set_name(command_prefix, "gideal");
183 commands[CHEM_INDEX].set_name(command_prefix, "chem");
187 commands[PIC_INDEX].set_name(command_prefix, "pic");
190 commands[GRN_INDEX].set_name(command_prefix, "grn");
193 commands[GRAP_INDEX].set_name(command_prefix, "grap");
198 commands[EQN_INDEX].set_name(command_prefix, "eqn");
201 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
204 commands[REFER_INDEX].set_name(command_prefix, "refer");
208 commands[TROFF_INDEX].append_arg(buf);
221 printf("GNU groff version %s\n", Version_string);
223 "Copyright (C) 2018 Free Software Foundation, Inc.\n"
224 "GNU groff comes with ABSOLUTELY NO WARRANTY.\n"
225 "You may redistribute copies of groff and its subprograms\n"
226 "under the terms of the GNU General Public License.\n"
227 "For more information about these matters, see the file\n"
229 printf("\ncalled subprograms:\n\n");
231 commands[POST_INDEX].append_arg(buf);
234 commands[SOELIM_INDEX].append_arg(buf);
235 commands[REFER_INDEX].append_arg(buf);
236 commands[PIC_INDEX].append_arg(buf);
237 commands[GRAP_INDEX].append_arg(buf);
238 commands[TBL_INDEX].append_arg(buf);
239 commands[GRN_INDEX].append_arg(buf);
240 commands[EQN_INDEX].append_arg(buf);
241 commands[TROFF_INDEX].append_arg(buf);
244 commands[EQN_INDEX].append_arg(buf);
251 commands[TROFF_INDEX].append_arg(buf);
254 commands[TROFF_INDEX].append_arg(buf);
263 if (strcmp(optarg, "xhtml") == 0) {
264 // force soelim to aid the html preprocessor
265 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
274 if (strcmp(optarg, "html") == 0)
275 // force soelim to aid the html preprocessor
276 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
277 if (strcmp(optarg, "Xps") == 0) {
278 warning("-TXps option is obsolete: use -X -Tps instead");
286 font::command_line_font_dir(optarg);
287 if (Fargs.length() > 0) {
288 Fargs += PATH_SEP_CHAR;
304 commands[TROFF_INDEX].append_arg(buf, optarg);
307 commands[EQN_INDEX].append_arg(buf, optarg);
308 commands[GRAP_INDEX].append_arg(buf, optarg);
309 commands[GRN_INDEX].append_arg(buf, optarg);
310 commands[TROFF_INDEX].append_arg(buf, optarg);
317 append_arg_to_string(optarg, Largs);
332 commands[PIC_INDEX].set_name(command_prefix, "pic");
334 commands[PRECONV_INDEX].set_name("preconv");
335 if (!Kflag && *encoding)
336 commands[PRECONV_INDEX].append_arg("-e", encoding);
339 commands[TROFF_INDEX].insert_arg("-U");
340 commands[PIC_INDEX].append_arg("-U");
342 font::set_unknown_desc_command_handler(handle_unknown_desc_command);
343 if (!font::load_desc())
344 fatal("invalid device '%1'", device);
346 fatal("no 'postpro' command in DESC file for device '%1'", device);
347 if (predriver && !zflag) {
348 commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
349 commands[TROFF_INDEX].set_name(predriver);
350 // pass the device arguments to the predrivers as well
351 commands[TROFF_INDEX].insert_args(Pargs);
352 if (eflag && is_xhtml)
353 commands[TROFF_INDEX].insert_arg("-e");
355 commands[TROFF_INDEX].insert_arg("-v");
357 const char *real_driver = 0;
359 real_driver = postdriver;
360 postdriver = (char *)GXDITVIEW;
361 commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
364 commands[POST_INDEX].set_name(postdriver);
365 int gxditview_flag = postdriver
366 && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
367 if (gxditview_flag && argc - optind == 1) {
368 commands[POST_INDEX].append_arg("-title");
369 commands[POST_INDEX].append_arg(argv[optind]);
370 commands[POST_INDEX].append_arg("-xrm");
371 commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
372 string filename_string("|");
373 append_arg_to_string(argv[0], filename_string);
374 append_arg_to_string("-Z", filename_string);
375 for (int i = 1; i < argc; i++)
376 append_arg_to_string(argv[i], filename_string);
377 filename_string += '\0';
378 commands[POST_INDEX].append_arg("-filename");
379 commands[POST_INDEX].append_arg(filename_string.contents());
381 if (gxditview_flag && Xflag) {
382 string print_string(real_driver);
384 print_string += " | ";
385 print_string += spooler;
386 print_string += Largs;
388 print_string += '\0';
389 commands[POST_INDEX].append_arg("-printCommand");
390 commands[POST_INDEX].append_arg(print_string.contents());
392 const char *p = Pargs.contents();
393 const char *end = p + Pargs.length();
395 commands[POST_INDEX].append_arg(p);
396 p = strchr(p, '\0') + 1;
399 commands[POST_INDEX].append_arg("-");
400 if (lflag && !vflag && !Xflag && spooler) {
401 commands[SPOOL_INDEX].set_name(BSHELL);
402 commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
404 Largs = spooler + Largs;
405 commands[SPOOL_INDEX].append_arg(Largs.contents());
408 commands[POST_INDEX].set_name(0);
409 commands[SPOOL_INDEX].set_name(0);
411 commands[TROFF_INDEX].append_arg("-T", device);
412 if (strcmp(device, "html") == 0) {
415 fatal("'-o' option is invalid with device 'xhtml'");
417 commands[EQN_INDEX].append_arg("-Tmathml:xhtml");
419 commands[EQN_INDEX].clear_name();
423 fatal("'-o' option is invalid with device 'html'");
424 // html renders equations as images via ps
425 commands[EQN_INDEX].append_arg("-Tps:html");
429 commands[EQN_INDEX].append_arg("-T", device);
431 commands[GRN_INDEX].append_arg("-T", device);
434 for (first_index = 0; first_index < TROFF_INDEX; first_index++)
435 if (commands[first_index].get_name() != 0)
438 if (argv[optind][0] == '-' && argv[optind][1] != '\0')
439 commands[first_index].append_arg("--");
440 for (int i = optind; i < argc; i++)
441 commands[first_index].append_arg(argv[i]);
443 commands[first_index].append_arg("-");
445 if (Fargs.length() > 0) {
446 string e = "GROFF_FONT_PATH";
449 char *fontpath = getenv("GROFF_FONT_PATH");
450 if (fontpath && *fontpath) {
455 if (putenv(strsave(e.contents())))
456 fatal("putenv failed");
459 // we save the original path in GROFF_PATH__ and put it into the
460 // environment -- troff will pick it up later.
461 char *path = getenv("PATH");
462 string e = "GROFF_PATH__";
467 if (putenv(strsave(e.contents())))
468 fatal("putenv failed");
469 char *binpath = getenv("GROFF_BIN_PATH");
472 if (binpath && *binpath)
475 binpath = relocatep(BINPATH);
483 if (putenv(strsave(f.contents())))
484 fatal("putenv failed");
487 print_commands(Vflag == 1 ? stdout : stderr);
490 return run_commands(vflag);
493 const char *xbasename(const char *s)
497 // DIR_SEPS[] are possible directory separator characters, see nonposix.h
498 // We want the rightmost separator of all possible ones.
499 // Example: d:/foo\\bar.
500 const char *p = strrchr(s, DIR_SEPS[0]), *p1;
501 const char *sep = &DIR_SEPS[1];
505 p1 = strrchr(s, *sep);
506 if (p1 && (!p || p1 > p))
510 return p ? p + 1 : s;
513 void handle_unknown_desc_command(const char *command, const char *arg,
514 const char *filename, int lineno)
516 if (strcmp(command, "print") == 0) {
518 error_with_file_and_line(filename, lineno,
519 "'print' command requires an argument");
521 spooler = strsave(arg);
523 if (strcmp(command, "prepro") == 0) {
525 error_with_file_and_line(filename, lineno,
526 "'prepro' command requires an argument");
528 for (const char *p = arg; *p; p++)
530 error_with_file_and_line(filename, lineno,
531 "invalid 'prepro' argument '%1'"
532 ": program name required", arg);
535 predriver = strsave(arg);
538 if (strcmp(command, "postpro") == 0) {
540 error_with_file_and_line(filename, lineno,
541 "'postpro' command requires an argument");
543 for (const char *p = arg; *p; p++)
545 error_with_file_and_line(filename, lineno,
546 "invalid 'postpro' argument '%1'"
547 ": program name required", arg);
550 postdriver = strsave(arg);
555 void print_commands(FILE *fp)
558 for (last = SPOOL_INDEX; last >= 0; last--)
559 if (commands[last].get_name() != 0)
561 for (int i = 0; i <= last; i++)
562 if (commands[i].get_name() != 0)
563 commands[i].print(i == last, fp);
566 // Run the commands. Return the code with which to exit.
568 int run_commands(int no_pipe)
572 for (int i = 0; i < NCOMMANDS; i++)
573 if (commands[i].get_name() != 0)
574 v[j++] = commands[i].get_argv();
575 return run_pipeline(j, v, no_pipe);
578 possible_command::possible_command()
583 possible_command::~possible_command()
589 void possible_command::set_name(const char *s)
595 void possible_command::clear_name()
603 void possible_command::set_name(const char *s1, const char *s2)
606 name = (char*)malloc(strlen(s1) + strlen(s2) + 1);
611 const char *possible_command::get_name()
616 void possible_command::clear_args()
621 void possible_command::append_arg(const char *s, const char *t)
629 void possible_command::insert_arg(const char *s)
637 void possible_command::insert_args(string s)
639 const char *p = s.contents();
640 const char *end = p + s.length();
644 // find the total number of arguments in our string
647 p = strchr(p, '\0') + 1;
649 // now insert each argument preserving the order
650 for (int i = l - 1; i >= 0; i--) {
652 for (int j = 0; j < i; j++)
653 p = strchr(p, '\0') + 1;
658 void possible_command::build_argv()
662 // Count the number of arguments.
663 int len = args.length();
668 for (int i = 0; i < len; i++)
672 // Build an argument vector.
673 argv = new char *[argc + 1];
675 for (int i = 1; i < argc; i++) {
677 p = strchr(p, '\0') + 1;
682 void possible_command::print(int is_last, FILE *fp)
685 if (IS_BSHELL(argv[0])
686 && argv[1] != 0 && strcmp(argv[1], BSHELL_DASH_C) == 0
687 && argv[2] != 0 && argv[3] == 0)
692 for (int i = 1; argv[i] != 0; i++) {
694 append_arg_to_string(argv[i], str);
704 void append_arg_to_string(const char *arg, string &str)
707 int needs_quoting = 0;
708 // Native Windows programs don't support '..' style of quoting, so
709 // always behave as if ARG included the single quote character.
710 #if defined(_WIN32) && !defined(__CYGWIN__)
711 int contains_single_quote = 1;
713 int contains_single_quote = 0;
716 for (p = arg; *p != '\0'; p++)
737 contains_single_quote = 1;
740 if (contains_single_quote || arg[0] == '\0') {
742 for (p = arg; *p != '\0'; p++)
744 #if !(defined(_WIN32) && !defined(__CYGWIN__))
752 if (*p == '"' || (*p == '\\' && p[1] == '"'))
762 else if (needs_quoting) {
771 char **possible_command::get_argv()
777 void synopsis(FILE *stream)
780 "usage: %s [-abceghijklpstvzCEGNRSUVXZ] [-dcs] [-ffam] [-mname] [-nnum]\n"
781 " [-olist] [-rcn] [-wname] [-Darg] [-Fdir] [-Idir] [-Karg] [-Larg]\n"
782 " [-Mdir] [-Parg] [-Tdev] [-Wname] [files...]\n",
790 "-h\tprint this message\n"
791 "-v\tprint version number\n"
792 "-e\tpreprocess with eqn\n"
793 "-g\tpreprocess with grn\n"
794 "-j\tpreprocess with chem\n"
795 "-k\tpreprocess with preconv\n"
796 "-p\tpreprocess with pic\n"
797 "-s\tpreprocess with soelim\n"
798 "-t\tpreprocess with tbl\n"
799 "-G\tpreprocess with grap\n"
800 "-J\tpreprocess with gideal\n"
801 "-R\tpreprocess with refer\n"
802 "-a\tproduce ASCII description of output\n"
803 "-b\tprint backtraces with errors or warnings\n"
804 "-c\tdisable color output\n"
805 "-dcs\tdefine a string c as s\n"
806 "-ffam\tuse fam as the default font family\n"
807 "-i\tread standard input after named input files\n"
808 "-l\tspool the output\n"
809 "-mname\tread macros tmac.name\n"
810 "-nnum\tnumber first page n\n"
811 "-olist\toutput only pages in list\n"
812 "-rcn\tdefine a number register c as n\n"
813 "-wname\tenable warning name\n"
814 "-z\tsuppress formatted output\n"
815 "-C\tenable compatibility mode\n"
816 "-Darg\tuse arg as default input encoding. Implies -k\n"
817 "-E\tinhibit all errors\n"
818 "-Fdir\tsearch dir for device directories\n"
819 "-Idir\tsearch dir for soelim, troff, and grops. Implies -s\n"
820 "-Karg\tuse arg as input encoding. Implies -k\n"
821 "-Larg\tpass arg to the spooler\n"
822 "-Mdir\tsearch dir for macro files\n"
823 "-N\tdon't allow newlines within eqn delimiters\n"
824 "-Parg\tpass arg to the postprocessor\n"
825 "-S\tenable safer mode (the default)\n"
826 "-Tdev\tuse device dev\n"
827 "-U\tenable unsafe mode\n"
828 "-V\tprint commands on stdout instead of running them\n"
829 "-Wname\tinhibit warning name\n"
830 "-X\tuse X11 previewer rather than usual postprocessor\n"
831 "-Z\tdon't postprocess\n"
837 void usage(FILE *stream)
840 fprintf(stream, "%s -h gives more help\n", program_name);
845 void c_error(const char *format, const char *arg1, const char *arg2,
848 error(format, arg1, arg2, arg3);
851 void c_fatal(const char *format, const char *arg1, const char *arg2,
854 fatal(format, arg1, arg2, arg3);