Blame lib/dbtexmf/dblatex/grubber/index.py

Packit 0f19cf
# This file is part of Rubber and thus covered by the GPL
Packit 0f19cf
# (c) Emmanuel Beffara, 2004--2006
Packit 0f19cf
"""
Packit 0f19cf
Indexing support with package 'index'.
Packit 0f19cf
Packit 0f19cf
This module handles the processing of the document's indices using a tool like
Packit 0f19cf
makeindex or xindy. It stores an MD5 sum of the source (.idx) file between two
Packit 0f19cf
runs, in order to detect modifications.
Packit 0f19cf
Packit 0f19cf
The following directives are provided to specify options for makeindex:
Packit 0f19cf
Packit 0f19cf
  tool <tool> =
Packit 0f19cf
    Choose which indexing tool should be used. Currently this can be either
Packit 0f19cf
    "makeindex" (by default) or "xindy".
Packit 0f19cf
Packit 0f19cf
  language <lang> =
Packit 0f19cf
    Choose the language used for sorting the index (xindy only).
Packit 0f19cf
Packit 0f19cf
  modules <mod> <mod> ... =
Packit 0f19cf
      Specify which modules xindy should use for the index.
Packit 0f19cf
Packit 0f19cf
  order <ordering> =
Packit 0f19cf
    Modify the ordering to be used (makeindex only, supported by xindy with
Packit 0f19cf
    warnings). The argument must be a space separated list of:
Packit 0f19cf
    - standard = use default ordering (no options, this is the default)
Packit 0f19cf
    - german = use German ordering (option "-g")
Packit 0f19cf
    - letter = use letter instead of word ordering (option "-l")
Packit 0f19cf
Packit 0f19cf
  path <directory> =
Packit 0f19cf
    Add the specified directory to the search path for styles.
Packit 0f19cf
Packit 0f19cf
  style <name> =
Packit 0f19cf
    Use the specified style file.
Packit 0f19cf
Packit 0f19cf
They all accept an optional argument first, enclosed in parentheses as in
Packit 0f19cf
"index.path (foo,bar) here/", to specify which index they apply to. Without
Packit 0f19cf
this argument, they apply to all indices declared at the point where they
Packit 0f19cf
occur.
Packit 0f19cf
"""
Packit 0f19cf
Packit 0f19cf
import os
Packit 0f19cf
from os.path import *
Packit 0f19cf
import re, string
Packit 0f19cf
import subprocess
Packit 0f19cf
import xml.dom.minidom
Packit 0f19cf
Packit 0f19cf
from subprocess import Popen, PIPE
Packit 0f19cf
from msg import _, msg
Packit 0f19cf
from plugins import TexModule
Packit 0f19cf
from util import md5_file
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class Xindy:
Packit 0f19cf
    """
Packit 0f19cf
    Xindy command wrapper
Packit 0f19cf
    """
Packit 0f19cf
    def __init__(self, doc, idxfile, target, transcript="",
Packit 0f19cf
                 opts=None, modules=None,
Packit 0f19cf
                 index_lang="", style=""):
Packit 0f19cf
        self.doc = doc
Packit 0f19cf
        self.idxfile = idxfile
Packit 0f19cf
        self.target = target
Packit 0f19cf
        self.transcript = transcript
Packit 0f19cf
        self.opts = opts or []
Packit 0f19cf
        self.modules = modules or []
Packit 0f19cf
        self.index_lang = index_lang
Packit 0f19cf
        self.path_var = "XINDY_SEARCHPATH"
Packit 0f19cf
        mapfile = os.path.join(os.path.dirname(__file__), "xindylang.xml")
Packit 0f19cf
        self.languages = self.map_languages(mapfile)
Packit 0f19cf
        self._re_hyperindex = re.compile(r"hyperindexformat{\\(.*?)}}{",
Packit 0f19cf
                                         re.M|re.DOTALL)
Packit 0f19cf
        self.invalid_index_ranges = []
Packit 0f19cf
Packit 0f19cf
    def map_languages(self, mapfile):
Packit 0f19cf
        languages = {}
Packit 0f19cf
        dom_document = xml.dom.minidom.parse(mapfile)
Packit 0f19cf
        for dom_fontspec in dom_document.getElementsByTagName('map'):
Packit 0f19cf
            lang = dom_fontspec.getAttribute('lang')
Packit 0f19cf
            xindylang = dom_fontspec.getAttribute('xindylang')
