Sync-to-go: src/src-roff..
[s-roff.git] / src / roff / roff.cpp
bloba1bcd35465b38d09c158ae6acfa4d0a13d6fb030
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 enum {
45 IDX_PRECONV,
46 IDX_SOELIM,
47 IDX_REFER,
48 IDX_GRAP,
49 IDX_PIC,
50 IDX_TBL,
51 IDX_GRN,
52 IDX_EQN,
53 IDX_TROFF,
54 IDX_POST,
55 IDX_SPOOL,
56 IDX_NCOMMANDS
58 CTA(IDX_NCOMMANDS <= MAX_COMMANDS);
60 class possible_command
62 char *name;
63 string args;
64 char **argv;
66 void build_argv();
68 public:
69 possible_command();
70 ~possible_command();
71 void clear_name();
72 void set_name(const char *);
73 void set_name(const char *, const char *);
74 const char *get_name();
75 void append_arg(const char *, const char * = 0);
76 void insert_arg(const char *);
77 void insert_args(string s);
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 *postdriver = 0;
86 char *predriver = 0;
88 possible_command commands[IDX_NCOMMANDS];
90 int run_commands(int no_pipe);
91 void print_commands(FILE *);
92 void append_arg_to_string(const char *arg, string &str);
93 void handle_unknown_desc_command(const char *command, const char *arg,
94 const char *filename, int lineno);
95 const char *xbasename(const char *); // FIXME lib.h??
97 void usage(FILE *stream);
98 void help();
100 int main(int argc, char **argv)
102 program_name = argv[0];
103 static char stderr_buf[BUFSIZ];
104 setbuf(stderr, stderr_buf);
105 string Pargs, Largs, Fargs;
106 int Kflag = 0;
107 int vflag = 0;
108 int Vflag = 0;
109 int zflag = 0;
110 int iflag = 0;
111 int oflag = 0;
112 int safer_flag = 1;
113 int is_xhtml = 0;
114 int eflag = 0;
115 int opt;
116 const char *command_prefix = getenv(U_ROFF_COMMAND_PREFIX);
117 const char *encoding = getenv(U_ROFF_ENCODING);
118 if (!command_prefix)
119 command_prefix = PROG_PREFIX;
120 commands[IDX_TROFF].set_name(command_prefix, "troff");
121 static const struct option long_options[] = {
122 { "help", no_argument, 0, 'h' },
123 { "version", no_argument, 0, 'v' },
124 { NULL, 0, 0, 0 }
126 while ((opt = getopt_long(
127 argc, argv,
128 "abcCd:D:eEf:F:gGhiI:lkK:L:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ",
129 long_options, NULL))
130 != EOF) {
131 char buf[3];
132 buf[0] = '-';
133 buf[1] = opt;
134 buf[2] = '\0';
135 switch (opt) {
136 case 'i':
137 iflag = 1;
138 break;
139 case 'I':
140 commands[IDX_SOELIM].set_name(command_prefix, "soelim");
141 commands[IDX_SOELIM].append_arg(buf, optarg);
142 // .psbb may need to search for files
143 commands[IDX_TROFF].append_arg(buf, optarg);
144 // \X'ps:import' may need to search for files
145 Pargs += buf;
146 Pargs += optarg;
147 Pargs += '\0';
148 break;
149 case 'D':
150 commands[IDX_PRECONV].set_name(command_prefix, "preconv");
151 commands[IDX_PRECONV].append_arg("-D", optarg);
152 break;
153 case 'K':
154 commands[IDX_PRECONV].append_arg("-e", optarg);
155 Kflag = 1;
156 // fall through
157 case 'k':
158 commands[IDX_PRECONV].set_name(command_prefix, "preconv");
159 break;
160 case 't':
161 commands[IDX_TBL].set_name(command_prefix, "tbl");
162 break;
163 case 'p':
164 commands[IDX_PIC].set_name(command_prefix, "pic");
165 break;
166 case 'g':
167 commands[IDX_GRN].set_name(command_prefix, "grn");
168 break;
169 case 'G':
170 commands[IDX_GRAP].set_name(command_prefix, "grap");
171 break;
172 case 'e':
173 eflag = 1;
174 commands[IDX_EQN].set_name(command_prefix, "eqn");
175 break;
176 case 's':
177 commands[IDX_SOELIM].set_name(command_prefix, "soelim");
178 break;
179 case 'R':
180 commands[IDX_REFER].set_name(command_prefix, "refer");
181 break;
182 case 'z':
183 case 'a':
184 commands[IDX_TROFF].append_arg(buf);
185 // fall through
186 case 'Z':
187 zflag++;
188 break;
189 case 'l':
190 lflag++;
191 break;
192 case 'V':
193 Vflag++;
194 break;
195 case 'v':
196 vflag = 1;
197 printf(L_ROFF " v" VERSION);
198 printf(L_ROFF_COPYRIGHT_PRINTOUT);
199 printf("\ncalled subprograms:\n\n");
200 fflush(stdout);
201 commands[IDX_POST].append_arg(buf);
202 // FALLTHRU
203 case 'C':
204 commands[IDX_SOELIM].append_arg(buf);
205 commands[IDX_REFER].append_arg(buf);
206 commands[IDX_PIC].append_arg(buf);
207 commands[IDX_GRAP].append_arg(buf);
208 commands[IDX_TBL].append_arg(buf);
209 commands[IDX_GRN].append_arg(buf);
210 commands[IDX_EQN].append_arg(buf);
211 commands[IDX_TROFF].append_arg(buf);
212 break;
213 case 'N':
214 commands[IDX_EQN].append_arg(buf);
215 break;
216 case 'h':
217 help();
218 break;
219 case 'E':
220 case 'b':
221 commands[IDX_TROFF].append_arg(buf);
222 break;
223 case 'c':
224 commands[IDX_TROFF].append_arg(buf);
225 break;
226 case 'S':
227 safer_flag = 1;
228 break;
229 case 'U':
230 safer_flag = 0;
231 break;
232 case 'T':
233 if (strcmp(optarg, "xhtml") == 0) {
234 // force soelim to aid the html preprocessor
235 commands[IDX_SOELIM].set_name(command_prefix, "soelim");
236 Pargs += "-x";
237 Pargs += '\0';
238 Pargs += 'x';
239 Pargs += '\0';
240 is_xhtml = 1;
241 device = "html";
242 break;
244 if (strcmp(optarg, "html") == 0)
245 // force soelim to aid the html preprocessor
246 commands[IDX_SOELIM].set_name(command_prefix, "soelim");
247 device = optarg;
248 break;
249 case 'F':
250 font::command_line_font_dir(optarg);
251 if (Fargs.length() > 0) {
252 Fargs += PATH_SEP_CHAR;
253 Fargs += optarg;
255 else
256 Fargs = optarg;
257 break;
258 case 'o':
259 oflag = 1;
260 case 'f':
261 case 'm':
262 case 'r':
263 case 'd':
264 case 'n':
265 case 'w':
266 case 'W':
267 commands[IDX_TROFF].append_arg(buf, optarg);
268 break;
269 case 'M':
270 commands[IDX_EQN].append_arg(buf, optarg);
271 commands[IDX_GRAP].append_arg(buf, optarg);
272 commands[IDX_GRN].append_arg(buf, optarg);
273 commands[IDX_TROFF].append_arg(buf, optarg);
274 break;
275 case 'P':
276 Pargs += optarg;
277 Pargs += '\0';
278 break;
279 case 'L':
280 append_arg_to_string(optarg, Largs);
281 break;
282 case '?':
283 usage(stderr);
284 exit(1);
285 break;
286 default:
287 assert(0);
288 break;
291 if (encoding) {
292 commands[IDX_PRECONV].set_name(command_prefix, "preconv");
293 if (!Kflag && *encoding)
294 commands[IDX_PRECONV].append_arg("-e", encoding);
296 if (!safer_flag) {
297 commands[IDX_TROFF].insert_arg("-U");
298 commands[IDX_PIC].append_arg("-U");
300 font::set_unknown_desc_command_handler(handle_unknown_desc_command);
301 if (!font::load_desc())
302 fatal("invalid device `%1'", device);
303 if (!postdriver)
304 fatal("no `postpro' command in DESC file for device `%1'", device);
305 if (predriver && !zflag) {
306 commands[IDX_TROFF].insert_arg(commands[IDX_TROFF].get_name());
307 commands[IDX_TROFF].set_name(predriver);
308 // pass the device arguments to the predrivers as well
309 commands[IDX_TROFF].insert_args(Pargs);
310 if (eflag && is_xhtml)
311 commands[IDX_TROFF].insert_arg("-e");
312 if (vflag)
313 commands[IDX_TROFF].insert_arg("-v");
315 const char *real_driver = 0;
316 if (postdriver)
317 commands[IDX_POST].set_name(postdriver);
318 const char *p = Pargs.contents();
319 const char *end = p + Pargs.length();
320 while (p < end) {
321 commands[IDX_POST].append_arg(p);
322 p = strchr(p, '\0') + 1;
324 if (lflag && !vflag && spooler) {
325 commands[IDX_SPOOL].set_name(BSHELL);
326 commands[IDX_SPOOL].append_arg(BSHELL_DASH_C);
327 Largs += '\0';
328 Largs = spooler + Largs;
329 commands[IDX_SPOOL].append_arg(Largs.contents());
331 if (zflag) {
332 commands[IDX_POST].set_name(0);
333 commands[IDX_SPOOL].set_name(0);
335 commands[IDX_TROFF].append_arg("-T", device);
336 if (strcmp(device, "html") == 0) {
337 if (is_xhtml) {
338 if (oflag)
339 fatal("`-o' option is invalid with device `xhtml'");
340 if (zflag)
341 commands[IDX_EQN].append_arg("-Tmathml:xhtml");
342 else if (eflag)
343 commands[IDX_EQN].clear_name();
345 else {
346 if (oflag)
347 fatal("`-o' option is invalid with device `html'");
348 // html renders equations as images via ps
349 commands[IDX_EQN].append_arg("-Tps:html");
352 else
353 commands[IDX_EQN].append_arg("-T", device);
355 commands[IDX_GRN].append_arg("-T", device);
357 int first_index;
358 for (first_index = 0; first_index < IDX_TROFF; ++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("-");
369 if (Fargs.length() > 0) {
370 string e = U_ROFF_FONT_PATH;
371 e += '=';
372 e += Fargs;
373 char *fontpath = getenv(U_ROFF_FONT_PATH);
374 if (fontpath && *fontpath) {
375 e += PATH_SEP_CHAR;
376 e += fontpath;
378 e += '\0';
379 if (putenv(strsave(e.contents())))
380 fatal("putenv failed");
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 = U_ROFF_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(U_ROFF_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_CHAR;
402 f += path;
404 f += '\0';
405 if (putenv(strsave(f.contents())))
406 fatal("putenv failed");
408 if (Vflag)
409 print_commands(Vflag == 1 ? stdout : stderr);
410 if (Vflag == 1)
411 exit(0);
412 return run_commands(vflag);
415 const char *xbasename(const char *s) // TODO -> library
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];
425 while (*sep)
427 p1 = strrchr(s, *sep);
428 if (p1 && (!p || p1 > p))
429 p = p1;
430 sep++;
432 return p ? p + 1 : s;
435 void handle_unknown_desc_command(const char *command, const char *arg,
436 const char *filename, int lineno)
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);
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;
457 predriver = strsave(arg);
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;
472 postdriver = strsave(arg);
477 void print_commands(FILE *fp)
479 int last;
480 for (last = IDX_SPOOL; 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, fp);
488 // Run the commands. Return the code with which to exit.
490 int run_commands(int no_pipe)
492 char **v[IDX_NCOMMANDS];
493 int j = 0;
494 for (int i = 0; i < IDX_NCOMMANDS; i++)
495 if (commands[i].get_name() != 0)
496 v[j++] = commands[i].get_argv();
497 return run_pipeline(j, v, no_pipe);
500 possible_command::possible_command()
501 : name(0), argv(0)
505 possible_command::~possible_command()
507 a_delete name;
508 a_delete argv;
511 void possible_command::set_name(const char *s)
513 a_delete name;
514 name = strsave(s);
517 void possible_command::clear_name()
519 a_delete name;
520 a_delete argv;
521 name = NULL;
522 argv = NULL;
525 void possible_command::set_name(const char *s1, const char *s2)
527 a_delete name;
528 name = new char[strlen(s1) + strlen(s2) + 1];
529 strcpy(name, s1);
530 strcat(name, s2);
533 const char *possible_command::get_name()
535 return name;
538 void possible_command::clear_args()
540 args.clear();
543 void possible_command::append_arg(const char *s, const char *t)
545 args += s;
546 if (t)
547 args += t;
548 args += '\0';
551 void possible_command::insert_arg(const char *s)
553 string str(s);
554 str += '\0';
555 str += args;
556 args = str;
559 void possible_command::insert_args(string s)
561 const char *p = s.contents();
562 const char *end = p + s.length();
563 int l = 0;
564 if (p >= end)
565 return;
566 // find the total number of arguments in our string
567 do {
568 l++;
569 p = strchr(p, '\0') + 1;
570 } while (p < end);
571 // now insert each argument preserving the order
572 for (int i = l - 1; i >= 0; i--) {
573 p = s.contents();
574 for (int j = 0; j < i; j++)
575 p = strchr(p, '\0') + 1;
576 insert_arg(p);
580 void possible_command::build_argv()
582 if (argv)
583 return;
584 // Count the number of arguments.
585 int len = args.length();
586 int argc = 1;
587 char *p = 0;
588 if (len > 0) {
589 p = &args[0];
590 for (int i = 0; i < len; i++)
591 if (p[i] == '\0')
592 argc++;
594 // Build an argument vector.
595 argv = new char *[argc + 1];
596 argv[0] = name;
597 for (int i = 1; i < argc; i++) {
598 argv[i] = p;
599 p = strchr(p, '\0') + 1;
601 argv[argc] = 0;
604 void possible_command::print(int is_last, FILE *fp)
606 build_argv();
607 if (IS_BSHELL(argv[0])
608 && argv[1] != 0 && strcmp(argv[1], BSHELL_DASH_C) == 0
609 && argv[2] != 0 && argv[3] == 0)
610 fputs(argv[2], fp);
611 else {
612 fputs(argv[0], fp);
613 string str;
614 for (int i = 1; argv[i] != 0; i++) {
615 str.clear();
616 append_arg_to_string(argv[i], str);
617 put_string(str, fp);
620 if (is_last)
621 putc('\n', fp);
622 else
623 fputs(" | ", fp);
626 void append_arg_to_string(const char *arg, string &str) // TODO quoting -> lib
628 str += ' ';
629 int needs_quoting = 0;
630 int contains_single_quote = 0;
631 const char*p;
632 for (p = arg; *p != '\0'; p++)
633 switch (*p) {
634 case ';':
635 case '&':
636 case '(':
637 case ')':
638 case '|':
639 case '^':
640 case '<':
641 case '>':
642 case '\n':
643 case ' ':
644 case '\t':
645 case '\\':
646 case '"':
647 case '$':
648 case '?':
649 case '*':
650 needs_quoting = 1;
651 break;
652 case '\'':
653 contains_single_quote = 1;
654 break;
656 if (contains_single_quote || arg[0] == '\0') {
657 str += '"';
658 for (p = arg; *p != '\0'; p++)
659 switch (*p) {
660 case '"':
661 case '\\':
662 case '$':
663 str += '\\';
664 // fall through
665 default:
666 str += *p;
667 break;
669 str += '"';
671 else if (needs_quoting) {
672 str += '\'';
673 str += arg;
674 str += '\'';
676 else
677 str += arg;
680 char **possible_command::get_argv()
682 build_argv();
683 return argv;
686 void synopsis(FILE *stream)
688 fprintf(stream,
689 "Synopsis: %s [-abceghiklpstvzCENRSUVXZ] [-Fdir] [-mname] [-Tdev] [-ffam]\n"
690 " [-wname] [-Wname] [-Mdir] [-dcs] [-rcn] [-nnum] [-olist] [-Parg]\n"
691 " [-Darg] [-Karg] [-Larg] [-Idir] [files...]\n",
692 program_name);
695 void help()
697 synopsis(stdout);
698 fputs("\n"
699 "-h\tprint this message\n"
700 "-k\tpreprocess with preconv\n"
701 "-t\tpreprocess with tbl\n"
702 "-p\tpreprocess with pic\n"
703 "-e\tpreprocess with eqn\n"
704 "-g\tpreprocess with grn\n"
705 "-G\tpreprocess with grap\n"
706 "-s\tpreprocess with soelim\n"
707 "-R\tpreprocess with refer\n"
708 "-Tdev\tuse device dev\n"
709 "-mname\tread macros tmac.name\n"
710 "-dcs\tdefine a string c as s\n"
711 "-rcn\tdefine a number register c as n\n"
712 "-nnum\tnumber first page n\n"
713 "-olist\toutput only pages in list\n"
714 "-ffam\tuse fam as the default font family\n"
715 "-Fdir\tsearch dir for device directories\n"
716 "-Mdir\tsearch dir for macro files\n"
717 "-v\tprint version number\n"
718 "-z\tsuppress formatted output\n"
719 "-Z\tdon't postprocess\n"
720 "-a\tproduce ASCII description of output\n"
721 "-i\tread standard input after named input files\n"
722 "-wname\tenable warning name\n"
723 "-Wname\tinhibit warning name\n"
724 "-E\tinhibit all errors\n"
725 "-b\tprint backtraces with errors or warnings\n"
726 "-l\tspool the output\n"
727 "-c\tdisable color output\n"
728 "-C\tenable compatibility mode\n"
729 "-V\tprint commands on stdout instead of running them\n"
730 "-Parg\tpass arg to the postprocessor\n"
731 "-Larg\tpass arg to the spooler\n"
732 "-N\tdon't allow newlines within eqn delimiters\n"
733 "-S\tenable safer mode (the default)\n"
734 "-U\tenable unsafe mode\n"
735 "-Idir\tsearch dir for soelim, troff, and grops. Implies -s\n"
736 "-Karg\tuse arg as input encoding. Implies -k\n"
737 "-Darg\tuse arg as default input encoding. Implies -k\n"
738 "\n",
739 stdout);
740 exit(0);
743 void usage(FILE *stream)
745 synopsis(stream);
746 fprintf(stream, "%s -h gives more help\n", program_name);
749 extern "C" {
750 void c_error(const char *format, const char *arg1, const char *arg2,
751 const char *arg3)
753 error(format, arg1, arg2, arg3);
756 void c_fatal(const char *format, const char *arg1, const char *arg2,
757 const char *arg3)
759 fatal(format, arg1, arg2, arg3);
761 } // extern "C"
763 // s-it2-mode