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