Packit 0f19cf
            if xindylang:
Packit 0f19cf
                languages[lang] = xindylang
Packit 0f19cf
        dom_document.unlink()
Packit 0f19cf
        return languages
Packit 0f19cf
Packit 0f19cf
    def command(self):
Packit 0f19cf
        cmd = []
Packit 0f19cf
        if self.doc.program == "xelatex":
Packit 0f19cf
            # If raw index is in UTF-8 the texindy command cannot be used
Packit 0f19cf
            cmd.extend(["xindy", "-M", "texindy", "-C", self.doc.encoding])
Packit 0f19cf
            # To behave even more like texindy
Packit 0f19cf
            cmd.extend(["-q", "-M", "page-ranges"])
Packit 0f19cf
        else:
Packit 0f19cf
            # Call texindy to handle LICR encoded raw index
Packit 0f19cf
            # Equivalent to xindy arguments (beware of module order):
Packit 0f19cf
            #   "xindy", "-M", "tex/inputenc/latin",
Packit 0f19cf
            #            "-M", "texindy", "-C", "latin",
Packit 0f19cf
            #            "-I", "latex"
Packit 0f19cf
            cmd.extend(["texindy"])
Packit 0f19cf
Packit 0f19cf
        # Specific output files?
Packit 0f19cf
        if self.target:
Packit 0f19cf
            cmd.extend(["-o", self.target])
Packit 0f19cf
        if self.transcript:
Packit 0f19cf
            cmd.extend(["-t", self.transcript])
Packit 0f19cf
Packit 0f19cf
        # Find out which language to use
Packit 0f19cf
        if self.index_lang:
Packit 0f19cf
            lang = self.index_lang
Packit 0f19cf
        elif self.doc.lang:
Packit 0f19cf
            lang = self.languages.get(self.doc.lang)
Packit 0f19cf
            if not(lang):
Packit 0f19cf
                msg.warn(_("xindy: lang '%s' not found" % \
Packit 0f19cf
                           self.doc.lang), pkg="index")
Packit 0f19cf
            else:
Packit 0f19cf
                msg.log(_("xindy: lang '%s' mapped to '%s'" % \
Packit 0f19cf
                           (self.doc.lang, lang)), pkg="index")
Packit 0f19cf
        else:
Packit 0f19cf
            lang = None
Packit 0f19cf
Packit 0f19cf
        if lang:
Packit 0f19cf
            cmd.extend(["-L", lang])
Packit 0f19cf
Packit 0f19cf
        for mod in self.modules:
Packit 0f19cf
            cmd.extend(["-M", mod])
Packit 0f19cf
Packit 0f19cf
        if self.opts:
Packit 0f19cf
            cmd.extend(self.opts)
Packit 0f19cf
Packit 0f19cf
        cmd.append(self.idxfile)
Packit 0f19cf
        return cmd
Packit 0f19cf
Packit 0f19cf
    def _find_index_encoding(self, logname):
Packit 0f19cf
        # Texindy produces latin-* indexes. Try to find out which one from
Packit 0f19cf
        # the modules loaded by the script (language dependent)
Packit 0f19cf
        re_lang = re.compile("loading module \"lang/.*/(latin[^.-]*)")
Packit 0f19cf
        logfile = open(logname)
Packit 0f19cf
        encoding = ""
Packit 0f19cf
        for line in logfile:
Packit 0f19cf
            m = re_lang.search(line)
Packit 0f19cf
            if m:
Packit 0f19cf
                encoding = m.group(1)
Packit 0f19cf
                break
Packit 0f19cf
Packit 0f19cf
        logfile.close()
Packit 0f19cf
        return encoding
Packit 0f19cf
Packit 0f19cf
    def _index_is_unicode(self):
Packit 0f19cf
        f = file(self.target, "r")
Packit 0f19cf
        is_unicode = True 
Packit 0f19cf
        for line in f:
Packit 0f19cf
            try:
Packit 0f19cf
                line.decode("utf8")
Packit 0f19cf
            except:
Packit 0f19cf
                is_unicode = False
Packit 0f19cf
                break
Packit 0f19cf
        f.close()
Packit 0f19cf
        return is_unicode
Packit 0f19cf
Packit 0f19cf
    def _sanitize_idxfile(self):
Packit 0f19cf
        #
Packit 0f19cf
        # Remove the 'hyperindexformat' of the new hyperref that makes a mess
