# This file is part of Rubber and thus covered by the GPL
# (c) Emmanuel Beffara, 2004--2006
# vim: noet:ts=4
"""
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, os.path, re

from rubber.util import _
from rubber.util import *

class Index:
	"""
	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.doc = doc
		self.source = doc.target + "." + source
		self.target = doc.target + "." + target
		self.transcript = doc.target + "." + transcript
		self.md5 = md5_file(self.source)

		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 True
		if not self.run_needed():
			return True

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

		if self.tool == "makeindex":
			cmd = ["makeindex", "-q", "-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: ':'.join(self.path + [os.getenv(path_var, '')]) }
		else:
			env = {}
		if self.doc.env.execute(cmd, env):
			msg.error(_("could not make index %s") % self.target)
			return False

		self.doc.must_compile = 1
		return True

	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 not os.path.exists(self.target):
			self.md5 = md5_file(self.source)
			return 1
		if not self.md5:
			self.md5 = md5_file(self.source)
			msg.log(_("the index file %s is new") % self.source, pkg="index")
			return 1
		new = md5_file(self.source)
		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 os.path.exists(file):
				msg.log(_("removing %s") % file, pkg="index")
				os.unlink(file)

re_optarg = re.compile(r'\((?P<list>[^()]*)\) *')

def setup (document, context):
	global doc, indices, defaults, commands
	doc = document
	indices = {}
	defaults = []
	commands = {}
	doc.hook_macro('makeindex', '', hook_makeindex)
	doc.hook_macro('newindex', 'aaa', hook_newindex)

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

def hook_makeindex (loc):
	register('default', 'idx', 'ind', 'ilg')

def hook_newindex (loc, index, idx, ind):
	register(index, idx, ind, 'ilg')
	msg.log(_("index %s registered") % index, pkg='index')

def command (cmd, args):
	names = None

	# Check if there is the optional argument.

	if len(args) > 0:
		match = re_optarg.match(args[0])
		if match:
			names = match.group('list').split(',')
			args = args[1:]

	# If not, this command will also be executed for newly created indices
	# later on.

	if names is None:
		defaults.append([cmd, args])
		names = indices.keys()

	# Then run the command for each index it concerns.

	for name in names:
		if name in indices:
			indices[name].command(cmd, args)
		elif name in commands:
			commands[name].append([cmd, args])
		else:
			commands[name] = [[cmd, args]]

def post_compile ():
	for index in indices.values():
		if not index.post_compile():
			return False
	return True

def clean ():
	for index in indices.values():
		index.clean()
