#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""

docbook2man.py (C-Munipack project)
Makes man files from a reference section of a docbook document
Copyright 2009 David Motl, dmotl@volny.cz

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""

import os
import sys
import time
import xml.dom.minidom

def textAppend(dst, src):
	""" Concatenate two strings """
	if len(src)==0:
		return dst
	elif len(dst)==0:
		return src
	else:
		return dst + src

def getData(node):
	""" Reads all CDATA from specified node and puts
		then to a single string """
	retval = ""
	for child in node.childNodes:
		if child.nodeType==xml.dom.Node.TEXT_NODE:
			text = child.data.replace("'", "\\'")
			text = text.replace("\"", "\\\"")
			text = text.replace("-", "\\-")
			index = text.find("\n")
			while index>=0:
				line = text[0:index].rstrip()
				retval = textAppend(retval, line) + " "
				text = text[index+1:].lstrip()
				index = text.find("\n")
			retval = textAppend(retval, text)
		if child.nodeType==xml.dom.Node.ELEMENT_NODE:
			if child.tagName == "application" or \
					child.tagName == "command" or \
					child.tagName == "userinput" or \
					child.tagName == "symbol" or \
					child.tagName == "varname" or \
					child.tagName == "refentrytitle" or \
					child.tagName == "literal" or \
					child.tagName == "filename":
				retval = textAppend(retval, "\\fB" + getData(child) + "\\fR")
			elif child.tagName == "option" or \
					child.tagName == "replaceable":
				retval = textAppend(retval, "\\fI" + getData(child) + "\\fR")
			elif child.tagName == "link" or \
					child.tagName == "person" or \
					child.tagName == "personname" or \
					child.tagName == "firstname" or \
					child.tagName == "surname" or \
					child.tagName == "citerefentry" or \
					child.tagName == "email":
				retval = textAppend(retval, getData(child))
			elif child.tagName == "manvolnum":
				retval = textAppend(retval, "(" + getData(child) + ")")
			else:
				print("Warning: Unknown element: '"+child.tagName+"'\n")
	return retval

def getElement(node, elementName):
	""" Get element node """
	for elem in node.childNodes:
		if elem.nodeType==xml.dom.Node.ELEMENT_NODE:
			if elem.tagName == elementName:
				return elem
	return None

def getElementData(node, elementName):
	""" Get CDATA for element specified by its name """
	elem = getElement(node, elementName)
	if elem!=None:
		return getData(elem)
	return ""

def processName(node, f):
	""" Process command name
		.SH NAME
		command \- purpose
	"""
	refname = getElementData(node, "refname")
	purpose = getElementData(node, "refpurpose")
	if len(refname)>0:
		f.write(".SH NAME\n")
		f.write(refname+" \\- "+purpose+"\n")
	return 0

def processSynopsis(node, f):
	""" Process command synopsis
		.SH SYNOPSIS
		.B airmass \fI[options] \fIsource_file ...\fR
	"""
	first = 1
	for cmdsynopsis in node.childNodes:
		if cmdsynopsis.nodeType==xml.dom.Node.ELEMENT_NODE:
			if cmdsynopsis.tagName == "cmdsynopsis":
				command = getElementData(cmdsynopsis, "command")
				if first == 1:
					f.write(".SH SYNOPSIS\n")
				else:
					f.write(".br\n")
				f.write(".B "+command)
				for arg in cmdsynopsis.childNodes:
					if arg.nodeType==xml.dom.Node.ELEMENT_NODE:
						if arg.tagName == "arg":
							text = getData(arg)
							repeat = arg.getAttribute("rep") == "repeat"
							optional = arg.getAttribute("choice") != "req"
							if not optional:
								if not repeat:
									f.write(" "+text+"")
								else:
									f.write(" "+text+" ...")
							else:
								if not repeat:
									f.write(" [ "+text+" ]")
								else:
									f.write(" [ "+text+" ... ]")
				f.write("\n")
				first = 0
	return 0

def processPara(node, f):
	""" Process paragraph text"""
	f.write(getData(node).rstrip().lstrip())
	f.write("\n")
	return 0

def processOption(node, f):
	""" Process command line options
		.TP
		.B -abbrev, --parameter_name value
		description
	"""
	f.write(".TP\n")
	f.write(".B "+getElementData(node, "title")+"\n")
	first = 1
	for child in node.childNodes:
		if child.nodeType==xml.dom.Node.ELEMENT_NODE:
			if child.tagName=="para":
				if first == 0:
					f.write(".PP\n")
				first = 0
				processPara(child, f)
	return 0

def processParameter(node, f):
	""" Process configuration file parameters
		.TP
		.B parameter = value
		description
	"""
	f.write(".TP\n")
	f.write(".B "+getElementData(node, "title")+"\n")
	first = 1
	for child in node.childNodes:
		if child.nodeType==xml.dom.Node.ELEMENT_NODE:
			if child.tagName=="para":
				if first == 0:
					f.write(".PP\n")
				first = 0
				processPara(child, f)
	return 0

def processExample(node, f):
	""" Process examples
		.TP
		.B title
		description
	"""
	f.write(".TP\n")
	f.write(".B "+getElementData(node, "title")+"\n")
	first = 1
	for child in node.childNodes:
		if child.nodeType==xml.dom.Node.ELEMENT_NODE:
			if child.tagName=="para":
				if first == 0:
					f.write(".PP\n")
				first = 0
				processPara(child, f)
	return 0