Packit 0f19cf
        # with Xindy. If not, the following error is raised by Xindy:
Packit 0f19cf
        # "WARNING: unknown cross-reference-class `hyperindexformat'! (ignored)"
Packit 0f19cf
        #
Packit 0f19cf
        f = file(self.idxfile, "r")
Packit 0f19cf
        data = f.read()
Packit 0f19cf
        f.close()
Packit 0f19cf
        data, nsub = self._re_hyperindex.subn(r"\1}{", data)
Packit 0f19cf
        if not(nsub):
Packit 0f19cf
            return
Packit 0f19cf
        msg.debug("Remove %d unsupported 'hyperindexformat' calls" % nsub)
Packit 0f19cf
        f = file(self.idxfile, "w")
Packit 0f19cf
        f.write(data)
Packit 0f19cf
        f.close()
Packit 0f19cf
Packit 0f19cf
    def _fix_invalid_ranges(self):
Packit 0f19cf
        if not(self.invalid_index_ranges): return
Packit 0f19cf
        f = open(self.idxfile)
Packit 0f19cf
        lines = f.readlines()
Packit 0f19cf
        f.close()
Packit 0f19cf
Packit 0f19cf
        # Track the lines with the wrong index ranges
Packit 0f19cf
        for i, line in enumerate(lines):
Packit 0f19cf
            for entry in self.invalid_index_ranges:
Packit 0f19cf
                if entry.index_key in line:
Packit 0f19cf
                    entry.add_line(i, line)
Packit 0f19cf
Packit 0f19cf
        # Summary of the lines to remove in order to fix the ranges
Packit 0f19cf
        skip_lines = []
Packit 0f19cf
        for entry in self.invalid_index_ranges:
Packit 0f19cf
            skip_lines.extend(entry.skip_lines)
Packit 0f19cf
            entry.reinit()
Packit 0f19cf
        if not(skip_lines): return
Packit 0f19cf
        
Packit 0f19cf
        # Remove the lines starting from the end to always have valid line num
Packit 0f19cf
        msg.debug("xindy: lines to remove from %s to fix ranges: %s" %\
Packit 0f19cf
                  (self.idxfile, skip_lines))
Packit 0f19cf
        skip_lines.sort()
Packit 0f19cf
        skip_lines.reverse()
Packit 0f19cf
        for line_num in skip_lines:
Packit 0f19cf
            del lines[line_num]
Packit 0f19cf
        f = open(self.idxfile, "w")
Packit 0f19cf
        f.writelines(lines)
Packit 0f19cf
        f.close()
Packit 0f19cf
Packit 0f19cf
    def _detect_invalid_ranges(self, data):
Packit 0f19cf
        # Look for warnings like this:
Packit 0f19cf
        #
Packit 0f19cf
        # WARNING: Found a :close-range in the index that wasn't opened before!
Packit 0f19cf
        #          Location-reference is 76 in keyword (Statute of Anne (1710))
Packit 0f19cf
        #          I'll continue and ignore this.
Packit 0f19cf
        #
Packit 0f19cf
        # Do it only once on the first run to find wrong indexes.
Packit 0f19cf
        if (self.invalid_index_ranges): return
Packit 0f19cf
        blocks = re.split("(WARNING:|ERROR:)", data, re.M)
Packit 0f19cf
        check_next_block = False
Packit 0f19cf
        for block in blocks:
Packit 0f19cf
            if "WARNING" in block:
Packit 0f19cf
                check_next_block = True
Packit 0f19cf
            elif check_next_block:
Packit 0f19cf
                m = re.search("Found.*?-range .*"\
Packit 0f19cf
                              "Location-reference is \d+ in keyword \((.*)\)",
Packit 0f19cf
                              block, re.M|re.DOTALL)
Packit 0f19cf
                if m: self.invalid_index_ranges.append(Indexentry(m.group(1)))
Packit 0f19cf
                check_next_block = False
Packit 0f19cf
Packit 0f19cf
    def run(self):
Packit 0f19cf
        self._sanitize_idxfile()
Packit 0f19cf
        self._fix_invalid_ranges()
Packit 0f19cf
        cmd = self.command()
Packit 0f19cf
        msg.debug(" ".join(cmd))
Packit 0f19cf
Packit 0f19cf
        # Collect the script output, and errors
Packit 0f19cf
        logname = join(dirname(self.target), "xindy.log")
