diff options
Diffstat (limited to 'texinfo/util/install-info.c')
-rw-r--r-- | texinfo/util/install-info.c | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/texinfo/util/install-info.c b/texinfo/util/install-info.c new file mode 100644 index 00000000000..53fa4aa1116 --- /dev/null +++ b/texinfo/util/install-info.c @@ -0,0 +1,1111 @@ +/* install-info -- create Info directory entry(ies) for an Info file. + Copyright (C) 1996 Free Software Foundation, Inc. + +$Id: install-info.c,v 1.12 1996/10/03 23:13:36 karl Exp $ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#define INSTALL_INFO_VERSION_STRING "GNU install-info (Texinfo 3.9) 1.2" + +#include <stdio.h> +#include <errno.h> +#include <getopt.h> +#include <sys/types.h> + +/* Get O_RDONLY. */ +#ifdef HAVE_SYS_FCNTL_H +#include <sys/fcntl.h> +#else +#include <fcntl.h> +#endif /* !HAVE_SYS_FCNTL_H */ +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> +#endif + +/* Name this program was invoked with. */ +char *progname; + +char *readfile (); +struct line_data *findlines (); +char *my_strerror (); +void fatal (); +void insert_entry_here (); +int compare_section_names (); + +struct spec_entry; + +/* Data structures. */ + +/* Record info about a single line from a file + as read into core. */ + +struct line_data +{ + /* The start of the line. */ + char *start; + /* The number of characters in the line, + excluding the terminating newline. */ + int size; + /* Vector containing pointers to the entries to add before this line. + The vector is null-terminated. */ + struct spec_entry **add_entries_before; + /* 1 means output any needed new sections before this line. */ + int add_sections_before; + /* 1 means don't output this line. */ + int delete; +}; + +/* This is used for a list of the specified menu section names + in which entries should be added. */ + +struct spec_section +{ + struct spec_section *next; + char *name; + /* 1 means we have not yet found an existing section with this name + in the dir file--so we will need to add a new section. */ + int missing; +}; + +/* This is used for a list of the entries specified to be added. */ + +struct spec_entry +{ + struct spec_entry *next; + char *text; +}; + +/* This is used for a list of nodes found by parsing the dir file. */ + +struct node +{ + struct node *next; + /* The node name. */ + char *name; + /* The line number of the line where the node starts. + This is the line that contains control-underscore. */ + int start_line; + /* The line number of the line where the node ends, + which is the end of the file or where the next line starts. */ + int end_line; + /* Start of first line in this node's menu + (the line after the * Menu: line). */ + char *menu_start; + /* The start of the chain of sections in this node's menu. */ + struct menu_section *sections; + /* The last menu section in the chain. */ + struct menu_section *last_section; +}; + +/* This is used for a list of sections found in a node's menu. + Each struct node has such a list in the sections field. */ + +struct menu_section +{ + struct menu_section *next; + char *name; + /* Line number of start of section. */ + int start_line; + /* Line number of end of section. */ + int end_line; +}; + +/* Memory allocation and string operations. */ + +/* Like malloc but get fatal error if memory is exhausted. */ + +void * +xmalloc (size) + unsigned int size; +{ + extern void *malloc (); + void *result = malloc (size); + if (result == NULL) + fatal ("virtual memory exhausted", 0); + return result; +} + +/* Like malloc but get fatal error if memory is exhausted. */ + +void * +xrealloc (obj, size) + void *obj; + unsigned int size; +{ + extern void *realloc (); + void *result = realloc (obj, size); + if (result == NULL) + fatal ("virtual memory exhausted", 0); + return result; +} + +/* Return a newly-allocated string whose contents concatenate those of s1, s2, s3. */ + +char * +concat (s1, s2, s3) + char *s1, *s2, *s3; +{ + int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3); + char *result = (char *) xmalloc (len1 + len2 + len3 + 1); + + strcpy (result, s1); + strcpy (result + len1, s2); + strcpy (result + len1 + len2, s3); + *(result + len1 + len2 + len3) = 0; + + return result; +} + +/* Return a string containing SIZE characters + copied from starting at STRING. */ + +char * +copy_string (string, size) + char *string; + int size; +{ + int i; + char *copy = (char *) xmalloc (size + 1); + for (i = 0; i < size; i++) + copy[i] = string[i]; + copy[size] = 0; + return copy; +} + +/* Error message functions. */ + +/* Print error message. `s1' is printf control string, `s2' is arg for it. */ + +/* VARARGS1 */ +void +error (s1, s2, s3) + char *s1, *s2, *s3; +{ + fprintf (stderr, "%s: ", progname); + fprintf (stderr, s1, s2, s3); + fprintf (stderr, "\n"); +} + +/* VARARGS1 */ +void +warning (s1, s2, s3) + char *s1, *s2, *s3; +{ + fprintf (stderr, "%s: Warning: ", progname); + fprintf (stderr, s1, s2, s3); + fprintf (stderr, "\n"); +} + +/* Print error message and exit. */ + +void +fatal (s1, s2, s3) + char *s1, *s2, *s3; +{ + error (s1, s2, s3); + exit (1); +} + +/* Print fatal error message based on errno, with file name NAME. */ + +void +pfatal_with_name (name) + char *name; +{ + char *s = concat ("", my_strerror (errno), " for %s"); + fatal (s, name); +} + +/* Given the full text of a menu entry, null terminated, + return just the menu item name (copied). */ + +char * +extract_menu_item_name (item_text) + char *item_text; +{ + char *p; + + if (*item_text == '*') + item_text++; + while (*item_text == ' ') + item_text++; + + p = item_text; + while (*p && *p != ':') p++; + return copy_string (item_text, p - item_text); +} + +/* Given the full text of a menu entry, terminated by null or newline, + return just the menu item file (copied). */ + +char * +extract_menu_file_name (item_text) + char *item_text; +{ + char *p = item_text; + + /* If we have text that looks like * ITEM: (FILE)NODE..., + extract just FILE. Otherwise return "(none)". */ + + if (*p == '*') + p++; + while (*p == ' ') + p++; + + /* Skip to and past the colon. */ + while (*p && *p != '\n' && *p != ':') p++; + if (*p == ':') p++; + + /* Skip past the open-paren. */ + while (1) + { + if (*p == '(') + break; + else if (*p == ' ' || *p == '\t') + p++; + else + return "(none)"; + } + p++; + + item_text = p; + + /* File name ends just before the close-paren. */ + while (*p && *p != '\n' && *p != ')') p++; + if (*p != ')') + return "(none)"; + + return copy_string (item_text, p - item_text); +} + +void +suggest_asking_for_help () +{ + fprintf (stderr, "\tTry `%s --help' for a complete list of options.\n", + progname); + exit (1); +} + +void +print_help () +{ + printf ("%s [OPTION]... [INFO-FILE [DIR-FILE]]\n\ + Install INFO-FILE in the Info directory file DIR-FILE.\n\ +\n\ +Options:\n\ +--delete Delete existing entries in INFO-FILE;\n\ + don't insert any new entries.\n\ +--dir-file=NAME Specify file name of Info directory file.\n\ + This is equivalent to using the DIR-FILE argument.\n\ +--entry=TEXT Insert TEXT as an Info directory entry.\n\ + TEXT should have the form of an Info menu item line\n\ + plus zero or more extra lines starting with whitespace.\n\ + If you specify more than one entry, they are all added.\n\ + If you don't specify any entries, they are determined\n\ + from information in the Info file itself.\n\ +--help Display this help and exit.\n\ +--info-file=FILE Specify Info file to install in the directory.\n\ + This is equivalent to using the INFO-FILE argument.\n\ +--info-dir=DIR Same as --dir-file=DIR/dir.\n\ +--item=TEXT Same as --entry TEXT.\n\ + An Info directory entry is actually a menu item.\n\ +--quiet Suppress warnings.\n\ +--remove Same as --delete.\n\ +--section=SEC Put this file's entries in section SEC of the directory.\n\ + If you specify more than one section, all the entries\n\ + are added in each of the sections.\n\ + If you don't specify any sections, they are determined\n\ + from information in the Info file itself.\n\ +--version Display version information and exit.\n\ +\n\ +Email bug reports to bug-texinfo@prep.ai.mit.edu.\n\ +", progname); +} + +/* Convert an errno value into a string describing the error. + We define this function here rather than using strerror + because not all systems have strerror. */ + +char * +my_strerror (errnum) + int errnum; +{ + extern char *sys_errlist[]; + extern int sys_nerr; + + if (errnum >= 0 && errnum < sys_nerr) + return sys_errlist[errnum]; + return (char *) "Unknown error"; +} + +/* This table defines all the long-named options, says whether they + use an argument, and maps them into equivalent single-letter options. */ + +struct option longopts[] = +{ + { "delete", no_argument, NULL, 'r' }, + { "dir-file", required_argument, NULL, 'd' }, + { "entry", required_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { "info-dir", required_argument, NULL, 'D' }, + { "info-file", required_argument, NULL, 'i' }, + { "item", required_argument, NULL, 'e' }, + { "quiet", no_argument, NULL, 'q' }, + { "remove", no_argument, NULL, 'r' }, + { "section", required_argument, NULL, 's' }, + { "version", no_argument, NULL, 'V' }, + { 0 } +}; + +main (argc, argv) + int argc; + char **argv; +{ + char *infile = 0, *dirfile = 0; + char *infile_sans_info; + unsigned infilelen_sans_info; + FILE *output; + + /* Record the text of the Info file, as a sequence of characters + and as a sequence of lines. */ + char *input_data; + int input_size; + struct line_data *input_lines; + int input_nlines; + + /* Record here the specified section names and directory entries. */ + struct spec_section *input_sections = NULL; + struct spec_entry *entries_to_add = NULL; + int n_entries_to_add = 0; + + /* Record the old text of the dir file, as plain characters, + as lines, and as nodes. */ + char *dir_data; + int dir_size; + int dir_nlines; + struct line_data *dir_lines; + struct node *dir_nodes; + + /* Nonzero means --delete was specified (just delete existing entries). */ + int delete_flag = 0; + int something_deleted = 0; + /* Nonzero means -q was specified. */ + int quiet_flag = 0; + + int node_header_flag; + int prefix_length; + int i; + + progname = argv[0]; + + while (1) + { + int opt = getopt_long (argc, argv, "i:d:e:s:hHr", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) + { + case 0: + /* If getopt returns 0, then it has already processed a + long-named option. We should do nothing. */ + break; + + case 1: + abort (); + + case 'd': + if (dirfile) + { + fprintf (stderr, "%s: Specify the Info directory only once.\n", + progname); + suggest_asking_for_help (); + } + dirfile = optarg; + break; + + case 'D': + if (dirfile) + { + fprintf (stderr, "%s: Specify the Info directory only once.\n", + progname); + suggest_asking_for_help (); + } + dirfile = concat (optarg, "", "/dir"); + break; + + case 'e': + { + struct spec_entry *next + = (struct spec_entry *) xmalloc (sizeof (struct spec_entry)); + if (! (*optarg != 0 && optarg[strlen (optarg) - 1] == '\n')) + optarg = concat (optarg, "\n", ""); + next->text = optarg; + next->next = entries_to_add; + entries_to_add = next; + n_entries_to_add++; + } + break; + + case 'h': + case 'H': + print_help (); + exit (0); + + case 'i': + if (infile) + { + fprintf (stderr, "%s: Specify the Info file only once.\n", + progname); + suggest_asking_for_help (); + } + infile = optarg; + break; + + case 'q': + quiet_flag = 1; + break; + + case 'r': + delete_flag = 1; + break; + + case 's': + { + struct spec_section *next + = (struct spec_section *) xmalloc (sizeof (struct spec_section)); + next->name = optarg; + next->next = input_sections; + next->missing = 1; + input_sections = next; + } + break; + + case 'V': + puts (INSTALL_INFO_VERSION_STRING); +puts ("Copyright (C) 1996 Free Software Foundation, Inc.\n\ +There is NO warranty. You may redistribute this software\n\ +under the terms of the GNU General Public License.\n\ +For more information about these matters, see the files named COPYING."); + exit (0); + + default: + suggest_asking_for_help (); + } + } + + /* Interpret the non-option arguments as file names. */ + for (; optind < argc; ++optind) + { + if (infile == 0) + infile = argv[optind]; + else if (dirfile == 0) + dirfile = argv[optind]; + else + error ("excess command line argument `%s'", argv[optind]); + } + + if (!infile) + fatal ("No input file specified"); + if (!dirfile) + fatal ("No dir file specified"); + + /* Read the Info file and parse it into lines. */ + + input_data = readfile (infile, &input_size); + input_lines = findlines (input_data, input_size, &input_nlines); + + /* Parse the input file to find the section names it specifies. */ + + if (input_sections == 0) + { + prefix_length = strlen ("INFO-DIR-SECTION "); + for (i = 0; i < input_nlines; i++) + { + if (!strncmp ("INFO-DIR-SECTION ", input_lines[i].start, + prefix_length)) + { + struct spec_section *next + = (struct spec_section *) xmalloc (sizeof (struct spec_section)); + next->name = copy_string (input_lines[i].start + prefix_length, + input_lines[i].size - prefix_length); + next->next = input_sections; + next->missing = 1; + input_sections = next; + } + } + } + + /* Default to section "Miscellaneous" if no sections specified. */ + if (input_sections == 0) + { + input_sections + = (struct spec_section *) xmalloc (sizeof (struct spec_section)); + input_sections->name = "Miscellaneous"; + input_sections->next = 0; + input_sections->missing = 1; + } + + /* Now find the directory entries specified in the file + and put them on entries_to_add. But not if entries + were specified explicitly with command options. */ + + if (entries_to_add == 0) + { + char *start_of_this_entry = 0; + for (i = 0; i < input_nlines; i++) + { + if (!strncmp ("START-INFO-DIR-ENTRY", input_lines[i].start, + input_lines[i].size) + && sizeof ("START-INFO-DIR-ENTRY") - 1 == input_lines[i].size) + { + if (start_of_this_entry != 0) + fatal ("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"); + start_of_this_entry = input_lines[i + 1].start; + } + if (!strncmp ("END-INFO-DIR-ENTRY", input_lines[i].start, + input_lines[i].size) + && sizeof ("END-INFO-DIR-ENTRY") - 1 == input_lines[i].size) + { + if (start_of_this_entry != 0) + { + struct spec_entry *next + = (struct spec_entry *) xmalloc (sizeof (struct spec_entry)); + next->text = copy_string (start_of_this_entry, + input_lines[i].start - start_of_this_entry); + next->next = entries_to_add; + entries_to_add = next; + n_entries_to_add++; + start_of_this_entry = 0; + } + else + fatal ("END-INFO-DIR-ENTRY without matching START-INFO-DIR-ENTRY"); + } + } + if (start_of_this_entry != 0) + fatal ("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"); + } + + if (!delete_flag) + if (entries_to_add == 0) + fatal ("no info dir entry in `%s'", infile); + + /* Now read in the Info dir file. */ + dir_data = readfile (dirfile, &dir_size); + dir_lines = findlines (dir_data, dir_size, &dir_nlines); + + /* We will be comparing the entries in the dir file against the + current filename, so need to strip off any directory prefix and any + .info suffix. */ + { + unsigned basename_len; + extern char *strrchr (); + char *infile_basename = strrchr (infile, '/'); + if (infile_basename) + infile_basename++; + else + infile_basename = infile; + + basename_len = strlen (infile_basename); + infile_sans_info + = (strlen (infile_basename) > 5 + && strcmp (infile_basename + basename_len - 5, ".info") == 0) + ? copy_string (infile_basename, basename_len - 5) + : infile_basename; + + infilelen_sans_info = strlen (infile_sans_info); + } + + /* Parse the dir file. Find all the nodes, and their menus, + and the sections of their menus. */ + + dir_nodes = 0; + node_header_flag = 0; + for (i = 0; i < dir_nlines; i++) + { + /* Parse node header lines. */ + if (node_header_flag) + { + int j, end; + for (j = 0; j < dir_lines[i].size; j++) + /* Find the node name and store it in the `struct node'. */ + if (!strncmp ("Node:", dir_lines[i].start + j, 5)) + { + char *line = dir_lines[i].start; + /* Find the start of the node name. */ + j += 5; + while (line[j] == ' ' || line[j] == '\t') + j++; + /* Find the end of the node name. */ + end = j; + while (line[end] != 0 && line[end] != ',' && line[end] != '\n' + && line[end] != '\t') + end++; + dir_nodes->name = copy_string (line + j, end - j); + } + node_header_flag = 0; + } + + /* Notice the start of a node. */ + if (*dir_lines[i].start == 037) + { + struct node *next + = (struct node *) xmalloc (sizeof (struct node)); + next->next = dir_nodes; + next->name = NULL; + next->start_line = i; + next->end_line = 0; + next->menu_start = NULL; + next->sections = NULL; + next->last_section = NULL; + + if (dir_nodes != 0) + dir_nodes->end_line = i; + /* Fill in the end of the last menu section + of the previous node. */ + if (dir_nodes != 0 && dir_nodes->last_section != 0) + dir_nodes->last_section->end_line = i; + + dir_nodes = next; + + /* The following line is the header of this node; + parse it. */ + node_header_flag = 1; + } + + /* Notice the lines that start menus. */ + if (dir_nodes != 0 + && !strncmp ("* Menu:", dir_lines[i].start, 7)) + dir_nodes->menu_start = dir_lines[i + 1].start; + + /* Notice sections in menus. */ + if (dir_nodes != 0 + && dir_nodes->menu_start != 0 + && *dir_lines[i].start != '\n' + && *dir_lines[i].start != '*' + && *dir_lines[i].start != ' ' + && *dir_lines[i].start != '\t') + { + /* Add this menu section to the node's list. + This list grows in forward order. */ + struct menu_section *next + = (struct menu_section *) xmalloc (sizeof (struct menu_section)); + next->start_line = i + 1; + next->next = 0; + next->end_line = 0; + next->name = copy_string (dir_lines[i].start, dir_lines[i].size); + if (dir_nodes->sections) + { + dir_nodes->last_section->next = next; + dir_nodes->last_section->end_line = i; + } + else + dir_nodes->sections = next; + dir_nodes->last_section = next; + } + + /* Check for an existing entry that should be deleted. + Delete all entries which specify this file name. */ + if (*dir_lines[i].start == '*') + { + char *p = dir_lines[i].start; + + while (*p != 0 && *p != ':') + p++; + p++; + while (*p == ' ') p++; + if (*p == '(') + { + p++; + if ((dir_lines[i].size + > (p - dir_lines[i].start + infilelen_sans_info)) + && !strncmp (p, infile_sans_info, infilelen_sans_info) + && p[infilelen_sans_info] == ')') + dir_lines[i].delete = 1; + } + } + /* Treat lines that start with whitespace + as continuations; if we are deleting an entry, + delete all its continuations as well. */ + else if (i > 0 + && (*dir_lines[i].start == ' ' + || *dir_lines[i].start == '\t')) + { + dir_lines[i].delete = dir_lines[i - 1].delete; + something_deleted = 1; + } + } + + /* Finish the info about the end of the last node. */ + if (dir_nodes != 0) + { + dir_nodes->end_line = dir_nlines; + if (dir_nodes->last_section != 0) + dir_nodes->last_section->end_line = dir_nlines; + } + + /* Decide where to add the new entries (unless --delete was used). + Find the menu sections to add them in. + In each section, find the proper alphabetical place to add + each of the entries. */ + + if (!delete_flag) + { + struct node *node; + struct menu_section *section; + struct spec_section *spec; + + for (node = dir_nodes; node; node = node->next) + for (section = node->sections; section; section = section->next) + { + for (i = section->end_line; i > section->start_line; i--) + if (dir_lines[i - 1].size != 0) + break; + section->end_line = i; + + for (spec = input_sections; spec; spec = spec->next) + if (!strcmp (spec->name, section->name)) + break; + if (spec) + { + int add_at_line = section->end_line; + struct spec_entry *entry; + /* Say we have found at least one section with this name, + so we need not add such a section. */ + spec->missing = 0; + /* For each entry, find the right place in this section + to add it. */ + for (entry = entries_to_add; entry; entry = entry->next) + { + int textlen = strlen (entry->text); + /* Subtract one because dir_lines is zero-based, + but the `end_line' and `start_line' members are + one-based. */ + for (i = section->end_line - 1; + i >= section->start_line - 1; i--) + { + /* If an entry exists with the same name, + and was not marked for deletion + (which means it is for some other file), + we are in trouble. */ + if (dir_lines[i].start[0] == '*' + && menu_line_equal (entry->text, textlen, + dir_lines[i].start, + dir_lines[i].size) + && !dir_lines[i].delete) + fatal ("menu item `%s' already exists, for file `%s'", + extract_menu_item_name (entry->text), + extract_menu_file_name (dir_lines[i].start)); + if (dir_lines[i].start[0] == '*' + && menu_line_lessp (entry->text, textlen, + dir_lines[i].start, + dir_lines[i].size)) + add_at_line = i; + } + insert_entry_here (entry, add_at_line, + dir_lines, n_entries_to_add); + } + } + } + + /* Mark the end of the Top node as the place to add any + new sections that are needed. */ + for (node = dir_nodes; node; node = node->next) + if (node->name && strcmp (node->name, "Top") == 0) + dir_lines[node->end_line].add_sections_before = 1; + } + + if (delete_flag && !something_deleted && !quiet_flag) + warning ("no entries found for `%s'; nothing deleted", infile); + + /* Output the old dir file, interpolating the new sections + and/or new entries where appropriate. */ + + output = fopen (dirfile, "w"); + if (!output) + { + perror (dirfile); + exit (1); + } + + for (i = 0; i <= dir_nlines; i++) + { + int j; + + /* If we decided to output some new entries before this line, + output them now. */ + if (dir_lines[i].add_entries_before) + for (j = 0; j < n_entries_to_add; j++) + { + struct spec_entry *this = dir_lines[i].add_entries_before[j]; + if (this == 0) + break; + fputs (this->text, output); + } + /* If we decided to add some sections here + because there are no such sections in the file, + output them now. */ + if (dir_lines[i].add_sections_before) + { + struct spec_section *spec; + struct spec_section **sections; + int n_sections = 0; + + /* Count the sections and allocate a vector for all of them. */ + for (spec = input_sections; spec; spec = spec->next) + n_sections++; + sections = ((struct spec_section **) + xmalloc (n_sections * sizeof (struct spec_section *))); + + /* Fill the vector SECTIONS with pointers to all the sections, + and sort them. */ + j = 0; + for (spec = input_sections; spec; spec = spec->next) + sections[j++] = spec; + qsort (sections, n_sections, sizeof (struct spec_section *), + compare_section_names); + + /* Generate the new sections in alphabetical order. + In each new section, output all of our entries. */ + for (j = 0; j < n_sections; j++) + { + spec = sections[j]; + if (spec->missing) + { + struct spec_entry *entry; + + putc ('\n', output); + fputs (spec->name, output); + putc ('\n', output); + for (entry = entries_to_add; entry; entry = entry->next) + fputs (entry->text, output); + } + } + + free (sections); + } + + /* Output the original dir lines unless marked for deletion. */ + if (i < dir_nlines && !dir_lines[i].delete) + { + fwrite (dir_lines[i].start, 1, dir_lines[i].size, output); + putc ('\n', output); + } + } + + fclose (output); + + exit (0); +} + +/* Read all of file FILNAME into memory + and return the address of the data. + Store the size into SIZEP. + If there is trouble, do a fatal error. */ + +char * +readfile (filename, sizep) + char *filename; + int *sizep; +{ + int data_size = 1024; + char *data = (char *) xmalloc (data_size); + int filled = 0; + int nread = 0; + + int desc = open (filename, O_RDONLY); + + if (desc < 0) + pfatal_with_name (filename); + + while (1) + { + nread = read (desc, data + filled, data_size - filled); + if (nread < 0) + pfatal_with_name (filename); + if (nread == 0) + break; + + filled += nread; + if (filled == data_size) + { + data_size *= 2; + data = (char *) xrealloc (data, data_size); + } + } + + *sizep = filled; + return data; +} + +/* Divide the text at DATA (of SIZE bytes) into lines. + Return a vector of struct line_data describing the lines. + Store the length of that vector into *NLINESP. */ + +struct line_data * +findlines (data, size, nlinesp) + char *data; + int size; + int *nlinesp; +{ + struct line_data *lines; + int lines_allocated = 512; + int filled = 0; + int i = 0; + int lineflag; + + lines = (struct line_data *) xmalloc (lines_allocated * sizeof (struct line_data)); + + lineflag = 1; + for (i = 0; i < size; i++) + { + if (lineflag) + { + if (filled == lines_allocated) + { + lines_allocated *= 2; + lines = (struct line_data *) xrealloc (lines, lines_allocated * sizeof (struct line_data)); + } + lines[filled].start = &data[i]; + lines[filled].add_entries_before = 0; + lines[filled].add_sections_before = 0; + lines[filled].delete = 0; + if (filled > 0) + lines[filled - 1].size + = lines[filled].start - lines[filled - 1].start - 1; + filled++; + } + lineflag = (data[i] == '\n'); + } + if (filled > 0) + lines[filled - 1].size = &data[i] - lines[filled - 1].start - lineflag; + + /* Do not leave garbage in the last element. */ + lines[filled].start = NULL; + lines[filled].add_entries_before = NULL; + lines[filled].add_sections_before = 0; + lines[filled].delete = 0; + lines[filled].size = 0; + + *nlinesp = filled; + return lines; +} + +/* Compare the menu item names in LINE1 (line length LEN1) + and LINE2 (line length LEN2). Return 1 if the item name + in LINE1 is less, 0 otherwise. */ + +int +menu_line_lessp (line1, len1, line2, len2) + char *line1; + int len1; + char *line2; + int len2; +{ + int minlen = (len1 < len2 ? len1 : len2); + int i; + + for (i = 0; i < minlen; i++) + { + /* If one item name is a prefix of the other, + the former one is less. */ + if (line1[i] == ':' && line2[i] != ':') + return 1; + if (line2[i] == ':' && line1[i] != ':') + return 0; + /* If they both continue and differ, one is less. */ + if (line1[i] < line2[i]) + return 1; + if (line1[i] > line2[i]) + return 0; + } + /* With a properly formatted dir file, + we can only get here if the item names are equal. */ + return 0; +} + +/* Compare the menu item names in LINE1 (line length LEN1) + and LINE2 (line length LEN2). Return 1 if the item names are equal, + 0 otherwise. */ + +int +menu_line_equal (line1, len1, line2, len2) + char *line1; + int len1; + char *line2; + int len2; +{ + int minlen = (len1 < len2 ? len1 : len2); + int i; + + for (i = 0; i < minlen; i++) + { + /* If both item names end here, they are equal. */ + if (line1[i] == ':' && line2[i] == ':') + return 1; + /* If they both continue and differ, one is less. */ + if (line1[i] != line2[i]) + return 0; + } + /* With a properly formatted dir file, + we can only get here if the item names are equal. */ + return 1; +} + +/* This is the comparison function for qsort + for a vector of pointers to struct spec_section. + Compare the section names. */ + +int +compare_section_names (sec1, sec2) + struct spec_section **sec1, **sec2; +{ + char *name1 = (*sec1)->name; + char *name2 = (*sec2)->name; + return strcmp (name1, name2); +} + +/* Insert ENTRY into the add_entries_before vector + for line number LINE_NUMBER of the dir file. + DIR_LINES and N_ENTRIES carry information from like-named variables + in main. */ + +void +insert_entry_here (entry, line_number, dir_lines, n_entries) + struct spec_entry *entry; + int line_number; + struct line_data *dir_lines; + int n_entries; +{ + int i; + + if (dir_lines[line_number].add_entries_before == 0) + { + dir_lines[line_number].add_entries_before + = (struct spec_entry **) xmalloc (n_entries * sizeof (struct spec_entry *)); + for (i = 0; i < n_entries; i++) + dir_lines[line_number].add_entries_before[i] = 0; + } + + for (i = 0; i < n_entries; i++) + if (dir_lines[line_number].add_entries_before[i] == 0) + break; + + if (i == n_entries) + abort (); + + dir_lines[line_number].add_entries_before[i] = entry; +} |