1/*	$NetBSD: info.c,v 1.2 2016/01/14 00:34:52 christos Exp $	*/
2
3/* info.c -- Display nodes of Info files in multiple windows.
4   Id: info.c,v 1.11 2004/04/11 17:56:45 karl Exp
5
6   Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
7   2004 Free Software Foundation, Inc.
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2, or (at your option)
12   any later version.
13
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23   Written by Brian Fox (bfox@ai.mit.edu). */
24
25#include "info.h"
26#include "indices.h"
27#include "dribble.h"
28#include "getopt.h"
29#if defined (HANDLE_MAN_PAGES)
30#  include "man.h"
31#endif /* HANDLE_MAN_PAGES */
32
33static char *program_name = "info";
34
35/* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
36static int apropos_p = 0;
37
38/* Variable containing the string to search for when apropos_p is non-zero. */
39static char *apropos_search_string = (char *)NULL;
40
41/* Non-zero means search all indices for INDEX_SEARCH_STRING.  Unlike
42   apropos, this puts the user at the node, running info. */
43static int index_search_p = 0;
44
45/* Non-zero means look for the node which describes the invocation
46   and command-line options of the program, and start the info
47   session at that node.  */
48static int goto_invocation_p = 0;
49
50/* Variable containing the string to search for when index_search_p is
51   non-zero. */
52static char *index_search_string = (char *)NULL;
53
54/* Non-zero means print version info only. */
55static int print_version_p = 0;
56
57/* Non-zero means print a short description of the options. */
58static int print_help_p = 0;
59
60/* Array of the names of nodes that the user specified with "--node" on the
61   command line. */
62static char **user_nodenames = (char **)NULL;
63static int user_nodenames_index = 0;
64static int user_nodenames_slots = 0;
65
66/* String specifying the first file to load.  This string can only be set
67   by the user specifying "--file" on the command line. */
68static char *user_filename = (char *)NULL;
69
70/* String specifying the name of the file to dump nodes to.  This value is
71   filled if the user speficies "--output" on the command line. */
72static char *user_output_filename = (char *)NULL;
73
74/* Non-zero indicates that when "--output" is specified, all of the menu
75   items of the specified nodes (and their subnodes as well) should be
76   dumped in the order encountered.  This basically can print a book. */
77int dump_subnodes = 0;
78
79/* Non-zero means make default keybindings be loosely modeled on vi(1).  */
80int vi_keys_p = 0;
81
82/* Non-zero means don't remove ANSI escape sequences.  */
83int raw_escapes_p = 1;
84
85/* Non-zero means print the absolute location of the file to be loaded.  */
86static int print_where_p = 0;
87
88#ifdef __MSDOS__
89/* Non-zero indicates that screen output should be made 'speech-friendly'.
90   Since on MSDOS the usual behavior is to write directly to the video
91   memory, speech synthesizer software cannot grab the output.  Therefore,
92   we provide a user option which tells us to avoid direct screen output
93   and use stdout instead (which loses the color output).  */
94int speech_friendly = 0;
95#endif
96
97/* Structure describing the options that Info accepts.  We pass this structure
98   to getopt_long ().  If you add or otherwise change this structure, you must
99   also change the string which follows it. */
100#define APROPOS_OPTION 1
101#define DRIBBLE_OPTION 2
102#define RESTORE_OPTION 3
103#define IDXSRCH_OPTION 4
104static struct option long_options[] = {
105  { "apropos", 1, 0, APROPOS_OPTION },
106  { "directory", 1, 0, 'd' },
107  { "dribble", 1, 0, DRIBBLE_OPTION },
108  { "file", 1, 0, 'f' },
109  { "help", 0, &print_help_p, 1 },
110  { "index-search", 1, 0, IDXSRCH_OPTION },
111  { "location", 0, &print_where_p, 1 },
112  { "node", 1, 0, 'n' },
113  { "output", 1, 0, 'o' },
114  { "raw-escapes", 0, &raw_escapes_p, 1 },
115  { "no-raw-escapes", 0, &raw_escapes_p, 0 },
116  { "restore", 1, 0, RESTORE_OPTION },
117  { "show-options", 0, 0, 'O' },
118  { "subnodes", 0, &dump_subnodes, 1 },
119  { "usage", 0, 0, 'O' },
120  { "version", 0, &print_version_p, 1 },
121  { "vi-keys", 0, &vi_keys_p, 1 },
122  { "where", 0, &print_where_p, 1 },
123#ifdef __MSDOS__
124  { "speech-friendly", 0, &speech_friendly, 1 },
125#endif
126  {NULL, 0, NULL, 0}
127};
128
129/* String describing the shorthand versions of the long options found above. */
130#ifdef __MSDOS__
131static char *short_options = "d:n:f:ho:ORswb";
132#else
133static char *short_options = "d:n:f:ho:ORws";
134#endif
135
136/* When non-zero, the Info window system has been initialized. */
137int info_windows_initialized_p = 0;
138
139/* Some "forward" declarations. */
140static void info_short_help (void);
141static void init_messages (void);
142
143
144/* **************************************************************** */
145/*                                                                  */
146/*                Main Entry Point to the Info Program              */
147/*                                                                  */
148/* **************************************************************** */
149
150int
151main (int argc, char **argv)
152{
153  int getopt_long_index;        /* Index returned by getopt_long (). */
154  NODE *initial_node;           /* First node loaded by Info. */
155
156#ifdef HAVE_SETLOCALE
157  /* Set locale via LC_ALL.  */
158  setlocale (LC_ALL, "");
159#endif
160
161#ifdef ENABLE_NLS
162  /* Set the text message domain.  */
163  bindtextdomain (PACKAGE, LOCALEDIR);
164  textdomain (PACKAGE);
165#endif
166
167  init_messages ();
168
169  while (1)
170    {
171      int option_character;
172
173      option_character = getopt_long
174        (argc, argv, short_options, long_options, &getopt_long_index);
175
176      /* getopt_long returns EOF when there are no more long options. */
177      if (option_character == EOF)
178        break;
179
180      /* If this is a long option, then get the short version of it. */
181      if (option_character == 0 && long_options[getopt_long_index].flag == 0)
182        option_character = long_options[getopt_long_index].val;
183
184      /* Case on the option that we have received. */
185      switch (option_character)
186        {
187        case 0:
188          break;
189
190          /* User wants to add a directory. */
191        case 'd':
192          info_add_path (optarg, INFOPATH_PREPEND);
193          break;
194
195          /* User is specifying a particular node. */
196        case 'n':
197          add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
198                                user_nodenames_slots, 10, char *);
199          break;
200
201          /* User is specifying a particular Info file. */
202        case 'f':
203          if (user_filename)
204            free (user_filename);
205
206          user_filename = xstrdup (optarg);
207          break;
208
209          /* Treat -h like --help. */
210        case 'h':
211          print_help_p = 1;
212          break;
213
214          /* User is specifying the name of a file to output to. */
215        case 'o':
216          if (user_output_filename)
217            free (user_output_filename);
218          user_output_filename = xstrdup (optarg);
219          break;
220
221         /* User has specified that she wants to find the "Options"
222             or "Invocation" node for the program.  */
223        case 'O':
224          goto_invocation_p = 1;
225          break;
226
227	  /* User has specified that she wants the escape sequences
228	     in man pages to be passed thru unaltered.  */
229        case 'R':
230          raw_escapes_p = 1;
231          break;
232
233          /* User is specifying that she wishes to dump the subnodes of
234             the node that she is dumping. */
235        case 's':
236          dump_subnodes = 1;
237          break;
238
239          /* For compatibility with man, -w is --where.  */
240        case 'w':
241          print_where_p = 1;
242          break;
243
244#ifdef __MSDOS__
245	  /* User wants speech-friendly output.  */
246	case 'b':
247	  speech_friendly = 1;
248	  break;
249#endif /* __MSDOS__ */
250
251          /* User has specified a string to search all indices for. */
252        case APROPOS_OPTION:
253          apropos_p = 1;
254          maybe_free (apropos_search_string);
255          apropos_search_string = xstrdup (optarg);
256          break;
257
258          /* User has specified a dribble file to receive keystrokes. */
259        case DRIBBLE_OPTION:
260          close_dribble_file ();
261          open_dribble_file (optarg);
262          break;
263
264          /* User has specified an alternate input stream. */
265        case RESTORE_OPTION:
266          info_set_input_from_file (optarg);
267          break;
268
269          /* User has specified a string to search all indices for. */
270        case IDXSRCH_OPTION:
271          index_search_p = 1;
272          maybe_free (index_search_string);
273          index_search_string = xstrdup (optarg);
274          break;
275
276        default:
277          fprintf (stderr, _("Try --help for more information.\n"));
278          xexit (1);
279        }
280    }
281
282  /* If the output device is not a terminal, and no output filename has been
283     specified, make user_output_filename be "-", so that the info is written
284     to stdout, and turn on the dumping of subnodes. */
285  if ((!isatty (fileno (stdout))) && (user_output_filename == (char *)NULL))
286    {
287      user_output_filename = xstrdup ("-");
288      dump_subnodes = 1;
289    }
290
291  /* If the user specified --version, then show the version and exit. */
292  if (print_version_p)
293    {
294      printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION);
295      puts ("");
296      puts ("Copyright (C) 2004 Free Software Foundation, Inc.");
297      printf (_("There is NO warranty.  You may redistribute this software\n\
298under the terms of the GNU General Public License.\n\
299For more information about these matters, see the files named COPYING.\n"));
300      xexit (0);
301    }
302
303  /* If the `--help' option was present, show the help and exit. */
304  if (print_help_p)
305    {
306      info_short_help ();
307      xexit (0);
308    }
309
310  /* If the user hasn't specified a path for Info files, default it.
311     Lowest priority is our messy hardwired list in filesys.h.
312     Then comes the user's INFODIR from the Makefile.
313     Highest priority is the environment variable, if set.  */
314  if (!infopath)
315    {
316      char *path_from_env = getenv ("INFOPATH");
317
318      if (path_from_env)
319        {
320          unsigned len = strlen (path_from_env);
321          /* Trailing : on INFOPATH means insert the default path.  */
322          if (len && path_from_env[len - 1] == PATH_SEP[0])
323            {
324              path_from_env[len - 1] = 0;
325              info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
326            }
327#ifdef INFODIR /* from the Makefile */
328          info_add_path (INFODIR, INFOPATH_PREPEND);
329#endif
330          info_add_path (path_from_env, INFOPATH_PREPEND);
331        }
332      else
333        {
334          info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
335#ifdef INFODIR /* from the Makefile */
336          info_add_path (INFODIR, INFOPATH_PREPEND);
337#endif
338#ifdef INFODIR2 /* from the Makefile, too */
339#  ifdef INFODIR
340          if (!STREQ (INFODIR, INFODIR2))
341#  endif
342            info_add_path (INFODIR2, INFOPATH_PREPEND);
343#endif
344        }
345    }
346
347  /* If the user specified a particular filename, add the path of that
348     file to the contents of INFOPATH. */
349  if (user_filename)
350    add_file_directory_to_path (user_filename);
351
352  /* If the user wants to search every known index for a given string,
353     do that now, and report the results. */
354  if (apropos_p)
355    {
356      info_apropos (apropos_search_string);
357      xexit (0);
358    }
359
360  /* Get the initial Info node.  It is either "(dir)Top", or what the user
361     specified with values in user_filename and user_nodenames. */
362  initial_node = info_get_node (user_filename,
363                                user_nodenames ? user_nodenames[0] : 0);
364
365  /* If we couldn't get the initial node, this user is in trouble. */
366  if (!initial_node)
367    {
368      if (info_recent_file_error)
369        info_error (info_recent_file_error, NULL, NULL);
370      else
371        info_error ((char *) msg_cant_find_node,
372                    user_nodenames ? user_nodenames[0] : "Top", NULL);
373      xexit (1);
374    }
375
376  /* Special cases for when the user specifies multiple nodes.  If we
377     are dumping to an output file, dump all of the nodes specified.
378     Otherwise, attempt to create enough windows to handle the nodes
379     that this user wants displayed. */
380  if (user_nodenames_index > 1)
381    {
382      free (initial_node);
383
384      if (print_where_p)
385        printf ("%s\n", user_filename ? user_filename : "unknown?!");
386      else if (user_output_filename)
387        dump_nodes_to_file
388          (user_filename, user_nodenames, user_output_filename, dump_subnodes);
389      else
390        begin_multiple_window_info_session (user_filename, user_nodenames);
391
392      xexit (0);
393    }
394
395  /* If there are arguments remaining, they are the names of menu items
396     in sequential info files starting from the first one loaded.  That
397     file name is either "dir", or the contents of user_filename if one
398     was specified. */
399  {
400    const char *errstr;
401    char *errarg1, *errarg2;
402
403    NODE *new_initial_node = info_follow_menus (initial_node, argv + optind,
404        &errstr, &errarg1, &errarg2);
405
406    if (new_initial_node && new_initial_node != initial_node)
407      initial_node = new_initial_node;
408
409    if (print_where_p)
410      {
411        if (initial_node->parent)
412          printf ("%s\n", initial_node->parent);
413        else if (initial_node->filename
414            && !is_dir_name (filename_non_directory (initial_node->filename)))
415          printf ("%s\n", initial_node->filename);
416        else
417          xexit (1);
418        xexit (0);
419      }
420
421    /* If the user specified that this node should be output, then do that
422       now.  Otherwise, start the Info session with this node.  Or act
423       accordingly if the initial node was not found.  */
424    if (user_output_filename && !goto_invocation_p)
425      {
426        if (!errstr)
427          dump_node_to_file (initial_node, user_output_filename,
428                             dump_subnodes);
429        else
430          info_error ((char *) errstr, errarg1, errarg2);
431      }
432    else
433      {
434
435        if (errstr)
436          begin_info_session_with_error (initial_node, (char *) errstr,
437              errarg1, errarg2);
438        /* If the user specified `--index-search=STRING' or
439           --show-options, start the info session in the node
440           corresponding to what they want. */
441        else if (index_search_p || goto_invocation_p)
442          {
443            int status = 0;
444
445            initialize_info_session (initial_node, 0);
446
447            if (goto_invocation_p
448                || index_entry_exists (windows, index_search_string))
449              {
450                terminal_prep_terminal ();
451                terminal_clear_screen ();
452                info_last_executed_command = (VFunction *)NULL;
453
454                if (index_search_p)
455                  do_info_index_search (windows, 0, index_search_string);
456                else
457                  {
458                    /* If they said "info --show-options foo bar baz",
459                       the last of the arguments is the program whose
460                       options they want to see.  */
461                    char **p = argv + optind;
462                    char *program;
463
464                    if (*p)
465                      {
466                        while (p[1])
467                          p++;
468                        program = xstrdup (*p);
469                      }
470                    else if (user_filename)
471		      /* If there's no command-line arguments to
472			 supply the program name, use the Info file
473			 name (sans extension and leading directories)
474			 instead.  */
475		      program = program_name_from_file_name (user_filename);
476		    else
477		      program = xstrdup ("");
478
479                    info_intuit_options_node (windows, initial_node, program);
480                    free (program);
481                  }
482
483		if (user_output_filename)
484		  {
485		    dump_node_to_file (windows->node, user_output_filename,
486				       dump_subnodes);
487		  }
488		else
489		  info_read_and_dispatch ();
490
491                /* On program exit, leave the cursor at the bottom of the
492                   window, and restore the terminal IO. */
493                terminal_goto_xy (0, screenheight - 1);
494                terminal_clear_to_eol ();
495                fflush (stdout);
496                terminal_unprep_terminal ();
497              }
498            else
499              {
500                fprintf (stderr, _("no index entries found for `%s'\n"),
501                         index_search_string);
502                status = 2;
503              }
504
505            close_dribble_file ();
506            xexit (status);
507          }
508        else
509          begin_info_session (initial_node);
510      }
511
512    xexit (0);
513  }
514
515  return 0; /* Avoid bogus warnings.  */
516}
517
518void
519add_file_directory_to_path (char *filename)
520{
521  char *directory_name = xstrdup (filename);
522  char *temp = filename_non_directory (directory_name);
523
524  if (temp != directory_name)
525    {
526      if (HAVE_DRIVE (directory_name) && temp == directory_name + 2)
527	{
528	  /* The directory of "d:foo" is stored as "d:.", to avoid
529	     mixing it with "d:/" when a slash is appended.  */
530	  *temp = '.';
531	  temp += 2;
532	}
533      temp[-1] = 0;
534      info_add_path (directory_name, INFOPATH_PREPEND);
535    }
536
537  free (directory_name);
538}
539
540
541/* Error handling.  */
542
543/* Non-zero if an error has been signalled. */
544int info_error_was_printed = 0;
545
546/* Non-zero means ring terminal bell on errors. */
547int info_error_rings_bell_p = 1;
548
549/* Print FORMAT with ARG1 and ARG2.  If the window system was initialized,
550   then the message is printed in the echo area.  Otherwise, a message is
551   output to stderr. */
552void
553info_error (char *format, void *arg1, void *arg2)
554{
555  info_error_was_printed = 1;
556
557  if (!info_windows_initialized_p || display_inhibited)
558    {
559      fprintf (stderr, "%s: ", program_name);
560      fprintf (stderr, format, arg1, arg2);
561      fprintf (stderr, "\n");
562      fflush (stderr);
563    }
564  else
565    {
566      if (!echo_area_is_active)
567        {
568          if (info_error_rings_bell_p)
569            terminal_ring_bell ();
570          window_message_in_echo_area (format, arg1, arg2);
571        }
572      else
573        {
574          NODE *temp;
575
576          temp = build_message_node (format, arg1, arg2);
577          if (info_error_rings_bell_p)
578            terminal_ring_bell ();
579          inform_in_echo_area (temp->contents);
580          free (temp->contents);
581          free (temp);
582        }
583    }
584}
585
586
587/* Produce a scaled down description of the available options to Info. */
588static void
589info_short_help (void)
590{
591#ifdef __MSDOS__
592  static const char speech_friendly_string[] = N_("\
593  -b, --speech-friendly        be friendly to speech synthesizers.\n");
594#else
595  static const char speech_friendly_string[] = "";
596#endif
597
598
599  printf (_("\
600Usage: %s [OPTION]... [MENU-ITEM...]\n\
601\n\
602Read documentation in Info format.\n\
603\n\
604Options:\n\
605      --apropos=STRING         look up STRING in all indices of all manuals.\n\
606  -d, --directory=DIR          add DIR to INFOPATH.\n\
607      --dribble=FILENAME       remember user keystrokes in FILENAME.\n\
608  -f, --file=FILENAME          specify Info file to visit.\n\
609  -h, --help                   display this help and exit.\n\
610      --index-search=STRING    go to node pointed by index entry STRING.\n\
611  -n, --node=NODENAME          specify nodes in first visited Info file.\n\
612  -o, --output=FILENAME        output selected nodes to FILENAME.\n\
613  -R, --raw-escapes            output \"raw\" ANSI escapes (default).\n\
614      --no-raw-escapes         output escapes as literal text.\n\
615      --restore=FILENAME       read initial keystrokes from FILENAME.\n\
616  -O, --show-options, --usage  go to command-line options node.\n%s\
617      --subnodes               recursively output menu items.\n\
618  -w, --where, --location      print physical location of Info file.\n\
619      --vi-keys                use vi-like and less-like key bindings.\n\
620      --version                display version information and exit.\n\
621\n\
622The first non-option argument, if present, is the menu entry to start from;\n\
623it is searched for in all `dir' files along INFOPATH.\n\
624If it is not present, info merges all `dir' files and shows the result.\n\
625Any remaining arguments are treated as the names of menu\n\
626items relative to the initial node visited.\n\
627\n\
628Examples:\n\
629  info                       show top-level dir menu\n\
630  info emacs                 start at emacs node from top-level dir\n\
631  info emacs buffers         start at buffers node within emacs manual\n\
632  info --show-options emacs  start at node with emacs' command line options\n\
633  info -f ./foo.info         show file ./foo.info, not searching dir\n\
634"),
635  program_name, speech_friendly_string);
636
637  puts (_("\n\
638Email bug reports to bug-texinfo@gnu.org,\n\
639general questions and discussion to help-texinfo@gnu.org.\n\
640Texinfo home page: http://www.gnu.org/software/texinfo/"));
641
642  xexit (0);
643}
644
645
646/* Initialize strings for gettext.  Because gettext doesn't handle N_ or
647   _ within macro definitions, we put shared messages into variables and
648   use them that way.  This also has the advantage that there's only one
649   copy of the strings.  */
650
651const char *msg_cant_find_node;
652const char *msg_cant_file_node;
653const char *msg_cant_find_window;
654const char *msg_cant_find_point;
655const char *msg_cant_kill_last;
656const char *msg_no_menu_node;
657const char *msg_no_foot_node;
658const char *msg_no_xref_node;
659const char *msg_no_pointer;
660const char *msg_unknown_command;
661const char *msg_term_too_dumb;
662const char *msg_at_node_bottom;
663const char *msg_at_node_top;
664const char *msg_one_window;
665const char *msg_win_too_small;
666const char *msg_cant_make_help;
667
668static void
669init_messages (void)
670{
671  msg_cant_find_node   = _("Cannot find node `%s'.");
672  msg_cant_file_node   = _("Cannot find node `(%s)%s'.");
673  msg_cant_find_window = _("Cannot find a window!");
674  msg_cant_find_point  = _("Point doesn't appear within this window's node!");
675  msg_cant_kill_last   = _("Cannot delete the last window.");
676  msg_no_menu_node     = _("No menu in this node.");
677  msg_no_foot_node     = _("No footnotes in this node.");
678  msg_no_xref_node     = _("No cross references in this node.");
679  msg_no_pointer       = _("No `%s' pointer for this node.");
680  msg_unknown_command  = _("Unknown Info command `%c'; try `?' for help.");
681  msg_term_too_dumb    = _("Terminal type `%s' is not smart enough to run Info.");
682  msg_at_node_bottom   = _("You are already at the last page of this node.");
683  msg_at_node_top      = _("You are already at the first page of this node.");
684  msg_one_window       = _("Only one window.");
685  msg_win_too_small    = _("Resulting window would be too small.");
686  msg_cant_make_help   = _("Not enough room for a help window, please delete a window.");
687}
688