Engine for LaTeX songbooks http://www.patacrep.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
5.1 KiB

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Template for .tex generation settings and utilities"""
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PackageLoader, TemplateNotFound
from jinja2.meta import find_referenced_templates as find_templates
import os
import re
import json
from songbook_core import errors
_LATEX_SUBS = (
(re.compile(r'\\'), r'\\textbackslash'),
(re.compile(r'([{}_#%&$])'), r'\\\1'),
(re.compile(r'~'), r'\~{}'),
(re.compile(r'\^'), r'\^{}'),
(re.compile(r'"'), r"''"),
(re.compile(r'\.\.\.+'), r'\\ldots'),
)
def _escape_tex(value):
'''Escape TeX special characters'''
newval = value
for pattern, replacement in _LATEX_SUBS:
newval = pattern.sub(replacement, newval)
return newval
class TexRenderer(object):
"""Render a template to a LaTeX file."""
def __init__(self, template, datadir, lang):
'''Start a new jinja2 environment for .tex creation.
Arguments:
- template: name of the template to use.
- datadir: location of the data directory (which max contain
file <datadir>/templates/<template>).
- lang: main language of songbook.
'''
self.lang = lang
self.texenv = Environment(
loader=ChoiceLoader([
FileSystemLoader(
os.path.join(datadir, 'templates')
),
PackageLoader(
'songbook_core', os.path.join('data', 'templates')
),
]),
)
self.texenv.block_start_string = '(*'
self.texenv.block_end_string = '*)'
self.texenv.variable_start_string = '(('
self.texenv.variable_end_string = '))'
self.texenv.comment_start_string = '(% variables %)'
self.texenv.comment_end_string = '(% endvariables %)'
self.texenv.line_comment_prefix = '(%%)'
self.texenv.filters['escape_tex'] = _escape_tex
self.texenv.trim_blocks = True
self.texenv.lstrip_blocks = True
try:
self.template = self.texenv.get_template(template)
except TemplateNotFound as exception:
raise errors.TemplateError(
exception,
"""Template "{template}" not found.""".format(
template=exception.name
),
)
def get_variables(self):
'''Get and return a dictionary with the default values
for all the variables
'''
data = self.parse_templates()
variables = dict()
for name, param in data.items():
variables[name] = self._get_default(param)
return variables
def _get_default(self, parameter):
'''Get the default value for the parameter, according to the language.
'''
default = None
try:
default = parameter['default']
except KeyError:
return None
if self.lang in default:
variable = default[self.lang]
elif "default" in default:
variable = default["default"]
elif "en" in default:
variable = default["en"]
elif len(default > 0):
variable = default.popitem()[1]
else:
variable = None
return variable
def parse_templates(self):
'''Recursively parse all the used templates to extract all the
variables as a dictionary.
'''
templates = self.get_templates(self.template)
templates |= set([self.template.name])
variables = {}
regex = re.compile(r'\(% variables %\)\n(?P<variables>.*)'
'\(% endvariables %\)', re.DOTALL)
for template_name in templates:
filename = self.texenv.get_template(template_name).filename
with open(filename, 'r') as template_file:
content = template_file.read()
match = re.search(regex, content)
if match:
content = match.group('variables')
variables.update(json.loads(content))
return variables
def get_templates(self, template):
'''Recursively get a set of all the templates used
by a particular template.
'''
with open(template.filename, 'r') as template_file:
content = template_file.readlines()
new_templates = list(find_templates(self.texenv.parse(content)))
all_templates = set(new_templates)
if len(new_templates) > 0:
for new_template_name in new_templates:
new_template = self.texenv.get_template(new_template_name)
# union of the sets
all_templates |= self.get_templates(new_template)
return all_templates
def render_tex(self, output, context):
'''Render a template into a .tex file
Arguments:
- output: a file object to write the result
- context: a dict of all the data to populate the template
'''
output.write(self.template.render(context))