Packit 0f19cf
        logfile = open(logname, "w")
Packit 0f19cf
        p = Popen(cmd, stdout=logfile, stderr=PIPE)
Packit 0f19cf
        errdata = p.communicate()[1]
Packit 0f19cf
        rc = p.wait()
Packit 0f19cf
        if msg.stdout:
Packit 0f19cf
            msg.stdout.write(errdata)
Packit 0f19cf
        else:
Packit 0f19cf
            msg.warn(_(errdata.strip()))
Packit 0f19cf
        logfile.close()
Packit 0f19cf
        if (rc != 0):
Packit 0f19cf
            msg.error(_("could not make index %s") % self.target)
Packit 0f19cf
            return 1
Packit 0f19cf
Packit 0f19cf
        self._detect_invalid_ranges(errdata)
Packit 0f19cf
Packit 0f19cf
        # Now convert the built index to UTF-8 if required
Packit 0f19cf
        if cmd[0] == "texindy" and self.doc.encoding == "utf8":
Packit 0f19cf
            if not(self._index_is_unicode()):
Packit 0f19cf
                encoding = self._find_index_encoding(logname)
Packit 0f19cf
                tmpindex = join(dirname(self.target), "new.ind")
Packit 0f19cf
                cmd = ["iconv", "-f", encoding, "-t", "utf8",
Packit 0f19cf
                       "-o", tmpindex, self.target]
Packit 0f19cf
                msg.debug(" ".join(cmd))
Packit 0f19cf
                rc = subprocess.call(cmd)
Packit 0f19cf
                if rc == 0: os.rename(tmpindex, self.target)
Packit 0f19cf
Packit 0f19cf
        return rc
Packit 0f19cf
Packit 0f19cf
class Indexentry:
Packit 0f19cf
    """
Packit 0f19cf
    Index entry wrapper from idxfile. Its role is to detect range anomalies
Packit 0f19cf
    """
Packit 0f19cf
    _re_entry = re.compile("\indexentry{(.*)\|([\(\)]?).*}{(\d+)}", re.DOTALL)
Packit 0f19cf
Packit 0f19cf
    def __init__(self, index_key):
Packit 0f19cf
        self.index_key = index_key
Packit 0f19cf
        self.skip_lines = []
Packit 0f19cf
        self.last_range_page = 0
Packit 0f19cf
        self.last_range_line = -1
Packit 0f19cf
        self.last_range_open = False
Packit 0f19cf
Packit 0f19cf
    def reinit(self):
Packit 0f19cf
        self.__init__(self.index_key)
Packit 0f19cf
Packit 0f19cf
    def add_line(self, line_num, indexentry):
Packit 0f19cf
        m = self._re_entry.search(indexentry)
Packit 0f19cf
        if not(m):
Packit 0f19cf
            return
Packit 0f19cf
        index_key = m.group(1).split("!")[-1]
Packit 0f19cf
        if index_key != self.index_key:
Packit 0f19cf
            return
Packit 0f19cf
        range_state = m.group(2)
Packit 0f19cf
        page = int(m.group(3))
Packit 0f19cf
Packit 0f19cf
        #print "Found %s at %d" % (index_key, page)
Packit 0f19cf
        if range_state == "(":
Packit 0f19cf
            # If a starting range overlap the previous range remove
Packit 0f19cf
            # this intermediate useless range close/open
Packit 0f19cf
            if page <= self.last_range_page:
Packit 0f19cf
                self.skip_lines += [self.last_range_line, line_num]
Packit 0f19cf
            self.last_range_page = page
Packit 0f19cf
            self.last_range_line = line_num
Packit 0f19cf
            self.last_range_open = True
Packit 0f19cf
        elif range_state == ")":
Packit 0f19cf
            self.last_range_page = page
Packit 0f19cf
            self.last_range_line = line_num
Packit 0f19cf
            self.last_range_open = False
Packit 0f19cf
        elif range_state == "":
Packit 0f19cf
            # If a single indexentry is within a range, skip it
Packit 0f19cf
            if self.last_range_open == True:
Packit 0f19cf
                self.skip_lines += [line_num]
Packit 0f19cf
        
Packit 0f19cf
Packit 0f19cf
class Makeindex:
Packit 0f19cf
    """
Packit 0f19cf
    Makeindex command wrapper
Packit 0f19cf
    """
