# This file is part of Rubber and thus covered by the GPL
# (c) Emmanuel Beffara, 2002--2006
"""
This is the command line interface for Rubber.
"""

import sys
import os.path
import string
from getopt import *

from rubber import _, msg
from rubber import *
from rubber.version import *
from rubber.util import parse_line

class Main (object):
	def __init__ (self):
		self.max_errors = 10
		self.include_only = None
		self.path = []
		msg.write = self.stderr_write

	def stderr_write (self, text, level=0):
		sys.stderr.write(text + "\n")

	def short_help (self):
		"""
		Display a short description of the command line.
		"""
		msg(0, _("""\
usage: rubber [options] sources...
For more information, try `rubber --help'."""))

	def help (self):
		"""
		Display the description of all the options and exit.
		"""
		print _("""\
This is Rubber version %s.
usage: rubber [options] sources...
available options:
      --cache              use the (experimental) caching mechanism
      --clean              remove produced files instead of compiling
  -c, --command=CMD        run the directive CMD before parsing (see man page)
  -e, --epilogue=CMD       run the directive CMD after parsing
  -f, --force              force at least one compilation
  -z, --gzip               compress the final document
  -h, --help               display this help
      --inplace            compile the documents from their source directory
      --into=DIR           go to directory DIR before compiling
  -l, --landscape          change paper orientation (if relevant)
  -n, --maxerr=NUM         display at most NUM errors (default: 10)
  -m, --module=MOD[:OPTS]  use module MOD (with options OPTS)
      --only=SOURCES       only include the specified SOURCES
  -o, --post=MOD[:OPTS]    postprocess with module MOD (with options OPTS)
  -d, --pdf                produce a pdf (synonym for -m pdftex or -o ps2pdf)
  -p, --ps                 process through dvips (synonym for -o dvips)
  -q, --quiet              suppress messages
  -r, --read=FILE          read additional directives from FILE
  -s, --short              display errors in a compact form
  -I, --texpath=DIR        add DIR to the search path for LaTeX
  -v, --verbose            increase verbosity
      --version            print version information and exit
  -W, --warn=TYPE          report warnings of the given TYPE (see man page)\
""") % version

	def parse_opts (self, cmdline, short="", long=[]):
		try:
			opts, args = getopt(
				cmdline, "I:c:de:fhklm:n:o:pqr:svW:z" + short,
				["cache", "clean", "command=", "epilogue=", "force", "gzip",
				 "help", "inplace", "into=", "keep", "landcape", "maxerr=",
				 "module=", "only=", "post=", "pdf", "ps", "quiet", "read=",
				 "short", "texpath=", "verbose", "version", "warn="] + long)
		except GetoptError, e:
			print e
			sys.exit(1)

		extra = []
		using_dvips = 0
		gzip_final = 0

		for (opt,arg) in opts:
			if opt == "--cache":
				self.cache = 1
			elif opt == "--clean":
				self.clean = 1
			elif opt in ("-c", "--command"):
				self.prologue.append(arg)
			elif opt in ("-e", "--epilogue"):
				self.epilogue.append(arg)
			elif opt in ("-f", "--force"):
				self.force = 1
			elif opt in ("-z", "--gzip"):
				gzip_final = 1
			elif opt in ("-h", "--help"):
				self.help()
				sys.exit(0)
			elif opt == "--inplace":
				self.place = None
			elif opt == "--into":
				self.place = arg
			elif opt in ("-k", "--keep"):
				self.clean = 0
			elif opt in ("-l", "--landscape"):
				self.prologue.append("paper landscape")
			elif opt in ("-n", "--maxerr"):
				self.max_errors = int(arg)
			elif opt in ("-m", "--module"):
				self.prologue.append("module " +
					string.replace(arg, ":", " ", 1))
			elif opt == "--only":
				self.include_only = arg.split(",")
			elif opt in ("-o", "--post"):
				self.epilogue.append("module " +
					string.replace(arg, ":", " ", 1))
			elif opt in ("-d", "--pdf"):
				if using_dvips:
					self.epilogue.append("module ps2pdf")
				else:
					self.prologue.append("module pdftex")
			elif opt in ("-p", "--ps"):
				self.epilogue.append("module dvips")
				using_dvips = 1
			elif opt in ("-q", "--quiet"):
				msg.level = msg.level - 1
			elif opt in ("-r" ,"--read"):
				self.prologue.append("read " + arg)
			elif opt in ("-s", "--short"):
				msg.short = 1
			elif opt in ("-I", "--texpath"):
				self.path.append(arg)
			elif opt in ("-v", "--verbose"):
				msg.level = msg.level + 1
			elif opt == "--version":
				print "Rubber version: " + version
				print "module path: " + moddir
				sys.exit(0)
			elif opt in ("-W", "--warn"):
				self.warn = 1
				if arg == "all":
					self.warn_boxes = 1
					self.warn_misc = 1
					self.warn_refs = 1
				if arg == "boxes":
					self.warn_boxes = 1
				elif arg == "misc":
					self.warn_misc = 1
				elif arg == "refs":
					self.warn_refs = 1

			elif arg == "":
				extra.append(opt)
			else:
				extra.extend([arg, opt])

		if gzip_final:
			self.epilogue.append("module gz")
		return extra + args

	def main (self, cmdline):
		"""
		Run Rubber for the specified command line. This processes each
		specified source in order (for making or cleaning). If an error
		happens while making one of the documents, the whole process stops.
		The method returns the program's exit code.
		"""
		self.prologue = []
		self.epilogue = []
		self.cache = 0
		self.clean = 0
		self.force = 0

		self.warn = 0
		self.warn_boxes = 0
		self.warn_misc = 0
		self.warn_refs = 0

		self.place = "."

		args = self.parse_opts(cmdline)

		initial_dir = os.getcwd()
		msg.cwd = os.path.join(initial_dir, "")

		if self.place != "." and self.place is not None:
			msg.path = self.place
			self.place = os.path.abspath(self.place)

		msg.log(_("This is Rubber version %s.") % version)

		for srcname in args:
			src = os.path.abspath(os.path.join(initial_dir, srcname))

			# Go to the appropriate directory

			if self.place != ".":
				if self.place is None:
					msg.path = os.path.dirname(src)
					os.chdir(os.path.dirname(src))
					src = os.path.basename(src)
				else:
					os.chdir(self.place)

			# Check the source and prepare it for processing
	
			env = Environment()
			if self.cache:
				env.cache_activate()

			if env.set_source(src):
				msg.error(_("cannot find %s") % src)
				return 1

			if self.include_only is not None:
				env.main.includeonly(self.include_only)

			if self.clean:
				if env.main.prods == []:
					msg.warn(_("there is no LaTeX source for %s") % srcname)
					continue
			else:
				env.make_source()

			env.main.push_vars(cwd = initial_dir)
			for dir in self.path:
				env.main.do_path(dir)
			for cmd in self.prologue:
				cmd = parse_line(cmd, env.main.vars)
				env.main.command(cmd[0], cmd[1:], {'file': 'command line'})
			env.main.pop_vars()

			env.main.parse()

			env.main.push_vars(cwd = initial_dir)
			for cmd in self.epilogue:
				cmd = parse_line(cmd, env.main.vars)
				env.main.command(cmd[0], cmd[1:], {'file': 'command line'})
			env.main.pop_vars()

			if self.cache:
				env.cache_dump()

			# Compile the document

			if self.clean:
				env.final.clean()
				continue

			if self.force:
				ret = env.main.make(1)
				if ret != 0 and env.final is not env.main:
					ret = env.final.make()
				else:
					# This is a hack for the call to get_errors() below
					# to work when compiling failed when using -f.
					env.final.failed_dep = env.main.failed_dep
			else:
				ret = env.final.make(self.force)

			if ret == 0:
				msg.info(_("There were errors compiling %s.") % srcname)
				number = self.max_errors
				for err in env.final.failed().get_errors():
					if number == 0:
						msg.info(_("More errors."))
						break
					msg.display(**err)
					number -= 1
				return 1

			if ret == 1:
				msg(1, _("nothing to be done for %s") % srcname)

			if self.warn:
				log = env.main.log
				if log.read(env.main.src_base + ".log"):
					msg.error(_("cannot read the log file"))
					return 1
				msg.display_all(log.parse(boxes=self.warn_boxes,
					refs=self.warn_refs, warnings=self.warn_misc))

		return 0

	def __call__ (self, cmdline):
		"""
		This method is a wrapper around the main method, showing a short help
		message when the command line is empty, and catching the keyboard
		interruption signal.
		"""
		if cmdline == []:
			self.short_help()
			return 1
		try:
			return self.main(cmdline)
		except KeyboardInterrupt:
			msg(0, _("*** interrupted"))
			return 2
