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