diff --git a/pyltk/__init__.py b/pyltk/__init__.py index 7a69a5c..c413072 100644 --- a/pyltk/__init__.py +++ b/pyltk/__init__.py @@ -6,6 +6,7 @@ from .documentclass import * from .figure import figure from .formatter import formatter from .input_element import input_element +from .includegraphics import includegraphics from .makebox import makebox from .resizebox import resizebox from .subfloat import subfloat diff --git a/pyltk/documentclass.py b/pyltk/documentclass.py index bc45900..665ef24 100644 --- a/pyltk/documentclass.py +++ b/pyltk/documentclass.py @@ -3,20 +3,41 @@ from collections import Iterable from .element import element +import enum +import os import subprocess +class change_directory: + + """Context manager for changing the current working directory""" + + def __init__(self, newPath): + self.newPath = os.path.expanduser(newPath) + + def __enter__(self): + self.savedPath = os.getcwd() + os.chdir(self.newPath) + + def __exit__(self, etype, value, traceback): + os.chdir(self.savedPath) + + +class CompileResult(enum.Enum): + + ALREADY_EXISTS = 1 + SUCCESS = 2 + ERROR = 3 + + class documentclass(element): - packages = {} - preamble = [] - options = [] classname = 'standalone' templates = { 'package': '\\usepackage{pkgoptions}{{{pkgname}}}', 'options': '[{content}]', - 'def': '\\def\\{name}#{nargs}{{{value}}}', + 'def': '\\def\\{name}{nargs}{{{value}}}', 'content': """\\documentclass{options}{{{classname}}} {packages} {preamble} @@ -26,11 +47,18 @@ class documentclass(element): """ } - def __init__(self, classname, childrens=[], options=[], packages=[]): + output_aux_folder = '.pyltk' + auto_tex_folder = '{}/tex2pdf'.format(output_aux_folder) + + def __init__(self, classname, childrens=[], options=[], + packages=[], preamble=[]): super().__init__(parent=None, childrens=childrens) self.options = options self.classname = classname + self.packages = {} self.add_packages(packages) + self.preamble = [] + self.add2preamble(preamble) def add_packages(self, packages): if type(packages) is str: @@ -49,19 +77,19 @@ class documentclass(element): def add_def(self, name, nargs, value): self.preamble.append(self.fmt().format('def', { 'name': name, - 'nargs': nargs, + 'nargs': '#{}'.format(nargs) if nargs > 0 else '', 'value': value })) def format_preamble(self, defs): - return '\n'.join(self.preamble) + return '\n'.join(str(p) for p in self.preamble) def format_packages(self, pkgs): out = [] - for pkg, opts in self.packages.items(): + for pkg in sorted(pkgs): options = '' - if opts is not True: - options = self.format_options(opts) + if pkgs[pkg] is not True: + options = self.format_options(pkgs[pkg]) out.append(self.fmt().format('package', { 'pkgname': pkg, 'pkgoptions': options @@ -77,12 +105,78 @@ class documentclass(element): 'content': super().content() }) - def compile(self, outfile, infile=None, outlog=None): - if infile is None: - infile = '/tmp/pyltk-{}.tex'.format(id(self)) + def compile(self, outfile, infile='auto', outlog=None): + """ Compile the given document into a PDF file. - with open(infile, 'w') as infd: - infd.write(self.content()) + If infile is not None, and infile already exists, the document + will only be compiled if the content of infile is different + from this document or if infile is newer than the output + document. - call = ['pdflatex', '-halt-on-error', '-jobname', outfile, infile] - subprocess.call(call, stdout=outlog) + Parameters: + - outfile Name of the output file (pdf extension may + be omitted). + - infile Name of the file to which latex code must be + written. If None, a temporary file will be created + and then erased. If 'auto', file will be saved in + an automatic folder for future use. + - outlog + """ + + outdir = os.path.dirname(outfile) + outfile = os.path.basename(outfile) + + res = CompileResult.ERROR + + with change_directory(outdir): + + os.makedirs(self.output_aux_folder, exist_ok=True) + + if outfile.endswith('.pdf'): + outfile = outfile[:-4] + + to_delete = False + + if infile == 'auto': + os.makedirs(self.auto_tex_folder, exist_ok=True) + infile = '{}/{}.tex'.format(self.auto_tex_folder, outfile) + + if infile is None: + to_delete = True + infile = '.pyltk-{}.tex'.format(outfile) + + to_create = True + new_content = self.content() + + # If the input file already exists... + if os.path.exists(infile): + with open(infile, 'r') as infd: + old_content = infd.read() + + # Content has not changed + if old_content == new_content \ + and os.path.exists('{}.pdf'.format(outfile)) \ + and os.path.getctime(infile) <= \ + os.path.getctime('{}.pdf'.format(outfile)): + to_create = False + + if to_create: + with open(infile, 'w') as infd: + infd.write(self.content()) + call = ['pdflatex', '-halt-on-error', + '-output-directory', self.output_aux_folder, + '-jobname', outfile, infile] + if subprocess.call(call, stdout=outlog) == 0: + os.rename('{}/{}.pdf'.format( + self.output_aux_folder, outfile), + '{}.pdf'.format(outfile)) + res = CompileResult.SUCCESS + else: + res = CompileResult.ERROR + else: + res = CompileResult.ALREADY_EXISTS + + if to_delete: + os.remove(infile) + + return res diff --git a/pyltk/element.py b/pyltk/element.py index d1f279e..28ef7be 100644 --- a/pyltk/element.py +++ b/pyltk/element.py @@ -26,7 +26,8 @@ class element: # Do not wrap non-element in wrapper_element raw = False - def __init__(self, parent=None, childrens=[], label=None, raw=False): + def __init__(self, parent=None, childrens=[], label=None, + raw=False, **kargs): """ Create a new element with given parameters. Parameters: @@ -34,7 +35,8 @@ class element: - childrens Childrens of the element - Single element or list of objects (element or not). - label Label of the element. - - raw Raw childrens (see description of `element`). """ + - raw Raw childrens (see description of `element`). + - kargs Options for the element. """ self.parent = parent self.raw = raw if childrens is None: @@ -44,6 +46,7 @@ class element: self.childrens = [] self.add(childrens) self.label = label + self.options = kargs def add(self, childrens): if not isinstance(childrens, Iterable): @@ -69,8 +72,9 @@ class element: if not options: return '' opts = [] - for k, v in options.items(): - opts.append('{key} = {{{value}}}'.format(key=k, value=v)) + # Note: Sorted to have a guaranteed output + for k in sorted(options): + opts.append('{key} = {{{value}}}'.format(key=k, value=options[k])) return self.fmt().format('options', { 'content': ', '.join(opts) }) diff --git a/pyltk/includegraphics.py b/pyltk/includegraphics.py new file mode 100644 index 0000000..fc017de --- /dev/null +++ b/pyltk/includegraphics.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- + +from .element import element + + +class includegraphics(element): + """ Class representing a latex includegrahics. """ + + templates = { + 'element': '\\includegraphics[{options}]{{{filename}}}' + } + + autolabel = False + + def __init__(self, filename, parent=None, **kargs): + super().__init__(parent, **kargs) + self.filename = filename + + def content(self): + return self.fmt().format('element', { + 'filename': self.filename, + 'options': self.format_options(self.options) + }) diff --git a/pyltk/subfloat.py b/pyltk/subfloat.py index a602688..2fc7247 100644 --- a/pyltk/subfloat.py +++ b/pyltk/subfloat.py @@ -7,11 +7,13 @@ class subfloat(element): """ Class representing a latex subfloat. """ templates = { - 'content': """\\subfloat[{caption}]{{ + 'content': """\\subfloat[{caption}{label}]{{ {inner} }}""" } + autolabel = False + def __init__(self, caption, parent=None, childrens=None, label=None): super().__init__(parent, childrens, label=label) self.caption = caption @@ -19,5 +21,6 @@ class subfloat(element): def content(self): return self.fmt().format('content', { 'caption': self.caption, + 'label': self.format_label(self.label), 'inner': super().content() })