xref: /netbsd/external/gpl2/texinfo/dist/info/info.c (revision 7350f6c8)
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 
33 static char *program_name = "info";
34 
35 /* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
36 static int apropos_p = 0;
37 
38 /* Variable containing the string to search for when apropos_p is non-zero. */
39 static 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. */
43 static 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.  */
48 static int goto_invocation_p = 0;
49 
50 /* Variable containing the string to search for when index_search_p is
51    non-zero. */
52 static char *index_search_string = (char *)NULL;
53 
54 /* Non-zero means print version info only. */
55 static int print_version_p = 0;
56 
57 /* Non-zero means print a short description of the options. */
58 static int print_help_p = 0;
59 
60 /* Array of the names of nodes that the user specified with "--node" on the
61    command line. */
62 static char **user_nodenames = (char **)NULL;
63 static int user_nodenames_index = 0;
64 static 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. */
68 static 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. */
72 static 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. */
77 int dump_subnodes = 0;
78 
79 /* Non-zero means make default keybindings be loosely modeled on vi(1).  */
80 int vi_keys_p = 0;
81 
82 /* Non-zero means don't remove ANSI escape sequences.  */
83 int raw_escapes_p = 1;
84 
85 /* Non-zero means print the absolute location of the file to be loaded.  */
86 static 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).  */
94 int 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
104 static 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__
131 static char *short_options = "d:n:f:ho:ORswb";
132 #else
133 static char *short_options = "d:n:f:ho:ORws";
134 #endif
135 
136 /* When non-zero, the Info window system has been initialized. */
137 int info_windows_initialized_p = 0;
138 
139 /* Some "forward" declarations. */
140 static void info_short_help (void);
141 static void init_messages (void);
142 
143 
144 /* **************************************************************** */
145 /*                                                                  */
146 /*                Main Entry Point to the Info Program              */
147 /*                                                                  */
148 /* **************************************************************** */
149 
150 int
main(int argc,char ** argv)151 main (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\
298 under the terms of the GNU General Public License.\n\
299 For 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 
518 void
add_file_directory_to_path(char * filename)519 add_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. */
544 int info_error_was_printed = 0;
545 
546 /* Non-zero means ring terminal bell on errors. */
547 int 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. */
552 void
info_error(char * format,void * arg1,void * arg2)553 info_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. */
588 static void
info_short_help(void)589 info_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 (_("\
600 Usage: %s [OPTION]... [MENU-ITEM...]\n\
601 \n\
602 Read documentation in Info format.\n\
603 \n\
604 Options:\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\
622 The first non-option argument, if present, is the menu entry to start from;\n\
623 it is searched for in all `dir' files along INFOPATH.\n\
624 If it is not present, info merges all `dir' files and shows the result.\n\
625 Any remaining arguments are treated as the names of menu\n\
626 items relative to the initial node visited.\n\
627 \n\
628 Examples:\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\
638 Email bug reports to bug-texinfo@gnu.org,\n\
639 general questions and discussion to help-texinfo@gnu.org.\n\
640 Texinfo 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 
651 const char *msg_cant_find_node;
652 const char *msg_cant_file_node;
653 const char *msg_cant_find_window;
654 const char *msg_cant_find_point;
655 const char *msg_cant_kill_last;
656 const char *msg_no_menu_node;
657 const char *msg_no_foot_node;
658 const char *msg_no_xref_node;
659 const char *msg_no_pointer;
660 const char *msg_unknown_command;
661 const char *msg_term_too_dumb;
662 const char *msg_at_node_bottom;
663 const char *msg_at_node_top;
664 const char *msg_one_window;
665 const char *msg_win_too_small;
666 const char *msg_cant_make_help;
667 
668 static void
init_messages(void)669 init_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