1210284Sjmallett// -*- C++ -*-
2232812Sjmallett/* Copyright (C) 1989-2000, 2001, 2002, 2003, 2004
3215990Sjmallett   Free Software Foundation, Inc.
4210284Sjmallett     Written by James Clark (jjc@jclark.com)
5210284Sjmallett
6215990SjmallettThis file is part of groff.
7215990Sjmallett
8215990Sjmallettgroff is free software; you can redistribute it and/or modify it under
9210284Sjmallettthe terms of the GNU General Public License as published by the Free
10215990SjmallettSoftware Foundation; either version 2, or (at your option) any later
11215990Sjmallettversion.
12210284Sjmallett
13215990Sjmallettgroff is distributed in the hope that it will be useful, but WITHOUT ANY
14215990SjmallettWARRANTY; without even the implied warranty of MERCHANTABILITY or
15215990SjmallettFITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16215990Sjmallettfor more details.
17215990Sjmallett
18232812SjmallettYou should have received a copy of the GNU General Public License along
19215990Sjmallettwith groff; see the file COPYING.  If not, write to the Free Software
20215990SjmallettFoundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21215990Sjmallett
22215990Sjmallett// A front end for groff.
23215990Sjmallett
24215990Sjmallett#include "lib.h"
25215990Sjmallett
26215990Sjmallett#include <stdlib.h>
27215990Sjmallett#include <signal.h>
28215990Sjmallett#include <errno.h>
29232812Sjmallett
30215990Sjmallett#include "assert.h"
31215990Sjmallett#include "errarg.h"
32215990Sjmallett#include "error.h"
33215990Sjmallett#include "stringclass.h"
34215990Sjmallett#include "cset.h"
35215990Sjmallett#include "font.h"
36215990Sjmallett#include "device.h"
37215990Sjmallett#include "pipeline.h"
38210284Sjmallett#include "nonposix.h"
39210284Sjmallett#include "defs.h"
40210284Sjmallett
41210284Sjmallett#define GXDITVIEW "gxditview"
42210284Sjmallett
43210284Sjmallett// troff will be passed an argument of -rXREG=1 if the -X option is
44210284Sjmallett// specified
45215990Sjmallett#define XREG ".X"
46210284Sjmallett
47210284Sjmallett#ifdef NEED_DECLARATION_PUTENV
48210284Sjmallettextern "C" {
49210284Sjmallett  int putenv(const char *);
50210284Sjmallett}
51232812Sjmallett#endif /* NEED_DECLARATION_PUTENV */
52210284Sjmallett
53210284Sjmallett// The number of commands must be in sync with MAX_COMMANDS in pipeline.h
54210284Sjmallettconst int SOELIM_INDEX = 0;
55210284Sjmallettconst int REFER_INDEX = SOELIM_INDEX + 1;
56210284Sjmallettconst int GRAP_INDEX = REFER_INDEX + 1;
57210284Sjmallettconst int PIC_INDEX = GRAP_INDEX + 1;
58210284Sjmallettconst int TBL_INDEX = PIC_INDEX + 1;
59210284Sjmallettconst int GRN_INDEX = TBL_INDEX + 1;
60215990Sjmallettconst int EQN_INDEX = GRN_INDEX + 1;
61210284Sjmallettconst int TROFF_INDEX = EQN_INDEX + 1;
62210284Sjmallettconst int POST_INDEX = TROFF_INDEX + 1;
63210284Sjmallettconst int SPOOL_INDEX = POST_INDEX + 1;
64210284Sjmallett
65210284Sjmallettconst int NCOMMANDS = SPOOL_INDEX + 1;
66210284Sjmallett
67210284Sjmallettclass possible_command {
68210284Sjmallett  char *name;
69210284Sjmallett  string args;
70210284Sjmallett  char **argv;
71210284Sjmallett
72210284Sjmallett  void build_argv();
73210284Sjmallettpublic:
74210284Sjmallett  possible_command();
75210284Sjmallett  ~possible_command();
76210284Sjmallett  void set_name(const char *);
77210284Sjmallett  void set_name(const char *, const char *);
78210284Sjmallett  const char *get_name();
79210284Sjmallett  void append_arg(const char *, const char * = 0);
80210284Sjmallett  void insert_arg(const char *);
81210284Sjmallett  void insert_args(string s);
82210284Sjmallett  void clear_args();
83210284Sjmallett  char **get_argv();
84210284Sjmallett  void print(int is_last, FILE *fp);
85210284Sjmallett};
86210284Sjmallett
87215990Sjmallettextern "C" const char *Version_string;
88210284Sjmallett
89210284Sjmallettint lflag = 0;
90210284Sjmallettchar *spooler = 0;
91210284Sjmallettchar *postdriver = 0;
92210284Sjmallettchar *predriver = 0;
93232812Sjmallett
94210284Sjmallettpossible_command commands[NCOMMANDS];
95210284Sjmallett
96215990Sjmallettint run_commands(int no_pipe);
97232812Sjmallettvoid print_commands(FILE *);
98210284Sjmallettvoid append_arg_to_string(const char *arg, string &str);
99232812Sjmallettvoid handle_unknown_desc_command(const char *command, const char *arg,
100232812Sjmallett				 const char *filename, int lineno);
101232812Sjmallettconst char *xbasename(const char *);
102232812Sjmallett
103232812Sjmallettvoid usage(FILE *stream);
104232812Sjmallettvoid help();
105232812Sjmallett
106232812Sjmallettint main(int argc, char **argv)
107215990Sjmallett{
108215990Sjmallett  program_name = argv[0];
109215990Sjmallett  static char stderr_buf[BUFSIZ];
110210284Sjmallett  setbuf(stderr, stderr_buf);
111215990Sjmallett  assert(NCOMMANDS <= MAX_COMMANDS);
112215990Sjmallett  string Pargs, Largs, Fargs;
113210284Sjmallett  int vflag = 0;
114215990Sjmallett  int Vflag = 0;
115210284Sjmallett  int zflag = 0;
116210284Sjmallett  int iflag = 0;
117210284Sjmallett  int Xflag = 0;
118210284Sjmallett  int oflag = 0;
119210284Sjmallett  int safer_flag = 1;
120210284Sjmallett  int opt;
121210284Sjmallett  const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
122232812Sjmallett  if (!command_prefix)
123232812Sjmallett    command_prefix = PROG_PREFIX;
124232812Sjmallett  commands[TROFF_INDEX].set_name(command_prefix, "troff");
125232812Sjmallett  static const struct option long_options[] = {
126232812Sjmallett    { "help", no_argument, 0, 'h' },
127232812Sjmallett    { "version", no_argument, 0, 'v' },
128232812Sjmallett    { NULL, 0, 0, 0 }
129232812Sjmallett  };
130232812Sjmallett  while ((opt = getopt_long(argc, argv,
131232812Sjmallett			    "abcCd:eEf:F:gGhiI:lL:m:M:n:No:pP:r:RsStT:UvVw:W:XzZ",
132232812Sjmallett			    long_options, NULL))
133232812Sjmallett	 != EOF) {
134232812Sjmallett    char buf[3];
135215990Sjmallett    buf[0] = '-';
136210284Sjmallett    buf[1] = opt;
137210284Sjmallett    buf[2] = '\0';
138232812Sjmallett    switch (opt) {
139232812Sjmallett    case 'i':
140210284Sjmallett      iflag = 1;
141210284Sjmallett      break;
142210284Sjmallett    case 'I':
143210284Sjmallett      commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
144215990Sjmallett      commands[SOELIM_INDEX].append_arg(buf, optarg);
145210284Sjmallett      // .psbb may need to search for files
146210284Sjmallett      commands[TROFF_INDEX].append_arg(buf, optarg);
147210284Sjmallett      // \X'ps:import' may need to search for files
148210284Sjmallett      Pargs += buf;
149210284Sjmallett      Pargs += optarg;
150210284Sjmallett      Pargs += '\0';
151210284Sjmallett      break;
152210284Sjmallett    case 't':
153210284Sjmallett      commands[TBL_INDEX].set_name(command_prefix, "tbl");
154210284Sjmallett      break;
155210284Sjmallett    case 'p':
156210284Sjmallett      commands[PIC_INDEX].set_name(command_prefix, "pic");
157210284Sjmallett      break;
158210284Sjmallett    case 'g':
159210284Sjmallett      commands[GRN_INDEX].set_name(command_prefix, "grn");
160215990Sjmallett      break;
161210284Sjmallett    case 'G':
162210284Sjmallett      commands[GRAP_INDEX].set_name(command_prefix, "grap");
163210284Sjmallett      break;
164210284Sjmallett    case 'e':
165210284Sjmallett      commands[EQN_INDEX].set_name(command_prefix, "eqn");
166210284Sjmallett      break;
167210284Sjmallett    case 's':
168210284Sjmallett      commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
169215990Sjmallett      break;
170210284Sjmallett    case 'R':
171210284Sjmallett      commands[REFER_INDEX].set_name(command_prefix, "refer");
172210284Sjmallett      break;
173210284Sjmallett    case 'z':
174210284Sjmallett    case 'a':
175210284Sjmallett      commands[TROFF_INDEX].append_arg(buf);
176210284Sjmallett      // fall through
177210284Sjmallett    case 'Z':
178210284Sjmallett      zflag++;
179232812Sjmallett      break;
180210284Sjmallett    case 'l':
181232812Sjmallett      lflag++;
182232812Sjmallett      break;
183232812Sjmallett    case 'V':
184232812Sjmallett      Vflag++;
185232812Sjmallett      break;
186232812Sjmallett    case 'v':
187210284Sjmallett      vflag = 1;
188232812Sjmallett      {
189232812Sjmallett	printf("GNU groff version %s\n", Version_string);
190232812Sjmallett	printf("Copyright (C) 2004 Free Software Foundation, Inc.\n"
191232812Sjmallett	       "GNU groff comes with ABSOLUTELY NO WARRANTY.\n"
192232812Sjmallett	       "You may redistribute copies of groff and its subprograms\n"
193232812Sjmallett	       "under the terms of the GNU General Public License.\n"
194232812Sjmallett	       "For more information about these matters, see the file named COPYING.\n");
195232812Sjmallett	printf("\ncalled subprograms:\n\n");
196232812Sjmallett        fflush(stdout);
197232812Sjmallett      }
198232812Sjmallett      commands[POST_INDEX].append_arg(buf);
199232812Sjmallett      // fall through
200232812Sjmallett    case 'C':
201232812Sjmallett      commands[SOELIM_INDEX].append_arg(buf);
202232812Sjmallett      commands[REFER_INDEX].append_arg(buf);
203232812Sjmallett      commands[PIC_INDEX].append_arg(buf);
204232812Sjmallett      commands[GRAP_INDEX].append_arg(buf);
205232812Sjmallett      commands[TBL_INDEX].append_arg(buf);
206232812Sjmallett      commands[GRN_INDEX].append_arg(buf);
207232812Sjmallett      commands[EQN_INDEX].append_arg(buf);
208232812Sjmallett      commands[TROFF_INDEX].append_arg(buf);
209232812Sjmallett      break;
210232812Sjmallett    case 'N':
211232812Sjmallett      commands[EQN_INDEX].append_arg(buf);
212232812Sjmallett      break;
213232812Sjmallett    case 'h':
214232812Sjmallett      help();
215232812Sjmallett      break;
216232812Sjmallett    case 'E':
217232812Sjmallett    case 'b':
218232812Sjmallett      commands[TROFF_INDEX].append_arg(buf);
219232812Sjmallett      break;
220232812Sjmallett    case 'c':
221232812Sjmallett      commands[TROFF_INDEX].append_arg(buf);
222232812Sjmallett      break;
223232812Sjmallett    case 'S':
224232812Sjmallett      safer_flag = 1;
225232812Sjmallett      break;
226232812Sjmallett    case 'U':
227232812Sjmallett      safer_flag = 0;
228232812Sjmallett      break;
229232812Sjmallett    case 'T':
230210284Sjmallett      if (strcmp(optarg, "html") == 0) {
231210284Sjmallett	// force soelim to aid the html preprocessor
232210284Sjmallett	commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
233210284Sjmallett      }
234210284Sjmallett      if (strcmp(optarg, "Xps") == 0) {
235210284Sjmallett	warning("-TXps option is obsolete: use -X -Tps instead");
236210284Sjmallett	device = "ps";
237210284Sjmallett	Xflag++;
238210284Sjmallett      }
239210284Sjmallett      else
240210284Sjmallett	device = optarg;
241210284Sjmallett      break;
242210284Sjmallett    case 'F':
243232812Sjmallett      font::command_line_font_dir(optarg);
244210284Sjmallett      if (Fargs.length() > 0) {
245210284Sjmallett	Fargs += PATH_SEP_CHAR;
246210284Sjmallett	Fargs += optarg;
247210284Sjmallett      }
248215990Sjmallett      else
249210284Sjmallett	Fargs = optarg;
250210284Sjmallett      break;
251210284Sjmallett    case 'o':
252210284Sjmallett      oflag = 1;
253210284Sjmallett    case 'f':
254210284Sjmallett    case 'm':
255210284Sjmallett    case 'r':
256210284Sjmallett    case 'd':
257210284Sjmallett    case 'n':
258210284Sjmallett    case 'w':
259232812Sjmallett    case 'W':
260210284Sjmallett      commands[TROFF_INDEX].append_arg(buf, optarg);
261210284Sjmallett      break;
262210284Sjmallett    case 'M':
263210284Sjmallett      commands[EQN_INDEX].append_arg(buf, optarg);
264210284Sjmallett      commands[GRAP_INDEX].append_arg(buf, optarg);
265210284Sjmallett      commands[GRN_INDEX].append_arg(buf, optarg);
266210284Sjmallett      commands[TROFF_INDEX].append_arg(buf, optarg);
267210284Sjmallett      break;
268210284Sjmallett    case 'P':
269210284Sjmallett      Pargs += optarg;
270210284Sjmallett      Pargs += '\0';
271210284Sjmallett      break;
272210284Sjmallett    case 'L':
273210284Sjmallett      append_arg_to_string(optarg, Largs);
274210284Sjmallett      break;
275210284Sjmallett    case 'X':
276210284Sjmallett      Xflag++;
277210284Sjmallett      break;
278210284Sjmallett    case '?':
279210284Sjmallett      usage(stderr);
280210284Sjmallett      exit(1);
281210284Sjmallett      break;
282210284Sjmallett    default:
283210284Sjmallett      assert(0);
284210284Sjmallett      break;
285210284Sjmallett    }
286210284Sjmallett  }
287210284Sjmallett  if (safer_flag)
288210284Sjmallett    commands[PIC_INDEX].append_arg("-S");
289210284Sjmallett  else
290210284Sjmallett    commands[TROFF_INDEX].insert_arg("-U");
291210284Sjmallett  font::set_unknown_desc_command_handler(handle_unknown_desc_command);
292210284Sjmallett  if (!font::load_desc())
293210284Sjmallett    fatal("invalid device `%1'", device);
294210284Sjmallett  if (!postdriver)
295210284Sjmallett    fatal("no `postpro' command in DESC file for device `%1'", device);
296210284Sjmallett  if (predriver && !zflag) {
297210284Sjmallett    commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
298210284Sjmallett    commands[TROFF_INDEX].set_name(predriver);
299210284Sjmallett    // pass the device arguments to the predrivers as well
300210284Sjmallett    commands[TROFF_INDEX].insert_args(Pargs);
301210284Sjmallett    if (vflag)
302210284Sjmallett      commands[TROFF_INDEX].insert_arg("-v");
303210284Sjmallett  }
304210284Sjmallett  const char *real_driver = 0;
305210284Sjmallett  if (Xflag) {
306210284Sjmallett    real_driver = postdriver;
307210284Sjmallett    postdriver = (char *)GXDITVIEW;
308210284Sjmallett    commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
309210284Sjmallett  }
310210284Sjmallett  if (postdriver)
311210284Sjmallett    commands[POST_INDEX].set_name(postdriver);
312210284Sjmallett  int gxditview_flag = postdriver && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
313210284Sjmallett  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
427const 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
447void 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
489void 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
502int 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
512possible_command::possible_command()
513: name(0), argv(0)
514{
515}
516
517possible_command::~possible_command()
518{
519  a_delete name;
520  a_delete argv;
521}
522
523void possible_command::set_name(const char *s)
524{
525  a_delete name;
526  name = strsave(s);
527}
528
529void 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
537const char *possible_command::get_name()
538{
539  return name;
540}
541
542void possible_command::clear_args()
543{
544  args.clear();
545}
546
547void 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
555void possible_command::insert_arg(const char *s)
556{
557  string str(s);
558  str += '\0';
559  str += args;
560  args = str;
561}
562
563void 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
584void 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
608void 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
630void 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
684char **possible_command::get_argv()
685{
686  build_argv();
687  return argv;
688}
689
690void 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
699void 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
745void usage(FILE *stream)
746{
747  synopsis(stream);
748  fprintf(stream, "%s -h gives more help\n", program_name);
749}
750
751extern "C" {
752
753void c_error(const char *format, const char *arg1, const char *arg2,
754	     const char *arg3)
755{
756  error(format, arg1, arg2, arg3);
757}
758
759void 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}
766