# This file is part of Rubber and thus covered by the GPL
# (c) Emmanuel Beffara, 2004--2006
"""
Indexing support with package 'index'.

This module handles the processing of the document's indices using a tool like
makeindex or xindy. It stores an MD5 sum of the source (.idx) file between two
runs, in order to detect modifications.

The following directives are provided to specify options for makeindex:

  tool <tool> =
    Choose which indexing tool should be used. Currently this can be either
    "makeindex" (by default) or "xindy".

  language <lang> =
    Choose the language used for sorting the index (xindy only).

  modules <mod> <mod> ... =
      Specify which modules xindy should use for the index.

  order <ordering> =
    Modify the ordering to be used (makeindex only, supported by xindy with
    warnings). The argument must be a space separated list of:
    - standard = use default ordering (no options, this is the default)
    - german = use German ordering (option "-g")
    - letter = use letter instead of word ordering (option "-l")

  path <directory> =
    Add the specified directory to the search path for styles.

  style <name> =
    Use the specified style file.

They all accept an optional argument first, enclosed in parentheses as in
"index.path (foo,bar) here/", to specify which index they apply to. Without
this argument, they apply to all indices declared at the point where they
occur.
"""

import os
from os.path import *
import re, string
import subprocess

from msg import _, msg
from plugins import TexModule
from util import md5_file

class Index(TexModule):
    """
    This class represents a single index.
    """
    def __init__ (self, doc, source, target, transcript):
        """
        Initialize the index, by specifying the source file (generated by
        LaTeX), the target file (the output of makeindex) and the transcript
        (e.g. .ilg) file.  Transcript is used by glosstex.py.
        """
        self.paranoid = True
        self.doc = doc
        self.pbase = doc.src_base
        self.source = doc.src_base + "." + source
        self.target = doc.src_base + "." + target
        self.transcript = doc.src_base + "." + transcript

        # In paranoid mode, can output only in current working dir
        if self.paranoid and (os.path.dirname(self.target) == os.getcwd()):
            self.target = os.path.basename(self.target)
            self.transcript = os.path.basename(self.transcript)

        if os.path.exists(self.source):
            self.md5 = md5_file(self.source)
        else:
            self.md5 = None

        self.tool = "makeindex"
        self.lang = None   # only for xindy
        self.modules = []  # only for xindy
        self.opts = []
        self.path = []
        self.style = None  # only for makeindex


    def do_language (self, lang):
        self.lang = lang

    def do_modules (self, *args):
        self.modules.extend(args)

    def do_order (self, *args):
        for opt in args:
            if opt == "standard": self.opts = []
            elif opt == "german": self.opts.append("-g")
            elif opt == "letter": self.opts.append("-l")
            else: msg.warn(
                _("unknown option '%s' for 'makeidx.order'") % opt)

    def do_path (self, path):
        self.path.append(self.doc.abspath(path))

    def do_style (self, style):
        self.style = style

    def do_tool (self, tool):
        if tool not in ("makeindex", "xindy"):
            msg.error(_("unknown indexing tool '%s'") % tool)
        self.tool = tool


    def post_compile (self):
        """
        Run makeindex if needed, with appropriate options and environment.
        """
        if not os.path.exists(self.source):
            msg.log(_("strange, there is no %s") % self.source, pkg="index")
            return 0
        if not self.run_needed():
            return 0

        msg.progress(_("processing index %s") % self.source)

        if self.tool == "makeindex":
            cmd = ["makeindex", "-o", self.target] + self.opts
            cmd.extend(["-t", self.transcript])
            if self.style:
                cmd.extend(["-s", self.style])
            cmd.append(self.source)
            path_var = "INDEXSTYLE"

        elif self.tool == "xindy":
            cmd = ["texindy", "--quiet"]
            for opt in self.opts:
                if opt == "-g":
                    if self.lang != "":
                        msg.warn(_("'language' overrides 'order german'"),
                            pkg="index")
                    else:
                        self.lang = "german-din"
                elif opt == "-l":
                    self.modules.append("letter-ordering")
                    msg.warn(_("use 'module letter-ordering' instead of 'order letter'"),
                        pkg="index")
                else:
                    msg.error("unknown option to xindy: %s" % opt, pkg="index")
            for mod in self.modules:
                cmd.extend(["--module", mod])
            if self.lang:
                cmd.extend(["--language", self.lang])
            cmd.append(self.source)
            path_var = "XINDY_SEARCHPATH"

        if self.path != []:
            env = { path_var:
                string.join(self.path + [os.getenv(path_var, "")], ":") }
        else:
            env = {}

        msg.debug(" ".join(cmd))
        # Makeindex outputs everything to stderr, even progress messages
        rc = subprocess.call(cmd, stderr=msg.stdout)
        if (rc != 0):
            msg.error(_("could not make index %s") % self.target)
            return 1

        # Beware with UTF-8 encoding, makeindex with headings can be messy
        if self.doc.encoding == "utf8" and self.style:
            f = file(self.target, "r")
            error = 0
            for line in f:
                try:
                    line.decode("utf8")
                except:
                    error = 1
                    break
            f.close()
            if error:
                print "here"
                # Retry without style
                msg.log(_("%s on UTF8 failed. Retry...") % self.tool)
                self.style = ""
                self.md5 = None
                return self.post_compile()

        self.doc.must_compile = 1
        return 0

    def run_needed (self):
        """
        Check if makeindex has to be run. This is the case either if the
        target file does not exist or if the source file has changed.
        """
        if os.path.getsize(self.source) == 0:
            msg.log(_("the index file %s is empty") % self.source, pkg="index")
            return 0
        new = md5_file(self.source)
        if not os.path.exists(self.target):
            self.md5 = new
            return 1
        if not self.md5:
            self.md5 = new
            msg.log(_("the index file %s is new") % self.source, pkg="index")
            return 1
        if self.md5 == new:
            msg.log(_("the index %s did not change") % self.source, pkg="index")
            return 0
        self.md5 = new
        msg.log(_("the index %s has changed") % self.source, pkg="index")
        return 1

    def clean (self):
        """
        Remove all generated files related to the index.
        """
        for file in self.source, self.target, self.transcript:
            if exists(file):
                msg.log(_("removing %s") % file, pkg="index")
                os.unlink(file)