Packit 0f19cf
    def __init__(self, doc, idxfile, target, transcript="",
Packit 0f19cf
                 opts=None, modules=None,
Packit 0f19cf
                 index_lang="", style=""):
Packit 0f19cf
        self.doc = doc
Packit 0f19cf
        self.idxfile = idxfile
Packit 0f19cf
        self.target = target
Packit 0f19cf
        self.transcript = transcript
Packit 0f19cf
        self.opts = opts or []
Packit 0f19cf
        self.path_var = "INDEXSTYLE"
Packit 0f19cf
        self.style = style
Packit 0f19cf
Packit 0f19cf
    def command(self):
Packit 0f19cf
        cmd = ["makeindex", "-o", self.target] + self.opts
Packit 0f19cf
        if self.transcript:
Packit 0f19cf
            cmd.extend(["-t", self.transcript])
Packit 0f19cf
        if self.style:
Packit 0f19cf
            cmd.extend(["-s", self.style])
Packit 0f19cf
        cmd.append(self.idxfile)
Packit 0f19cf
        return cmd
Packit 0f19cf
Packit 0f19cf
    def _index_is_unicode(self):
Packit 0f19cf
        f = file(self.target, "r")
Packit 0f19cf
        is_unicode = True 
Packit 0f19cf
        for line in f:
Packit 0f19cf
            try:
Packit 0f19cf
                line.decode("utf8")
Packit 0f19cf
            except:
Packit 0f19cf
                is_unicode = False
Packit 0f19cf
                break
Packit 0f19cf
        f.close()
Packit 0f19cf
        return is_unicode
Packit 0f19cf
Packit 0f19cf
    def run(self):
Packit 0f19cf
        cmd = self.command()
Packit 0f19cf
        msg.debug(" ".join(cmd))
Packit 0f19cf
Packit 0f19cf
        # Makeindex outputs everything to stderr, even progress messages
Packit 0f19cf
        rc = subprocess.call(cmd, stderr=msg.stdout)
Packit 0f19cf
        if (rc != 0):
Packit 0f19cf
            msg.error(_("could not make index %s") % self.target)
Packit 0f19cf
            return 1
Packit 0f19cf
Packit 0f19cf
        # Beware with UTF-8 encoding, makeindex with headings can be messy
Packit 0f19cf
        # because it puts in the headings the first 8bits char of the words
Packit 0f19cf
        # under the heading which can be an invalid character in UTF-8
Packit 0f19cf
        if (self.style and self.doc.encoding == "utf8"):
Packit 0f19cf
            if not(self._index_is_unicode()):
Packit 0f19cf
                # Retry without style to avoid headings
Packit 0f19cf
                msg.warn(_("makeindex on UTF8 failed. Retry..."))
Packit 0f19cf
                self.style = ""
Packit 0f19cf
                return self.run()
Packit 0f19cf
Packit 0f19cf
        return rc
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class Index(TexModule):
Packit 0f19cf
    """
Packit 0f19cf
    This class represents a single index.
Packit 0f19cf
    """
Packit 0f19cf
    def __init__ (self, doc, source, target, transcript):
Packit 0f19cf
        """
Packit 0f19cf
        Initialize the index, by specifying the source file (generated by
Packit 0f19cf
        LaTeX), the target file (the output of makeindex) and the transcript
Packit 0f19cf
        (e.g. .ilg) file.  Transcript is used by glosstex.py.
Packit 0f19cf
        """
Packit 0f19cf
        self.paranoid = True
Packit 0f19cf
        self.doc = doc
Packit 0f19cf
        self.pbase = doc.src_base
Packit 0f19cf
        self.source = doc.src_base + "." + source
Packit 0f19cf
        self.target = doc.src_base + "." + target
Packit 0f19cf
        self.transcript = doc.src_base + "." + transcript
Packit 0f19cf
Packit 0f19cf
        # In paranoid mode, can output only in current working dir
Packit 0f19cf
        if self.paranoid and (os.path.dirname(self.target) == os.getcwd()):
Packit 0f19cf
            self.target = os.path.basename(self.target)
Packit 0f19cf
            self.transcript = os.path.basename(self.transcript)
Packit 0f19cf
Packit 0f19cf
        if os.path.exists(self.source):
Packit 0f19cf
            self.md5 = md5_file(self.source)
Packit 0f19cf
        else:
Packit 0f19cf
            self.md5 = None
Packit 0f19cf
Packit 0f19cf
        self.tool = "makeindex"
