(root)/
texinfo-7.1/
info/
info.c
       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  }