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