#!/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.checksum import calculate_checksum, is_different
[docs]class Task():
"""
Parent Task manager.
:param filepath: filepath of the main file
:param build: relative filepath of the build dir
"""
def __init__(self, filepath, build='build'):
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'
self.eps = self.name + '.eps'
self.pdf = self.name + '.pdf'
self.png = self.name + '.png'
self.svg = self.name + '.svg'
[docs] def get_name(self):
"""
Return the name of the task.
"""
return self.id
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
def _pdf_to_svg(self):
"""
Convert pdf to svg.
"""
logging.info('pdf -> 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')
# 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')
# 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
[docs] def check_dependencies(self, db):
"""
Check if dependencies have been modified.
:param db: `DataBase` instance
"""
self.current_hashes = {}
for dep in self.dependencies:
self.current_hashes[dep] = calculate_checksum(dep)
db_hashes = db.get(self.id, 'deps')
return is_different(self.current_hashes, db_hashes)
[docs] def check_targets(self, db, pdf_only=False):
"""
Check if targets have been modified.
:param db: `DataBase` instance
:param pdf_only: Check only the status for pdf
"""
status = db.get(self.id, 'targets')
if pdf_only:
return not status['pdf']
else:
if False in status.values():
return True
else:
return False
def _pre_make(self):
"""
Make a tex file.
"""
logging.debug('Default pre_make() in class Task, nothing to do!')
pass
[docs] def make_pdf(self, db):
"""
Compile the figure in pdf.
"""
if self.check_dependencies(db) or self.check_targets(db, pdf_only=True):
logging.info('Build in pdf %s' % self.name)
self._pre_make()
# Build a pdf
self._tex_to_pdf()
db.set(self.id, 'deps', self.current_hashes)
target_status = {'tex': True,
'pdf': True,
'svg': False,
'eps': False,
'png': False}
db.set(self.id, 'targets', target_status)
db.set(self.id, 'export', target_status)
else:
logging.info('Nothing to do for %s' % self.name)
[docs] def make(self, db):
"""
Compile the figure in all formats.
"""
if self.check_dependencies(db) or self.check_targets(db, pdf_only=False):
logging.info('Build in all formats %s' % self.name)
self._pre_make()
# Build a pdf
self._tex_to_pdf()
# Build other formats
self._pdf_to_svg()
self._pdf_to_eps()
self._pdf_to_png()
db.set(self.id, 'deps', self.current_hashes)
target_status = {'tex': True,
'pdf': True,
'svg': True,
'eps': True,
'png': True}
db.set(self.id, 'targets', target_status)
db.set(self.id, 'export', target_status)
else:
logging.info('Nothing to do for %s' % self.name)
[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, db, dst='/tmp'):
"""
Export built files.
:param db: `DataBase` instance
:param dst: filepath of the destination directory
"""
logging.info('Export %s' % self.name)
status = db.get(self.id, 'export')
for ext, func in (('tex', self.export_tex),
('pdf', self.export_pdf),
('svg', self.export_svg),
('eps', self.export_eps),
('png', self.export_png),):
if status[ext]:
path = os.path.join(dst, ext)
os.makedirs(path, exist_ok=True)
func(path)
export_status = {'tex': False,
'pdf': False,
'svg': False,
'eps': False,
'png': False}
db.set(self.id, 'export', export_status)
[docs]class TikzTask(Task):
"""
Tikz Task manager.
"""
def __init__(self, filepath, datafiles=[],
build='build'):
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'):
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()