Packit 0f19cf
        self.tool_obj = None
Packit 0f19cf
        self.lang = None   # only for xindy
Packit 0f19cf
        self.modules = []  # only for xindy
Packit 0f19cf
        self.opts = []
Packit 0f19cf
        self.path = []
Packit 0f19cf
        self.style = None  # only for makeindex
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
    def do_language (self, lang):
Packit 0f19cf
        self.lang = lang
Packit 0f19cf
Packit 0f19cf
    def do_modules (self, *args):
Packit 0f19cf
        self.modules.extend(args)
Packit 0f19cf
Packit 0f19cf
    def do_order (self, *args):
Packit 0f19cf
        for opt in args:
Packit 0f19cf
            if opt == "standard": self.opts = []
Packit 0f19cf
            elif opt == "german": self.opts.append("-g")
Packit 0f19cf
            elif opt == "letter": self.opts.append("-l")
Packit 0f19cf
            else: msg.warn(
Packit 0f19cf
                _("unknown option '%s' for 'makeidx.order'") % opt)
Packit 0f19cf
Packit 0f19cf
    def do_path (self, path):
Packit 0f19cf
        self.path.append(self.doc.abspath(path))
Packit 0f19cf
Packit 0f19cf
    def do_style (self, style):
Packit 0f19cf
        self.style = style
Packit 0f19cf
Packit 0f19cf
    def do_tool (self, tool):
Packit 0f19cf
        if tool not in ("makeindex", "xindy"):
Packit 0f19cf
            msg.error(_("unknown indexing tool '%s'") % tool)
Packit 0f19cf
        self.tool = tool
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
    def post_compile (self):
Packit 0f19cf
        """
Packit 0f19cf
        Run the indexer tool
Packit 0f19cf
        """
Packit 0f19cf
        if not os.path.exists(self.source):
Packit 0f19cf
            msg.log(_("strange, there is no %s") % self.source, pkg="index")
Packit 0f19cf
            return 0
Packit 0f19cf
        if not self.run_needed():
Packit 0f19cf
            return 0
Packit 0f19cf
Packit 0f19cf
        msg.progress(_("processing index %s") % self.source)
Packit 0f19cf
Packit 0f19cf
        if not(self.tool_obj):
Packit 0f19cf
            if self.tool == "makeindex":
Packit 0f19cf
                index_cls = Makeindex
Packit 0f19cf
            elif self.tool == "xindy":
Packit 0f19cf
                index_cls = Xindy
Packit 0f19cf
Packit 0f19cf
            self.tool_obj = index_cls(self.doc,
Packit 0f19cf
                              self.source,
Packit 0f19cf
                              self.target,
Packit 0f19cf
                              transcript=self.transcript,
Packit 0f19cf
                              opts=self.opts,
Packit 0f19cf
                              modules=self.modules,
Packit 0f19cf
                              index_lang=self.lang,
Packit 0f19cf
                              style=self.style)
Packit 0f19cf
Packit 0f19cf
        rc = self.tool_obj.run()
Packit 0f19cf
        if rc != 0:
Packit 0f19cf
            return rc
Packit 0f19cf
Packit 0f19cf
        self.doc.must_compile = 1
Packit 0f19cf
        return 0
Packit 0f19cf
Packit 0f19cf
    def run_needed (self):
Packit 0f19cf
        """
Packit 0f19cf
        Check if makeindex has to be run. This is the case either if the
Packit 0f19cf
        target file does not exist or if the source file has changed.
Packit 0f19cf
        """
Packit 0f19cf
        if os.path.getsize(self.source) == 0:
Packit 0f19cf
            msg.log(_("the index file %s is empty") % self.source, pkg="index")
Packit 0f19cf
            return 0
Packit 0f19cf
        new = md5_file(self.source)
Packit 0f19cf
        if not os.path.exists(self.target):
Packit 0f19cf
            self.md5 = new
Packit 0f19cf
            return 1
Packit 0f19cf
        if not self.md5:
Packit 0f19cf
            self.md5 = new
Packit 0f19cf
            msg.log(_("the index file %s is new") % self.source, pkg="index")
Packit 0f19cf
            return 1
Packit 0f19cf
        if self.md5 == new:
Packit 0f19cf
            msg.log(_("the index %s did not change") % self.source, pkg="index")
Packit 0f19cf
            return 0
Packit 0f19cf
        self.md5 = new
