#!/usr/bin/env python
# PYTHON 2.X REQURIED
from StringIO import StringIO
import sys,re,os
from time import asctime
import getopt
import string

'''tex-monolith by Emily Dumas: Strip comments and expand include files in tex/latex'''

_TEX_MONOLITH_VERSION = '0.0.1'

# no longer used
def CommentStrip(s, output = None):
    try:
        s + ' '
        input = StringIO(s)
    except TypeError:
        input = s
    if not output:
        output = StringIO()
    
    for l in input:
        if (l[0] <> '%'):
            output.write(l)
    return output.getvalue()

INPUT_RE = re.compile(r'\\input\{+(?P<fn>[^\{\}]+)\}')
GPATH_RE = re.compile(r'\\graphicspath\{+(?P<path>.+)\}')
GRAPHICS_RE = re.compile(r'\\(?:includegraphics|psfig|epsfig)(?:\[.*\])*\{+(?P<fn>[^\{\}]+)\}')
BIBSTYLE_RE = re.compile(r'\\bibliographystyle\{(?P<bst>[^\{\}]+)\}')
BIBFILE_RE = re.compile(r'\\bibliography\{(?P<fn>[^\{\}]+)\}')
COMMENT_RE = re.compile(r'^\s*%.*')
END_RE = re.compile('\\end\{document\}')

class expander(StringIO):
    def __init__(self,input=None,rootfn=None,mark_include=False,mark_bib=False,strip=True,stamp=False):
        StringIO.__init__(self)
        self.mark_include = mark_include
        self.mark_bib = mark_bib
        self.stamp = stamp
        self.strip = strip
        self.gpaths = [',']
        self.files = []
        self.graphics = []
        self.bibstyle = None
        self.input_extns = ['.tex','.inc']
        self.rootfn = rootfn
        self.dispatch = [ (COMMENT_RE, self.comment_handler),
                          (GRAPHICS_RE, self.graphics_handler),
                          (GPATH_RE, self.gpath_handler),
                          (BIBSTYLE_RE, self.bibstyle_handler),
                          (BIBFILE_RE, self.bibfile_handler),
                          (END_RE, self.end_handler) ]
        if input != None:
            self.process(input)

    def gpath_handler(self,l,m):
        self.gpaths.append(m.group('path'))

    def graphics_handler(self,l,m):
        self.graphics.append(m.group('fn'))
        self.write(l)

    def bibstyle_handler(self,l,m):
        self.bibstyle = m.group('bst')

    def bibfile_handler(self,l,m):
        fn = os.path.join(os.path.dirname(self.rootfn),string.join(os.path.basename(self.rootfn).split(".")[:-1],".")) + '.bbl' 
        if os.path.exists(fn):
            f = self.openfile(fn)
            if self.mark_bib:
                self.write('%% -- BEGIN BIBLIOGRAPHY --\n')
            self.process(f)
            if self.mark_bib:
                self.write('%% -- END BIBLIOGRAPHY --\n')

    def comment_handler(self,l,m):
        if not self.strip:
            self.write(l)

    def end_handler(self,l,m):
        self.write(l)
        if self.stamp:
            self.write('\n%% tex-monolith processed %s\n' % asctime())

    def openfile(self,fn):
        if os.path.exists(fn):
            self.files.append(fn)
            return file(fn,'rt')
        else:
            for x in self.input_extns:
                if os.path.exists(fn + x):
                    self.files.append(fn+x)
                    return file(fn+x,'rt')
            raise IOError, 'Unable to open "%s"' % fn

    def process(self,stream):
        for l in stream:
            m = INPUT_RE.search(l)
            if m:
                fnbase = m.group(1)
                f = self.openfile(fnbase)
                if f:
                    if self.mark_include:
                        self.write('%% -- BEGIN "%s" --\n' % fnbase)
                    self.process(f)
                    if self.mark_include:
                        self.write('%% -- END "%s" --\n' % fnbase)
            else:
                matched = False
                for r,h in self.dispatch:
                    m = r.search(l)
                    if m:
                        matched = True
                        h(l,m)
                        break
                if not matched:
                    self.write(l)

    def report(self,stream):
        stream.write('Graphics files:\n')
        for f in self.graphics:
            stream.write('\t'+f+'\n')
        if self.bibstyle:
            stream.write('Bibliography style:\n\t%s\n' % self.bibstyle)
        

def usage():
    sys.stderr.write('''
USAGE: tex-monolith [options] INPUT

Read tex file from INPUT, write as monolithic tex file to STDOUT.
A monolithic tex file is one in which all \includes have been
replaced by the contents of the files they include, and a bibtex
bibliography has been replaced by the tex source in the associated
.bbl file.  By default, comments are also stripped.

Options:
    -h or --help
        Display this help message.

    -m or --mark
        Add comments to the output file marking boundaries between
        different input files.

''')
       

def main():
    mark = False
    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], "hm",
                                       ["help", "mark"])
    except getopt.GetoptError,e:
        sys.stderr.write("ERROR: "+str(e)+"\n")
        usage()
        sys.exit(2)

    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-m","--mark"):
            mark = True

    if len(args) == 0:
        sys.stderr.write("ERROR: no input file specified.\n")
        usage()
        sys.exit(2)

    if len(args) > 1:
        sys.stderr.write("ERROR: multiple input files specified.\n")
        usage()
        sys.exit(2)

    fn = args[0]
    if not os.path.exists(fn):
        sys.stderr.write("ERROR: \"%s\" not found." % fn)
        sys.exit(127)

    e = expander(open(fn),rootfn=fn,stamp=True,mark_include=mark,mark_bib=mark)
    sys.stdout.write(e.getvalue())
    e.report(sys.stderr)
        
if __name__=='__main__':
    main()
