1 /* info.c -- Display nodes of Info files in multiple windows.
2
3 Copyright 1993-2023 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 Originally written by Brian Fox. */
19
20 #include "info.h"
21 #include "filesys.h"
22 #include "scan.h"
23 #include "util.h"
24 #include "session.h"
25 #include "indices.h"
26 #include "dribble.h"
27 #include "getopt.h"
28 #include "man.h"
29 #include "variables.h"
30
31 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 = 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 static char *invocation_program_name = 0;
49
50 /* Variable containing the string to search for when index_search_p is
51 non-zero. */
52 static char *index_search_string = 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 /* Name of file to start session with. Default file for --node arguments. */
61 static char *initial_file = 0;
62
63 /* Array of the names of nodes that the user specified with "--node" on the
64 command line. */
65 static char **user_nodenames = NULL;
66 static size_t user_nodenames_index = 0;
67 static size_t user_nodenames_slots = 0;
68
69 /* References to the nodes to start the session with. */
70 static REFERENCE **ref_list = NULL;
71 static size_t ref_slots = 0;
72 static size_t ref_index = 0;
73
74 /* String specifying the first file to load. This string can only be set
75 by the user specifying "--file" on the command line. */
76 static char *user_filename = NULL;
77
78 /* String specifying the name of the file to dump nodes to. This value is
79 filled if the user speficies "--output" on the command line. */
80 static char *user_output_filename = NULL;
81
82 /* Non-zero indicates that when "--output" is specified, all of the menu
83 items of the specified nodes (and their subnodes as well) should be
84 dumped in the order encountered. This basically can print a book. */
85 int dump_subnodes = 0;
86
87 /* Non-zero means make default keybindings be loosely modeled on vi(1). */
88 int vi_keys_p = 0;
89
90 /* Non-zero means don't remove ANSI escape sequences. */
91 int raw_escapes_p = 1;
92
93 /* Print/visit all matching documents. */
94 static int all_matches_p = 0;
95
96 /* Non-zero means print the absolute location of the file to be loaded. */
97 static int print_where_p = 0;
98
99 /* Non-zero means don't try to be smart when searching for nodes. */
100 int strict_node_location_p = 0;
101
102 #if defined(__MSDOS__) || defined(__MINGW32__)
103 /* Non-zero indicates that screen output should be made 'speech-friendly'.
104 Since on MSDOS the usual behavior is to write directly to the video
105 memory, speech synthesizer software cannot grab the output. Therefore,
106 we provide a user option which tells us to avoid direct screen output
107 and use stdout instead (which loses the color output). */
108 int speech_friendly = 0;
109 #endif
110
111 /* Structure describing the options that Info accepts. We pass this structure
112 to getopt_long (). If you add or otherwise change this structure, you must
113 also change the string which follows it. */
114 #define DRIBBLE_OPTION 2
115 #define RESTORE_OPTION 3
116 #define IDXSRCH_OPTION 4
117 #define INITFLE_OPTION 5
118 #define VIRTIDX_OPTION 6
119
120 static struct option long_options[] = {
121 { "all", 0, 0, 'a' },
122 { "apropos", 1, 0, 'k' },
123 { "debug", 1, 0, 'x' },
124 { "directory", 1, 0, 'd' },
125 { "dribble", 1, 0, DRIBBLE_OPTION },
126 { "file", 1, 0, 'f' },
127 { "help", 0, &print_help_p, 1 },
128 { "index-search", 1, 0, IDXSRCH_OPTION },
129 { "init-file", 1, 0, INITFLE_OPTION },
130 { "location", 0, &print_where_p, 1 },
131 { "node", 1, 0, 'n' },
132 { "output", 1, 0, 'o' },
133 { "raw-escapes", 0, &raw_escapes_p, 1 },
134 { "no-raw-escapes", 0, &raw_escapes_p, 0 },
135 { "show-malformed-multibytes", 0, &show_malformed_multibyte_p, 1 },
136 { "no-show-malformed-multibytes", 0, &show_malformed_multibyte_p, 0 },
137 { "restore", 1, 0, RESTORE_OPTION },
138 { "show-options", 0, 0, 'O' },
139 { "strict-node-location", 0, &strict_node_location_p, 1 },
140 { "subnodes", 0, &dump_subnodes, 1 },
141 { "usage", 0, 0, 'O' },
142 { "variable", 1, 0, 'v' },
143 { "version", 0, &print_version_p, 1 },
144 { "vi-keys", 0, &vi_keys_p, 1 },
145 { "where", 0, &print_where_p, 1 },
146 #if defined(__MSDOS__) || defined(__MINGW32__)
147 { "speech-friendly", 0, &speech_friendly, 1 },
148 #endif
149 {NULL, 0, NULL, 0}
150 };
151
152 /* String describing the shorthand versions of the long options found above. */
153 #if defined(__MSDOS__) || defined(__MINGW32__)
154 static char *short_options = "ak:d:n:f:ho:ORsv:wbx:";
155 #else
156 static char *short_options = "ak:d:n:f:ho:ORv:wsx:";
157 #endif
158
159 /* When non-zero, the Info window system has been initialized. */
160 int info_windows_initialized_p = 0;
161
162 /* Some "forward" declarations. */
163 static void info_short_help (void);
164 static void init_messages (void);
165
166
167 /* Find the first file to load (and possibly the first node as well).
168 If the --file option is given, use that as the file, otherwise try to
169 interpret the first non-option argument, either by looking it up as a dir
170 entry, looking for a file by that name, or finding a man page by that name.
171 Set INITIAL_FILE to the name of the initial Info file. */
172 static void
173 get_initial_file (int *argc, char ***argv, char **error)
174 {
175 REFERENCE *entry;
176
177 /* If --file was not used and there is a slash in the first non-option
178 argument (e.g. "info subdir/file.info"), do not search the dir files
179 for a matching entry. */
180 if (!user_filename
181 && (*argv)[0]
182 && HAS_SLASH ((*argv)[0])
183 && (*argv)[0][0] != '(') /* don't treat "(manual)node" as a filename */
184 {
185 user_filename = xstrdup ((*argv)[0]);
186 (*argv)++; /* Advance past first remaining argument. */
187 (*argc)--;
188 }
189
190 /* User used "--file". */
191 if (user_filename)
192 {
193 if (!IS_ABSOLUTE(user_filename) && HAS_SLASH(user_filename)
194 && !(user_filename[0] == '.' && IS_SLASH(user_filename[1])))
195 {
196 /* Prefix "./" to the filename to prevent a lookup
197 in INFOPATH. */
198 char *s;
199 xasprintf (&s, "%s%s", "./", user_filename);
200 free (user_filename);
201 user_filename = s;
202 }
203 if (IS_ABSOLUTE(user_filename) || HAS_SLASH(user_filename))
204 initial_file = info_add_extension (0, user_filename, 0);
205 else
206 initial_file = info_find_fullpath (user_filename, 0);
207
208 if (!initial_file)
209 {
210 if (!filesys_error_number)
211 filesys_error_number = ENOENT;
212 *error = filesys_error_string (user_filename, filesys_error_number);
213 }
214
215 return;
216 }
217
218 if (!(*argv)[0])
219 {
220 /* No more non-option arguments. */
221 initial_file = xstrdup("dir");
222 return;
223 }
224
225 /* If first argument begins with '(', add it as if it were given with
226 '--node'. This is to support invoking like
227 "info '(emacs)Buffers'". If it is a well-formed node spec then
228 the rest of the arguments are menu entries to follow, or an
229 index entry. */
230 if ((*argv)[0][0] == '(')
231 {
232 info_parse_node ((*argv)[0]);
233 if (info_parsed_filename)
234 {
235 initial_file = info_find_fullpath (info_parsed_filename, 0);
236 if (initial_file)
237 {
238 add_pointer_to_array (info_new_reference (initial_file,
239 info_parsed_nodename),
240 ref_index, ref_list, ref_slots, 2);
241 /* Remove this argument from the argument list. */
242 memmove (*argv, *argv + 1, (*argc)-- * sizeof (char *));
243 return;
244 }
245 }
246 }
247
248 /* If there are any more arguments, the initial file is the
249 dir entry given by the first one. */
250 {
251 /* If they say info info (or info -O info, etc.), show them
252 info-stnd.texi. (Get info.texi with info -f info.) */
253 if ((*argv)[0] && mbscasecmp ((*argv)[0], "info") == 0)
254 (*argv)[0] = "info-stnd";
255
256 entry = lookup_dir_entry ((*argv)[0], 0);
257 if (entry)
258 {
259 initial_file = info_find_fullpath (entry->filename, 0);
260 if (initial_file)
261 {
262 REFERENCE *copy;
263 (*argv)++; /* Advance past first remaining argument. */
264 (*argc)--;
265
266 copy = info_copy_reference (entry);
267 /* Store full path, so that we find the already loaded file in
268 info_find_file, and show the full path if --where is used. */
269 free (copy->filename);
270 copy->filename = xstrdup (initial_file);
271 add_pointer_to_array (copy, ref_index, ref_list, ref_slots, 2);
272 return;
273 }
274 }
275 }
276
277 /* File name lookup. */
278 {
279 /* Try finding a file with this name, in case
280 it exists, but wasn't listed in dir. */
281 initial_file = info_find_fullpath ((*argv)[0], 0);
282 if (initial_file)
283 {
284 add_pointer_to_array (info_new_reference ((*argv)[0], "Top"),
285 ref_index, ref_list, ref_slots, 2);
286 (*argv)++; /* Advance past first remaining argument. */
287 (*argc)--;
288 return;
289 }
290 else
291 xasprintf (error, _("No menu item '%s' in node '%s'"),
292 (*argv)[0], "(dir)Top");
293 }
294
295 /* Fall back to loading man page. */
296 {
297 int man_exists;
298
299 debug (3, ("falling back to manpage node"));
300
301 man_exists = check_manpage_node ((*argv)[0]);
302 if (man_exists)
303 {
304 add_pointer_to_array
305 (info_new_reference (MANPAGE_FILE_BUFFER_NAME, (*argv)[0]),
306 ref_index, ref_list, ref_slots, 2);
307
308 initial_file = MANPAGE_FILE_BUFFER_NAME;
309 return;
310 }
311 }
312
313 /* Inexact dir lookup. */
314 {
315 entry = lookup_dir_entry ((*argv)[0], 1);
316 if (entry)
317 {
318 initial_file = info_find_fullpath (entry->filename, 0);
319 if (initial_file)
320 {
321 REFERENCE *copy;
322 (*argv)++; /* Advance past first remaining argument. */
323 (*argc)--;
324 /* Clear error message. */
325 free (*error);
326 *error = 0;
327
328 copy = info_copy_reference (entry);
329 /* Store full path, so that we find the already loaded file in
330 info_find_file, and show the full path if --where is used. */
331 free (copy->filename);
332 copy->filename = initial_file;
333 add_pointer_to_array (copy, ref_index, ref_list, ref_slots, 2);
334 return;
335 }
336 }
337 }
338
339 return;
340 }
341
342 /* Expand list of nodes to be loaded. */
343 static void
344 add_initial_nodes (int argc, char **argv, char **error)
345 {
346 /* Add nodes specified with --node. */
347 if (user_nodenames)
348 {
349 int i;
350
351 /* If any --node arguments were given, the node in ref_list[0] is only
352 used to set initial_file. */
353 if (user_nodenames_index > 0 && ref_index > 0)
354 {
355 info_reference_free (ref_list[0]);
356 ref_list[0] = 0;
357 ref_index = 0;
358 }
359
360 for (i = 0; user_nodenames[i]; i++)
361 {
362 char *node_filename = 0;
363 char *node_nodename = 0;
364
365 /* Parse node spec to support invoking
366 like info --node "(emacs)Buffers". */
367 info_parse_node (user_nodenames[i]);
368 if (info_parsed_filename)
369 {
370 node_filename = info_parsed_filename;
371 node_nodename = info_parsed_nodename;
372 }
373 else
374 {
375 FILE_BUFFER *file_buffer;
376 TAG *tag;
377 int j;
378
379 if (!initial_file)
380 continue; /* Shouldn't happen. */
381
382 /* Check for a node by this name, and if there isn't one
383 look for an inexact match. */
384
385 node_filename = initial_file;
386 node_nodename = 0;
387
388 file_buffer = info_find_file (node_filename);
389 if (!file_buffer)
390 continue;
391
392 /* First look for an exact match. */
393 for (j = 0; (tag = file_buffer->tags[j]); j++)
394 if (strcmp (user_nodenames[i], tag->nodename) == 0)
395 {
396 node_nodename = tag->nodename;
397 break;
398 }
399
400 if (!node_nodename)
401 {
402 int best_guess = -1;
403 int len = strlen (user_nodenames[i]);
404 for (j = 0; (tag = file_buffer->tags[j]); j++)
405 {
406 if (mbscasecmp (user_nodenames[i], tag->nodename) == 0)
407 {
408 /* Exact, case-insensitive match. */
409 node_nodename = tag->nodename;
410 best_guess = -1;
411 break;
412 }
413 else if (best_guess == -1
414 && (mbsncasecmp (user_nodenames[i],
415 tag->nodename, len) == 0))
416 /* Case-insensitive initial substring. */
417 best_guess = j;
418 }
419 if (best_guess != -1)
420 {
421 node_nodename = file_buffer->tags[best_guess]->nodename;
422 }
423 }
424
425 if (!node_nodename)
426 {
427 free (*error);
428 xasprintf (error, _("Cannot find node '%s'"),
429 user_nodenames[i]);
430 continue;
431 }
432 }
433
434 if (node_filename && node_nodename)
435 add_pointer_to_array
436 (info_new_reference (node_filename, node_nodename),
437 ref_index, ref_list, ref_slots, 2);
438 }
439 }
440
441 if (goto_invocation_p)
442 {
443 NODE *top_node = 0;
444 REFERENCE *invoc_ref = 0;
445
446 char *program;
447
448 if (ref_index == 0)
449 {
450 info_error (_("No program name given"));
451 exit (1);
452 }
453
454 if (invocation_program_name)
455 program = xstrdup (invocation_program_name);
456 else if (ref_list[0] && ref_list[0]->filename)
457 /* If there's no command-line arguments to
458 supply the program name, use the Info file
459 name (sans extension and leading directories)
460 instead. */
461 program = program_name_from_file_name (ref_list[0]->filename);
462 else
463 program = xstrdup ("");
464
465 if (ref_index > 0)
466 top_node = info_get_node (ref_list[0]->filename,
467 ref_list[0]->nodename);
468 if (top_node)
469 invoc_ref = info_intuit_options_node (top_node, program);
470 if (invoc_ref)
471 {
472 info_reference_free (ref_list[0]);
473 ref_index = 0;
474
475 add_pointer_to_array (invoc_ref, ref_index, ref_list, ref_slots, 2);
476 }
477 free (program);
478 }
479
480 /* Default is the "Top" node if there were no other nodes. */
481 if (ref_index == 0 && initial_file)
482 {
483 add_pointer_to_array (info_new_reference (initial_file, "Top"),
484 ref_index, ref_list, ref_slots, 2);
485 }
486
487 /* If there are arguments remaining, they are the names of menu items
488 in sequential info files starting from the first one loaded. */
489 if (*argv && ref_index > 0)
490 {
491 NODE *initial_node; /* Node to start following menus from. */
492 NODE *node_via_menus;
493
494 initial_node = info_get_node_with_defaults (ref_list[0]->filename,
495 ref_list[0]->nodename, 0);
496 if (!initial_node)
497 return;
498
499 node_via_menus = info_follow_menus (initial_node, argv, error, 1);
500 if (node_via_menus)
501 {
502 argv += argc; argc = 0;
503
504 info_reference_free (ref_list[0]);
505 ref_list[0] = info_new_reference (node_via_menus->fullpath,
506 node_via_menus->nodename);
507 free_history_node (node_via_menus);
508 }
509
510 /* If no nodes found, and there is exactly one argument remaining,
511 check for it as an index entry. */
512 else if (argc == 1 && argv[0])
513 {
514 FILE_BUFFER *fb;
515 REFERENCE *match;
516
517 debug (3, ("looking in indices"));
518 fb = info_find_file (ref_list[0]->filename);
519 if (fb)
520 {
521 match = look_in_indices (fb, argv[0], 0);
522 if (match)
523 {
524 argv += argc; argc = 0;
525 free (*error); *error = 0;
526
527 info_reference_free (ref_list[0]);
528 ref_list[0] = info_copy_reference (match);
529 }
530 }
531 }
532
533 /* If there are arguments remaining, follow menus inexactly. */
534 if (argc != 0)
535 {
536 initial_node = info_get_node_with_defaults (ref_list[0]->filename,
537 ref_list[0]->nodename,
538 0);
539 free (*error); *error = 0;
540 node_via_menus = info_follow_menus (initial_node, argv, error, 0);
541 if (node_via_menus)
542 {
543 if (argc >= 2 || !*error)
544 {
545 argv += argc; argc = 0;
546
547 info_reference_free (ref_list[0]);
548 ref_list[0] = info_new_reference (node_via_menus->fullpath,
549 node_via_menus->nodename);
550 }
551 free_history_node (node_via_menus);
552 }
553 }
554
555 /* If still no nodes found, and there is exactly one argument remaining,
556 look in indices sloppily. */
557 if (argc == 1)
558 {
559 FILE_BUFFER *fb;
560 REFERENCE *nearest;
561
562 debug (3, ("looking in indices sloppily"));
563 fb = info_find_file (ref_list[0]->filename);
564 if (fb)
565 {
566 nearest = look_in_indices (fb, argv[0], 1);
567 if (nearest)
568 {
569 argv += argc; argc = 0;
570 free (*error); *error = 0;
571
572 info_reference_free (ref_list[0]);
573 ref_list[0] = info_copy_reference (nearest);
574 }
575 }
576 }
577 }
578
579 return;
580 }
581
582 static void
583 info_find_matching_files (char *filename)
584 {
585 int i;
586 char *searchdir;
587
588 NODE *man_node;
589
590 /* Check for dir entries first. */
591 i = 0;
592 for (searchdir = infopath_first (&i); searchdir;
593 searchdir = infopath_next (&i))
594 {
595 REFERENCE *new_ref = dir_entry_of_infodir (filename, searchdir);
596
597 if (new_ref)
598 add_pointer_to_array (new_ref, ref_index, ref_list, ref_slots, 2);
599 }
600
601 /* Look for files with matching names. */
602 i = 0;
603 while (1)
604 {
605 char *p;
606 int j;
607
608 p = info_file_find_next_in_path (filename, &i, 0);
609 if (!p)
610 break;
611
612 /* Add to list only if the file is not in the list already (which would
613 happen if there was a dir entry with the label and filename both
614 being this file). */
615 for (j = 0; j < ref_index; j++)
616 {
617 if (!strcmp (p, ref_list[j]->filename))
618 break;
619 }
620
621 if (j == ref_index)
622 {
623 add_pointer_to_array (info_new_reference (p, 0),
624 ref_index, ref_list, ref_slots, 2);
625 }
626 free (p);
627 }
628
629 /* Check for man page. */
630 man_node = get_manpage_node (filename);
631 if (man_node)
632 {
633 free (man_node);
634 add_pointer_to_array
635 (info_new_reference (MANPAGE_FILE_BUFFER_NAME, filename),
636 ref_index, ref_list, ref_slots, 2);
637 }
638 }
639
640
641 static void
642 set_debug_level (const char *arg)
643 {
644 char *p;
645 long n = strtol (arg, &p, 10);
646 if (*p)
647 {
648 fprintf (stderr, _("invalid number: %s\n"), arg);
649 exit (EXIT_FAILURE);
650 }
651 if (n < 0 || n > UINT_MAX)
652 debug_level = UINT_MAX;
653 else
654 debug_level = n;
655 }
656
657 static void
658 add_file_directory_to_path (char *filename)
659 {
660 char *directory_name = xstrdup (filename);
661 char *temp = filename_non_directory (directory_name);
662
663 if (temp != directory_name)
664 {
665 if (HAVE_DRIVE (directory_name) && temp == directory_name + 2)
666 {
667 /* The directory of "d:foo" is stored as "d:.", to avoid
668 mixing it with "d:/" when a slash is appended. */
669 *temp = '.';
670 temp += 2;
671 }
672 temp[-1] = 0;
673 infopath_add (directory_name);
674 }
675
676 free (directory_name);
677 }
678
679
680 /* **************************************************************** */
681 /* */
682 /* Main Entry Point to the Info Program */
683 /* */
684 /* **************************************************************** */
685
686 int
687 main (int argc, char *argv[])
688 {
689 int getopt_long_index; /* Index returned by getopt_long (). */
690 char *init_file = 0; /* Name of init file specified. */
691 char *error = 0; /* Error message to display in mini-buffer. */
692
693 #ifdef HAVE_SETLOCALE
694 /* Set locale via LC_ALL. */
695 setlocale (LC_ALL, "");
696 #endif /* HAVE_SETLOCALE */
697
698 #ifdef ENABLE_NLS
699 /* Set the text message domain. */
700 bindtextdomain (PACKAGE, LOCALEDIR);
701 textdomain (PACKAGE);
702 #endif
703
704 init_messages ();
705 while (1)
706 {
707 int option_character;
708
709 option_character = getopt_long (argc, argv, short_options, long_options,
710 &getopt_long_index);
711
712 /* getopt_long returns EOF when there are no more long options. */
713 if (option_character == EOF)
714 break;
715
716 /* If this is a long option, then get the short version of it. */
717 if (option_character == 0 && long_options[getopt_long_index].flag == 0)
718 option_character = long_options[getopt_long_index].val;
719
720 /* Case on the option that we have received. */
721 switch (option_character)
722 {
723 case 0:
724 break;
725
726 case 'a':
727 all_matches_p = 1;
728 break;
729
730 /* User wants to add a directory. */
731 case 'd':
732 infopath_add (optarg);
733 break;
734
735 /* User is specifying a particular node. */
736 case 'n':
737 add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
738 user_nodenames_slots, 10);
739 break;
740
741 /* User is specifying a particular Info file. */
742 case 'f':
743 if (user_filename)
744 free (user_filename);
745
746 user_filename = xstrdup (optarg);
747 break;
748
749 /* Treat -h like --help. */
750 case 'h':
751 print_help_p = 1;
752 break;
753
754 /* User is specifying the name of a file to output to. */
755 case 'o':
756 if (user_output_filename)
757 free (user_output_filename);
758 user_output_filename = xstrdup (optarg);
759 break;
760
761 /* User has specified that she wants to find the "Options"
762 or "Invocation" node for the program. */
763 case 'O':
764 goto_invocation_p = 1;
765 break;
766
767 /* User has specified that she wants the escape sequences
768 in man pages to be passed thru unaltered. */
769 case 'R':
770 raw_escapes_p = 1;
771 break;
772
773 /* User is specifying that she wishes to dump the subnodes of
774 the node that she is dumping. */
775 case 's':
776 dump_subnodes = 1;
777 break;
778
779 /* For compatibility with man, -w is --where. */
780 case 'w':
781 print_where_p = 1;
782 break;
783
784 #if defined(__MSDOS__) || defined(__MINGW32__)
785 /* User wants speech-friendly output. */
786 case 'b':
787 speech_friendly = 1;
788 break;
789 #endif /* __MSDOS__ || __MINGW32__ */
790
791 /* User has specified a string to search all indices for. */
792 case 'k':
793 apropos_p = 1;
794 free (apropos_search_string);
795 apropos_search_string = xstrdup (optarg);
796 break;
797
798 /* User has specified a dribble file to receive keystrokes. */
799 case DRIBBLE_OPTION:
800 close_dribble_file ();
801 open_dribble_file (optarg);
802 break;
803
804 /* User has specified an alternate input stream. */
805 case RESTORE_OPTION:
806 info_set_input_from_file (optarg);
807 break;
808
809 /* User has specified a string to search all indices for. */
810 case IDXSRCH_OPTION:
811 index_search_p = 1;
812 free (index_search_string);
813 index_search_string = xstrdup (optarg);
814 break;
815
816 /* User has specified a file to use as the init file. */
817 case INITFLE_OPTION:
818 init_file = optarg;
819 break;
820
821 case 'v':
822 {
823 VARIABLE_ALIST *var;
824 char *p;
825 p = strchr (optarg, '=');
826 if (!p)
827 {
828 info_error (_("malformed variable assignment: %s"), optarg);
829 exit (EXIT_FAILURE);
830 }
831 *p++ = 0;
832
833 if (!(var = variable_by_name (optarg)))
834 {
835 info_error (_("%s: no such variable"), optarg);
836 exit (EXIT_FAILURE);
837 }
838
839 if (!set_variable_to_value (var, p, SET_ON_COMMAND_LINE))
840 {
841 info_error (_("value %s is not valid for variable %s"),
842 p, optarg);
843 exit (EXIT_FAILURE);
844 }
845 }
846 break;
847
848 case 'x':
849 set_debug_level (optarg);
850 break;
851
852 default:
853 fprintf (stderr, _("Try --help for more information.\n"));
854 exit (EXIT_FAILURE);
855 }
856 }
857
858 /* If the output device is not a terminal, and no output filename has been
859 specified, make user_output_filename be "-", so that the info is written
860 to stdout, and turn on the dumping of subnodes. */
861 if ((!isatty (fileno (stdout))) && (user_output_filename == NULL))
862 {
863 user_output_filename = xstrdup ("-");
864 dump_subnodes = 1;
865 }
866
867 /* If the user specified --version, then show the version and exit. */
868 if (print_version_p)
869 {
870 printf ("info (GNU %s) %s\n", PACKAGE, VERSION);
871 puts ("");
872 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
873 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
874 This is free software: you are free to change and redistribute it.\n\
875 There is NO WARRANTY, to the extent permitted by law.\n"),
876 "2023");
877 exit (EXIT_SUCCESS);
878 }
879
880 /* If the `--help' option was present, show the help and exit. */
881 if (print_help_p)
882 {
883 info_short_help ();
884 exit (EXIT_SUCCESS);
885 }
886
887 argc -= optind;
888 argv += optind;
889
890 /* Load custom key mappings and variable settings */
891 initialize_terminal_and_keymaps (init_file);
892
893 /* Add extra search directories to any already specified with
894 --directory. */
895 infopath_init ();
896
897 /* If the user wants to search every known index for a given string,
898 do that now, and report the results. */
899 if (apropos_p)
900 {
901 REFERENCE **apropos_list;
902
903 apropos_list = apropos_in_all_indices (apropos_search_string, 0);
904
905 if (!apropos_list)
906 info_error (_(APROPOS_NONE), apropos_search_string);
907 else
908 {
909 register int i;
910 REFERENCE *entry;
911
912 for (i = 0; (entry = apropos_list[i]); i++)
913 fprintf (stdout, "\"(%s)%s\" -- %s\n",
914 entry->filename, entry->nodename, entry->label);
915 }
916 exit (0);
917 }
918
919 /* Initialize empty list of nodes to load. */
920 add_pointer_to_array (0, ref_index, ref_list, ref_slots, 2);
921 ref_index--;
922
923 /* --all */
924 if (all_matches_p && !index_search_p)
925 {
926 if (!user_filename && argv[0])
927 {
928 user_filename = xstrdup (argv[0]);
929 argv++; argc--;
930 }
931 else if (!user_filename)
932 {
933 exit (1);
934 }
935 info_find_matching_files (user_filename);
936 /* If only one match, don't start in a menu of matches. */
937 if (ref_index == 1)
938 all_matches_p = 0;
939
940 /* --where */
941 if (print_where_p)
942 {
943 int i;
944 if (!ref_list)
945 exit (1);
946
947 for (i = 0; ref_list[i]; i++)
948 printf ("%s\n", ref_list[i]->filename);
949 exit (0);
950 }
951 }
952 else
953 {
954 if (goto_invocation_p)
955 {
956 /* If they said "info --show-options foo bar baz",
957 the last of the arguments is the program whose
958 options they want to see. */
959 char **p = argv;
960 if (*p)
961 {
962 while (p[1])
963 p++;
964 invocation_program_name = *p;
965 }
966 }
967
968 get_initial_file (&argc, &argv, &error);
969
970 /* If the user specified a particular filename, add the path of that file
971 to the contents of INFOPATH, for '--variable follow-strategy=path'. */
972 if (user_filename)
973 add_file_directory_to_path (user_filename);
974
975 /* If the user specified `--index-search=STRING --all', create
976 and display the menu of results. */
977 if (index_search_p && all_matches_p && initial_file)
978 {
979 FILE_BUFFER *initial_fb;
980 initial_fb = info_find_file (initial_file);
981 if (initial_fb)
982 {
983 NODE *node = create_virtual_index (initial_fb,
984 index_search_string);
985 if (node)
986 {
987 if (user_output_filename)
988 {
989 FILE *output_stream = 0;
990 if (strcmp (user_output_filename, "-") == 0)
991 output_stream = stdout;
992 else
993 output_stream = fopen (user_output_filename, "w");
994 if (output_stream)
995 {
996 write_node_to_stream (node, output_stream);
997 }
998 exit (0);
999 }
1000 else
1001 {
1002 initialize_info_session ();
1003 info_set_node_of_window (active_window, node);
1004 info_read_and_dispatch ();
1005 close_info_session ();
1006 exit (0);
1007 }
1008 }
1009 }
1010 }
1011
1012 /* If the user specified `--index-search=STRING',
1013 start the info session in the node corresponding
1014 to what they want. */
1015 else if (index_search_p && initial_file && !user_output_filename)
1016 {
1017 FILE_BUFFER *initial_fb;
1018 initial_fb = info_find_file (initial_file);
1019 if (initial_fb)
1020 {
1021 REFERENCE *result;
1022 int i, match_offset;
1023
1024 result = next_index_match (initial_fb, index_search_string, 0, 1,
1025 &i, &match_offset);
1026
1027 if (result)
1028 {
1029 initialize_info_session ();
1030 report_index_match (i, match_offset);
1031 info_select_reference (active_window, result);
1032 info_read_and_dispatch ();
1033 close_info_session ();
1034 exit (0);
1035 }
1036 }
1037
1038 fprintf (stderr, _("no index entries found for '%s'\n"),
1039 index_search_string);
1040 close_dribble_file ();
1041 exit (1);
1042 }
1043
1044 /* Add nodes to start with (unless we fell back to the man page). */
1045 if (!ref_list[0] || strcmp (ref_list[0]->filename,
1046 MANPAGE_FILE_BUFFER_NAME))
1047 {
1048 add_initial_nodes (argc, argv, &error);
1049 }
1050
1051 /* --where */
1052 if (print_where_p)
1053 {
1054 if (initial_file)
1055 printf ("%s\n", initial_file);
1056 exit (0);
1057 }
1058
1059 }
1060
1061 /* --output */
1062 if (user_output_filename)
1063 {
1064 preprocess_nodes_p = 0;
1065 dump_nodes_to_file (ref_list, user_output_filename, dump_subnodes);
1066
1067 if (error)
1068 {
1069 info_error ("%s", error);
1070 exit (1);
1071 }
1072 exit (0);
1073 }
1074
1075 if (ref_index == 0)
1076 {
1077 if (error)
1078 {
1079 info_error ("%s", error);
1080 exit (1);
1081 }
1082 exit (0);
1083 }
1084
1085 info_session (ref_list, all_matches_p ? user_filename : 0, error);
1086 close_info_session ();
1087 exit (0);
1088 }
1089
1090
1091 /* Produce a scaled down description of the available options to Info. */
1092 static void
1093 info_short_help (void)
1094 {
1095 /* Note: split usage information up into separate strings when usage
1096 revised to make it easier for translators. */
1097
1098 printf (_("\
1099 Usage: %s [OPTION]... [MENU-ITEM...]\n\
1100 \n\
1101 Read documentation in Info format.\n"), program_name);
1102 puts ("");
1103
1104 puts (_("\
1105 Frequently-used options:\n\
1106 -a, --all use all matching manuals\n\
1107 -k, --apropos=STRING look up STRING in all indices of all manuals\n\
1108 -d, --directory=DIR add DIR to INFOPATH\n\
1109 -f, --file=MANUAL specify Info manual to visit"));
1110
1111 puts (_("\
1112 -h, --help display this help and exit\n\
1113 --index-search=STRING go to node pointed by index entry STRING\n\
1114 -n, --node=NODENAME specify nodes in first visited Info file\n\
1115 -o, --output=FILE output selected nodes to FILE"));
1116
1117 #if defined(__MSDOS__) || defined(__MINGW32__)
1118 puts (_("\
1119 -b, --speech-friendly be friendly to speech synthesizers"));
1120 #endif
1121
1122 puts (_("\
1123 --subnodes recursively output menu items\n\
1124 -v, --variable VAR=VALUE assign VALUE to Info variable VAR\n\
1125 --version display version information and exit\n\
1126 -w, --where, --location print physical location of Info file"));
1127
1128 puts (_("\n\
1129 The first non-option argument, if present, is the menu entry to start from;\n\
1130 it is searched for in all 'dir' files along INFOPATH.\n\
1131 If it is not present, info merges all 'dir' files and shows the result.\n\
1132 Any remaining arguments are treated as the names of menu\n\
1133 items relative to the initial node visited."));
1134
1135 puts (_("\n\
1136 For a summary of key bindings, type H within Info."));
1137 puts ("");
1138
1139 puts (_("\
1140 Examples:"));
1141
1142 puts (_("\
1143 info show top-level dir menu"));
1144 puts (_("\
1145 info info-stnd show the manual for this Info program"));
1146 puts (_("\
1147 info emacs start at emacs node from top-level dir"));
1148 puts (_("\
1149 info emacs buffers select buffers menu entry in emacs manual"));
1150 puts (_("\
1151 info emacs -n Files start at Files node within emacs manual"));
1152 puts (_("\
1153 info '(emacs)Files' alternative way to start at Files node"));
1154 puts (_("\
1155 info --subnodes -o out.txt emacs\n\
1156 dump entire emacs manual to out.txt"));
1157 puts (_("\
1158 info -f ./foo.info show file ./foo.info, not searching dir"));
1159
1160 puts ("");
1161
1162 puts (_("\
1163 Email bug reports to bug-texinfo@gnu.org,\n\
1164 general questions and discussion to help-texinfo@gnu.org.\n\
1165 Texinfo home page: http://www.gnu.org/software/texinfo/"));
1166
1167 exit (EXIT_SUCCESS);
1168 }
1169
1170
1171 /* Initialize strings for gettext. Because gettext doesn't handle N_ or
1172 _ within macro definitions, we put shared messages into variables and
1173 use them that way. This also has the advantage that there's only one
1174 copy of the strings. */
1175
1176 const char *msg_cant_find_node;
1177 const char *msg_cant_file_node;
1178 const char *msg_cant_find_window;
1179 const char *msg_cant_find_point;
1180 const char *msg_cant_kill_last;
1181 const char *msg_no_menu_node;
1182 const char *msg_no_foot_node;
1183 const char *msg_no_xref_node;
1184 const char *msg_no_pointer;
1185 const char *msg_unknown_command;
1186 const char *msg_term_too_dumb;
1187 const char *msg_at_node_bottom;
1188 const char *msg_at_node_top;
1189 const char *msg_one_window;
1190 const char *msg_win_too_small;
1191 const char *msg_cant_make_help;
1192
1193 static void
1194 init_messages (void)
1195 {
1196 msg_cant_find_node = _("Cannot find node '%s'");
1197 msg_cant_file_node = _("Cannot find node '(%s)%s'");
1198 msg_cant_find_window = _("Cannot find a window!");
1199 msg_cant_find_point = _("Point doesn't appear within this window's node!");
1200 msg_cant_kill_last = _("Cannot delete the last window");
1201 msg_no_menu_node = _("No menu in this node");
1202 msg_no_foot_node = _("No footnotes in this node");
1203 msg_no_xref_node = _("No cross references in this node");
1204 msg_no_pointer = _("No '%s' pointer for this node");
1205 msg_unknown_command = _("Unknown Info command '%c'; try '?' for help");
1206 msg_term_too_dumb = _("Terminal type '%s' is not smart enough to run Info");
1207 msg_at_node_bottom = _("You are already at the last page of this node");
1208 msg_at_node_top = _("You are already at the first page of this node");
1209 msg_one_window = _("Only one window");
1210 msg_win_too_small = _("Resulting window would be too small");
1211 msg_cant_make_help = _("Not enough room for a help window, please delete a window");
1212 }