From 0fa1ab34eae6e8b12d167a01d3124e53d347661e Mon Sep 17 00:00:00 2001 From: nanoy Date: Sun, 6 Jan 2019 15:57:02 +0100 Subject: [PATCH] Fix 3.2.1 --- django_tex/__init__.py | 0 django_tex/core.py | 40 +++++++++++++++++++++++++++++++++++++++ django_tex/engine.py | 11 +++++++++++ django_tex/environment.py | 16 ++++++++++++++++ django_tex/exceptions.py | 40 +++++++++++++++++++++++++++++++++++++++ django_tex/filters.py | 9 +++++++++ django_tex/models.py | 19 +++++++++++++++++++ django_tex/views.py | 17 +++++++++++++++++ 8 files changed, 152 insertions(+) create mode 100644 django_tex/__init__.py create mode 100644 django_tex/core.py create mode 100644 django_tex/engine.py create mode 100644 django_tex/environment.py create mode 100644 django_tex/exceptions.py create mode 100644 django_tex/filters.py create mode 100644 django_tex/models.py create mode 100644 django_tex/views.py diff --git a/django_tex/__init__.py b/django_tex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_tex/core.py b/django_tex/core.py new file mode 100644 index 0000000..1c4d409 --- /dev/null +++ b/django_tex/core.py @@ -0,0 +1,40 @@ + +import os +from subprocess import PIPE, run +import tempfile + +from django.template.loader import get_template + +from django_tex.exceptions import TexError +from django.conf import settings + +DEFAULT_INTERPRETER = 'lualatex' + +def run_tex(source): + with tempfile.TemporaryDirectory() as tempdir: + filename = os.path.join(tempdir, 'texput.tex') + with open(filename, 'x', encoding='utf-8') as f: + f.write(source) + latex_interpreter = getattr(settings, 'LATEX_INTERPRETER', DEFAULT_INTERPRETER) + latex_command = f'cd "{tempdir}" && {latex_interpreter} -interaction=batchmode {os.path.basename(filename)}' + process = run(latex_command, shell=True, stdout=PIPE, stderr=PIPE) + try: + if process.returncode == 1: + with open(os.path.join(tempdir, 'texput.log'), encoding='utf8') as f: + log = f.read() + raise TexError(log=log, source=source) + with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as pdf_file: + pdf = pdf_file.read() + except FileNotFoundError: + if process.stderr: + raise Exception(process.stderr.decode('utf-8')) + raise + return pdf + +def compile_template_to_pdf(template_name, context): + source = render_template_with_context(template_name, context) + return run_tex(source) + +def render_template_with_context(template_name, context): + template = get_template(template_name, using='tex') + return template.render(context) diff --git a/django_tex/engine.py b/django_tex/engine.py new file mode 100644 index 0000000..33c1d7f --- /dev/null +++ b/django_tex/engine.py @@ -0,0 +1,11 @@ + +from django.template.backends.jinja2 import Jinja2 + +class TeXEngine(Jinja2): + app_dirname = 'templates' + + def __init__(self, params): + default_environment = 'django_tex.environment.environment' + if 'environment' not in params['OPTIONS'] or not params['OPTIONS']['environment']: + params['OPTIONS']['environment'] = default_environment + super().__init__(params) diff --git a/django_tex/environment.py b/django_tex/environment.py new file mode 100644 index 0000000..472a443 --- /dev/null +++ b/django_tex/environment.py @@ -0,0 +1,16 @@ + +from jinja2 import Environment + +from django.template.defaultfilters import register + +from django_tex.filters import FILTERS as tex_specific_filters + +# Django's built-in filters ... +filters = register.filters +# ... updated with tex specific filters +filters.update(tex_specific_filters) + +def environment(**options): + env = Environment(**options) + env.filters = filters + return env diff --git a/django_tex/exceptions.py b/django_tex/exceptions.py new file mode 100644 index 0000000..5d4ee38 --- /dev/null +++ b/django_tex/exceptions.py @@ -0,0 +1,40 @@ + +import re + +def prettify_message(message): + ''' + Helper methods that removes consecutive whitespaces and newline characters + ''' + # Replace consecutive whitespaces with a single whitespace + message = re.sub(r'[ ]{2,}', ' ', message) + # Replace consecutive newline characters, optionally separated by whitespace, with a single newline + message = re.sub(r'([\r\n][ \t]*)+', '\n', message) + return message + +def tokenizer(code): + token_specification = [ + ('ERROR', r'\! (?:.+[\r\n])+[\r\n]+'), + ('WARNING', r'latex warning.*'), + ('NOFILE', r'no file.*') + ] + token_regex = '|'.join('(?P<{}>{})'.format(label, regex) for label, regex in token_specification) + for m in re.finditer(token_regex, code, re.IGNORECASE): + token_dict = dict(type=m.lastgroup, message=prettify_message(m.group())) + yield token_dict + +class TexError(Exception): + + def __init__(self, log, source): + self.log = log + self.source = source + self.tokens = list(tokenizer(self.log)) + self.message = self.get_message() + + def get_message(self): + for token in self.tokens: + if token['type'] == 'ERROR': + return token['message'] + return 'No error message found in log' + + def __str__(self): + return self.message diff --git a/django_tex/filters.py b/django_tex/filters.py new file mode 100644 index 0000000..732374f --- /dev/null +++ b/django_tex/filters.py @@ -0,0 +1,9 @@ +from django.utils.formats import localize_input + +def do_linebreaks(value): + return value.replace('\n', '\\\\\n') + +FILTERS = { + 'localize': localize_input, + 'linebreaks': do_linebreaks +} \ No newline at end of file diff --git a/django_tex/models.py b/django_tex/models.py new file mode 100644 index 0000000..214d31e --- /dev/null +++ b/django_tex/models.py @@ -0,0 +1,19 @@ +from django.db import models +from django.core.exceptions import ValidationError +from django.template import TemplateDoesNotExist +from django.utils.translation import ugettext_lazy as _ +from django.template.loader import get_template + +def validate_template_path(name): + try: + get_template(name, using='tex') + except TemplateDoesNotExist: + raise ValidationError(_('Template not found.')) + +class TeXTemplateFile(models.Model): + + title = models.CharField(max_length=255) + name = models.CharField(max_length=255, validators=[validate_template_path,]) + + class Meta: + abstract = True diff --git a/django_tex/views.py b/django_tex/views.py new file mode 100644 index 0000000..df65223 --- /dev/null +++ b/django_tex/views.py @@ -0,0 +1,17 @@ + +from django.http import HttpResponse + +from django_tex.core import compile_template_to_pdf + +class PDFResponse(HttpResponse): + + def __init__(self, content, filename=None): + super(PDFResponse, self).__init__(content_type='application/pdf') + self['Content-Disposition'] = 'filename="{}"'.format(filename) + self.write(content) + + +def render_to_pdf(request, template_name, context=None, filename=None): + # Request is not needed and only included to make the signature conform to django's render function + pdf = compile_template_to_pdf(template_name, context) + return PDFResponse(pdf, filename=filename)