Packit 0f19cf
        msg.log(_("the index %s has changed") % self.source, pkg="index")
Packit 0f19cf
        return 1
Packit 0f19cf
Packit 0f19cf
    def clean (self):
Packit 0f19cf
        """
Packit 0f19cf
        Remove all generated files related to the index.
Packit 0f19cf
        """
Packit 0f19cf
        for file in self.source, self.target, self.transcript:
Packit 0f19cf
            if exists(file):
Packit 0f19cf
                msg.log(_("removing %s") % file, pkg="index")
Packit 0f19cf
                os.unlink(file)
Packit 0f19cf
Packit 0f19cf
re_newindex = re.compile(" *{(?P<idx>[^{}]*)} *{(?P<ind>[^{}]*)}")
Packit 0f19cf
re_optarg = re.compile("\((?P<list>[^()]*)\) *")
Packit 0f19cf
Packit 0f19cf
class Module (TexModule):
Packit 0f19cf
    def __init__ (self, doc, dict):
Packit 0f19cf
        """
Packit 0f19cf
        Initialize the module with no index.
Packit 0f19cf
        """
Packit 0f19cf
        self.doc = doc
Packit 0f19cf
        self.indices = {}
Packit 0f19cf
        self.defaults = []
Packit 0f19cf
        self.commands = {}
Packit 0f19cf
        doc.parser.add_hook("makeindex", self.makeindex)
Packit 0f19cf
        doc.parser.add_hook("newindex", self.newindex)
Packit 0f19cf
Packit 0f19cf
    def register (self, name, idx, ind, ilg):
Packit 0f19cf
        """
Packit 0f19cf
        Register a new index.
Packit 0f19cf
        """
Packit 0f19cf
        index = self.indices[name] = Index(self.doc, idx, ind, ilg)
Packit 0f19cf
        for cmd in self.defaults:
Packit 0f19cf
            index.command(*cmd)
Packit 0f19cf
        if self.commands.has_key(name):
Packit 0f19cf
            for cmd in self.commands[name]:
Packit 0f19cf
                index.command(*cmd)
Packit 0f19cf
Packit 0f19cf
    def makeindex (self, dict):
Packit 0f19cf
        """
Packit 0f19cf
        Register the standard index.
Packit 0f19cf
        """
Packit 0f19cf
        self.register("default", "idx", "ind", "ilg")
Packit 0f19cf
Packit 0f19cf
    def newindex (self, dict):
Packit 0f19cf
        """
Packit 0f19cf
        Register a new index.
Packit 0f19cf
        """
Packit 0f19cf
        m = re_newindex.match(dict["line"])
Packit 0f19cf
        if not m:
Packit 0f19cf
            return
Packit 0f19cf
        index = dict["arg"]
Packit 0f19cf
        d = m.groupdict()
Packit 0f19cf
        self.register(index, d["idx"], d["ind"], "ilg")
Packit 0f19cf
        msg.log(_("index %s registered") % index, pkg="index")
Packit 0f19cf
Packit 0f19cf
    def command (self, cmd, args):
Packit 0f19cf
        indices = self.indices
Packit 0f19cf
        names = None
Packit 0f19cf
        if len(args) > 0:
Packit 0f19cf
            m = re_optarg.match(args[0])
Packit 0f19cf
            if m:
Packit 0f19cf
                names = m.group("list").split(",")
Packit 0f19cf
                args = args[1:]
Packit 0f19cf
        if names is None:
Packit 0f19cf
            self.defaults.append([cmd, args])
Packit 0f19cf
            names = indices.keys()
Packit 0f19cf
        for index in names:
Packit 0f19cf
            if indices.has_key(index):
Packit 0f19cf
                indices[index].command(cmd, args[1:])
Packit 0f19cf
            elif self.commands.has_key(index):
Packit 0f19cf
                self.commands[index].append([cmd, args])
Packit 0f19cf
            else:
Packit 0f19cf
                self.commands[index] = [[cmd, args]]
Packit 0f19cf
Packit 0f19cf
    def post_compile (self):
Packit 0f19cf
        for index in self.indices.values():
Packit 0f19cf
            if index.post_compile():
Packit 0f19cf
                return 1
Packit 0f19cf
        return 0
Packit 0f19cf
Packit 0f19cf
    def clean (self):
Packit 0f19cf
        for index in self.indices.values():
Packit 0f19cf
            index.clean()
Packit 0f19cf
        return 0
Packit 0f19cf