initial source import
diff --git a/doc/tools/buildindex.py b/doc/tools/buildindex.py
new file mode 100755
index 0000000..5a41c0e
--- /dev/null
+++ b/doc/tools/buildindex.py
@@ -0,0 +1,353 @@
+#! /usr/bin/env python
+
+__version__ = '$Revision: 1.1.1.1 $'
+
+import os
+import re
+import string
+import sys
+
+
+class Node:
+    __rmjunk = re.compile("<#\d+#>")
+
+    continuation = 0
+
+    def __init__(self, link, str, seqno):
+        self.links = [link]
+        self.seqno = seqno
+        # remove <#\d+#> left in by moving the data out of LaTeX2HTML
+        str = self.__rmjunk.sub('', str)
+        # build up the text
+        self.text = split_entry_text(str)
+        self.key = split_entry_key(str)
+
+    def __cmp__(self, other):
+        """Comparison operator includes sequence number, for use with
+        list.sort()."""
+        return self.cmp_entry(other) or cmp(self.seqno, other.seqno)
+
+    def cmp_entry(self, other):
+        """Comparison 'operator' that ignores sequence number."""
+        c = 0
+        for i in range(min(len(self.key), len(other.key))):
+            c = (cmp_part(self.key[i], other.key[i])
+                 or cmp_part(self.text[i], other.text[i]))
+            if c:
+                break
+        return c or cmp(self.key, other.key) or cmp(self.text, other.text)
+
+    def __repr__(self):
+        return "<Node for %s (%s)>" % (string.join(self.text, '!'), self.seqno)
+
+    def __str__(self):
+        return string.join(self.key, '!')
+
+    def dump(self):
+        return "%s\1%s###%s\n" \
+               % (string.join(self.links, "\1"),
+                  string.join(self.text, '!'),
+                  self.seqno)
+
+
+def cmp_part(s1, s2):
+    result = cmp(s1, s2)
+    if result == 0:
+        return 0
+    l1 = string.lower(s1)
+    l2 = string.lower(s2)
+    minlen = min(len(s1), len(s2))
+    if len(s1) < len(s2) and l1 == l2[:len(s1)]:
+        result = -1
+    elif len(s2) < len(s1) and l2 == l1[:len(s2)]:
+        result = 1
+    else:
+        result = cmp(l1, l2) or cmp(s1, s2)
+    return result
+
+
+def split_entry(str, which):
+    stuff = []
+    parts = string.split(str, '!')
+    parts = map(string.split, parts, ['@'] * len(parts))
+    for entry in parts:
+        if len(entry) != 1:
+            key = entry[which]
+        else:
+            key = entry[0]
+        stuff.append(key)
+    return stuff
+
+
+_rmtt = re.compile(r"""(.*)<tt(?: class=['"][a-z0-9]+["'])?>(.*)</tt>(.*)$""",
+                   re.IGNORECASE)
+_rmparens = re.compile(r"\(\)")
+
+def split_entry_key(str):
+    parts = split_entry(str, 1)
+    for i in range(len(parts)):
+        m = _rmtt.match(parts[i])
+        if m:
+            parts[i] = string.join(m.group(1, 2, 3), '')
+        else:
+            parts[i] = string.lower(parts[i])
+        # remove '()' from the key:
+        parts[i] = _rmparens.sub('', parts[i])
+    return map(trim_ignored_letters, parts)
+
+
+def split_entry_text(str):
+    if '<' in str:
+        m = _rmtt.match(str)
+        if m:
+            str = string.join(m.group(1, 2, 3), '')
+    return split_entry(str, 1)
+
+
+def load(fp):
+    nodes = []
+    rx = re.compile("(.*)\1(.*)###(.*)$")
+    while 1:
+        line = fp.readline()
+        if not line:
+            break
+        m = rx.match(line)
+        if m:
+            link, str, seqno = m.group(1, 2, 3)
+            nodes.append(Node(link, str, seqno))
+    return nodes
+
+
+def trim_ignored_letters(s):
+    # ignore $ to keep environment variables with the
+    # leading letter from the name
+    s = string.lower(s)
+    if s[0] == "$":
+        return s[1:]
+    else:
+        return s
+
+def get_first_letter(s):
+    return string.lower(trim_ignored_letters(s)[0])
+
+
+def split_letters(nodes):
+    letter_groups = []
+    if nodes:
+        group = []
+        append = group.append
+        letter = get_first_letter(nodes[0].text[0])
+        letter_groups.append((letter, group))
+        for node in nodes:
+            nletter = get_first_letter(node.text[0])
+            if letter != nletter:
+                letter = nletter
+                group = []
+                letter_groups.append((letter, group))
+                append = group.append
+            append(node)
+    return letter_groups
+
+
+# need a function to separate the nodes into columns...
+def split_columns(nodes, columns=1):
+    if columns <= 1:
+        return [nodes]
+    # This is a rough height; we may have to increase to avoid breaks before
+    # a subitem.
+    colheight = len(nodes) / columns
+    numlong = len(nodes) % columns
+    if numlong:
+        colheight = colheight + 1
+    else:
+        numlong = columns
+    cols = []
+    for i in range(numlong):
+        start = i * colheight
+        end = start + colheight
+        cols.append(nodes[start:end])
+    del nodes[:end]
+    colheight = colheight - 1
+    try:
+        numshort = len(nodes) / colheight
+    except ZeroDivisionError:
+        cols = cols + (columns - len(cols)) * [[]]
+    else:
+        for i in range(numshort):
+            start = i * colheight
+            end = start + colheight
+            cols.append(nodes[start:end])
+    #
+    # If items continue across columns, make sure they are marked
+    # as continuations so the user knows to look at the previous column.
+    #
+    for i in range(len(cols) - 1):
+        try:
+            prev = cols[i][-1]
+            next = cols[i + 1][0]
+        except IndexError:
+            return cols
+        else:
+            n = min(len(prev.key), len(next.key))
+            for j in range(n):
+                if prev.key[j] != next.key[j]:
+                    break
+                next.continuation = j + 1
+    return cols
+
+
+DL_LEVEL_INDENT = "  "
+
+def format_column(nodes):
+    strings = ["<dl compact>"]
+    append = strings.append
+    level = 0
+    previous = []
+    for node in nodes:
+        current = node.text
+        count = 0
+        for i in range(min(len(current), len(previous))):
+            if previous[i] != current[i]:
+                break
+            count = i + 1
+        if count > level:
+            append("<dl compact>" * (count - level) + "\n")
+            level = count
+        elif level > count:
+            append("\n")
+            append(level * DL_LEVEL_INDENT)
+            append("</dl>" * (level - count))
+            level = count
+        # else: level == count
+        for i in range(count, len(current) - 1):
+            term = node.text[i]
+            level = level + 1
+            if node.continuation > i:
+                extra = " (continued)"
+            else:
+                extra = ""
+            append("\n<dt>%s%s\n<dd>\n%s<dl compact>"
+                   % (term, extra, level * DL_LEVEL_INDENT))
+        append("\n%s<dt>%s%s</a>"
+               % (level * DL_LEVEL_INDENT, node.links[0], node.text[-1]))
+        for link in node.links[1:]:
+            append(",\n%s    %s[Link]</a>" % (level * DL_LEVEL_INDENT, link))
+        previous = current
+    append("\n")
+    append("</dl>" * (level + 1))
+    return string.join(strings, '')
+
+
+def format_nodes(nodes, columns=1):
+    strings = []
+    append = strings.append
+    if columns > 1:
+        colnos = range(columns)
+        colheight = len(nodes) / columns
+        if len(nodes) % columns:
+            colheight = colheight + 1
+        colwidth = 100 / columns
+        append('<table width="100%"><tr valign="top">')
+        for col in split_columns(nodes, columns):
+            append('<td width="%d%%">\n' % colwidth)
+            append(format_column(col))
+            append("\n</td>")
+        append("\n</tr></table>")
+    else:
+        append(format_column(nodes))
+    append("\n<p>\n")
+    return string.join(strings, '')
+
+
+def format_letter(letter):
+    if letter == '.':
+        lettername = ". (dot)"
+    elif letter == '_':
+        lettername = "_ (underscore)"
+    else:
+        lettername = string.upper(letter)
+    return "\n<hr>\n<h2><a name=\"letter-%s\">%s</a></h2>\n\n" \
+           % (letter, lettername)
+
+
+def format_html_letters(nodes, columns=1):
+    letter_groups = split_letters(nodes)
+    items = []
+    for letter, nodes in letter_groups:
+        s = "<b><a href=\"#letter-%s\">%s</a></b>" % (letter, letter)
+        items.append(s)
+    s = ["<hr><center>\n%s</center>\n" % string.join(items, " |\n")]
+    for letter, nodes in letter_groups:
+        s.append(format_letter(letter))
+        s.append(format_nodes(nodes, columns))
+    return string.join(s, '')
+
+def format_html(nodes, columns):
+    return format_nodes(nodes, columns)
+
+
+def collapse(nodes):
+    """Collapse sequences of nodes with matching keys into a single node.
+    Destructive."""
+    if len(nodes) < 2:
+        return
+    prev = nodes[0]
+    i = 1
+    while i < len(nodes):
+        node = nodes[i]
+        if not node.cmp_entry(prev):
+            prev.links.append(node.links[0])
+            del nodes[i]
+        else:
+            i = i + 1
+            prev = node
+
+
+def dump(nodes, fp):
+    for node in nodes:
+        fp.write(node.dump())
+
+
+def process_nodes(nodes, columns, letters):
+    nodes.sort()
+    collapse(nodes)
+    if letters:
+        return format_html_letters(nodes, columns)
+    else:
+        return format_html(nodes, columns)
+
+
+def main():
+    import getopt
+    ifn = "-"
+    ofn = "-"
+    columns = 1
+    letters = 0
+    opts, args = getopt.getopt(sys.argv[1:], "c:lo:",
+                               ["columns=", "letters", "output="])
+    for opt, val in opts:
+        if opt in ("-o", "--output"):
+            ofn = val
+        elif opt in ("-c", "--columns"):
+            columns = string.atoi(val)
+        elif opt in ("-l", "--letters"):
+            letters = 1
+    if not args:
+        args = [ifn]
+    nodes = []
+    for fn in args:
+        nodes = nodes + load(open(fn))
+    num_nodes = len(nodes)
+    html = process_nodes(nodes, columns, letters)
+    program = os.path.basename(sys.argv[0])
+    if ofn == "-":
+        sys.stdout.write(html)
+        sys.stderr.write("\n%s: %d index nodes" % (program, num_nodes))
+    else:
+        open(ofn, "w").write(html)
+        print
+        print "%s: %d index nodes" % (program, num_nodes)
+
+
+if __name__ == "__main__":
+    main()