re_newindex = re.compile(" *{(?P<idx>[^{}]*)} *{(?P<ind>[^{}]*)}")
re_optarg = re.compile("\((?P<list>[^()]*)\) *")

class Module (TexModule):
    def __init__ (self, doc, dict):
        """
        Initialize the module with no index.
        """
        self.doc = doc
        self.indices = {}
        self.defaults = []
        self.commands = {}
        doc.parser.add_hook("makeindex", self.makeindex)
        doc.parser.add_hook("newindex", self.newindex)

    def register (self, name, idx, ind, ilg):
        """
        Register a new index.
        """
        index = self.indices[name] = Index(self.doc, idx, ind, ilg)
        for cmd in self.defaults:
            index.command(*cmd)
        if self.commands.has_key(name):
            for cmd in self.commands[name]:
                index.command(*cmd)

    def makeindex (self, dict):
        """
        Register the standard index.
        """
        self.register("default", "idx", "ind", "ilg")

    def newindex (self, dict):
        """
        Register a new index.
        """
        m = re_newindex.match(dict["line"])
        if not m:
            return
        index = dict["arg"]
        d = m.groupdict()
        self.register(index, d["idx"], d["ind"], "ilg")
        msg.log(_("index %s registered") % index, pkg="index")

    def command (self, cmd, args):
        indices = self.indices
        names = None
        if len(args) > 0:
            m = re_optarg.match(args[0])
            if m:
                names = m.group("list").split(",")
                args = args[1:]
        if names is None:
            self.defaults.append([cmd, args])
            names = indices.keys()
        for index in names:
            if indices.has_key(index):
                indices[index].command(cmd, args[1:])
            elif self.commands.has_key(index):
                self.commands[index].append([cmd, args])
            else:
                self.commands[index] = [[cmd, args]]

    def post_compile (self):
        for index in self.indices.values():
            if index.post_compile():
                return 1
        return 0

    def clean (self):
        for index in self.indices.values():
            index.clean()
        return 0

