aboutsummaryrefslogtreecommitdiff
path: root/texinfo/util
diff options
context:
space:
mode:
authorJason Merrill <jason@gcc.gnu.org>1997-08-21 18:57:35 -0400
committerJason Merrill <jason@gcc.gnu.org>1997-08-21 18:57:35 -0400
commit6599da043e22e96ac830fb50a61c1b6d95d1b142 (patch)
treea3b19970338bdae580faff126a716e1d5520400c /texinfo/util
parent8975416cfb6269ad94b6330d42960cca8b0925b7 (diff)
downloadgcc-6599da043e22e96ac830fb50a61c1b6d95d1b142.zip
gcc-6599da043e22e96ac830fb50a61c1b6d95d1b142.tar.gz
gcc-6599da043e22e96ac830fb50a61c1b6d95d1b142.tar.bz2
Initial revision
From-SVN: r14877
Diffstat (limited to 'texinfo/util')
-rw-r--r--texinfo/util/Makefile.in105
-rw-r--r--texinfo/util/deref.c238
-rwxr-xr-xtexinfo/util/fixfonts84
-rwxr-xr-xtexinfo/util/gen-dir-node176
-rw-r--r--texinfo/util/install-info.c1111
-rwxr-xr-xtexinfo/util/mkinstalldirs40
-rwxr-xr-xtexinfo/util/tex3patch71
-rwxr-xr-xtexinfo/util/texi2dvi367
-rw-r--r--texinfo/util/texindex.c1793
9 files changed, 3985 insertions, 0 deletions
diff --git a/texinfo/util/Makefile.in b/texinfo/util/Makefile.in
new file mode 100644
index 0000000..9108632
--- /dev/null
+++ b/texinfo/util/Makefile.in
@@ -0,0 +1,105 @@
+# Makefile for GNU Texindex and other utilities.
+# $Id: Makefile.in,v 1.1 1997/08/21 22:58:12 jason Exp $
+#
+# Copyright (C) 1990, 91, 92, 96 Free Software Foundation, Inc.
+
+# 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, 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.
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+VPATH = $(srcdir):$(common)
+
+common = $(srcdir)/../libtxi
+
+EXEEXT = @EXEEXT@
+CC = @CC@
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_DATA = @INSTALL_DATA@
+
+LN = ln
+RM = rm -f
+MKDIR = mkdir
+
+DEFS = @DEFS@
+LIBS = -L../libtxi -ltxi @LIBS@
+LOADLIBES = $(LIBS)
+
+SHELL = /bin/sh
+
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+# Prefix for each installed program, normally empty or `g'.
+binprefix =
+# Prefix for each installed man page, normally empty or `g'.
+manprefix =
+mandir = @mandir@/man1
+manext = 1
+infodir = @infodir@
+
+#### End of system configuration section. ####
+
+all: texindex$(EXEEXT) install-info$(EXEEXT)
+sub-all: all
+
+.c.o:
+ $(CC) -c $(CPPFLAGS) $(DEFS) -I. -I$(srcdir) -I$(common) $(CFLAGS) $<
+
+
+install: all
+ $(INSTALL_PROGRAM) texindex$(EXEEXT) $(bindir)/texindex$(EXEEXT)
+ $(INSTALL_PROGRAM) $(srcdir)/texi2dvi $(bindir)/texi2dvi
+ $(INSTALL_PROGRAM) install-info$(EXEEXT) $(bindir)/install-info$(EXEEXT)
+
+install-info:
+dvi:
+
+uninstall:
+ rm -f $(bindir)/texindex$(EXEEXT) $(bindir)/texi2dvi $(bindir)/install-info$(EXEEXT)
+
+Makefile: Makefile.in ../config.status
+ cd ..; $(SHELL) config.status
+
+TAGS:
+ etags *.c *.h $(common)/getopt*.c $(common)/getopt.h
+
+clean:
+ rm -f *.o a.out core core.* texindex install-info
+
+mostlyclean: clean
+
+distclean: clean
+ rm -f Makefile config.status
+
+realclean: distclean
+ rm -f TAGS
+
+texindex: texindex.o ../libtxi/libtxi.a
+ $(CC) $(LDFLAGS) -o texindex texindex.o $(LOADLIBES)
+
+texindex.o: texindex.c $(common)/getopt.h
+
+install-info: install-info.o
+ $(CC) $(LDFLAGS) -o install-info install-info.o $(LOADLIBES)
+
+install-info.o: install-info.c $(common)/getopt.h
+
+# Prevent GNU make v3 from overflowing arg limit on SysV.
+.NOEXPORT:
diff --git a/texinfo/util/deref.c b/texinfo/util/deref.c
new file mode 100644
index 0000000..c15bc1a
--- /dev/null
+++ b/texinfo/util/deref.c
@@ -0,0 +1,238 @@
+/*
+ * deref.c
+
+ * compile command: gcc -g -o deref deref.c
+
+ * execute command: deref filename.texi > newfile.texi
+
+ * To: bob@gnu.ai.mit.edu
+ * Subject: another tool
+ * Date: 18 Dec 91 16:03:13 EST (Wed)
+ * From: gatech!skeeve!arnold@eddie.mit.edu (Arnold D. Robbins)
+ *
+ * Here is deref.c. It turns texinfo cross references back into the
+ * one argument form. It has the same limitations as fixref; one xref per
+ * line and can't cross lines. You can use it to find references that do
+ * cross a line boundary this way:
+ *
+ * deref < manual > /dev/null 2>errs
+ *
+ * (This assumes bash or /bin/sh.) The file errs will have list of lines
+ * where deref could not find matching braces.
+ *
+ * A gawk manual processed by deref goes through makeinfo without complaint.
+ * Compile with gcc and you should be set.
+ *
+ * Enjoy,
+ *
+ * Arnold
+ * -----------
+ */
+
+/*
+ * deref.c
+ *
+ * Make all texinfo references into the one argument form.
+ *
+ * Arnold Robbins
+ * arnold@skeeve.atl.ga.us
+ * December, 1991
+ *
+ * Copyright, 1991, Arnold Robbins
+ */
+
+/*
+ * LIMITATIONS:
+ * One texinfo cross reference per line.
+ * Cross references may not cross newlines.
+ * Use of fgets for input (to be fixed).
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+
+/* for gcc on the 3B1, delete if this gives you grief */
+extern int fclose (FILE * fp);
+extern int fprintf (FILE * fp, const char *str,...);
+
+extern char *strerror (int errno);
+extern char *strchr (char *cp, int ch);
+extern int strncmp (const char *s1, const char *s2, int count);
+
+extern int errno;
+
+void process (FILE * fp);
+void repair (char *line, char *ref, int toffset);
+
+int Errs = 0;
+char *Name = "stdin";
+int Line = 0;
+char *Me;
+
+/* main --- handle arguments, global vars for errors */
+
+int
+main (int argc, char **argv)
+{
+ FILE *fp;
+
+ Me = argv[0];
+
+ if (argc == 1)
+ process (stdin);
+ else
+ for (argc--, argv++; *argv != NULL; argc--, argv++)
+ {
+ if (argv[0][0] == '-' && argv[0][1] == '\0')
+ {
+ Name = "stdin";
+ Line = 0;
+ process (stdin);
+ }
+ else if ((fp = fopen (*argv, "r")) != NULL)
+ {
+ Name = *argv;
+ Line = 0;
+ process (fp);
+ fclose (fp);
+ }
+ else
+ {
+ fprintf (stderr, "%s: can not open: %s\n",
+ *argv, strerror (errno));
+ Errs++;
+ }
+ }
+ return Errs != 0;
+}
+
+/* isref --- decide if we've seen a texinfo cross reference */
+
+int
+isref (char *cp)
+{
+ if (strncmp (cp, "@ref{", 5) == 0)
+ return 5;
+ if (strncmp (cp, "@xref{", 6) == 0)
+ return 6;
+ if (strncmp (cp, "@pxref{", 7) == 0)
+ return 7;
+ return 0;
+}
+
+/* process --- read files, look for references, fix them up */
+
+void
+process (FILE * fp)
+{
+ char buf[BUFSIZ];
+ char *cp;
+ int count;
+
+ while (fgets (buf, sizeof buf, fp) != NULL)
+ {
+ Line++;
+ cp = strchr (buf, '@');
+ if (cp == NULL)
+ {
+ fputs (buf, stdout);
+ continue;
+ }
+ do
+ {
+ count = isref (cp);
+ if (count == 0)
+ {
+ cp++;
+ cp = strchr (cp, '@');
+ if (cp == NULL)
+ {
+ fputs (buf, stdout);
+ goto next;
+ }
+ continue;
+ }
+ /* got one */
+ repair (buf, cp, count);
+ break;
+ }
+ while (cp != NULL);
+ next:;
+ }
+}
+
+/* repair --- turn all texinfo cross references into the one argument form */
+
+void
+repair (char *line, char *ref, int toffset)
+{
+ int braces = 1; /* have seen first left brace */
+ char *cp;
+
+ ref += toffset;
+
+ /* output line up to and including left brace in reference */
+ for (cp = line; cp <= ref; cp++)
+ putchar (*cp);
+
+ /* output node name */
+ for (; *cp && *cp != '}' && *cp != ',' && *cp != '\n'; cp++)
+ putchar (*cp);
+
+ if (*cp != '}')
+ { /* could have been one arg xref */
+ /* skip to matching right brace */
+ for (; braces > 0; cp++)
+ {
+ switch (*cp)
+ {
+ case '@':
+ cp++; /* blindly skip next character */
+ break;
+ case '{':
+ braces++;
+ break;
+ case '}':
+ braces--;
+ break;
+ case '\n':
+ case '\0':
+ Errs++;
+ fprintf (stderr,
+ "%s: %s: %d: mismatched braces\n",
+ Me, Name, Line);
+ goto out;
+ default:
+ break;
+ }
+ }
+ out:
+ ;
+ }
+
+ putchar ('}');
+ if (*cp == '}')
+ cp++;
+
+ /* now the rest of the line */
+ for (; *cp; cp++)
+ putchar (*cp);
+ return;
+}
+
+/* strerror --- return error string, delete if in your library */
+
+char *
+strerror (int errno)
+{
+ static char buf[100];
+ extern int sys_nerr;
+ extern char *sys_errlist[];
+
+ if (errno < sys_nerr && errno >= 0)
+ return sys_errlist[errno];
+
+ sprintf (buf, "unknown error %d", errno);
+ return buf;
+}
diff --git a/texinfo/util/fixfonts b/texinfo/util/fixfonts
new file mode 100755
index 0000000..ee2ea71
--- /dev/null
+++ b/texinfo/util/fixfonts
@@ -0,0 +1,84 @@
+#!/bin/sh
+# Make links named `lcircle10' for all TFM and GF/PK files, if no
+# lcircle10 files already exist.
+
+# Don't override definition of prefix and/or libdir if they are
+# already defined in the environment.
+if test "z${prefix}" = "z" ; then
+ prefix=/usr/local
+else
+ # prefix may contain references to other variables, thanks to make.
+ eval prefix=\""${prefix}"\"
+fi
+
+if test "z${libdir}" = "z" ; then
+ libdir="${prefix}/lib/tex"
+else
+ # libdir may contain references to other variables, thanks to make.
+ eval libdir=\""${libdir}"\"
+fi
+
+texlibdir="${libdir}"
+texfontdir="${texlibdir}/fonts"
+
+# Directories for the different font formats, in case they're not all
+# stored in one place.
+textfmdir="${textfmdir-${texfontdir}}"
+texpkdir="${texpkdir-${texfontdir}}"
+texgfdir="${texgfdir-${texfontdir}}"
+
+test "z${TMPDIR}" = "z" && TMPDIR="/tmp"
+
+tempfile="${TMPDIR}/circ$$"
+tempfile2="${TMPDIR}/circ2$$"
+
+# EXIT SIGHUP SIGINT SIGQUIT SIGTERM
+#trap 'rm -f "${tempfile}" "${tempfile2}"' 0 1 2 3 15
+
+# Find all the fonts with names that include `circle'.
+(cd "${texfontdir}"; find . -name '*circle*' -print > "${tempfile}")
+
+# If they have lcircle10.tfm, assume everything is there, and quit.
+if grep 'lcircle10\.tfm' "${tempfile}" > /dev/null 2>&1 ; then
+ echo "Found lcircle10.tfm."
+ exit 0
+fi
+
+# No TFM file for lcircle. Make a link to circle10.tfm if it exists,
+# and then make a link to the bitmap files.
+grep 'circle10\.tfm' "${tempfile}" > "${tempfile2}" \
+ || {
+ echo "I can't find any circle fonts in ${texfontdir}.
+If it isn't installed somewhere else, you need to get the Metafont sources
+from somewhere, e.g., labrea.stanford.edu:pub/tex/latex/circle10.mf, and
+run Metafont on them."
+ exit 1
+ }
+
+# We have circle10.tfm. (If we have it more than once, take the first
+# one.) Make the link.
+tempfile2_line1="`sed -ne '1p;q' \"${tempfile2}\"`"
+ln "${tempfile2_line1}" "${textfmdir}/lcircle10.tfm"
+echo "Linked to ${tempfile2_line1}."
+
+# Now make a link for the PK files, if any.
+(cd "${texpkdir}"
+ for f in `grep 'circle10.*pk' "${tempfile}"` ; do
+ set - `echo "$f" \
+ | sed -ne '/\//!s/^/.\//;s/\(.*\)\/\([^\/][^\/]*\)$/\1 \2/;p'`
+ ln "$f" "${1}/l${2}"
+ echo "Linked to $f."
+ done
+)
+
+# And finally for the GF files.
+(cd "${texgfdir}"
+ for f in `grep 'circle10.*gf' "${tempfile}"` ; do
+ set - `echo "$f" \
+ | sed -ne '/\//!s/^/.\//;s/\(.*\)\/\([^\/][^\/]*\)$/\1 \2/;p'`
+ ln "$f" "${1}/l${2}"
+ echo "Linked to $f."
+ done
+)
+
+# eof
diff --git a/texinfo/util/gen-dir-node b/texinfo/util/gen-dir-node
new file mode 100755
index 0000000..ed82504
--- /dev/null
+++ b/texinfo/util/gen-dir-node
@@ -0,0 +1,176 @@
+#!/bin/sh
+# $Id: gen-dir-node,v 1.1 1997/08/21 22:58:12 jason Exp $
+# Generate the top-level Info node, given a directory of Info files
+# and (optionally) a skeleton file. The output will be suitable for a
+# top-level dir file. The skeleton file contains info topic names in the
+# order they should appear in the output. There are three special
+# lines that alter the behavior: a line consisting of just "--" causes
+# the next line to be echoed verbatim to the output. A line
+# containing just "%%" causes all the remaining filenames (wildcards
+# allowed) in the rest of the file to be ignored. A line containing
+# just "!!" exits the script when reached (unless preceded by a line
+# containing just "--"). Once the script reaches the end of the
+# skeleton file, it goes through the remaining files in the directory
+# in order, putting their entries at the end. The script will use the
+# ENTRY information in each info file if it exists. Otherwise it will
+# make a minimal entry.
+
+# sent by Jeffrey Osier <jeffrey@cygnus.com>, who thinks it came from
+# zoo@winternet.com (david d `zoo' zuhn)
+
+# modified 7 April 1995 by Joe Harrington <jh@tecate.gsfc.nasa.gov> to
+# take special flags
+
+INFODIR=$1
+if [ $# = 2 ] ; then
+ SKELETON=$2
+else
+ SKELETON=/dev/null
+fi
+
+skip=
+
+if [ $# -gt 2 ] ; then
+ echo usage: $0 info-directory [ skeleton-file ] 1>&2
+ exit 1
+else
+ true
+fi
+
+if [ ! -d ${INFODIR} ] ; then
+ echo "$0: first argument must specify a directory"
+ exit 1
+fi
+
+### output the dir header
+echo "-*- Text -*-"
+echo "This file was generated automatically by $0."
+echo "This version was generated on `date`"
+echo "by `whoami`@`hostname` for `(cd ${INFODIR}; pwd)`"
+
+cat << moobler
+
+This is the file .../info/dir, which contains the topmost node of the
+Info hierarchy. The first time you invoke Info you start off
+looking at that node, which is (dir)Top.
+
+File: dir Node: Top This is the top of the INFO tree
+ This (the Directory node) gives a menu of major topics.
+ Typing "d" returns here, "q" exits, "?" lists all INFO commands, "h"
+ gives a primer for first-timers, "mTexinfo<Return>" visits Texinfo topic,
+ etc.
+ Or click mouse button 2 on a menu item or cross reference to select it.
+ --- PLEASE ADD DOCUMENTATION TO THIS TREE. (See INFO topic first.) ---
+
+* Menu: The list of major topics begins on the next line.
+
+moobler
+
+### go through the list of files in the skeleton. If an info file
+### exists, grab the ENTRY information from it. If an entry exists
+### use it, otherwise create a minimal dir entry.
+###
+### Then remove that file from the list of existing files. If any
+### additional files remain (ones that don't have a skeleton entry),
+### then generate entries for those in the same way, putting the info for
+### those at the end....
+
+infofiles=`(cd ${INFODIR}; ls | egrep -v '\-|^dir$|^dir\.info$|^dir\.orig$')`
+
+# echoing gets clobbered by backquotes; we do it the hard way...
+lines=`wc $SKELETON | awk '{print $1}'`
+line=1
+while [ $lines -ge $line ] ; do
+ # Read one line from the file. This is so that we can echo lines with
+ # whitespace and quoted characters in them.
+ fileline=`awk NR==$line $SKELETON`
+
+ # flag fancy features
+ if [ ! -z "$echoline" ] ; then # echo line
+ echo "$fileline"
+ fileline=
+ echoline=
+ elif [ "${fileline}" = "--" ] ; then # should we echo the next line?
+ echoline=1
+ elif [ "${fileline}" = "%%" ] ; then # eliminate remaining files from dir?
+ skip=1
+ elif [ "${fileline}" = "!!" ] ; then # quit now
+ exit 0
+ fi
+
+ # handle files if they exist
+ for file in $fileline"" ; do # expand wildcards ("" handles blank lines)
+
+ fname=
+
+ if [ -z "$echoline" -a ! -z "$file" ] ; then
+
+ # Find the file to operate upon. Check both possible names.
+ infoname=`echo $file | sed 's/\.info$//'`
+ noext=
+ ext=
+ if [ -f ${INFODIR}/$infoname ] ; then
+ noext=$infoname
+ fi
+ if [ -f ${INFODIR}/${infoname}.info ] ; then
+ ext=${infoname}.info
+ fi
+
+ # If it exists with both names take what was said in the file.
+ if [ ! -z "$ext" -a ! -z "$noext" ]; then
+ fname=$file
+ warn="### Warning: $ext and $noext both exist! Using ${file}. ###"
+ elif [ ! \( -z "$ext" -a -z "$noext" \) ]; then
+ # just take the name if it exists only once
+ fname=${noext}${ext}
+ fi
+
+ # if we found something and aren't skipping, do the entry
+ if [ ! -z "$fname" ] ; then
+ if [ -z "$skip" ] ; then
+
+ if [ ! -z "$warn" ] ; then # issue any warning
+ echo $warn
+ warn=
+ fi
+
+ entry=`sed -e '1,/START-INFO-DIR-ENTRY/d' \
+ -e '/END-INFO-DIR-ENTRY/,$d' ${INFODIR}/$fname`
+ if [ ! -z "${entry}" ] ; then
+ echo "${entry}"
+ else
+ echo "* ${infoname}: (${fname})."
+ fi
+ fi
+
+ # remove the name from the directory listing
+ infofiles=`echo ${infofiles} | sed -e "s/ ${fname} / /" \
+ -e "s/^${fname} //" \
+ -e "s/ ${fname}$//"`
+
+ fi
+
+ fi
+
+ done
+
+ line=`expr $line + 1`
+done
+
+if [ -z "${infofiles}" ] ; then
+ exit 0
+else
+ echo
+fi
+
+for file in ${infofiles}; do
+ infoname=`echo $file | sed 's/\.info$//'`
+ entry=`sed -e '1,/START-INFO-DIR-ENTRY/d' \
+ -e '/END-INFO-DIR-ENTRY/,$d' ${INFODIR}/${file}`
+
+ if [ ! -z "${entry}" ] ; then
+ echo "${entry}"
+ else
+ echo "* ${infoname}: (${file})."
+ fi
+done
diff --git a/texinfo/util/install-info.c b/texinfo/util/install-info.c
new file mode 100644
index 0000000..53fa4aa
--- /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;
+}
diff --git a/texinfo/util/mkinstalldirs b/texinfo/util/mkinstalldirs
new file mode 100755
index 0000000..934c671
--- /dev/null
+++ b/texinfo/util/mkinstalldirs
@@ -0,0 +1,40 @@
+#! /bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+# Created: 1993-05-16
+# Public domain
+
+# $Id: mkinstalldirs,v 1.1 1997/08/21 22:58:12 jason Exp $
+
+errstatus=0
+
+for file
+do
+ set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+ shift
+
+ pathcomp=
+ for d
+ do
+ pathcomp="$pathcomp$d"
+ case "$pathcomp" in
+ -* ) pathcomp=./$pathcomp ;;
+ esac
+
+ if test ! -d "$pathcomp"; then
+ echo "mkdir $pathcomp" 1>&2
+
+ mkdir "$pathcomp" || lasterr=$?
+
+ if test ! -d "$pathcomp"; then
+ errstatus=$lasterr
+ fi
+ fi
+
+ pathcomp="$pathcomp/"
+ done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/texinfo/util/tex3patch b/texinfo/util/tex3patch
new file mode 100755
index 0000000..1708c75
--- /dev/null
+++ b/texinfo/util/tex3patch
@@ -0,0 +1,71 @@
+#!/bin/sh
+# Auxiliary script to work around TeX 3.0 bug. ---- tex3patch ----
+# patches texinfo.tex in current directory, or in directory given as arg.
+
+ANYVERSION=no
+
+for arg in $1 $2
+do
+ case $arg in
+ --dammit | -d ) ANYVERSION=yes ;;
+
+ * ) dir=$arg
+ esac
+done
+
+if [ -z "$dir" ]; then
+ dir='.'
+fi
+
+if [ \( 2 -lt $# \) -o \
+ \( ! -f $dir/texinfo.tex \) ]; then
+ echo "To patch texinfo.tex for peaceful coexistence with Unix TeX 3.0,"
+ echo "run $0"
+ echo "with no arguments in the same directory as texinfo.tex; or run"
+ echo " $0 DIRECTORY"
+ echo "(where DIRECTORY is a path leading to texinfo.tex)."
+ exit
+fi
+
+if [ -z "$TMPDIR" ]; then
+ TMPDIR=/tmp
+fi
+
+echo "Checking for \`dummy.tfm'"
+
+( cd $TMPDIR; tex '\relax \batchmode \font\foo=dummy \bye' )
+
+grep -s '3.0' $TMPDIR/texput.log
+if [ 1 = "$?" -a "$ANYVERSION" != "yes" ]; then
+ echo "You probably do not need this patch,"
+ echo "since your TeX does not seem to be version 3.0."
+ echo "If you insist on applying the patch, run $0"
+ echo "again with the option \`--dammit'"
+ exit
+fi
+
+grep -s 'file not found' $TMPDIR/texput.log
+if [ 0 = $? ]; then
+ echo "This patch requires the dummy font metric file \`dummy.tfm',"
+ echo "which does not seem to be part of your TeX installation."
+ echo "Please get your TeX maintainer to install \`dummy.tfm',"
+ echo "then run this script again."
+ exit
+fi
+rm $TMPDIR/texput.log
+
+echo "Patching $dir/texinfo.tex"
+
+sed -e 's/%%*\\font\\nullfont/\\font\\nullfont/' \
+ $dir/texinfo.tex >$TMPDIR/texinfo.tex
+mv $dir/texinfo.tex $dir/texinfo.tex-distrib; mv $TMPDIR/texinfo.tex $dir
+
+if [ 0 = $? ]; then
+ echo "Patched $dir/texinfo.tex to avoid TeX 3.0 bug."
+ echo "The original version is saved as $dir/texinfo.tex-distrib."
+else
+ echo "Patch failed. Sorry."
+fi
+----------------------------------------tex3patch ends
+
+
diff --git a/texinfo/util/texi2dvi b/texinfo/util/texi2dvi
new file mode 100755
index 0000000..d9f1b3e
--- /dev/null
+++ b/texinfo/util/texi2dvi
@@ -0,0 +1,367 @@
+#! /bin/sh
+# texi2dvi --- smartly produce DVI files from texinfo sources
+
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+
+# $Id: texi2dvi,v 1.1 1997/08/21 22:58:13 jason 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, 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, you can either send email to this
+# program's maintainer or write to: The Free Software Foundation,
+# Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
+
+# Commentary:
+
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+
+# Please send bug reports, etc. to bug-texinfo@prep.ai.mit.edu
+# If possible, please send a copy of the output of the script called with
+# the `--debug' option when making a bug report.
+
+# In the interest of general portability, some common bourne shell
+# constructs were avoided because they weren't guaranteed to be available
+# in some earlier implementations. I've tried to make this program as
+# portable as possible. Welcome to unix, where the lowest common
+# denominator is rapidly diminishing.
+#
+# Among the more interesting lossages I noticed with some bourne shells
+# are:
+# * No shell functions.
+# * No `unset' builtin.
+# * `shift' cannot take a numeric argument, and signals an error if
+# there are no arguments to shift.
+
+# Code:
+
+# Name by which this script was invoked.
+progname=`echo "$0" | sed -e 's/[^\/]*\///g'`
+
+# This string is expanded by rcs automatically when this file is checked out.
+rcs_revision='$Revision: 1.1 $'
+version=`set - $rcs_revision; echo $2`
+
+# To prevent hairy quoting and escaping later.
+bq='`'
+eq="'"
+
+usage="Usage: $0 [OPTION]... FILE...
+Run a Texinfo document through TeX.
+
+Options:
+-D, --debug Turn on shell debugging ($bq${bq}set -x$eq$eq).
+-t, --texinfo CMD Insert CMD after @setfilename before running TeX.
+--verbose Report on what is done.
+-h, --help Display this help and exit.
+-v, --version Display version information and exit.
+
+The values of the TEX, TEXINDEX, and MAKEINFO environment variables are
+used to run those commands, if they are set.
+
+Email bug reports to bug-texinfo@prep.ai.mit.edu.
+"
+
+# Initialize variables.
+# Don't use `unset' since old bourne shells don't have this command.
+# Instead, assign them an empty value.
+# Some of these, like TEX and TEXINDEX, may be inherited from the environment.
+backup_extension=.bak # these files get deleted if all goes well.
+debug=
+orig_pwd="`pwd`"
+textra=
+verbose=false
+makeinfo="${MAKEINFO-makeinfo}"
+texindex="${TEXINDEX-texindex}"
+tex="${TEX-tex}"
+
+# Save this so we can construct a new TEXINPUTS path for each file.
+TEXINPUTS_orig="$TEXINPUTS"
+export TEXINPUTS
+
+# Parse command line arguments.
+# Make sure that all wildcarded options are long enough to be unambiguous.
+# It's a good idea to document the full long option name in each case.
+# Long options which take arguments will need a `*' appended to the
+# canonical name to match the value appended after the `=' character.
+while : ; do
+ case $# in 0) break ;; esac
+ case "$1" in
+ -D | --debug | --d* ) debug=t; shift ;;
+ -h | --help | --h* ) echo "$usage"; exit 0 ;;
+ # OK, we should do real option parsing here, but be lazy for now.
+ -t | --texinfo | --t*) shift; textra="$textra $1"; shift ;;
+ -v | --vers* )
+ echo "$progname (Texinfo 3.9) $version"
+ echo "Copyright (C) 1996 Free Software Foundation, Inc.
+There is NO warranty. You may redistribute this software
+under the terms of the GNU General Public License.
+For more information about these matters, see the files named COPYING."
+ exit 0 ;;
+ --verb* ) verbose=echo; shift ;;
+ -- ) # Stop option processing
+ shift
+ break
+ ;;
+ -* )
+ case "$1" in
+ --*=* ) arg=`echo "$1" | sed -e 's/=.*//'` ;;
+ * ) arg="$1" ;;
+ esac
+ exec 1>&2
+ echo "$progname: Unknown or ambiguous option $bq$arg$eq."
+ echo "$progname: Try $bq--help$eq for more information."
+ exit 1
+ ;;
+ * )
+ break
+ ;;
+ esac
+done
+
+# See if there are any command line args left (which will be interpreted as
+# filename arguments).
+if test $# -eq 0; then
+ exec 1>&2
+ echo "$progname: At least one file name is required as an argument."
+ echo "$progname: Try $bq--help$eq for more information."
+ exit 2
+fi
+
+test "$debug" = t && set -x
+
+# Texify files
+for command_line_filename in ${1+"$@"} ; do
+ $verbose "Processing $command_line_filename ..."
+
+ # See if file exists. If it doesn't we're in trouble since, even
+ # though the user may be able to reenter a valid filename at the tex
+ # prompt (assuming they're attending the terminal), this script won't
+ # be able to find the right index files and so forth.
+ if test ! -r "${command_line_filename}" ; then
+ echo "$0: Could not read ${command_line_filename}." >&2
+ continue
+ fi
+
+ # Roughly equivalent to `dirname ...`, but more portable
+ directory="`echo ${command_line_filename} | sed 's/\/[^\/]*$//'`"
+ filename_texi="`basename ${command_line_filename}`"
+ # Strip off the last extension part (probably .texinfo or .texi)
+ filename_noext="`echo ${filename_texi} | sed 's/\.[^.]*$//'`"
+
+ # Use same basename since we want to generate aux files with the same
+ # basename as the manual. Use extension .texi for the temp file so
+ # that TeX will ignore it. Thus, we must use a subdirectory.
+ #
+ # Output the macro-expanded file to here.
+ tmp_dir=${TMPDIR-/tmp}/$$
+ filename_tmp=$tmp_dir/$filename_noext.texi
+ # Output the file with the user's extra commands to here.
+ filename_tmp2=$tmp_dir.2/$filename_noext.texi
+ mkdir $tmp_dir $tmp_dir.2
+
+ # If directory and file are the same, then it's probably because there's
+ # no pathname component. Set dirname to `.', the current directory.
+ if test "z${directory}" = "z${command_line_filename}" ; then
+ directory=.
+ fi
+
+ # Source file might @include additional texinfo sources. Put `.' and
+ # directory where source file(s) reside in TEXINPUTS before anything
+ # else. `.' goes first to ensure that any old .aux, .cps, etc. files in
+ # ${directory} don't get used in preference to fresher files in `.'.
+ TEXINPUTS=".:${directory}:${TEXINPUTS_orig}"
+
+ # Expand macro commands in the original source file using Makeinfo;
+ # the macro syntax bfox implemented is impossible to implement in TeX.
+ # Always use `end' footnote style, since the `separate' style
+ # generates different output (arguably this is a bug in -E).
+ # Discard main info output, the user asked to run TeX, not makeinfo.
+ # Redirect output to /dev/null to throw away `Making info file...' msg.
+ $verbose "Macro-expanding $command_line_filename to $filename_tmp ..."
+ $makeinfo --footnote-style=end -E $filename_tmp -o /dev/null \
+ $command_line_filename >/dev/null
+
+ # But if there were no macros, or makeinfo failed for some reason,
+ # just use the original file. (It shouldn't make any difference, but
+ # let's be safe.)
+ if test $? -ne 0 || cmp -s $filename_tmp $command_line_filename; then
+ $verbose "Reverting to $command_line_filename ..."
+ filename_input=$command_line_filename
+ else
+ filename_input=$filename_tmp
+ fi
+
+ # Used most commonly for @finalout, @smallbook, etc.
+ if test -n "$textra"; then
+ $verbose "Inserting extra commands: $textra."
+ sed '/^@setfilename/a\
+'"$textra" $filename_input >$filename_tmp2
+ filename_input=$filename_tmp2
+ fi
+
+ while true; do # will break out of loop below
+ # "Unset" variables that might have values from previous iterations and
+ # which won't be completely reset later.
+ definite_index_files=
+
+ # Find all files having root filename with a two-letter extension,
+ # determine whether they're really index files, and save them. Foo.aux
+ # is actually the cross-references file, but we need to keep track of
+ # that too.
+ possible_index_files="`eval echo ${filename_noext}.?? ${filename_noext}.aux`"
+ for this_file in ${possible_index_files} ; do
+ # If file is empty, forget it.
+ test -s "${this_file}" || continue
+
+ # Examine first character of file. If it's not suitable to be an
+ # index or xref file, don't process it.
+ first_character="`sed -n '1s/^\(.\).*$/\1/p;q' ${this_file}`"
+ if test "x${first_character}" = "x\\" \
+ || test "x${first_character}" = "x'"; then
+ definite_index_files="${definite_index_files} ${this_file}"
+ fi
+ done
+ orig_index_files="${definite_index_files}"
+ orig_index_files_sans_aux="`echo ${definite_index_files} \
+ | sed 's/'${filename_noext}'\.aux//;
+ s/^[ ]*//;s/[ ]*$//;'`"
+
+ # Now save copies of original index files so we have some means of
+ # comparison later.
+ $verbose "Backing up current index files: $orig_index_files ..."
+ for index_file_to_save in ${orig_index_files} ; do
+ cp "${index_file_to_save}" "${index_file_to_save}${backup_extension}"
+ done
+
+ # Run texindex on current index files. If they already exist, and
+ # after running TeX a first time the index files don't change, then
+ # there's no reason to run TeX again. But we won't know that if the
+ # index files are out of date or nonexistent.
+ if test -n "${orig_index_files_sans_aux}" ; then
+ $verbose "Running $texindex $orig_index_files_sans_aux ..."
+ ${texindex} ${orig_index_files_sans_aux}
+ fi
+
+ # Finally, run TeX.
+ $verbose "Running $tex $filename_input ..."
+ ${tex} "$filename_input"
+
+ # Check if index files changed.
+ #
+ definite_index_files=
+ # Get list of new index files.
+ possible_index_files="`eval echo ${filename_noext}.?? ${filename_noext}.aux`"
+ for this_file in ${possible_index_files} ; do
+ # If file is empty, forget it.
+ test -s "${this_file}" || continue
+
+ # Examine first character of file. If it's not a backslash or
+ # single quote, then it's definitely not an index or xref file.
+ # (Will have to check for @ when we switch to Texinfo syntax in
+ # all these files...)
+ first_character="`sed -n '1s/^\(.\).*$/\1/p;q' ${this_file}`"
+ if test "x${first_character}" = "x\\" \
+ || test "x${first_character}" = "x'"; then
+ definite_index_files="${definite_index_files} ${this_file}"
+ fi
+ done
+ new_index_files="${definite_index_files}"
+ new_index_files_sans_aux="`echo ${definite_index_files} \
+ | sed 's/'${filename_noext}'\.aux//;
+ s/^[ ]*//;s/[ ]*$//;'`"
+
+ # If old and new list don't at least have the same file list, then one
+ # file or another has definitely changed.
+ $verbose "Original index files =$orig_index_files"
+ $verbose "New index files =$new_index_files"
+ if test "z${orig_index_files}" != "z${new_index_files}" ; then
+ index_files_changed_p=t
+ else
+ # File list is the same. We must compare each file until we find a
+ # difference.
+ index_files_changed_p=
+ for this_file in ${new_index_files} ; do
+ $verbose "Comparing index file $this_file ..."
+ # cmp -s will return nonzero exit status if files differ.
+ cmp -s "${this_file}" "${this_file}${backup_extension}"
+ if test $? -ne 0 ; then
+ # We only need to keep comparing until we find *one* that
+ # differs, because we'll have to run texindex & tex no
+ # matter what.
+ index_files_changed_p=t
+ $verbose "Index file $this_file differed:"
+ test $verbose = echo \
+ && diff -c "${this_file}${backup_extension}" "${this_file}"
+ break
+ fi
+ done
+ fi
+
+ # If index files have changed since TeX has been run, or if the aux
+ # file wasn't present originally, run texindex and TeX again.
+ if test "${index_files_changed_p}" ; then :; else
+ # Nothing changed. We're done with TeX.
+ break
+ fi
+ done
+
+ # Generate list of files to delete, then call rm once with the entire
+ # list. This is significantly faster than multiple executions of rm.
+ file_list=
+ for file in ${orig_index_files} ; do
+ file_list="${file_list} ${file}${backup_extension}"
+ done
+ if test -n "${file_list}" ; then
+ $verbose "Removing $file_list $tmp_dir $tmp_dir.2 ..."
+ rm -f ${file_list}
+ rm -rf $tmp_dir $tmp_dir.2
+ fi
+done
+
+$verbose "$0 done."
+true # exit successfully.
+
+# texi2dvi ends here
+# $Log: texi2dvi,v $
+# Revision 1.1 1997/08/21 22:58:13 jason
+# Initial revision
+#
+# Revision 1.10 1996/10/04 18:21:55 karl
+# Include only the current year in the copyright message.
+#
+# Revision 1.9 1996/10/04 11:49:48 karl
+# Exit successfully. From arnold.
+#
+# Revision 1.8 1996/10/03 23:14:26 karl
+# Only show diff if verbose.
+# Update version number.
+#
+# Revision 1.7 1996/09/29 22:56:08 karl
+# Use $progname instead of $0 for --version.
+#
+# Revision 1.6 1996/09/28 21:01:23 karl
+# Recompute original index files each time through loop.
+# Make indentation uniform.
+# Use same basename for the temp input files.
+# Standardize --version output.
+#
+# Revision 1.5 1996/09/26 14:46:34 karl
+# (texi2dvi): Run TeX until the aux/index files stabilize, instead of just
+# twice. From: David Shaw <daves@gsms01.alcatel.com.au>.
+#
+# Revision 1.4 1996/08/27 18:59:26 karl
+# Include bug reporting address.
+#
+# Revision 1.3 1996/07/26 18:20:56 karl
+# Do macro expansion with makeinfo before running TeX.
+# Various expansion safety measures added for test; avoid use of -o.
+#
diff --git a/texinfo/util/texindex.c b/texinfo/util/texindex.c
new file mode 100644
index 0000000..306aa1f
--- /dev/null
+++ b/texinfo/util/texindex.c
@@ -0,0 +1,1793 @@
+/* Prepare TeX index dribble output into an actual index.
+ $Id: texindex.c,v 1.1 1997/08/21 22:58:13 jason Exp $
+
+ Copyright (C) 1987, 91, 92, 96 Free Software Foundation, Inc.
+
+ 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, 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. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include "getopt.h"
+
+#define TEXINDEX_VERSION_STRING "GNU Texindex (Texinfo 3.9) 2.1"
+
+#if defined (emacs)
+# include "../src/config.h"
+/* Some s/os.h files redefine these. */
+# undef read
+# undef close
+# undef write
+# undef open
+#endif
+
+#if defined (HAVE_STRING_H)
+# include <string.h>
+#endif /* HAVE_STRING_H */
+
+#if !defined (HAVE_STRCHR)
+char *strrchr ();
+#endif /* !HAVE_STRCHR */
+
+#if defined (STDC_HEADERS)
+# include <stdlib.h>
+#else /* !STDC_HEADERS */
+char *getenv (), *malloc (), *realloc ();
+#endif /* !STDC_HEADERS */
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#else /* !HAVE_UNISTD_H */
+off_t lseek ();
+#endif /* !HAVE_UNISTD_H */
+
+#if !defined (HAVE_MEMSET)
+#undef memset
+#define memset(ptr, ignore, count) bzero (ptr, count)
+#endif
+
+
+char *mktemp ();
+
+#if defined (VMS)
+# include <file.h>
+# define TI_NO_ERROR ((1 << 28) | 1)
+# define TI_FATAL_ERROR ((1 << 28) | 4)
+# define unlink delete
+#else /* !VMS */
+# if defined (HAVE_SYS_FCNTL_H)
+# include <sys/types.h>
+# include <sys/fcntl.h>
+# endif /* HAVE_SYS_FCNTL_H */
+
+# if defined (_AIX) || !defined (_POSIX_VERSION)
+# include <sys/file.h>
+# else /* !AIX && _POSIX_VERSION */
+# if !defined (HAVE_SYS_FCNTL_H)
+# include <fcntl.h>
+# endif /* !HAVE_FCNTL_H */
+# endif /* !_AIX && _POSIX_VERSION */
+# define TI_NO_ERROR 0
+# define TI_FATAL_ERROR 1
+#endif /* !VMS */
+
+#if !defined (SEEK_SET)
+# define SEEK_SET 0
+# define SEEK_CUR 1
+# define SEEK_END 2
+#endif /* !SEEK_SET */
+
+#ifndef errno
+extern int errno;
+#endif
+#ifndef strerror
+extern char *strerror ();
+#endif
+
+/* When sorting in core, this structure describes one line
+ and the position and length of its first keyfield. */
+struct lineinfo
+{
+ char *text; /* The actual text of the line. */
+ union {
+ char *text; /* The start of the key (for textual comparison). */
+ long number; /* The numeric value (for numeric comparison). */
+ } key;
+ long keylen; /* Length of KEY field. */
+};
+
+/* This structure describes a field to use as a sort key. */
+struct keyfield
+{
+ int startwords; /* Number of words to skip. */
+ int startchars; /* Number of additional chars to skip. */
+ int endwords; /* Number of words to ignore at end. */
+ int endchars; /* Ditto for characters of last word. */
+ char ignore_blanks; /* Non-zero means ignore spaces and tabs. */
+ char fold_case; /* Non-zero means case doesn't matter. */
+ char reverse; /* Non-zero means compare in reverse order. */
+ char numeric; /* Non-zeros means field is ASCII numeric. */
+ char positional; /* Sort according to file position. */
+ char braced; /* Count balanced-braced groupings as fields. */
+};
+
+/* Vector of keyfields to use. */
+struct keyfield keyfields[3];
+
+/* Number of keyfields stored in that vector. */
+int num_keyfields = 3;
+
+/* Vector of input file names, terminated with a null pointer. */
+char **infiles;
+
+/* Vector of corresponding output file names, or NULL, meaning default it
+ (add an `s' to the end). */
+char **outfiles;
+
+/* Length of `infiles'. */
+int num_infiles;
+
+/* Pointer to the array of pointers to lines being sorted. */
+char **linearray;
+
+/* The allocated length of `linearray'. */
+long nlines;
+
+/* Directory to use for temporary files. On Unix, it ends with a slash. */
+char *tempdir;
+
+/* Start of filename to use for temporary files. */
+char *tempbase;
+
+/* Number of last temporary file. */
+int tempcount;
+
+/* Number of last temporary file already deleted.
+ Temporary files are deleted by `flush_tempfiles' in order of creation. */
+int last_deleted_tempcount;
+
+/* During in-core sort, this points to the base of the data block
+ which contains all the lines of data. */
+char *text_base;
+
+/* Additional command switches .*/
+
+/* Nonzero means do not delete tempfiles -- for debugging. */
+int keep_tempfiles;
+
+/* The name this program was run with. */
+char *program_name;
+
+/* Forward declarations of functions in this file. */
+
+void decode_command ();
+void sort_in_core ();
+void sort_offline ();
+char **parsefile ();
+char *find_field ();
+char *find_pos ();
+long find_value ();
+char *find_braced_pos ();
+char *find_braced_end ();
+void writelines ();
+int compare_field ();
+int compare_full ();
+long readline ();
+int merge_files ();
+int merge_direct ();
+void pfatal_with_name ();
+void fatal ();
+void error ();
+void *xmalloc (), *xrealloc ();
+char *concat ();
+char *maketempname ();
+void flush_tempfiles ();
+char *tempcopy ();
+
+#define MAX_IN_CORE_SORT 500000
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+
+ tempcount = 0;
+ last_deleted_tempcount = 0;
+
+ program_name = strrchr (argv[0], '/');
+ if (program_name != (char *)NULL)
+ program_name++;
+ else
+ program_name = argv[0];
+
+ /* Describe the kind of sorting to do. */
+ /* The first keyfield uses the first braced field and folds case. */
+ keyfields[0].braced = 1;
+ keyfields[0].fold_case = 1;
+ keyfields[0].endwords = -1;
+ keyfields[0].endchars = -1;
+
+ /* The second keyfield uses the second braced field, numerically. */
+ keyfields[1].braced = 1;
+ keyfields[1].numeric = 1;
+ keyfields[1].startwords = 1;
+ keyfields[1].endwords = -1;
+ keyfields[1].endchars = -1;
+
+ /* The third keyfield (which is ignored while discarding duplicates)
+ compares the whole line. */
+ keyfields[2].endwords = -1;
+ keyfields[2].endchars = -1;
+
+ decode_command (argc, argv);
+
+ tempbase = mktemp (concat ("txiXXXXXX", "", ""));
+
+ /* Process input files completely, one by one. */
+
+ for (i = 0; i < num_infiles; i++)
+ {
+ int desc;
+ long ptr;
+ char *outfile;
+
+ desc = open (infiles[i], O_RDONLY, 0);
+ if (desc < 0)
+ pfatal_with_name (infiles[i]);
+ lseek (desc, (off_t) 0, SEEK_END);
+ ptr = (long) lseek (desc, (off_t) 0, SEEK_CUR);
+
+ close (desc);
+
+ outfile = outfiles[i];
+ if (!outfile)
+ {
+ outfile = concat (infiles[i], "s", "");
+ }
+
+ if (ptr < MAX_IN_CORE_SORT)
+ /* Sort a small amount of data. */
+ sort_in_core (infiles[i], ptr, outfile);
+ else
+ sort_offline (infiles[i], ptr, outfile);
+ }
+
+ flush_tempfiles (tempcount);
+ exit (TI_NO_ERROR);
+}
+
+typedef struct
+{
+ char *long_name;
+ char *short_name;
+ int *variable_ref;
+ int variable_value;
+ char *arg_name;
+ char *doc_string;
+} TEXINDEX_OPTION;
+
+TEXINDEX_OPTION texindex_options[] = {
+ { "--keep", "-k", &keep_tempfiles, 1, (char *)NULL,
+ "keep temporary files around after processing" },
+ { "--no-keep", 0, &keep_tempfiles, 0, (char *)NULL,
+ "do not keep temporary files around after processing (default)" },
+ { "--output", "-o", (int *)NULL, 0, "FILE",
+ "send output to FILE" },
+ { "--version", (char *)NULL, (int *)NULL, 0, (char *)NULL,
+ "display version information and exit" },
+ { "--help", "-h", (int *)NULL, 0, (char *)NULL,
+ "display this help and exit" },
+ { (char *)NULL, (char *)NULL, (int *)NULL, 0, (char *)NULL }
+};
+
+void
+usage (result_value)
+ int result_value;
+{
+ register int i;
+ FILE *f = result_value ? stderr : stdout;
+
+ fprintf (f, "Usage: %s [OPTION]... FILE...\n", program_name);
+ fprintf (f, "Generate a sorted index for each TeX output FILE.\n");
+ /* Avoid trigraph nonsense. */
+ fprintf (f, "Usually FILE... is `foo.??\' for a document `foo.texi'.\n");
+ fprintf (f, "\nOptions:\n");
+
+ for (i = 0; texindex_options[i].long_name; i++)
+ {
+ if (texindex_options[i].short_name)
+ fprintf (f, "%s, ", texindex_options[i].short_name);
+
+ fprintf (f, "%s %s",
+ texindex_options[i].long_name,
+ texindex_options[i].arg_name
+ ? texindex_options[i].arg_name : "");
+
+ fprintf (f, "\t%s\n", texindex_options[i].doc_string);
+ }
+ puts ("\nEmail bug reports to bug-texinfo@prep.ai.mit.edu.");
+
+ exit (result_value);
+}
+
+/* Decode the command line arguments to set the parameter variables
+ and set up the vector of keyfields and the vector of input files. */
+
+void
+decode_command (argc, argv)
+ int argc;
+ char **argv;
+{
+ int arg_index = 1;
+ int optc;
+ char **ip;
+ char **op;
+
+ /* Store default values into parameter variables. */
+
+ tempdir = getenv ("TMPDIR");
+#ifdef VMS
+ if (tempdir == NULL)
+ tempdir = "sys$scratch:";
+#else
+ if (tempdir == NULL)
+ tempdir = "/tmp/";
+ else
+ tempdir = concat (tempdir, "/", "");
+#endif
+
+ keep_tempfiles = 0;
+
+ /* Allocate ARGC input files, which must be enough. */
+
+ infiles = (char **) xmalloc (argc * sizeof (char *));
+ outfiles = (char **) xmalloc (argc * sizeof (char *));
+ ip = infiles;
+ op = outfiles;
+
+ while (arg_index < argc)
+ {
+ char *arg = argv[arg_index++];
+
+ if (*arg == '-')
+ {
+ if (strcmp (arg, "--version") == 0)
+ {
+ puts (TEXINDEX_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);
+ }
+ else if ((strcmp (arg, "--keep") == 0) ||
+ (strcmp (arg, "-k") == 0))
+ {
+ keep_tempfiles = 1;
+ }
+ else if ((strcmp (arg, "--help") == 0) ||
+ (strcmp (arg, "-h") == 0))
+ {
+ usage (0);
+ }
+ else if ((strcmp (arg, "--output") == 0) ||
+ (strcmp (arg, "-o") == 0))
+ {
+ if (argv[arg_index] != (char *)NULL)
+ {
+ arg_index++;
+ if (op > outfiles)
+ *(op - 1) = argv[arg_index];
+ }
+ else
+ usage (1);
+ }
+ else
+ usage (1);
+ }
+ else
+ {
+ *ip++ = arg;
+ *op++ = (char *)NULL;
+ }
+ }
+
+ /* Record number of keyfields and terminate list of filenames. */
+ num_infiles = ip - infiles;
+ *ip = (char *)NULL;
+ if (num_infiles == 0)
+ usage (1);
+}
+
+/* Return a name for a temporary file. */
+
+char *
+maketempname (count)
+ int count;
+{
+ char tempsuffix[10];
+ sprintf (tempsuffix, "%d", count);
+ return concat (tempdir, tempbase, tempsuffix);
+}
+
+/* Delete all temporary files up to TO_COUNT. */
+
+void
+flush_tempfiles (to_count)
+ int to_count;
+{
+ if (keep_tempfiles)
+ return;
+ while (last_deleted_tempcount < to_count)
+ unlink (maketempname (++last_deleted_tempcount));
+}
+
+/* Copy the input file open on IDESC into a temporary file
+ and return the temporary file name. */
+
+#define BUFSIZE 1024
+
+char *
+tempcopy (idesc)
+ int idesc;
+{
+ char *outfile = maketempname (++tempcount);
+ int odesc;
+ char buffer[BUFSIZE];
+
+ odesc = open (outfile, O_WRONLY | O_CREAT, 0666);
+
+ if (odesc < 0)
+ pfatal_with_name (outfile);
+
+ while (1)
+ {
+ int nread = read (idesc, buffer, BUFSIZE);
+ write (odesc, buffer, nread);
+ if (!nread)
+ break;
+ }
+
+ close (odesc);
+
+ return outfile;
+}
+
+/* Compare LINE1 and LINE2 according to the specified set of keyfields. */
+
+int
+compare_full (line1, line2)
+ char **line1, **line2;
+{
+ int i;
+
+ /* Compare using the first keyfield;
+ if that does not distinguish the lines, try the second keyfield;
+ and so on. */
+
+ for (i = 0; i < num_keyfields; i++)
+ {
+ long length1, length2;
+ char *start1 = find_field (&keyfields[i], *line1, &length1);
+ char *start2 = find_field (&keyfields[i], *line2, &length2);
+ int tem = compare_field (&keyfields[i], start1, length1, *line1 - text_base,
+ start2, length2, *line2 - text_base);
+ if (tem)
+ {
+ if (keyfields[i].reverse)
+ return -tem;
+ return tem;
+ }
+ }
+
+ return 0; /* Lines match exactly. */
+}
+
+/* Compare LINE1 and LINE2, described by structures
+ in which the first keyfield is identified in advance.
+ For positional sorting, assumes that the order of the lines in core
+ reflects their nominal order. */
+
+int
+compare_prepared (line1, line2)
+ struct lineinfo *line1, *line2;
+{
+ int i;
+ int tem;
+ char *text1, *text2;
+
+ /* Compare using the first keyfield, which has been found for us already. */
+ if (keyfields->positional)
+ {
+ if (line1->text - text_base > line2->text - text_base)
+ tem = 1;
+ else
+ tem = -1;
+ }
+ else if (keyfields->numeric)
+ tem = line1->key.number - line2->key.number;
+ else
+ tem = compare_field (keyfields, line1->key.text, line1->keylen, 0,
+ line2->key.text, line2->keylen, 0);
+ if (tem)
+ {
+ if (keyfields->reverse)
+ return -tem;
+ return tem;
+ }
+
+ text1 = line1->text;
+ text2 = line2->text;
+
+ /* Compare using the second keyfield;
+ if that does not distinguish the lines, try the third keyfield;
+ and so on. */
+
+ for (i = 1; i < num_keyfields; i++)
+ {
+ long length1, length2;
+ char *start1 = find_field (&keyfields[i], text1, &length1);
+ char *start2 = find_field (&keyfields[i], text2, &length2);
+ int tem = compare_field (&keyfields[i], start1, length1, text1 - text_base,
+ start2, length2, text2 - text_base);
+ if (tem)
+ {
+ if (keyfields[i].reverse)
+ return -tem;
+ return tem;
+ }
+ }
+
+ return 0; /* Lines match exactly. */
+}
+
+/* Like compare_full but more general.
+ You can pass any strings, and you can say how many keyfields to use.
+ POS1 and POS2 should indicate the nominal positional ordering of
+ the two lines in the input. */
+
+int
+compare_general (str1, str2, pos1, pos2, use_keyfields)
+ char *str1, *str2;
+ long pos1, pos2;
+ int use_keyfields;
+{
+ int i;
+
+ /* Compare using the first keyfield;
+ if that does not distinguish the lines, try the second keyfield;
+ and so on. */
+
+ for (i = 0; i < use_keyfields; i++)
+ {
+ long length1, length2;
+ char *start1 = find_field (&keyfields[i], str1, &length1);
+ char *start2 = find_field (&keyfields[i], str2, &length2);
+ int tem = compare_field (&keyfields[i], start1, length1, pos1,
+ start2, length2, pos2);
+ if (tem)
+ {
+ if (keyfields[i].reverse)
+ return -tem;
+ return tem;
+ }
+ }
+
+ return 0; /* Lines match exactly. */
+}
+
+/* Find the start and length of a field in STR according to KEYFIELD.
+ A pointer to the starting character is returned, and the length
+ is stored into the int that LENGTHPTR points to. */
+
+char *
+find_field (keyfield, str, lengthptr)
+ struct keyfield *keyfield;
+ char *str;
+ long *lengthptr;
+{
+ char *start;
+ char *end;
+ char *(*fun) ();
+
+ if (keyfield->braced)
+ fun = find_braced_pos;
+ else
+ fun = find_pos;
+
+ start = (*fun) (str, keyfield->startwords, keyfield->startchars,
+ keyfield->ignore_blanks);
+ if (keyfield->endwords < 0)
+ {
+ if (keyfield->braced)
+ end = find_braced_end (start);
+ else
+ {
+ end = start;
+ while (*end && *end != '\n')
+ end++;
+ }
+ }
+ else
+ {
+ end = (*fun) (str, keyfield->endwords, keyfield->endchars, 0);
+ if (end - str < start - str)
+ end = start;
+ }
+ *lengthptr = end - start;
+ return start;
+}
+
+/* Return a pointer to a specified place within STR,
+ skipping (from the beginning) WORDS words and then CHARS chars.
+ If IGNORE_BLANKS is nonzero, we skip all blanks
+ after finding the specified word. */
+
+char *
+find_pos (str, words, chars, ignore_blanks)
+ char *str;
+ int words, chars;
+ int ignore_blanks;
+{
+ int i;
+ char *p = str;
+
+ for (i = 0; i < words; i++)
+ {
+ char c;
+ /* Find next bunch of nonblanks and skip them. */
+ while ((c = *p) == ' ' || c == '\t')
+ p++;
+ while ((c = *p) && c != '\n' && !(c == ' ' || c == '\t'))
+ p++;
+ if (!*p || *p == '\n')
+ return p;
+ }
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ for (i = 0; i < chars; i++)
+ {
+ if (!*p || *p == '\n')
+ break;
+ p++;
+ }
+ return p;
+}
+
+/* Like find_pos but assumes that each field is surrounded by braces
+ and that braces within fields are balanced. */
+
+char *
+find_braced_pos (str, words, chars, ignore_blanks)
+ char *str;
+ int words, chars;
+ int ignore_blanks;
+{
+ int i;
+ int bracelevel;
+ char *p = str;
+ char c;
+
+ for (i = 0; i < words; i++)
+ {
+ bracelevel = 1;
+ while ((c = *p++) != '{' && c != '\n' && c)
+ /* Do nothing. */ ;
+ if (c != '{')
+ return p - 1;
+ while (bracelevel)
+ {
+ c = *p++;
+ if (c == '{')
+ bracelevel++;
+ if (c == '}')
+ bracelevel--;
+ if (c == 0 || c == '\n')
+ return p - 1;
+ }
+ }
+
+ while ((c = *p++) != '{' && c != '\n' && c)
+ /* Do nothing. */ ;
+
+ if (c != '{')
+ return p - 1;
+
+ if (ignore_blanks)
+ while ((c = *p) == ' ' || c == '\t')
+ p++;
+
+ for (i = 0; i < chars; i++)
+ {
+ if (!*p || *p == '\n')
+ break;
+ p++;
+ }
+ return p;
+}
+
+/* Find the end of the balanced-brace field which starts at STR.
+ The position returned is just before the closing brace. */
+
+char *
+find_braced_end (str)
+ char *str;
+{
+ int bracelevel;
+ char *p = str;
+ char c;
+
+ bracelevel = 1;
+ while (bracelevel)
+ {
+ c = *p++;
+ if (c == '{')
+ bracelevel++;
+ if (c == '}')
+ bracelevel--;
+ if (c == 0 || c == '\n')
+ return p - 1;
+ }
+ return p - 1;
+}
+
+long
+find_value (start, length)
+ char *start;
+ long length;
+{
+ while (length != 0L)
+ {
+ if (isdigit (*start))
+ return atol (start);
+ length--;
+ start++;
+ }
+ return 0l;
+}
+
+/* Vector used to translate characters for comparison.
+ This is how we make all alphanumerics follow all else,
+ and ignore case in the first sorting. */
+int char_order[256];
+
+void
+init_char_order ()
+{
+ int i;
+ for (i = 1; i < 256; i++)
+ char_order[i] = i;
+
+ for (i = '0'; i <= '9'; i++)
+ char_order[i] += 512;
+
+ for (i = 'a'; i <= 'z'; i++)
+ {
+ char_order[i] = 512 + i;
+ char_order[i + 'A' - 'a'] = 512 + i;
+ }
+}
+
+/* Compare two fields (each specified as a start pointer and a character count)
+ according to KEYFIELD.
+ The sign of the value reports the relation between the fields. */
+
+int
+compare_field (keyfield, start1, length1, pos1, start2, length2, pos2)
+ struct keyfield *keyfield;
+ char *start1;
+ long length1;
+ long pos1;
+ char *start2;
+ long length2;
+ long pos2;
+{
+ if (keyfields->positional)
+ {
+ if (pos1 > pos2)
+ return 1;
+ else
+ return -1;
+ }
+ if (keyfield->numeric)
+ {
+ long value = find_value (start1, length1) - find_value (start2, length2);
+ if (value > 0)
+ return 1;
+ if (value < 0)
+ return -1;
+ return 0;
+ }
+ else
+ {
+ char *p1 = start1;
+ char *p2 = start2;
+ char *e1 = start1 + length1;
+ char *e2 = start2 + length2;
+
+ while (1)
+ {
+ int c1, c2;
+
+ if (p1 == e1)
+ c1 = 0;
+ else
+ c1 = *p1++;
+ if (p2 == e2)
+ c2 = 0;
+ else
+ c2 = *p2++;
+
+ if (char_order[c1] != char_order[c2])
+ return char_order[c1] - char_order[c2];
+ if (!c1)
+ break;
+ }
+
+ /* Strings are equal except possibly for case. */
+ p1 = start1;
+ p2 = start2;
+ while (1)
+ {
+ int c1, c2;
+
+ if (p1 == e1)
+ c1 = 0;
+ else
+ c1 = *p1++;
+ if (p2 == e2)
+ c2 = 0;
+ else
+ c2 = *p2++;
+
+ if (c1 != c2)
+ /* Reverse sign here so upper case comes out last. */
+ return c2 - c1;
+ if (!c1)
+ break;
+ }
+
+ return 0;
+ }
+}
+
+/* A `struct linebuffer' is a structure which holds a line of text.
+ `readline' reads a line from a stream into a linebuffer
+ and works regardless of the length of the line. */
+
+struct linebuffer
+{
+ long size;
+ char *buffer;
+};
+
+/* Initialize LINEBUFFER for use. */
+
+void
+initbuffer (linebuffer)
+ struct linebuffer *linebuffer;
+{
+ linebuffer->size = 200;
+ linebuffer->buffer = (char *) xmalloc (200);
+}
+
+/* Read a line of text from STREAM into LINEBUFFER.
+ Return the length of the line. */
+
+long
+readline (linebuffer, stream)
+ struct linebuffer *linebuffer;
+ FILE *stream;
+{
+ char *buffer = linebuffer->buffer;
+ char *p = linebuffer->buffer;
+ char *end = p + linebuffer->size;
+
+ while (1)
+ {
+ int c = getc (stream);
+ if (p == end)
+ {
+ buffer = (char *) xrealloc (buffer, linebuffer->size *= 2);
+ p += buffer - linebuffer->buffer;
+ end += buffer - linebuffer->buffer;
+ linebuffer->buffer = buffer;
+ }
+ if (c < 0 || c == '\n')
+ {
+ *p = 0;
+ break;
+ }
+ *p++ = c;
+ }
+
+ return p - buffer;
+}
+
+/* Sort an input file too big to sort in core. */
+
+void
+sort_offline (infile, nfiles, total, outfile)
+ char *infile;
+ int nfiles;
+ long total;
+ char *outfile;
+{
+ /* More than enough. */
+ int ntemps = 2 * (total + MAX_IN_CORE_SORT - 1) / MAX_IN_CORE_SORT;
+ char **tempfiles = (char **) xmalloc (ntemps * sizeof (char *));
+ FILE *istream = fopen (infile, "r");
+ int i;
+ struct linebuffer lb;
+ long linelength;
+ int failure = 0;
+
+ initbuffer (&lb);
+
+ /* Read in one line of input data. */
+
+ linelength = readline (&lb, istream);
+
+ if (lb.buffer[0] != '\\' && lb.buffer[0] != '@')
+ {
+ error ("%s: not a texinfo index file", infile);
+ return;
+ }
+
+ /* Split up the input into `ntemps' temporary files, or maybe fewer,
+ and put the new files' names into `tempfiles' */
+
+ for (i = 0; i < ntemps; i++)
+ {
+ char *outname = maketempname (++tempcount);
+ FILE *ostream = fopen (outname, "w");
+ long tempsize = 0;
+
+ if (!ostream)
+ pfatal_with_name (outname);
+ tempfiles[i] = outname;
+
+ /* Copy lines into this temp file as long as it does not make file
+ "too big" or until there are no more lines. */
+
+ while (tempsize + linelength + 1 <= MAX_IN_CORE_SORT)
+ {
+ tempsize += linelength + 1;
+ fputs (lb.buffer, ostream);
+ putc ('\n', ostream);
+
+ /* Read another line of input data. */
+
+ linelength = readline (&lb, istream);
+ if (!linelength && feof (istream))
+ break;
+
+ if (lb.buffer[0] != '\\' && lb.buffer[0] != '@')
+ {
+ error ("%s: not a texinfo index file", infile);
+ failure = 1;
+ goto fail;
+ }
+ }
+ fclose (ostream);
+ if (feof (istream))
+ break;
+ }
+
+ free (lb.buffer);
+
+fail:
+ /* Record number of temp files we actually needed. */
+
+ ntemps = i;
+
+ /* Sort each tempfile into another tempfile.
+ Delete the first set of tempfiles and put the names of the second
+ into `tempfiles'. */
+
+ for (i = 0; i < ntemps; i++)
+ {
+ char *newtemp = maketempname (++tempcount);
+ sort_in_core (&tempfiles[i], MAX_IN_CORE_SORT, newtemp);
+ if (!keep_tempfiles)
+ unlink (tempfiles[i]);
+ tempfiles[i] = newtemp;
+ }
+
+ if (failure)
+ return;
+
+ /* Merge the tempfiles together and indexify. */
+
+ merge_files (tempfiles, ntemps, outfile);
+}
+
+/* Sort INFILE, whose size is TOTAL,
+ assuming that is small enough to be done in-core,
+ then indexify it and send the output to OUTFILE (or to stdout). */
+
+void
+sort_in_core (infile, total, outfile)
+ char *infile;
+ long total;
+ char *outfile;
+{
+ char **nextline;
+ char *data = (char *) xmalloc (total + 1);
+ char *file_data;
+ long file_size;
+ int i;
+ FILE *ostream = stdout;
+ struct lineinfo *lineinfo;
+
+ /* Read the contents of the file into the moby array `data'. */
+
+ int desc = open (infile, O_RDONLY, 0);
+
+ if (desc < 0)
+ fatal ("failure reopening %s", infile);
+ for (file_size = 0;;)
+ {
+ i = read (desc, data + file_size, total - file_size);
+ if (i <= 0)
+ break;
+ file_size += i;
+ }
+ file_data = data;
+ data[file_size] = 0;
+
+ close (desc);
+
+ if (file_size > 0 && data[0] != '\\' && data[0] != '@')
+ {
+ error ("%s: not a texinfo index file", infile);
+ return;
+ }
+
+ init_char_order ();
+
+ /* Sort routines want to know this address. */
+
+ text_base = data;
+
+ /* Create the array of pointers to lines, with a default size
+ frequently enough. */
+
+ nlines = total / 50;
+ if (!nlines)
+ nlines = 2;
+ linearray = (char **) xmalloc (nlines * sizeof (char *));
+
+ /* `nextline' points to the next free slot in this array.
+ `nlines' is the allocated size. */
+
+ nextline = linearray;
+
+ /* Parse the input file's data, and make entries for the lines. */
+
+ nextline = parsefile (infile, nextline, file_data, file_size);
+ if (nextline == 0)
+ {
+ error ("%s: not a texinfo index file", infile);
+ return;
+ }
+
+ /* Sort the lines. */
+
+ /* If we have enough space, find the first keyfield of each line in advance.
+ Make a `struct lineinfo' for each line, which records the keyfield
+ as well as the line, and sort them. */
+
+ lineinfo = (struct lineinfo *) malloc ((nextline - linearray) * sizeof (struct lineinfo));
+
+ if (lineinfo)
+ {
+ struct lineinfo *lp;
+ char **p;
+
+ for (lp = lineinfo, p = linearray; p != nextline; lp++, p++)
+ {
+ lp->text = *p;
+ lp->key.text = find_field (keyfields, *p, &lp->keylen);
+ if (keyfields->numeric)
+ lp->key.number = find_value (lp->key.text, lp->keylen);
+ }
+
+ qsort (lineinfo, nextline - linearray, sizeof (struct lineinfo),
+ compare_prepared);
+
+ for (lp = lineinfo, p = linearray; p != nextline; lp++, p++)
+ *p = lp->text;
+
+ free (lineinfo);
+ }
+ else
+ qsort (linearray, nextline - linearray, sizeof (char *), compare_full);
+
+ /* Open the output file. */
+
+ if (outfile)
+ {
+ ostream = fopen (outfile, "w");
+ if (!ostream)
+ pfatal_with_name (outfile);
+ }
+
+ writelines (linearray, nextline - linearray, ostream);
+ if (outfile)
+ fclose (ostream);
+
+ free (linearray);
+ free (data);
+}
+
+/* Parse an input string in core into lines.
+ DATA is the input string, and SIZE is its length.
+ Data goes in LINEARRAY starting at NEXTLINE.
+ The value returned is the first entry in LINEARRAY still unused.
+ Value 0 means input file contents are invalid. */
+
+char **
+parsefile (filename, nextline, data, size)
+ char *filename;
+ char **nextline;
+ char *data;
+ long size;
+{
+ char *p, *end;
+ char **line = nextline;
+
+ p = data;
+ end = p + size;
+ *end = 0;
+
+ while (p != end)
+ {
+ if (p[0] != '\\' && p[0] != '@')
+ return 0;
+
+ *line = p;
+ while (*p && *p != '\n')
+ p++;
+ if (p != end)
+ p++;
+
+ line++;
+ if (line == linearray + nlines)
+ {
+ char **old = linearray;
+ linearray = (char **) xrealloc (linearray, sizeof (char *) * (nlines *= 4));
+ line += linearray - old;
+ }
+ }
+
+ return line;
+}
+
+/* Indexification is a filter applied to the sorted lines
+ as they are being written to the output file.
+ Multiple entries for the same name, with different page numbers,
+ get combined into a single entry with multiple page numbers.
+ The first braced field, which is used for sorting, is discarded.
+ However, its first character is examined, folded to lower case,
+ and if it is different from that in the previous line fed to us
+ a \initial line is written with one argument, the new initial.
+
+ If an entry has four braced fields, then the second and third
+ constitute primary and secondary names.
+ In this case, each change of primary name
+ generates a \primary line which contains only the primary name,
+ and in between these are \secondary lines which contain
+ just a secondary name and page numbers. */
+
+/* The last primary name we wrote a \primary entry for.
+ If only one level of indexing is being done, this is the last name seen. */
+char *lastprimary;
+/* Length of storage allocated for lastprimary. */
+int lastprimarylength;
+
+/* Similar, for the secondary name. */
+char *lastsecondary;
+int lastsecondarylength;
+
+/* Zero if we are not in the middle of writing an entry.
+ One if we have written the beginning of an entry but have not
+ yet written any page numbers into it.
+ Greater than one if we have written the beginning of an entry
+ plus at least one page number. */
+int pending;
+
+/* The initial (for sorting purposes) of the last primary entry written.
+ When this changes, a \initial {c} line is written */
+
+char *lastinitial;
+
+int lastinitiallength;
+
+/* When we need a string of length 1 for the value of lastinitial,
+ store it here. */
+
+char lastinitial1[2];
+
+/* Initialize static storage for writing an index. */
+
+void
+init_index ()
+{
+ pending = 0;
+ lastinitial = lastinitial1;
+ lastinitial1[0] = 0;
+ lastinitial1[1] = 0;
+ lastinitiallength = 0;
+ lastprimarylength = 100;
+ lastprimary = (char *) xmalloc (lastprimarylength + 1);
+ memset (lastprimary, '\0', lastprimarylength + 1);
+ lastsecondarylength = 100;
+ lastsecondary = (char *) xmalloc (lastsecondarylength + 1);
+ memset (lastsecondary, '\0', lastsecondarylength + 1);
+}
+
+/* Indexify. Merge entries for the same name,
+ insert headers for each initial character, etc. */
+
+void
+indexify (line, ostream)
+ char *line;
+ FILE *ostream;
+{
+ char *primary, *secondary, *pagenumber;
+ int primarylength, secondarylength = 0, pagelength;
+ int nosecondary;
+ int initiallength;
+ char *initial;
+ char initial1[2];
+ register char *p;
+
+ /* First, analyze the parts of the entry fed to us this time. */
+
+ p = find_braced_pos (line, 0, 0, 0);
+ if (*p == '{')
+ {
+ initial = p;
+ /* Get length of inner pair of braces starting at `p',
+ including that inner pair of braces. */
+ initiallength = find_braced_end (p + 1) + 1 - p;
+ }
+ else
+ {
+ initial = initial1;
+ initial1[0] = *p;
+ initial1[1] = 0;
+ initiallength = 1;
+
+ if (initial1[0] >= 'a' && initial1[0] <= 'z')
+ initial1[0] -= 040;
+ }
+
+ pagenumber = find_braced_pos (line, 1, 0, 0);
+ pagelength = find_braced_end (pagenumber) - pagenumber;
+ if (pagelength == 0)
+ abort ();
+
+ primary = find_braced_pos (line, 2, 0, 0);
+ primarylength = find_braced_end (primary) - primary;
+
+ secondary = find_braced_pos (line, 3, 0, 0);
+ nosecondary = !*secondary;
+ if (!nosecondary)
+ secondarylength = find_braced_end (secondary) - secondary;
+
+ /* If the primary is different from before, make a new primary entry. */
+ if (strncmp (primary, lastprimary, primarylength))
+ {
+ /* Close off current secondary entry first, if one is open. */
+ if (pending)
+ {
+ fputs ("}\n", ostream);
+ pending = 0;
+ }
+
+ /* If this primary has a different initial, include an entry for
+ the initial. */
+ if (initiallength != lastinitiallength ||
+ strncmp (initial, lastinitial, initiallength))
+ {
+ fprintf (ostream, "\\initial {");
+ fwrite (initial, 1, initiallength, ostream);
+ fprintf (ostream, "}\n", initial);
+ if (initial == initial1)
+ {
+ lastinitial = lastinitial1;
+ *lastinitial1 = *initial1;
+ }
+ else
+ {
+ lastinitial = initial;
+ }
+ lastinitiallength = initiallength;
+ }
+
+ /* Make the entry for the primary. */
+ if (nosecondary)
+ fputs ("\\entry {", ostream);
+ else
+ fputs ("\\primary {", ostream);
+ fwrite (primary, primarylength, 1, ostream);
+ if (nosecondary)
+ {
+ fputs ("}{", ostream);
+ pending = 1;
+ }
+ else
+ fputs ("}\n", ostream);
+
+ /* Record name of most recent primary. */
+ if (lastprimarylength < primarylength)
+ {
+ lastprimarylength = primarylength + 100;
+ lastprimary = (char *) xrealloc (lastprimary,
+ 1 + lastprimarylength);
+ }
+ strncpy (lastprimary, primary, primarylength);
+ lastprimary[primarylength] = 0;
+
+ /* There is no current secondary within this primary, now. */
+ lastsecondary[0] = 0;
+ }
+
+ /* Should not have an entry with no subtopic following one with a subtopic. */
+
+ if (nosecondary && *lastsecondary)
+ error ("entry %s follows an entry with a secondary name", line);
+
+ /* Start a new secondary entry if necessary. */
+ if (!nosecondary && strncmp (secondary, lastsecondary, secondarylength))
+ {
+ if (pending)
+ {
+ fputs ("}\n", ostream);
+ pending = 0;
+ }
+
+ /* Write the entry for the secondary. */
+ fputs ("\\secondary {", ostream);
+ fwrite (secondary, secondarylength, 1, ostream);
+ fputs ("}{", ostream);
+ pending = 1;
+
+ /* Record name of most recent secondary. */
+ if (lastsecondarylength < secondarylength)
+ {
+ lastsecondarylength = secondarylength + 100;
+ lastsecondary = (char *) xrealloc (lastsecondary,
+ 1 + lastsecondarylength);
+ }
+ strncpy (lastsecondary, secondary, secondarylength);
+ lastsecondary[secondarylength] = 0;
+ }
+
+ /* Here to add one more page number to the current entry. */
+ if (pending++ != 1)
+ fputs (", ", ostream); /* Punctuate first, if this is not the first. */
+ fwrite (pagenumber, pagelength, 1, ostream);
+}
+
+/* Close out any unfinished output entry. */
+
+void
+finish_index (ostream)
+ FILE *ostream;
+{
+ if (pending)
+ fputs ("}\n", ostream);
+ free (lastprimary);
+ free (lastsecondary);
+}
+
+/* Copy the lines in the sorted order.
+ Each line is copied out of the input file it was found in. */
+
+void
+writelines (linearray, nlines, ostream)
+ char **linearray;
+ int nlines;
+ FILE *ostream;
+{
+ char **stop_line = linearray + nlines;
+ char **next_line;
+
+ init_index ();
+
+ /* Output the text of the lines, and free the buffer space. */
+
+ for (next_line = linearray; next_line != stop_line; next_line++)
+ {
+ /* If -u was specified, output the line only if distinct from previous one. */
+ if (next_line == linearray
+ /* Compare previous line with this one, using only the
+ explicitly specd keyfields. */
+ || compare_general (*(next_line - 1), *next_line, 0L, 0L, num_keyfields - 1))
+ {
+ char *p = *next_line;
+ char c;
+
+ while ((c = *p++) && c != '\n')
+ /* Do nothing. */ ;
+ *(p - 1) = 0;
+ indexify (*next_line, ostream);
+ }
+ }
+
+ finish_index (ostream);
+}
+
+/* Assume (and optionally verify) that each input file is sorted;
+ merge them and output the result.
+ Returns nonzero if any input file fails to be sorted.
+
+ This is the high-level interface that can handle an unlimited
+ number of files. */
+
+#define MAX_DIRECT_MERGE 10
+
+int
+merge_files (infiles, nfiles, outfile)
+ char **infiles;
+ int nfiles;
+ char *outfile;
+{
+ char **tempfiles;
+ int ntemps;
+ int i;
+ int value = 0;
+ int start_tempcount = tempcount;
+
+ if (nfiles <= MAX_DIRECT_MERGE)
+ return merge_direct (infiles, nfiles, outfile);
+
+ /* Merge groups of MAX_DIRECT_MERGE input files at a time,
+ making a temporary file to hold each group's result. */
+
+ ntemps = (nfiles + MAX_DIRECT_MERGE - 1) / MAX_DIRECT_MERGE;
+ tempfiles = (char **) xmalloc (ntemps * sizeof (char *));
+ for (i = 0; i < ntemps; i++)
+ {
+ int nf = MAX_DIRECT_MERGE;
+ if (i + 1 == ntemps)
+ nf = nfiles - i * MAX_DIRECT_MERGE;
+ tempfiles[i] = maketempname (++tempcount);
+ value |= merge_direct (&infiles[i * MAX_DIRECT_MERGE], nf, tempfiles[i]);
+ }
+
+ /* All temporary files that existed before are no longer needed
+ since their contents have been merged into our new tempfiles.
+ So delete them. */
+ flush_tempfiles (start_tempcount);
+
+ /* Now merge the temporary files we created. */
+
+ merge_files (tempfiles, ntemps, outfile);
+
+ free (tempfiles);
+
+ return value;
+}
+
+/* Assume (and optionally verify) that each input file is sorted;
+ merge them and output the result.
+ Returns nonzero if any input file fails to be sorted.
+
+ This version of merging will not work if the number of
+ input files gets too high. Higher level functions
+ use it only with a bounded number of input files. */
+
+int
+merge_direct (infiles, nfiles, outfile)
+ char **infiles;
+ int nfiles;
+ char *outfile;
+{
+ struct linebuffer *lb1, *lb2;
+ struct linebuffer **thisline, **prevline;
+ FILE **streams;
+ int i;
+ int nleft;
+ int lossage = 0;
+ int *file_lossage;
+ struct linebuffer *prev_out = 0;
+ FILE *ostream = stdout;
+
+ if (outfile)
+ {
+ ostream = fopen (outfile, "w");
+ }
+ if (!ostream)
+ pfatal_with_name (outfile);
+
+ init_index ();
+
+ if (nfiles == 0)
+ {
+ if (outfile)
+ fclose (ostream);
+ return 0;
+ }
+
+ /* For each file, make two line buffers.
+ Also, for each file, there is an element of `thisline'
+ which points at any time to one of the file's two buffers,
+ and an element of `prevline' which points to the other buffer.
+ `thisline' is supposed to point to the next available line from the file,
+ while `prevline' holds the last file line used,
+ which is remembered so that we can verify that the file is properly sorted. */
+
+ /* lb1 and lb2 contain one buffer each per file. */
+ lb1 = (struct linebuffer *) xmalloc (nfiles * sizeof (struct linebuffer));
+ lb2 = (struct linebuffer *) xmalloc (nfiles * sizeof (struct linebuffer));
+
+ /* thisline[i] points to the linebuffer holding the next available line in file i,
+ or is zero if there are no lines left in that file. */
+ thisline = (struct linebuffer **)
+ xmalloc (nfiles * sizeof (struct linebuffer *));
+ /* prevline[i] points to the linebuffer holding the last used line
+ from file i. This is just for verifying that file i is properly
+ sorted. */
+ prevline = (struct linebuffer **)
+ xmalloc (nfiles * sizeof (struct linebuffer *));
+ /* streams[i] holds the input stream for file i. */
+ streams = (FILE **) xmalloc (nfiles * sizeof (FILE *));
+ /* file_lossage[i] is nonzero if we already know file i is not
+ properly sorted. */
+ file_lossage = (int *) xmalloc (nfiles * sizeof (int));
+
+ /* Allocate and initialize all that storage. */
+
+ for (i = 0; i < nfiles; i++)
+ {
+ initbuffer (&lb1[i]);
+ initbuffer (&lb2[i]);
+ thisline[i] = &lb1[i];
+ prevline[i] = &lb2[i];
+ file_lossage[i] = 0;
+ streams[i] = fopen (infiles[i], "r");
+ if (!streams[i])
+ pfatal_with_name (infiles[i]);
+
+ readline (thisline[i], streams[i]);
+ }
+
+ /* Keep count of number of files not at eof. */
+ nleft = nfiles;
+
+ while (nleft)
+ {
+ struct linebuffer *best = 0;
+ struct linebuffer *exch;
+ int bestfile = -1;
+ int i;
+
+ /* Look at the next avail line of each file; choose the least one. */
+
+ for (i = 0; i < nfiles; i++)
+ {
+ if (thisline[i] &&
+ (!best ||
+ 0 < compare_general (best->buffer, thisline[i]->buffer,
+ (long) bestfile, (long) i, num_keyfields)))
+ {
+ best = thisline[i];
+ bestfile = i;
+ }
+ }
+
+ /* Output that line, unless it matches the previous one and we
+ don't want duplicates. */
+
+ if (!(prev_out &&
+ !compare_general (prev_out->buffer,
+ best->buffer, 0L, 1L, num_keyfields - 1)))
+ indexify (best->buffer, ostream);
+ prev_out = best;
+
+ /* Now make the line the previous of its file, and fetch a new
+ line from that file. */
+
+ exch = prevline[bestfile];
+ prevline[bestfile] = thisline[bestfile];
+ thisline[bestfile] = exch;
+
+ while (1)
+ {
+ /* If the file has no more, mark it empty. */
+
+ if (feof (streams[bestfile]))
+ {
+ thisline[bestfile] = 0;
+ /* Update the number of files still not empty. */
+ nleft--;
+ break;
+ }
+ readline (thisline[bestfile], streams[bestfile]);
+ if (thisline[bestfile]->buffer[0] || !feof (streams[bestfile]))
+ break;
+ }
+ }
+
+ finish_index (ostream);
+
+ /* Free all storage and close all input streams. */
+
+ for (i = 0; i < nfiles; i++)
+ {
+ fclose (streams[i]);
+ free (lb1[i].buffer);
+ free (lb2[i].buffer);
+ }
+ free (file_lossage);
+ free (lb1);
+ free (lb2);
+ free (thisline);
+ free (prevline);
+ free (streams);
+
+ if (outfile)
+ fclose (ostream);
+
+ return lossage;
+}
+
+/* Print error message and exit. */
+
+void
+fatal (format, arg)
+ char *format, *arg;
+{
+ error (format, arg);
+ exit (TI_FATAL_ERROR);
+}
+
+/* Print error message. FORMAT is printf control string, ARG is arg for it. */
+void
+error (format, arg)
+ char *format, *arg;
+{
+ printf ("%s: ", program_name);
+ printf (format, arg);
+ if (format[strlen (format) -1] != '\n')
+ printf ("\n");
+}
+
+void
+perror_with_name (name)
+ char *name;
+{
+ char *s;
+
+ s = strerror (errno);
+ printf ("%s: ", program_name);
+ printf ("%s; for file `%s'.\n", s, name);
+}
+
+void
+pfatal_with_name (name)
+ char *name;
+{
+ char *s;
+
+ s = strerror (errno);
+ printf ("%s: ", program_name);
+ printf ("%s; for file `%s'.\n", s, name);
+ exit (TI_FATAL_ERROR);
+}
+
+/* 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;
+}
+
+#if !defined (HAVE_STRERROR)
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+char *
+strerror (num)
+ int num;
+{
+ if (num >= sys_nerr)
+ return ("");
+ else
+ return (sys_errlist[num]);
+}
+#endif /* !HAVE_STRERROR */
+
+#if !defined (HAVE_STRCHR)
+char *
+strrchr (string, character)
+ char *string;
+ int character;
+{
+ register int i;
+
+ for (i = strlen (string) - 1; i > -1; i--)
+ if (string[i] == character)
+ return (string + i);
+
+ return ((char *)NULL);
+}
+#endif /* HAVE_STRCHR */
+
+/* Just like malloc, but kills the program in case of fatal error. */
+void *
+xmalloc (nbytes)
+ int nbytes;
+{
+ void *temp = (void *) malloc (nbytes);
+
+ if (nbytes && temp == (void *)NULL)
+ memory_error ("xmalloc", nbytes);
+
+ return (temp);
+}
+
+/* Like realloc (), but barfs if there isn't enough memory. */
+void *
+xrealloc (pointer, nbytes)
+ void *pointer;
+ int nbytes;
+{
+ void *temp;
+
+ if (!pointer)
+ temp = (void *)xmalloc (nbytes);
+ else
+ temp = (void *)realloc (pointer, nbytes);
+
+ if (nbytes && !temp)
+ memory_error ("xrealloc", nbytes);
+
+ return (temp);
+}
+
+memory_error (callers_name, bytes_wanted)
+ char *callers_name;
+ int bytes_wanted;
+{
+ char printable_string[80];
+
+ sprintf (printable_string,
+ "Virtual memory exhausted in %s ()! Needed %d bytes.",
+ callers_name, bytes_wanted);
+
+ error (printable_string);
+ abort ();
+}
+