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


[docs]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'
[docs] def get_name(self): """ Return the name of the task. """ return self.id
[docs] 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
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs]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()
[docs]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()