Sync-to-go: update copyright for 2015
[s-roff.git] / src / roff / roff.cpp
blob1c966f938c48a9686e815eb360d3216b8c8ec530
1 /*@ A front end for S-roff.
3 * Copyright (c) 2014 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
5 * Copyright (C) 1989 - 2008
6 * Free Software Foundation, Inc.
7 * Written by James Clark (jjc@jclark.com)
9 * This is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License as published by the Free
11 * Software Foundation; either version 2, or (at your option) any later
12 * version.
14 * This is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 * for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with groff; see the file COPYING. If not, write to the Free Software
21 * Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.
24 #include "config.h"
25 #include "roff-config.h"
27 #include <assert.h>
28 #include <errno.h>
29 #include <signal.h>
30 #include <stdlib.h>
32 #include "cset.h"
33 #include "defs.h"
34 #include "device.h"
35 #include "errarg.h"
36 #include "error.h"
37 #include "font.h"
38 #include "lib.h"
39 #include "nonposix.h"
40 #include "pipeline.h"
41 #include "stringclass.h"
43 // The number of commands must be in sync with MAX_COMMANDS in pipeline.h
44 const int PRECONV_INDEX = 0;
45 const int SOELIM_INDEX = PRECONV_INDEX + 1;
46 const int REFER_INDEX = SOELIM_INDEX + 1;
47 const int GRAP_INDEX = REFER_INDEX + 1;
48 const int PIC_INDEX = GRAP_INDEX + 1;
49 const int TBL_INDEX = PIC_INDEX + 1;
50 const int GRN_INDEX = TBL_INDEX + 1;
51 const int EQN_INDEX = GRN_INDEX + 1;
52 const int TROFF_INDEX = EQN_INDEX + 1;
53 const int POST_INDEX = TROFF_INDEX + 1;
54 const int SPOOL_INDEX = POST_INDEX + 1;
56 const int NCOMMANDS = SPOOL_INDEX + 1;
57 CTA(NCOMMANDS <= MAX_COMMANDS);
59 class possible_command
61 char *name;
62 string args;
63 char **argv;
65 void build_argv();
67 public:
68 possible_command();
69 ~possible_command();
70 void clear_name();
71 void set_name(const char *);
72 void set_name(const char *, const char *);
73 const char *get_name();
74 void append_arg(const char *, const char * = 0);
75 void insert_arg(const char *);
76 void insert_args(string s);
77 void clear_args();
78 char **get_argv();
79 void print(int is_last, FILE *fp);
82 int lflag = 0;
83 char *spooler = 0;
84 char *postdriver = 0;
85 char *predriver = 0;
87 possible_command commands[NCOMMANDS];
89 int run_commands(int no_pipe);
90 void print_commands(FILE *);
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 *); // FIXME lib.h??
96 void usage(FILE *stream);
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 string Pargs, Largs, Fargs;
105 int Kflag = 0;
106 int vflag = 0;
107 int Vflag = 0;
108 int zflag = 0;
109 int iflag = 0;
110 int oflag = 0;
111 int safer_flag = 1;
112 int is_xhtml = 0;
113 int eflag = 0;
114 int opt;
115 const char *command_prefix = getenv(U_ROFF_COMMAND_PREFIX);
116 const char *encoding = getenv(U_ROFF_ENCODING);
117 if (!command_prefix)
118 command_prefix = PROG_PREFIX;
119 commands[TROFF_INDEX].set_name(command_prefix, "troff");
120 static const struct option long_options[] = {
121 { "help", no_argument, 0, 'h' },
122 { "version", no_argument, 0, 'v' },
123 { NULL, 0, 0, 0 }
125 while ((opt = getopt_long(
126 argc, argv,
127 "abcCd:D:eEf:F:gGhiI:lkK:L:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ",
128 long_options, NULL))
129 != EOF) {
130 char buf[3];
131 buf[0] = '-';
132 buf[1] = opt;
133 buf[2] = '\0';
134 switch (opt) {
135 case 'i':
136 iflag = 1;
137 break;
138 case 'I':
139 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
140 commands[SOELIM_INDEX].append_arg(buf, optarg);
141 // .psbb may need to search for files
142 commands[TROFF_INDEX].append_arg(buf, optarg);
143 // \X'ps:import' may need to search for files
144 Pargs += buf;
145 Pargs += optarg;
146 Pargs += '\0';
147 break;
148 case 'D':
149 commands[PRECONV_INDEX].set_name(command_prefix, "preconv");
150 commands[PRECONV_INDEX].append_arg("-D", optarg);
151 break;
152 case 'K':
153 commands[PRECONV_INDEX].append_arg("-e", optarg);
154 Kflag = 1;
155 // fall through
156 case 'k':
157 commands[PRECONV_INDEX].set_name(command_prefix, "preconv");
158 break;
159 case 't':
160 commands[TBL_INDEX].set_name(command_prefix, "tbl");
161 break;
162 case 'p':
163 commands[PIC_INDEX].set_name(command_prefix, "pic");
164 break;
165 case 'g':
166 commands[GRN_INDEX].set_name(command_prefix, "grn");
167 break;
168 case 'G':
169 commands[GRAP_INDEX].set_name(command_prefix, "grap");
170 break;
171 case 'e':
172 eflag = 1;
173 commands[EQN_INDEX].set_name(command_prefix, "eqn");
174 break;
175 case 's':
176 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
177 break;
178 case 'R':
179 commands[REFER_INDEX].set_name(command_prefix, "refer");
180 break;
181 case 'z':
182 case 'a':
183 commands[TROFF_INDEX].append_arg(buf);
184 // fall through
185 case 'Z':
186 zflag++;
187 break;
188 case 'l':
189 lflag++;
190 break;
191 case 'V':
192 Vflag++;
193 break;
194 case 'v':
195 vflag = 1;
196 printf(L_ROFF " v" VERSION);
197 printf(L_ROFF_COPYRIGHT_PRINTOUT);
198 printf("\ncalled subprograms:\n\n");
199 fflush(stdout);
200 commands[POST_INDEX].append_arg(buf);
201 // FALLTHRU
202 case 'C':
203 commands[SOELIM_INDEX].append_arg(buf);
204 commands[REFER_INDEX].append_arg(buf);
205 commands[PIC_INDEX].append_arg(buf);
206 commands[GRAP_INDEX].append_arg(buf);
207 commands[TBL_INDEX].append_arg(buf);
208 commands[GRN_INDEX].append_arg(buf);
209 commands[EQN_INDEX].append_arg(buf);
210 commands[TROFF_INDEX].append_arg(buf);
211 break;
212 case 'N':
213 commands[EQN_INDEX].append_arg(buf);
214 break;
215 case 'h':
216 help();
217 break;
218 case 'E':
219 case 'b':
220 commands[TROFF_INDEX].append_arg(buf);
221 break;
222 case 'c':
223 commands[TROFF_INDEX].append_arg(buf);
224 break;
225 case 'S':
226 safer_flag = 1;
227 break;
228 case 'U':
229 safer_flag = 0;
230 break;
231 case 'T':
232 if (strcmp(optarg, "xhtml") == 0) {
233 // force soelim to aid the html preprocessor
234 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
235 Pargs += "-x";
236 Pargs += '\0';
237 Pargs += 'x';
238 Pargs += '\0';
239 is_xhtml = 1;
240 device = "html";
241 break;
243 if (strcmp(optarg, "html") == 0)
244 // force soelim to aid the html preprocessor
245 commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
246 device = optarg;
247 break;
248 case 'F':
249 font::command_line_font_dir(optarg);
250 if (Fargs.length() > 0) {
251 Fargs += PATH_SEP_CHAR;
252 Fargs += optarg;
254 else
255 Fargs = optarg;
256 break;
257 case 'o':
258 oflag = 1;
259 case 'f':
260 case 'm':
261 case 'r':
262 case 'd':
263 case 'n':
264 case 'w':
265 case 'W':
266 commands[TROFF_INDEX].append_arg(buf, optarg);
267 break;
268 case 'M':
269 commands[EQN_INDEX].append_arg(buf, optarg);
270 commands[GRAP_INDEX].append_arg(buf, optarg);
271 commands[GRN_INDEX].append_arg(buf, optarg);
272 commands[TROFF_INDEX].append_arg(buf, optarg);
273 break;
274 case 'P':
275 Pargs += optarg;
276 Pargs += '\0';
277 break;
278 case 'L':
279 append_arg_to_string(optarg, Largs);
280 break;
281 case '?':
282 usage(stderr);
283 exit(1);
284 break;
285 default:
286 assert(0);
287 break;
290 if (encoding) {
291 commands[PRECONV_INDEX].set_name(command_prefix, "preconv");
292 if (!Kflag && *encoding)
293 commands[PRECONV_INDEX].append_arg("-e", encoding);
295 if (!safer_flag) {
296 commands[TROFF_INDEX].insert_arg("-U");
297 commands[PIC_INDEX].append_arg("-U");
299 font::set_unknown_desc_command_handler(handle_unknown_desc_command);
300 if (!font::load_desc())
301 fatal("invalid device `%1'", device);
302 if (!postdriver)
303 fatal("no `postpro' command in DESC file for device `%1'", device);
304 if (predriver && !zflag) {
305 commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
306 commands[TROFF_INDEX].set_name(predriver);
307 // pass the device arguments to the predrivers as well
308 commands[TROFF_INDEX].insert_args(Pargs);
309 if (eflag && is_xhtml)
310 commands[TROFF_INDEX].insert_arg("-e");
311 if (vflag)
312 commands[TROFF_INDEX].insert_arg("-v");
314 const char *real_driver = 0;
315 if (postdriver)
316 commands[POST_INDEX].set_name(postdriver);
317 const char *p = Pargs.contents();
318 const char *end = p + Pargs.length();
319 while (p < end) {
320 commands[POST_INDEX].append_arg(p);
321 p = strchr(p, '\0') + 1;
323 if (lflag && !vflag && spooler) {
324 commands[SPOOL_INDEX].set_name(BSHELL);
325 commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
326 Largs += '\0';
327 Largs = spooler + Largs;
328 commands[SPOOL_INDEX].append_arg(Largs.contents());
330 if (zflag) {
331 commands[POST_INDEX].set_name(0);
332 commands[SPOOL_INDEX].set_name(0);
334 commands[TROFF_INDEX].append_arg("-T", device);
335 if (strcmp(device, "html") == 0) {
336 if (is_xhtml) {
337 if (oflag)
338 fatal("`-o' option is invalid with device `xhtml'");
339 if (zflag)
340 commands[EQN_INDEX].append_arg("-Tmathml:xhtml");
341 else if (eflag)
342 commands[EQN_INDEX].clear_name();
344 else {
345 if (oflag)
346 fatal("`-o' option is invalid with device `html'");
347 // html renders equations as images via ps
348 commands[EQN_INDEX].append_arg("-Tps:html");
351 else
352 commands[EQN_INDEX].append_arg("-T", device);
354 commands[GRN_INDEX].append_arg("-T", device);
356 int first_index;
357 for (first_index = 0; first_index < TROFF_INDEX; first_index++)
358 if (commands[first_index].get_name() != 0)
359 break;
360 if (optind < argc) {
361 if (argv[optind][0] == '-' && argv[optind][1] != '\0')
362 commands[first_index].append_arg("--");
363 for (int i = optind; i < argc; i++)
364 commands[first_index].append_arg(argv[i]);
365 if (iflag)
366 commands[first_index].append_arg("-");
368 if (Fargs.length() > 0) {
369 string e = U_ROFF_FONT_PATH;
370 e += '=';
371 e += Fargs;
372 char *fontpath = getenv(U_ROFF_FONT_PATH);
373 if (fontpath && *fontpath) {
374 e += PATH_SEP_CHAR;
375 e += fontpath;
377 e += '\0';
378 if (putenv(strsave(e.contents())))
379 fatal("putenv failed");
382 // we save the original path in GROFF_PATH__ and put it into the
383 // environment -- troff will pick it up later.
384 char *path = getenv("PATH");
385 string e = U_ROFF_PATH__;
386 e += '=';
387 if (path && *path)
388 e += path;
389 e += '\0';
390 if (putenv(strsave(e.contents())))
391 fatal("putenv failed");
392 char *binpath = getenv(U_ROFF_BIN_PATH);
393 string f = "PATH";
394 f += '=';
395 if (binpath && *binpath)
396 f += binpath;
397 else
398 f += BINPATH;
399 if (path && *path) {
400 f += PATH_SEP_CHAR;
401 f += path;
403 f += '\0';
404 if (putenv(strsave(f.contents())))
405 fatal("putenv failed");
407 if (Vflag)
408 print_commands(Vflag == 1 ? stdout : stderr);
409 if (Vflag == 1)
410 exit(0);
411 return run_commands(vflag);
414 const char *xbasename(const char *s) // TODO -> library
416 if (!s)
417 return 0;
418 // DIR_SEPS[] are possible directory separator characters, see nonposix.h
419 // We want the rightmost separator of all possible ones.
420 // Example: d:/foo\\bar.
421 const char *p = strrchr(s, DIR_SEPS[0]), *p1;
422 const char *sep = &DIR_SEPS[1];
424 while (*sep)
426 p1 = strrchr(s, *sep);
427 if (p1 && (!p || p1 > p))
428 p = p1;
429 sep++;
431 return p ? p + 1 : s;
434 void handle_unknown_desc_command(const char *command, const char *arg,
435 const char *filename, int lineno)
437 if (strcmp(command, "print") == 0) {
438 if (arg == 0)
439 error_with_file_and_line(filename, lineno,
440 "`print' command requires an argument");
441 else
442 spooler = strsave(arg);
444 if (strcmp(command, "prepro") == 0) {
445 if (arg == 0)
446 error_with_file_and_line(filename, lineno,
447 "`prepro' command requires an argument");
448 else {
449 for (const char *p = arg; *p; p++)
450 if (csspace(*p)) {
451 error_with_file_and_line(filename, lineno,
452 "invalid `prepro' argument `%1'"
453 ": program name required", arg);
454 return;
456 predriver = strsave(arg);
459 if (strcmp(command, "postpro") == 0) {
460 if (arg == 0)
461 error_with_file_and_line(filename, lineno,
462 "`postpro' command requires an argument");
463 else {
464 for (const char *p = arg; *p; p++)
465 if (csspace(*p)) {
466 error_with_file_and_line(filename, lineno,
467 "invalid `postpro' argument `%1'"
468 ": program name required", arg);
469 return;
471 postdriver = strsave(arg);
476 void print_commands(FILE *fp)
478 int last;
479 for (last = SPOOL_INDEX; last >= 0; last--)
480 if (commands[last].get_name() != 0)
481 break;
482 for (int i = 0; i <= last; i++)
483 if (commands[i].get_name() != 0)
484 commands[i].print(i == last, fp);
487 // Run the commands. Return the code with which to exit.
489 int run_commands(int no_pipe)
491 char **v[NCOMMANDS];
492 int j = 0;
493 for (int i = 0; i < NCOMMANDS; i++)
494 if (commands[i].get_name() != 0)
495 v[j++] = commands[i].get_argv();
496 return run_pipeline(j, v, no_pipe);
499 possible_command::possible_command()
500 : name(0), argv(0)
504 possible_command::~possible_command()
506 a_delete name;
507 a_delete argv;
510 void possible_command::set_name(const char *s)
512 a_delete name;
513 name = strsave(s);
516 void possible_command::clear_name()
518 a_delete name;
519 a_delete argv;
520 name = NULL;
521 argv = NULL;
524 void possible_command::set_name(const char *s1, const char *s2)
526 a_delete name;
527 name = new char[strlen(s1) + strlen(s2) + 1];
528 strcpy(name, s1);
529 strcat(name, s2);
532 const char *possible_command::get_name()
534 return name;
537 void possible_command::clear_args()
539 args.clear();
542 void possible_command::append_arg(const char *s, const char *t)
544 args += s;
545 if (t)
546 args += t;
547 args += '\0';
550 void possible_command::insert_arg(const char *s)
552 string str(s);
553 str += '\0';
554 str += args;
555 args = str;
558 void possible_command::insert_args(string s)
560 const char *p = s.contents();
561 const char *end = p + s.length();
562 int l = 0;
563 if (p >= end)
564 return;
565 // find the total number of arguments in our string
566 do {
567 l++;
568 p = strchr(p, '\0') + 1;
569 } while (p < end);
570 // now insert each argument preserving the order
571 for (int i = l - 1; i >= 0; i--) {
572 p = s.contents();
573 for (int j = 0; j < i; j++)
574 p = strchr(p, '\0') + 1;
575 insert_arg(p);
579 void possible_command::build_argv()
581 if (argv)
582 return;
583 // Count the number of arguments.
584 int len = args.length();
585 int argc = 1;
586 char *p = 0;
587 if (len > 0) {
588 p = &args[0];
589 for (int i = 0; i < len; i++)
590 if (p[i] == '\0')
591 argc++;
593 // Build an argument vector.
594 argv = new char *[argc + 1];
595 argv[0] = name;
596 for (int i = 1; i < argc; i++) {
597 argv[i] = p;
598 p = strchr(p, '\0') + 1;
600 argv[argc] = 0;
603 void possible_command::print(int is_last, FILE *fp)
605 build_argv();
606 if (IS_BSHELL(argv[0])
607 && argv[1] != 0 && strcmp(argv[1], BSHELL_DASH_C) == 0
608 && argv[2] != 0 && argv[3] == 0)
609 fputs(argv[2], fp);
610 else {
611 fputs(argv[0], fp);
612 string str;
613 for (int i = 1; argv[i] != 0; i++) {
614 str.clear();
615 append_arg_to_string(argv[i], str);
616 put_string(str, fp);
619 if (is_last)
620 putc('\n', fp);
621 else
622 fputs(" | ", fp);
625 void append_arg_to_string(const char *arg, string &str) // TODO quoting -> lib
627 str += ' ';
628 int needs_quoting = 0;
629 int contains_single_quote = 0;
630 const char*p;
631 for (p = arg; *p != '\0'; p++)
632 switch (*p) {
633 case ';':
634 case '&':
635 case '(':
636 case ')':
637 case '|':
638 case '^':
639 case '<':
640 case '>':
641 case '\n':
642 case ' ':
643 case '\t':
644 case '\\':
645 case '"':
646 case '$':
647 case '?':
648 case '*':
649 needs_quoting = 1;
650 break;
651 case '\'':
652 contains_single_quote = 1;
653 break;
655 if (contains_single_quote || arg[0] == '\0') {
656 str += '"';
657 for (p = arg; *p != '\0'; p++)
658 switch (*p) {
659 case '"':
660 case '\\':
661 case '$':
662 str += '\\';
663 // fall through
664 default:
665 str += *p;
666 break;
668 str += '"';
670 else if (needs_quoting) {
671 str += '\'';
672 str += arg;
673 str += '\'';
675 else
676 str += arg;
679 char **possible_command::get_argv()
681 build_argv();
682 return argv;
685 void synopsis(FILE *stream)
687 fprintf(stream,
688 "Synopsis: %s [-abceghiklpstvzCENRSUVXZ] [-Fdir] [-mname] [-Tdev] [-ffam]\n"
689 " [-wname] [-Wname] [-Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg]\n"
690 " [-Darg] [-Karg] [-Larg] [-Idir] [files...]\n",
691 program_name);
694 void help()
696 synopsis(stdout);
697 fputs("\n"
698 "-h\tprint this message\n"
699 "-k\tpreprocess with preconv\n"
700 "-t\tpreprocess with tbl\n"
701 "-p\tpreprocess with pic\n"
702 "-e\tpreprocess with eqn\n"
703 "-g\tpreprocess with grn\n"
704 "-G\tpreprocess with grap\n"
705 "-s\tpreprocess with soelim\n"
706 "-R\tpreprocess with refer\n"
707 "-Tdev\tuse device dev\n"
708 "-mname\tread macros tmac.name\n"
709 "-dcs\tdefine a string c as s\n"
710 "-rcn\tdefine a number register c as n\n"
711 "-nnum\tnumber first page n\n"
712 "-olist\toutput only pages in list\n"
713 "-ffam\tuse fam as the default font family\n"
714 "-Fdir\tsearch dir for device directories\n"
715 "-Mdir\tsearch dir for macro files\n"
716 "-v\tprint version number\n"
717 "-z\tsuppress formatted output\n"
718 "-Z\tdon't postprocess\n"
719 "-a\tproduce ASCII description of output\n"
720 "-i\tread standard input after named input files\n"
721 "-wname\tenable warning name\n"
722 "-Wname\tinhibit warning name\n"
723 "-E\tinhibit all errors\n"
724 "-b\tprint backtraces with errors or warnings\n"
725 "-l\tspool the output\n"
726 "-c\tdisable color output\n"
727 "-C\tenable compatibility mode\n"
728 "-V\tprint commands on stdout instead of running them\n"
729 "-Parg\tpass arg to the postprocessor\n"
730 "-Larg\tpass arg to the spooler\n"
731 "-N\tdon't allow newlines within eqn delimiters\n"
732 "-S\tenable safer mode (the default)\n"
733 "-U\tenable unsafe mode\n"
734 "-Idir\tsearch dir for soelim, troff, and grops. Implies -s\n"
735 "-Karg\tuse arg as input encoding. Implies -k\n"
736 "-Darg\tuse arg as default input encoding. Implies -k\n"
737 "\n",
738 stdout);
739 exit(0);
742 void usage(FILE *stream)
744 synopsis(stream);
745 fprintf(stream, "%s -h gives more help\n", program_name);
748 extern "C" {
749 void c_error(const char *format, const char *arg1, const char *arg2,
750 const char *arg3)
752 error(format, arg1, arg2, arg3);
755 void c_fatal(const char *format, const char *arg1, const char *arg2,
756 const char *arg3)
758 fatal(format, arg1, arg2, arg3);
760 } // extern "C"
762 // s-it2-mode