1/* info.c -- Display nodes of Info files in multiple windows.
2   $Id: info.c,v 1.1 2004/10/28 18:14:09 zooey Exp $
3
4   Copyright (C) 1993, 96, 97, 98 Free Software Foundation, Inc.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20   Written by Brian Fox (bfox@ai.mit.edu). */
21
22#include "info.h"
23#include "indices.h"
24#include "dribble.h"
25#include "getopt.h"
26#if defined (HANDLE_MAN_PAGES)
27#  include "man.h"
28#endif /* HANDLE_MAN_PAGES */
29
30/* The version numbers of this version of Info. */
31int info_major_version = 2;
32int info_minor_version = 18;
33
34/* basename (argv[0]) */
35static char *program_name = NULL;
36
37/* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
38static int apropos_p = 0;
39
40/* Variable containing the string to search for when apropos_p is non-zero. */
41static char *apropos_search_string = (char *)NULL;
42
43/* Non-zero means search all indices for INDEX_SEARCH_STRING.  Unlike
44   apropos, this puts the user at the node, running info. */
45static int index_search_p = 0;
46
47/* Variable containing the string to search for when index_search_p is
48   non-zero. */
49static char *index_search_string = (char *)NULL;
50
51/* Non-zero means print version info only. */
52static int print_version_p = 0;
53
54/* Non-zero means print a short description of the options. */
55static int print_help_p = 0;
56
57/* Array of the names of nodes that the user specified with "--node" on the
58   command line. */
59static char **user_nodenames = (char **)NULL;
60static int user_nodenames_index = 0;
61static int user_nodenames_slots = 0;
62
63/* String specifying the first file to load.  This string can only be set
64   by the user specifying "--file" on the command line. */
65static char *user_filename = (char *)NULL;
66
67/* String specifying the name of the file to dump nodes to.  This value is
68   filled if the user speficies "--output" on the command line. */
69static char *user_output_filename = (char *)NULL;
70
71/* Non-zero indicates that when "--output" is specified, all of the menu
72   items of the specified nodes (and their subnodes as well) should be
73   dumped in the order encountered.  This basically can print a book. */
74int dump_subnodes = 0;
75
76/* Structure describing the options that Info accepts.  We pass this structure
77   to getopt_long ().  If you add or otherwise change this structure, you must
78   also change the string which follows it. */
79#define APROPOS_OPTION 1
80#define DRIBBLE_OPTION 2
81#define RESTORE_OPTION 3
82#define IDXSRCH_OPTION 4
83static struct option long_options[] = {
84  { "apropos", 1, 0, APROPOS_OPTION },
85  { "directory", 1, 0, 'd' },
86  { "node", 1, 0, 'n' },
87  { "file", 1, 0, 'f' },
88  { "subnodes", 0, &dump_subnodes, 1 },
89  { "output", 1, 0, 'o' },
90  { "help", 0, &print_help_p, 1 },
91  { "version", 0, &print_version_p, 1 },
92  { "dribble", 1, 0, DRIBBLE_OPTION },
93  { "restore", 1, 0, RESTORE_OPTION },
94  { "index-search", 1, 0, IDXSRCH_OPTION },
95  {NULL, 0, NULL, 0}
96};
97
98/* String describing the shorthand versions of the long options found above. */
99static char *short_options = "d:n:f:o:s";
100
101/* When non-zero, the Info window system has been initialized. */
102int info_windows_initialized_p = 0;
103
104/* Some "forward" declarations. */
105static void info_short_help (), remember_info_program_name ();
106
107
108/* **************************************************************** */
109/*                                                                  */
110/*                Main Entry Point to the Info Program              */
111/*                                                                  */
112/* **************************************************************** */
113
114int
115main (argc, argv)
116     int argc;
117     char **argv;
118{
119  int getopt_long_index;        /* Index returned by getopt_long (). */
120  NODE *initial_node;           /* First node loaded by Info. */
121
122  remember_info_program_name (argv[0]);
123
124#ifdef HAVE_SETLOCALE
125  /* Set locale via LC_ALL.  */
126  setlocale (LC_ALL, "");
127#endif
128
129  /* Set the text message domain.  */
130  bindtextdomain (PACKAGE, LOCALEDIR);
131  textdomain (PACKAGE);
132
133  while (1)
134    {
135      int option_character;
136
137      option_character = getopt_long
138        (argc, argv, short_options, long_options, &getopt_long_index);
139
140      /* getopt_long () returns EOF when there are no more long options. */
141      if (option_character == EOF)
142        break;
143
144      /* If this is a long option, then get the short version of it. */
145      if (option_character == 0 && long_options[getopt_long_index].flag == 0)
146        option_character = long_options[getopt_long_index].val;
147
148      /* Case on the option that we have received. */
149      switch (option_character)
150        {
151        case 0:
152          break;
153
154          /* User wants to add a directory. */
155        case 'd':
156          info_add_path (optarg, INFOPATH_PREPEND);
157          break;
158
159          /* User is specifying a particular node. */
160        case 'n':
161          add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
162                                user_nodenames_slots, 10, char *);
163          break;
164
165          /* User is specifying a particular Info file. */
166        case 'f':
167          if (user_filename)
168            free (user_filename);
169
170          user_filename = xstrdup (optarg);
171          break;
172
173          /* User is specifying the name of a file to output to. */
174        case 'o':
175          if (user_output_filename)
176            free (user_output_filename);
177          user_output_filename = xstrdup (optarg);
178          break;
179
180          /* User is specifying that she wishes to dump the subnodes of
181             the node that she is dumping. */
182        case 's':
183          dump_subnodes = 1;
184          break;
185
186          /* User has specified a string to search all indices for. */
187        case APROPOS_OPTION:
188          apropos_p = 1;
189          maybe_free (apropos_search_string);
190          apropos_search_string = xstrdup (optarg);
191          break;
192
193          /* User has specified a dribble file to receive keystrokes. */
194        case DRIBBLE_OPTION:
195          close_dribble_file ();
196          open_dribble_file (optarg);
197          break;
198
199          /* User has specified an alternate input stream. */
200        case RESTORE_OPTION:
201          info_set_input_from_file (optarg);
202          break;
203
204          /* User has specified a string to search all indices for. */
205        case IDXSRCH_OPTION:
206          index_search_p = 1;
207          maybe_free (index_search_string);
208          index_search_string = xstrdup (optarg);
209          break;
210
211        default:
212          fprintf (stderr, _("Try --help for more information."));
213          exit (1);
214        }
215    }
216
217  /* If the output device is not a terminal, and no output filename has been
218     specified, make user_output_filename be "-", so that the info is written
219     to stdout, and turn on the dumping of subnodes. */
220  if ((!isatty (fileno (stdout))) && (user_output_filename == (char *)NULL))
221    {
222      user_output_filename = xstrdup ("-");
223      dump_subnodes = 1;
224    }
225
226  /* If the user specified --version, then show the version and exit. */
227  if (print_version_p)
228    {
229      printf ("%s (GNU %s %s) %s\n", program_name, PACKAGE, VERSION,
230              version_string ());
231      printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
232There is NO warranty.  You may redistribute this software\n\
233under the terms of the GNU General Public License.\n\
234For more information about these matters, see the files named COPYING.\n"),
235		  "1998");
236      exit (0);
237    }
238
239  /* If the `--help' option was present, show the help and exit. */
240  if (print_help_p)
241    {
242      info_short_help ();
243      exit (0);
244    }
245
246  /* If the user hasn't specified a path for Info files, default it.
247     Lowest priority is our messy hardwired list in filesys.h.
248     Then comes the user's INFODIR from the Makefile.
249     Highest priority is the environment variable, if set.  */
250  if (!infopath)
251    {
252      char *path_from_env = getenv ("INFOPATH");
253
254      if (path_from_env)
255        {
256          unsigned len = strlen (path_from_env);
257          /* Trailing : on INFOPATH means insert the default path.  */
258          if (len && path_from_env[len - 1] == ':')
259            {
260              path_from_env[len - 1] = 0;
261              info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
262            }
263#ifdef INFODIR /* from the Makefile */
264          info_add_path (INFODIR, INFOPATH_PREPEND);
265#endif
266          info_add_path (path_from_env, INFOPATH_PREPEND);
267        }
268      else
269        {
270          info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
271#ifdef INFODIR /* from the Makefile */
272         info_add_path (INFODIR, INFOPATH_PREPEND);
273#endif
274        }
275    }
276
277  /* If the user specified a particular filename, add the path of that
278     file to the contents of INFOPATH. */
279  if (user_filename)
280    {
281      char *directory_name = xstrdup (user_filename);
282      char *temp = filename_non_directory (directory_name);
283
284      if (temp != directory_name)
285        {
286          *temp = 0;
287          info_add_path (directory_name, INFOPATH_PREPEND);
288        }
289
290      free (directory_name);
291    }
292
293  /* If the user wants to search every known index for a given string,
294     do that now, and report the results. */
295  if (apropos_p)
296    {
297      info_apropos (apropos_search_string);
298      exit (0);
299    }
300
301  /* Get the initial Info node.  It is either "(dir)Top", or what the user
302     specifed with values in user_filename and user_nodenames. */
303  initial_node = info_get_node (user_filename,
304                                user_nodenames ? user_nodenames[0] : NULL);
305
306  /* If we couldn't get the initial node, this user is in trouble. */
307  if (!initial_node)
308    {
309      if (info_recent_file_error)
310        info_error (info_recent_file_error);
311      else
312        info_error
313          (CANT_FIND_NODE, user_nodenames ? user_nodenames[0] : "Top");
314      exit (1);
315    }
316
317  /* Special cases for when the user specifies multiple nodes.  If we
318     are dumping to an output file, dump all of the nodes specified.
319     Otherwise, attempt to create enough windows to handle the nodes
320     that this user wants displayed. */
321  if (user_nodenames_index > 1)
322    {
323      free (initial_node);
324
325      if (user_output_filename)
326        dump_nodes_to_file
327          (user_filename, user_nodenames, user_output_filename, dump_subnodes);
328      else
329        begin_multiple_window_info_session (user_filename, user_nodenames);
330
331      exit (0);
332    }
333
334  /* If the user specified `--index-search=STRING', start the info
335     session in the node corresponding to the first match. */
336  if (index_search_p)
337    {
338      int status = 0;
339
340      initialize_info_session (initial_node, 0);
341
342      if (index_entry_exists (windows, index_search_string))
343        {
344          terminal_clear_screen ();
345          terminal_prep_terminal ();
346          display_update_display (windows);
347          info_last_executed_command = (VFunction *)NULL;
348
349          do_info_index_search (windows, 0, index_search_string);
350
351          info_read_and_dispatch ();
352
353          terminal_unprep_terminal ();
354
355          /* On program exit, leave the cursor at the bottom of the
356             window, and restore the terminal IO. */
357          terminal_goto_xy (0, screenheight - 1);
358          terminal_clear_to_eol ();
359          fflush (stdout);
360        }
361      else
362        {
363          fputs (_("no entries found\n"), stderr);
364          status = 2;
365        }
366
367      close_dribble_file ();
368      exit (status);
369    }
370
371  /* If there are arguments remaining, they are the names of menu items
372     in sequential info files starting from the first one loaded.  That
373     file name is either "dir", or the contents of user_filename if one
374     was specified. */
375  while (optind != argc)
376    {
377      REFERENCE **menu;
378      REFERENCE *entry;
379      NODE *node;
380      char *arg;
381      static char *first_arg = (char *)NULL;
382
383      /* Remember the name of the menu entry we want. */
384      arg = argv[optind++];
385
386      if (!first_arg)
387        first_arg = arg;
388
389      /* Build and return a list of the menu items in this node. */
390      menu = info_menu_of_node (initial_node);
391
392      /* If there wasn't a menu item in this node, stop here, but let
393         the user continue to use Info.  Perhaps they wanted this node
394         and didn't realize it. */
395      if (!menu)
396        {
397#if defined (HANDLE_MAN_PAGES)
398          if (first_arg == arg)
399            {
400              node = make_manpage_node (first_arg);
401              if (node)
402                goto maybe_got_node;
403            }
404#endif /* HANDLE_MAN_PAGES */
405          begin_info_session_with_error
406            (initial_node, _("There is no menu in this node."));
407          exit (0);
408        }
409
410      /* Find the specified menu item. */
411      entry = info_get_labeled_reference (arg, menu);
412
413      /* If the item wasn't found, search the list sloppily.  Perhaps this
414         user typed "buffer" when they really meant "Buffers". */
415      if (!entry)
416        {
417          register int i;
418          int best_guess = -1;
419
420          for (i = 0; (entry = menu[i]); i++)
421            {
422              if (strcasecmp (entry->label, arg) == 0)
423                break;
424              else
425                if (strncasecmp (entry->label, arg, strlen (arg)) == 0)
426                  best_guess = i;
427            }
428
429          if (!entry && best_guess != -1)
430            entry = menu[best_guess];
431        }
432
433      /* If we failed to find the reference, start Info with the current
434         node anyway.  It is probably a misspelling. */
435      if (!entry)
436        {
437          char *error_message = _("There is no menu item \"%s\" in this node.");
438
439#if defined (HANDLE_MAN_PAGES)
440          if (first_arg == arg)
441            {
442              node = make_manpage_node (first_arg);
443              if (node)
444                goto maybe_got_node;
445            }
446#endif /* HANDLE_MAN_PAGES */
447
448          info_free_references (menu);
449
450          /* If we were supposed to dump this node, complain. */
451          if (user_output_filename)
452            info_error (error_message, arg);
453          else
454            begin_info_session_with_error (initial_node, error_message, arg);
455
456          exit (0);
457        }
458
459      /* We have found the reference that the user specified.  Clean it
460         up a little bit. */
461      if (!entry->filename)
462        {
463          if (initial_node->parent)
464            entry->filename = xstrdup (initial_node->parent);
465          else
466            entry->filename = xstrdup (initial_node->filename);
467        }
468
469      /* Find this node.  If we can find it, then turn the initial_node
470         into this one.  If we cannot find it, try using the label of the
471         entry as a file (i.e., "(LABEL)Top").  Otherwise the Info file is
472         malformed in some way, and we will just use the current value of
473         initial node. */
474      node = info_get_node (entry->filename, entry->nodename);
475
476#if defined (HANDLE_MAN_PAGES)
477          if ((first_arg == arg) && !node)
478            {
479              node = make_manpage_node (first_arg);
480              if (node)
481                goto maybe_got_node;
482            }
483#endif /* HANDLE_MAN_PAGES */
484
485      if (!node && entry->nodename &&
486          (strcmp (entry->label, entry->nodename) == 0))
487        node = info_get_node (entry->label, "Top");
488
489    maybe_got_node:
490      if (node)
491        {
492          free (initial_node);
493          initial_node = node;
494          info_free_references (menu);
495        }
496      else
497        {
498          char *temp = xstrdup (entry->label);
499          char *error_message;
500
501          error_message = _("Unable to find the node referenced by \"%s\".");
502
503          info_free_references (menu);
504
505          /* If we were trying to dump the node, then give up.  Otherwise,
506             start the session with an error message. */
507          if (user_output_filename)
508            info_error (error_message, temp);
509          else
510            begin_info_session_with_error (initial_node, error_message, temp);
511
512          exit (0);
513        }
514    }
515
516  /* If the user specified that this node should be output, then do that
517     now.  Otherwise, start the Info session with this node. */
518  if (user_output_filename)
519    dump_node_to_file (initial_node, user_output_filename, dump_subnodes);
520  else
521    begin_info_session (initial_node);
522
523  exit (0);
524}
525
526/* Return a string describing the current version of Info. */
527char *
528version_string ()
529{
530  static char *vstring = (char *)NULL;
531
532  if (!vstring)
533    {
534      vstring = (char *)xmalloc (50);
535      sprintf (vstring, "%d.%d", info_major_version, info_minor_version);
536    }
537  return (vstring);
538}
539
540
541/* Error handling.  */
542
543static void
544remember_info_program_name (fullpath)
545     char *fullpath;
546{
547  char *filename;
548
549  filename = filename_non_directory (fullpath);
550  program_name = xstrdup (filename);
551}
552
553/* Non-zero if an error has been signalled. */
554int info_error_was_printed = 0;
555
556/* Non-zero means ring terminal bell on errors. */
557int info_error_rings_bell_p = 1;
558
559/* Print FORMAT with ARG1 and ARG2.  If the window system was initialized,
560   then the message is printed in the echo area.  Otherwise, a message is
561   output to stderr. */
562void
563info_error (format, arg1, arg2)
564     char *format;
565     void *arg1, *arg2;
566{
567  info_error_was_printed = 1;
568
569  if (!info_windows_initialized_p || display_inhibited)
570    {
571      fprintf (stderr, "%s: ", program_name);
572      fprintf (stderr, format, arg1, arg2);
573      fprintf (stderr, "\n");
574      fflush (stderr);
575    }
576  else
577    {
578      if (!echo_area_is_active)
579        {
580          if (info_error_rings_bell_p)
581            terminal_ring_bell ();
582          window_message_in_echo_area (format, arg1, arg2);
583        }
584      else
585        {
586          NODE *temp;
587
588          temp = build_message_node (format, arg1, arg2);
589          if (info_error_rings_bell_p)
590            terminal_ring_bell ();
591          inform_in_echo_area (temp->contents);
592          free (temp->contents);
593          free (temp);
594        }
595    }
596}
597
598/* Produce a scaled down description of the available options to Info. */
599static void
600info_short_help ()
601{
602  printf (_("\
603Usage: %s [OPTION]... [INFO-FILE [MENU-ITEM...]]\n\
604\n\
605Read documentation in Info format.\n\
606For more complete documentation on how to use Info, run `info info options'.\n\
607\n\
608Options:\n\
609--directory DIR              add DIR to INFOPATH.\n\
610--dribble FILENAME           remember user keystrokes in FILENAME.\n\
611--file FILENAME              specify Info file to visit.\n\
612--node NODENAME              specify nodes in first visited Info file.\n\
613--output FILENAME            output selected nodes to FILENAME.\n\
614--restore FILENAME           read initial keystrokes from FILENAME.\n\
615--subnodes                   recursively output menu items.\n\
616--help                       display this help and exit.\n\
617--version                    display version information and exit.\n\
618\n\
619The first argument, if present, is the name of the Info file to read.\n\
620Any remaining arguments are treated as the names of menu\n\
621items in the initial node visited.  For example, `info emacs buffers'\n\
622moves to the node `buffers' in the info file `emacs'.\n\
623\n\
624Email bug reports to bug-texinfo@gnu.org."), program_name);
625
626  exit (0);
627}
628