diff options
author | Joshua Peck Macdonald <jmacd@FreeBSD.org> | 1997-01-11 02:12:38 +0000 |
---|---|---|
committer | Joshua Peck Macdonald <jmacd@FreeBSD.org> | 1997-01-11 02:12:38 +0000 |
commit | 253c2b00b74a914f105a971446b873fc1f38919a (patch) | |
tree | cc1b5b231b9a50d0219c9921e490665c5cea9eca /contrib/texinfo/util |
This is unmodified GNU texinfo-3.9 source. I'll be commiting a fewvendor/misc-GNU/texinfo/3.9
patches in a bit.
-josh
Notes
Notes:
svn path=/vendor/texinfo/dist/; revision=21495
svn path=/vendor/texinfo/3.9/; revision=21497; tag=vendor/misc-GNU/texinfo/3.9
Diffstat (limited to 'contrib/texinfo/util')
-rw-r--r-- | contrib/texinfo/util/Makefile.in | 101 | ||||
-rw-r--r-- | contrib/texinfo/util/deref.c | 238 | ||||
-rwxr-xr-x | contrib/texinfo/util/fixfonts | 84 | ||||
-rwxr-xr-x | contrib/texinfo/util/gen-dir-node | 176 | ||||
-rw-r--r-- | contrib/texinfo/util/install-info.c | 1111 | ||||
-rwxr-xr-x | contrib/texinfo/util/mkinstalldirs | 40 | ||||
-rwxr-xr-x | contrib/texinfo/util/tex3patch | 71 | ||||
-rwxr-xr-x | contrib/texinfo/util/texi2dvi | 364 | ||||
-rw-r--r-- | contrib/texinfo/util/texindex.c | 1793 |
9 files changed, 3978 insertions, 0 deletions
diff --git a/contrib/texinfo/util/Makefile.in b/contrib/texinfo/util/Makefile.in new file mode 100644 index 000000000000..aa3b85a8bd68 --- /dev/null +++ b/contrib/texinfo/util/Makefile.in @@ -0,0 +1,101 @@ +# Makefile for GNU Texindex and other utilities. +# $Id: Makefile.in,v 1.5 1996/09/29 20:07:06 karl 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 + +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 = $(exec_prefix)/bin +# Prefix for each installed program, normally empty or `g'. +binprefix = +# Prefix for each installed man page, normally empty or `g'. +manprefix = +mandir = $(prefix)/man/man1 +manext = 1 +infodir = $(prefix)/info + +#### End of system configuration section. #### + +all: texindex install-info +sub-all: all + +.c.o: + $(CC) -c $(CPPFLAGS) $(DEFS) -I. -I$(srcdir) -I$(common) $(CFLAGS) $< + + +install: all + $(INSTALL_PROGRAM) texindex $(bindir)/texindex + $(INSTALL_PROGRAM) $(srcdir)/texi2dvi $(bindir)/texi2dvi + $(INSTALL_PROGRAM) install-info $(bindir)/install-info + +uninstall: + rm -f $(bindir)/texindex $(bindir)/texi2dvi $(bindir)/install-info + +Makefile: Makefile.in ../config.status + cd ..; sh 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/contrib/texinfo/util/deref.c b/contrib/texinfo/util/deref.c new file mode 100644 index 000000000000..c15bc1abcf15 --- /dev/null +++ b/contrib/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/contrib/texinfo/util/fixfonts b/contrib/texinfo/util/fixfonts new file mode 100755 index 000000000000..ee2ea7192198 --- /dev/null +++ b/contrib/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/contrib/texinfo/util/gen-dir-node b/contrib/texinfo/util/gen-dir-node new file mode 100755 index 000000000000..8f13088f920d --- /dev/null +++ b/contrib/texinfo/util/gen-dir-node @@ -0,0 +1,176 @@ +#!/bin/sh +# $Id: gen-dir-node,v 1.2 1996/10/03 18:49:48 karl 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/contrib/texinfo/util/install-info.c b/contrib/texinfo/util/install-info.c new file mode 100644 index 000000000000..53fa4aa11164 --- /dev/null +++ b/contrib/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/contrib/texinfo/util/mkinstalldirs b/contrib/texinfo/util/mkinstalldirs new file mode 100755 index 000000000000..a01481be4367 --- /dev/null +++ b/contrib/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.10 1996/05/03 07:37:52 friedman 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/contrib/texinfo/util/tex3patch b/contrib/texinfo/util/tex3patch new file mode 100755 index 000000000000..1708c7588bbb --- /dev/null +++ b/contrib/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/contrib/texinfo/util/texi2dvi b/contrib/texinfo/util/texi2dvi new file mode 100755 index 000000000000..9b2e48eefcc4 --- /dev/null +++ b/contrib/texinfo/util/texi2dvi @@ -0,0 +1,364 @@ +#! /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.10 1996/10/04 18:21:55 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, 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.10 $' +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.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/contrib/texinfo/util/texindex.c b/contrib/texinfo/util/texindex.c new file mode 100644 index 000000000000..47a56791611f --- /dev/null +++ b/contrib/texinfo/util/texindex.c @@ -0,0 +1,1793 @@ +/* Prepare TeX index dribble output into an actual index. + $Id: texindex.c,v 1.6 1996/10/04 18:21:30 karl 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 + +void +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 (); +} + |