task — scifig 0.1 documentation

# Source code for task

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

    """
    A :class:`Task` is composed of the following steps:

        1. Transform the source files to a tex file
        2. convert tex to pdf with :func:`_tex_to_pdf()`
        3. convert pdf to svg with :func:`_pdf_to_svg()`
        4. convert pdf to eps with :func:`_pdf_to_eps()`
        5. convert pdf to png with :func:`_pdf_to_png()`

    Step 1 can be complex and could require several sub-steps.
    Thus, the role of :func:`_pre_make()` is to do all these sub-steps.

    Steps 2 to 5 usually do not depend on the initial type of the task.
    The function :func:`make()` do all of them.

    Each format has its own export function. The function :func:`export()`
    exports all of them.

    """

    import os
    import os.path
    import shutil
    import subprocess
    import logging
    import re

    from libscifig import database as dblib

    class Task():
        """
        Parent Task manager.

        :param filepath: filepath of the main file
        :param build: relative filepath of the build dir
        :param db: relative filepath of the db file
        """
        def __init__(self, filepath, build='build', db='db.db'):
            self.db = db
            self.cwd = os.getcwd()
            self.id = 'ID:' + os.path.relpath(filepath)
            self.dependencies = []
            self.dependencies.append(filepath)
            self.dirname, filename = os.path.split(filepath)
            self.name = os.path.splitext(filename)[0]
            self.buildpath = os.path.join(build, os.path.relpath(self.dirname))
            self.pdfmaker = '/usr/bin/pdflatex'
            self.svgmaker = '/usr/bin/pdf2svg'
            self.epsmaker = '/usr/bin/pdftops'
            self.pngmaker = '/usr/bin/gs'

        def get_name(self):
            """
            Return the name of the task.
            """
            return self.id

        def check(self):
            """
            Check if the task needs to be done.
            """
            return dblib.check_modification(self.id, self.dependencies, self.db)

        def _tex_to_pdf(self):
            """
            Convert tex to pdf.
            """
            logging.info('tex -> pdf')
            # Prepare and run the command
            command = [self.pdfmaker, self.name + '.tex']
            # in plt, all path are relative, need to move
            logging.debug('chdir: %s' % self.buildpath)
            os.chdir(self.buildpath)
            logging.debug('Command: %s' % command)
            process = subprocess.Popen(command, stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # go back to the cur dir
            os.chdir(self.cwd)

            stdout, stderr = process.communicate()
            logging.debug(stdout.decode())
            errors = stderr.decode()
            if errors:
                logging.error(errors)  # TODO color

            self.pdf = self.name + '.pdf'

        def _pdf_to_svg(self):
            """
            Convert pdf to svg.
            """
            logging.info('pdf -> svg')
            self.svg = self.name + '.svg'
            # Prepare and run the command
            command = [self.svgmaker, self.name + '.pdf', self.svg]
            # in plt, all path are relative, need to move
            logging.debug('chdir: %s' % self.buildpath)
            os.chdir(self.buildpath)
            logging.debug('Command: %s' % command)
            process = subprocess.Popen(command, stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # go back to the cur dir
            os.chdir(self.cwd)

            stdout, stderr = process.communicate()
            logging.debug(stdout.decode())
            errors = stderr.decode()
            if errors:
                logging.error(errors)  # TODO color

        def _pdf_to_eps(self):
            """
            Convert pdf to eps.
            """
            logging.info('pdf -> eps')
            self.eps = self.name + '.eps'
            # Prepare and run the command
            command = [self.epsmaker, '-eps', self.name + '.pdf', self.eps]
            # in plt, all path are relative, need to move
            logging.debug('chdir: %s' % self.buildpath)
            os.chdir(self.buildpath)
            logging.debug('Command: %s' % command)
            process = subprocess.Popen(command, stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # go back to the cur dir
            os.chdir(self.cwd)

            stdout, stderr = process.communicate()
            logging.debug(stdout.decode())
            errors = stderr.decode()
            if errors:
                logging.error(errors)  # TODO color

        # TODO sDEVICE: pngalpha and pnggray
        def _pdf_to_png(self, dpi=600):
            """
            Convert pdf to png.
            """
            logging.info('pdf -> png')
            self.png = self.name + '.png'
            # Prepare and run the command
            command = [self.pngmaker, '-sDEVICE=png16m', '-o',
                       self.png, '-r' + str(dpi), self.name + '.pdf']
            # in plt, all path are relative, need to move
            logging.debug('chdir: %s' % self.buildpath)
            os.chdir(self.buildpath)
            logging.debug('Command: %s' % command)
            process = subprocess.Popen(command, stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # go back to the cur dir
            os.chdir(self.cwd)

            stdout, stderr = process.communicate()
            logging.debug(stdout.decode())
            errors = stderr.decode()
            if errors:
                logging.error(errors)  # TODO color

        def _pre_make(self):
            """
            make a tex file.
            """
            logging.debug('Default pre_make() in class Task, nothing to do!')
            pass

        def make(self):
            """
            Compile the figure in all formats.
            """
            self._pre_make()
            # Build a pdf
            self._tex_to_pdf()
            self._pdf_to_svg()
            self._pdf_to_eps()
            self._pdf_to_png()
            dblib.store_checksum(self.id, self.dependencies, self.db)

        def export_tex(self, dst='/tmp'):
            """
            Export TEX files.

            :param dst: filepath of the destination directory
            """
            dst = os.path.expanduser(dst)
            tex_src = self.tex
            logging.debug('Export %s to %s' % (tex_src, dst))
            shutil.copy(tex_src, dst)

        def export_pdf(self, dst='/tmp'):
            """
            Export built PDF files.

            :param dst: filepath of the destination directory
            """
            dst = os.path.expanduser(dst)
            pdf_src = os.path.join(self.buildpath, self.pdf)
            logging.debug('Export %s to %s' % (pdf_src, dst))
            shutil.copy(pdf_src, dst)

        def export_svg(self, dst='/tmp'):
            """
            Export built SVG files.

            :param dst: filepath of the destination directory
            """
            dst = os.path.expanduser(dst)
            svg_src = os.path.join(self.buildpath, self.svg)
            logging.debug('Export %s to %s' % (svg_src, dst))
            shutil.copy(svg_src, dst)

        def export_eps(self, dst='/tmp'):
            """
            Export built EPS files.

            :param dst: filepath of the destination directory
            """
            dst = os.path.expanduser(dst)
            eps_src = os.path.join(self.buildpath, self.eps)
            logging.debug('Export %s to %s' % (eps_src, dst))
            shutil.copy(eps_src, dst)

        def export_png(self, dst='/tmp'):
            """
            Export built png files.

            :param dst: filepath of the destination directory
            """
            dst = os.path.expanduser(dst)
            png_src = os.path.join(self.buildpath, self.png)
            logging.debug('Export %s to %s' % (png_src, dst))
            shutil.copy(png_src, dst)

        def export(self, dst='/tmp'):
            """
            Export built files.

            :param dst: filepath of the destination directory
            """
            for ext, func in (('tex', self.export_tex),
                              ('pdf', self.export_pdf),
                              ('svg', self.export_svg),
                              ('eps', self.export_eps),
                              ('png', self.export_png),):

                path = os.path.join(dst, ext)
                os.makedirs(path, exist_ok=True)
                func(path)

    class TikzTask(Task):
        """
        Tikz Task manager.
        """
        def __init__(self, filepath, datafiles=[],
                     build='build', db='db.db'):
            Task.__init__(self, filepath, build=build)
            self.data = datafiles
            self.dependencies.extend(datafiles)
            self.tex = os.path.join(build, self.dirname, self.name + '.tex')
            self.tikz = filepath

        def _tikz_to_tex(self):
            """
            Convert tikz to tex.

            :raises: SyntaxError
            """
            # Copy data files
            for data in self.data:
                # Data starts from the root.
                # We need the relative path from the individual directory
                # (ex: src/figure/)
                dest = os.path.join(self.buildpath, os.path.relpath(data,
                                    start=os.path.split(self.tikz)[0]))
                # Data may be in subdirectories
                # We reproduce the tree
                os.makedirs(os.path.split(dest)[0], exist_ok=True)
                logging.debug('copy %s file to %s' % (data, dest))
                shutil.copy(data, dest)
            logging.info('tikz -> tex')
            tex_content = '\\documentclass{standalone}\n\n'
            tex_content += '\\usepackage{gnuplot-lua-tikz}\n'
            tex_content += """\\usepackage{tikz}
    \\usepackage{amssymb}
    \\usepackage{amsfonts}
    \\usepackage{mathrsfs}
    \\usepackage{amsmath}
    \\usepackage[amssymb]{SIunits}\n
    """

            # tikz contains 2 parts
            # above \begin{tikzpicture} -> extra libs...
            # and bellow (code...)
            # For a correct rendering, the top part
            # must be before \begin{document}
            with open(self.tikz, 'r') as fh:
                tikz_content = fh.read()
            tikz_content = tikz_content.split("\\begin{tikzpicture}")
            tex_content += tikz_content[0]
            tex_content += "\\begin{document}\n"
            tex_content += "\\begin{tikzpicture}"
            try:
                tex_content += tikz_content[1]
            except IndexError:
                # The file does not contain tikzpicture
                raise SyntaxError('The file %s does not contain \\begin{tikzpicture}' % self.tikz)
            tex_content += '\\end{document}'

            with open(self.tex, 'w') as fh:
                fh.write(tex_content)

        def _pre_make(self):
            """
            make a tex file.
            """
            logging.debug('pre_make(): Tikz file %s' % self.tikz)
            # Make build path
            logging.debug('pre_make build path %s' % self.buildpath)
            os.makedirs(self.buildpath, exist_ok=True)
            # First convert tikz to tex
            self._tikz_to_tex()

    class GnuplotTask(Task):
        """
        Gnuplot Task manager.
        """
        def __init__(self, filepath, datafiles=[], tikzsnippet=False,
                     tikzsnippet1=False, tikzsnippet2=False,
                     build='build', db='db.db'):
            Task.__init__(self, filepath, build=build)
            self.plt = filepath

            self.data = datafiles
            self.dependencies.extend(datafiles)
            self.pltcopy = os.path.join(build, self.dirname, self.name + '.plt')
            self.tex = os.path.join(build, self.dirname, self.name + '.tex')
            self.plttikz = os.path.join(build, self.dirname, self.name + '.plttikz')
            self.tikzsnippet = tikzsnippet
            self.tikzsnippet1 = tikzsnippet1
            self.tikzsnippet2 = tikzsnippet2
            if tikzsnippet:
                self.snippetfile = os.path.join(self.dirname, self.name + '.tikzsnippet')
                logging.debug('Append dependency: %s' % self.snippetfile)
                self.dependencies.append(self.snippetfile)
            if tikzsnippet1:
                self.snippet1file = os.path.join(self.dirname, self.name + '.tikzsnippet1')
                logging.debug('Append dependency: %s' % self.snippet1file)
                self.dependencies.append(self.snippet1file)
            if tikzsnippet2:
                self.snippet2file = os.path.join(self.dirname, self.name + '.tikzsnippet2')
                logging.debug('Append dependency: %s' % self.snippet2file)
                self.dependencies.append(self.snippet2file)

            self.gnuplot = '/usr/bin/gnuplot'

        def _plt_to_plttikz(self):
            """
            Convert plt to plttikz.
            """
            logging.info('plt -> plttikz')
            logging.debug('copy plt file to %s' % self.buildpath)
            shutil.copyfile(self.plt, self.pltcopy)

            # Copy data files
            for data in self.data:
                # Data starts from the root.
                # We need the relative path from the individual directory (ex: src/figure/)
                dest = os.path.join(self.buildpath, os.path.relpath(data,
                                    start=os.path.split(self.plt)[0]))
                # Data may be in subdirectories
                # We reproduce the tree
                os.makedirs(os.path.split(dest)[0], exist_ok=True)
                shutil.copy(data, dest)
            # Prepare and run the command
            command = [self.gnuplot, self.name + '.plt']
            logging.debug('Command: %s' % command)
            # in plt, all path are relative, need to move
            os.chdir(self.buildpath)
            process = subprocess.Popen(command, stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # go back to the cur dir
            os.chdir(self.cwd)

            stdout, stderr = process.communicate()
            with open(self.plttikz, 'w') as fh:
                fh.write(stdout.decode())
            errors = stderr.decode()
            if errors:
                logging.error(errors)  # TODO color

        def _plttikz_to_tex(self):
            """
            Convert plttikz to tex.

            :raises: SyntaxError
            """
            logging.info('plttikz -> tex')
            tex_content = '\\documentclass{standalone}\n\n'
            tex_content += '\\usepackage{gnuplot-lua-tikz}\n'
            tex_content += """\\usepackage{tikz}
    \\usepackage{amssymb}
    \\usepackage{amsfonts}
    \\usepackage{mathrsfs}
    \\usepackage{amsmath}
    \\usepackage[amssymb]{SIunits}\n
    """

            # Inject headers
            if self.tikzsnippet:
                logging.debug('Read tikzsnippet')
                with open(self.snippetfile, 'r') as fh:
                    snippet = fh.read()
                snippet = re.sub('\\\\end{tikzpicture}', '', snippet)
                snippet = snippet.split('\\begin{tikzpicture}')
                logging.debug('Inject header tikzsnippet')
                tex_content += snippet[0]
            if self.tikzsnippet1:
                logging.debug('Read tikzsnippet1')
                with open(self.snippet1file, 'r') as fh:
                    snippet1 = fh.read()
                snippet1 = re.sub('\\\\end{tikzpicture}', '', snippet1)
                snippet1 = snippet1.split('\\begin{tikzpicture}')
                logging.debug('Inject header tikzsnippet1')
                tex_content += snippet1[0]
            if self.tikzsnippet2:
                logging.debug('Read tikzsnippet2')
                with open(self.snippet2file, 'r') as fh:
                    snippet2 = fh.read()
                snippet2 = re.sub('\\\\end{tikzpicture}', '', snippet2)
                snippet2 = snippet2.split('\\begin{tikzpicture}')
                logging.debug('Inject header tikzsnippet2')
                tex_content += snippet2[0]

            tex_content += '\\begin{document}\n'
            # Write the beginning (it is a gnuplot tikz code)
            tex_content += '\\begin{tikzpicture}[gnuplot]\n'

            if self.tikzsnippet:
                logging.debug('Inject body tikzsnippet')
                try:
                    tex_content += snippet[1]
                except IndexError:
                    # The file does not contain tikzpicture
                    raise SyntaxError('The file %s does not contain \\begin{tikzpicture}' % self.tikzsnippet)
            if self.tikzsnippet1:
                logging.debug('Inject body tikzsnippet1')
                try:
                    tex_content += snippet1[1]
                except IndexError:
                    # The file does not contain tikzpicture
                    raise SyntaxError('The file %s does not contain \\begin{tikzpicture}' % self.tikzsnippet1)

            # Inject plttikz
            with open(self.plttikz, 'r') as fh:
                plttikz_content = fh.read()
            plttikz_content = plttikz_content.replace('\\end{tikzpicture}', '')
            tex_content += plttikz_content.replace('\\begin{tikzpicture}[gnuplot]', '')

            if self.tikzsnippet2:
                logging.debug('Inject body tikzsnippet2')
                try:
                    tex_content += snippet2[1]
                except IndexError:
                    # The file does not contain tikzpicture
                    raise SyntaxError('The file %s does not contain \\begin{tikzpicture}' % self.tikzsnippet2)

            tex_content += '\\end{tikzpicture}\n'
            tex_content += '\\end{document}'

            with open(self.tex, 'w') as fh:
                fh.write(tex_content)

        def _pre_make(self):
            """
            make a tex file.
            """
            logging.debug('pre_make(): Gnuplot file %s' % self.plt)
            # Make build path
            logging.debug('pre_make build path %s' % self.buildpath)
            os.makedirs(self.buildpath, exist_ok=True)
            # First convert plt to plttikz
            self._plt_to_plttikz()
            # Then, make a tex
            self._plttikz_to_tex()