def processContent(node, title, first, f):
	for child in node.childNodes:
		if child.nodeType==xml.dom.Node.ELEMENT_NODE:
			if child.tagName=="para":
				if first == 0:
					f.write(".PP\n")
				first = 0
				processPara(child, f)
			elif child.tagName=="title":
			    pass
			elif child.tagName=="refsect2":
				if title.upper()=="OPTIONS":
					processOption(child, f)
				elif title.upper()=="CONFIGURATION FILE":
					processParameter(child, f)
				elif title.upper()=="EXAMPLES":
					processExample(child, f)
	return first

def processSection(node, preface, f):
	""" Process section
		.SH title
		text
	"""
	first = True
	title = getElementData(node, "title")
	label = node.getAttribute("label")
	f.write(".SH "+title.upper()+"\n")
	# Common information
	for sect1 in preface.getElementsByTagName("sect1"):
		t2 = sect1.getAttribute("label")
		if label == t2:
		    first  = processContent(sect1, title, first, f)
		    break
	# Command specific data
	processContent(node, title, first, f)
	return 0

def processRefEntry(refentry, info, preface, seealso, output_dir):
	""" Make man page for a reference entry """
	files = []
	refmeta = getElement(refentry, "refmeta")
	title = getElementData(refmeta, "refentrytitle")
	manvol = getElementData(refmeta, "manvolnum")
	package = getElementData(info, "title")
	version = getElementData(info, "edition")
	if len(package)>0 and len(version)>0 and len(title)>0 and len(manvol)>0:
		basename = title + "." + manvol
		f = open(output_dir + "/" + basename, "w")
		# File header
		# .TH airmass 1  "June 7, 2008" "C-Munipack 1.2" "C-Munipack Toolkit"
		f.write(".TH "+title+" "+manvol+" ")
		f.write("\"" + time.strftime("%B %d, %Y", time.localtime()) + "\" ")
		f.write("\"version "+version+"\" \""+package+"\"\n")
		# Command name
		refnamediv = getElement(refentry, "refnamediv")
		if refnamediv!=None:
			processName(refnamediv, f)
		# Command synopsis
		refsynopsisdiv = getElement(refentry, "refsynopsisdiv")
		if refsynopsisdiv!=None:
			processSynopsis(refsynopsisdiv, f)
		# Reference sections
		for refsect1 in refentry.getElementsByTagName("refsect1"):
		    processSection(refsect1, preface, f)
		# See also section
		f.write(".SH SEE ALSO\n")
		first = True
		for entry in seealso:
			e_title, e_manvol = entry
			if e_title!=title or e_manvol!=manvol:
				if not first:
					f.write(", ")
				f.write("\\fB" + e_title + "\\fR(" + e_manvol + ")")
				first = False
		f.write("\n")
		f.close()
		files.append(basename)
        # Check that all required sections are present
		for sect1 in preface.getElementsByTagName("sect1"):
		    t2 = sect1.getAttribute("label")
		    if (sect1.getAttribute("type") == "required"):
    		     found = False
    		     for refsect1 in refentry.getElementsByTagName("refsect1"):
    		         t1 = refsect1.getAttribute("label")
    		         if (t1==t2):
    		             found = True
    		             break
    		     if not found:
    		         print("Warning: Required section '"+t2+"' not found for command '" + title + "'.")
	return files

def makeSeeAlsoEntry(refentry):
	""" Reads a single reference entry and returns tuple (title, manvol) """
	refmeta = getElement(refentry, "refmeta")
	if refmeta!=None:
		title = getElementData(refmeta, "refentrytitle")
		manvol = getElementData(refmeta, "manvolnum")
		if len(title)>0 and len(manvol)>0:
			return title, manvol
	return None

# Main function
def make_manpages(docbook, output_dir):
    """ Reads the XML file and makes man pages """
    files = []
    # Get info node
    info = None
    for node in docbook.documentElement.getElementsByTagName("info"):
        info = node
        break
    if (info == None):
        exit("Missing required 'info' node.")
    # Get reference node
    reference = None
    for node in docbook.documentElement.getElementsByTagName("reference"):
        reference = node
        break
    if (reference == None):
        exit("Missing required 'reference' node.")
    # Get preface node
    preface = None
    for node in docbook.documentElement.getElementsByTagName("preface"):
        preface = node
        break
    if (info == None):
        exit("Missing required 'preface' node.")
	# Make list of commands (for automatically generated "See also" sections)
    seealso = list()
    for refentry in reference.getElementsByTagName("refentry"):
        entry = makeSeeAlsoEntry(refentry)
        if entry != None:
            seealso.append(entry)
    # Process reference entries and make output files
    for refentry in reference.getElementsByTagName("refentry"):
        f = processRefEntry(refentry, info, preface, seealso, output_dir)
        if f!=None:
            files = files + f
    return files

if __name__ == '__main__':
    source_dir = sys.argv[1]
    output_dir = sys.argv[2]
    # Open source file
    source_file = os.path.join(source_dir, "manpages.xml")
    source = xml.dom.minidom.parse(source_file)
    if source == None:
        exit("Source file '"+source_file+"' not found.")
    # Check output directory
    try:
    	os.mkdir(output_dir)
    except OSError:
    	pass
    # Make man files
    files = make_manpages(source, output_dir)
    # Finished
    source.unlink()
