diff --git a/setup.py b/setup.py index cbcb359a..81d5eb62 100755 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup(name='songbook-core', requires=[ "argparse", "codecs", "distutils", "fnmatch", "glob", "json", "locale", "logging", "os", "plasTeX", "re", "subprocess", "sys", - "textwrap", "unidecode" + "textwrap", "unidecode", "jinja2" ], packages=['songbook_core'], package_data={'songbook_core': ['data/latex/*', diff --git a/songbook_core/build.py b/songbook_core/build.py index 5416aff4..e4a71d2f 100755 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -16,12 +16,18 @@ from songbook_core import errors from songbook_core.files import recursive_find from songbook_core.index import process_sxd from songbook_core.songs import Song, SongsList +from songbook_core.templates import render_tex EOL = "\n" +DEFAULT_AUTHWORDS = { + "after": ["by"], + "ignore": ["unknown"], + "sep": ["and"], + } def parse_template(template): - """Return the list of parameters defined in the template.""" + """Return the list of default parameters defined in the template.""" embedded_json_pattern = re.compile(r"^%%:") with open(template) as template_file: code = [ @@ -34,93 +40,15 @@ def parse_template(template): data = json.loads(''.join(code)) parameters = dict() for param in data: - parameters[param["name"]] = param - return parameters - - -# pylint: disable=too-many-return-statements -def to_value(parameter, data): - """Convert 'data' to a LaTeX string. - - Conversion is done according to the template parameter it corresponds to. - """ - if "type" not in parameter: - return data - elif parameter["type"] == "stringlist": - if "join" in parameter: - join_text = parameter["join"] - else: - join_text = '' - return join_text.join(data) - elif parameter["type"] == "color": - return data[1:] - elif parameter["type"] == "font": - return data + 'pt' - elif parameter["type"] == "enum": - return data - elif parameter["type"] == "file": - return data - elif parameter["type"] == "flag": - if "join" in parameter: - join_text = parameter["join"] - else: - join_text = '' - return join_text.join(data) - - -def format_declaration(name, parameter): - """Return LaTeX code to declare variale 'name'. - - 'parameter' is the corresponding line of the template (which can provide - default value). - """ - value = "" - if "default" in parameter: - value = parameter["default"] - return ( - r'\def\set@{name}#1{{\def\get{name}{{#1}}}}'.format(name=name) - + EOL - + format_definition(name, to_value(parameter, value)) - ) - - -def format_definition(name, value): - """Write LaTeX code to set a value to a variable""" - return r'\set@{name}{{{value}}}'.format(name=name, value=value) + EOL - - -def clean(basename): - """Clean (some) temporary files used during compilation. - - Depending of the LaTeX modules used in the template, there may be others - that are note deleted by this function.""" - generated_extensions = [ - "_auth.sbx", - "_auth.sxd", - ".aux", - ".log", - ".out", - ".sxc", - ".tex", - "_title.sbx", - "_title.sxd", - ] - - for ext in generated_extensions: try: - os.unlink(basename + ext) - except Exception as exception: - raise errors.CleaningError(basename + ext, exception) + parameters[param["name"]] = param["default"] + except KeyError: + parameters[param["name"]] = None + return parameters -DEFAULT_AUTHWORDS = { - "after": ["by"], - "ignore": ["unknown"], - "sep": ["and"], - } -# pylint: disable=too-few-public-methods class Songbook(object): - """Reprensent a songbook (.sb) file. + """Represent a songbook (.sb) file. - Low level: provide a Python representation of the values stored in the '.sb' file. @@ -132,11 +60,7 @@ class Songbook(object): self.basename = basename # Default values: will be updated while parsing raw_songbook self.config = { - 'template': os.path.join( - __SHAREDIR__, - "templates", - "default.tmpl", - ), + 'template': "default.tex", 'titleprefixwords': [], 'authwords': {}, 'lang': 'french', @@ -168,7 +92,6 @@ class Songbook(object): ] + [',']) ] - def _parse(self, raw_songbook): """Parse raw_songbook. @@ -176,7 +99,7 @@ class Songbook(object): are stored verbatim in self.config. """ self.config.update(raw_songbook) - + self.config['datadir'] = os.path.abspath(self.config['datadir']) ### Some post-processing # Compute song list if self.config['songs'] is None: @@ -187,9 +110,9 @@ class Songbook(object): ) for filename in recursive_find( - os.path.join(self.config['datadir'], 'songs'), - '*.sg', - ) + os.path.join(self.config['datadir'], 'songs'), + '*.sg', + ) ] self.songslist = SongsList(self.config['datadir'], self.config["lang"]) self.songslist.append_list(self.config['songs']) @@ -200,88 +123,46 @@ class Songbook(object): self.config['authwords'][key] = value def write_tex(self, output): - r"""Build the '.tex' file corresponding to self. + """Build the '.tex' file corresponding to self. Arguments: - output: a file object, in which the file will be written. + """ + context = parse_template(os.path.join( + self.config['datadir'], + 'templates', + self.config['template'] + )) + context.update(self.config) + context['titleprefixkeys'] = ["after", "sep", "ignore"] + context['songlist'] = self.songslist + context['filename'] = output.name[:-4] + render_tex(output, context, self.config['datadir']) - TODO: Update this. - - Quelques notes concernant le rendu - - self.config['titleprefixwords'] - > return EOL.join([ - > r"\titleprefixwords{%s}" % prefix - > for prefix - > in self.config['titleprefixwords'] - > ]) - - - self.config['authwords']: - > # Processing authwords values - > tex = [] - > for key in ["after", "sep", "ignore"]: - > for word in self.config['authwords'][key]: - > if key == "after": - > tex.append(r"\auth%sword{%s}" % ("by", word)) - > else: - > tex.append(r"\auth%sword{%s}" % (key, word)) - > return EOL.join(tex) - - - Liste des langues utilisées (pour chargement par babel): - self.songlist.languages(). Donc la commande pour Babel peut - ressembler à : - > r"\PassOptionsToPackage{%s}{babel}" % ",".join( - > self.songslist.languages() - > ) - """ - parameters = parse_template(os.path.join( - self.config['datadir'], - 'templates', - self.config['template'] - )) - output.write(( - '%% This file has been automatically ' - 'generated, do not edit!\n' - )) - output.write(r'\makeatletter' + EOL) - # output automatic parameters - output.write(format_declaration("name", {"default": self.basename})) - output.write(format_declaration("songslist", {"type": "stringlist"})) - # output template parameter command - for name, parameter in parameters.iteritems(): - output.write(format_declaration(name, parameter)) - # output template parameter values - for name, value in self.config.iteritems(): - if name in parameters: - output.write(format_definition( - name, - to_value(parameters[name], value), - )) - - if len(self.config['songs']) > 0: - output.write(format_definition('songslist', self.songslist.latex())) - output.write(r'\makeatother' + EOL) - - # output template - comment_pattern = re.compile(r"^\s*%") - with codecs.open( - os.path.join( - self.config['datadir'], - 'templates', - self.config['template'] - ), - 'r', - 'utf-8', - ) as template_file: - content = [ - line - for line - in template_file - if not comment_pattern.match(line) - ] - output.write(u''.join(content)) +def clean(basename): + """Clean (some) temporary files used during compilation. + + Depending of the LaTeX modules used in the template, there may be others + that are not deleted by this function.""" + generated_extensions = [ + "_auth.sbx", + "_auth.sxd", + ".aux", + ".log", + ".out", + ".sxc", + ".tex", + "_title.sbx", + "_title.sxd", + ] + for ext in generated_extensions: + try: + os.unlink(basename + ext) + except Exception as exception: + raise errors.CleaningError(basename + ext, exception) def buildsongbook( @@ -302,6 +183,12 @@ def buildsongbook( songbook = Songbook(raw_songbook, basename) with codecs.open("{}.tex".format(basename), 'w', 'utf-8') as output: songbook.write_tex(output) +############################################################################## +############################################################################## +# import sys +# sys.exit(0) +############################################################################## +############################################################################## if not 'TEXINPUTS' in os.environ.keys(): os.environ['TEXINPUTS'] = '' @@ -316,7 +203,7 @@ def buildsongbook( # pdflatex options pdflatex_options = [] - pdflatex_options.append("--shell-escape") # Lilypond compilation + pdflatex_options.append("--shell-escape") # Lilypond compilation if not interactive: pdflatex_options.append("-halt-on-error") diff --git a/songbook_core/data/templates/default.tex b/songbook_core/data/templates/default.tex new file mode 100644 index 00000000..e6d07747 --- /dev/null +++ b/songbook_core/data/templates/default.tex @@ -0,0 +1,123 @@ +(% comment %) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Template parameters +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%:[ +%%: {"name":"title", "description":"Title", "default":"Recueil de chansons pour guitare", "mandatory":true}, +%%: {"name":"author", "description":"Author", "default":"The Songbook Team", "mandatory":true}, +%%: {"name":"booktype", "description":"Type", "type":"enum", "values":["chorded","lyric"], "default":"chorded", "mandatory":true}, +%%: {"name":"lang", "description":"Language", "default":"english"}, +%%: {"name":"instruments", "description":"Instruments", "type":"flag", "values":["guitar","ukulele"], "join":",", "mandatory":true, "default":["guitar"]}, +%%: {"name":"bookoptions", "description":"Options", "type":"flag", "values":["diagram","importantdiagramonly","lilypond","pictures","tabs","repeatchords","onesongperpage"], "join":",", "mandatory":true, "default":["diagram","pictures"]}, +%%: {"name":"version", "description":"Version", "default":"unknown"}, +%%: {"name":"subtitle", "description":"Subtitle"}, +%%: {"name":"web", "description":"Web", "default":"http://www.patacrep.com"}, +%%: {"name":"mail", "description":"Email", "default":"crep@team-on-fire.com"}, +%%: {"name":"picture", "description":"Picture", "type":"file", "default":"treble_a"}, +%%: {"name":"picturecopyright", "description":"Copyright", "default":"Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"}, +%%: {"name":"footer", "description":"Footer", "default":"\\begin{flushright}Generated using Songbook (\\url{http://www.patacrep.com})\\end{flushright}"}, +%%: {"name":"mainfontsize", "description":"Font Size", "type":"font", "default":"10"}, +%%: {"name":"songnumberbgcolor", "description":"Number Shade", "type":"color", "default":"D1E4AE"}, +%%: {"name":"notebgcolor", "description":"Note Shade", "type":"color", "default":"D1E4AE"}, +%%: {"name":"indexbgcolor", "description":"Index Shade", "type":"color", "default":"D1E4AE"}, +%%: {"name":"titleprefixwords", "description":"Ignore some words in the beginning of song titles"}, +%%: {"name":"authwords", "descriptipn":"Set of options to process author string (LaTeX commands authsepword, authignoreword, authbyword)"}, +%%: {"name":"languages", "description":"List of languages used by songs", "default":""} +%%:] +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +(% endcomment %) +(* extends "layout.tex" *) +(* block extrapackages *) + \usepackage[ + a4paper % paper size + ,includeheadfoot % include header and footer into text size + ,hmarginratio=1:1 % ratio between inner and outer margin (default) + ,outer=1.8cm % outer margin (right) + ,vmarginratio=1:1 % ratio between top and bottom margin + ,bmargin=1.3cm % bottom margin + ]{geometry} + \usepackage{chords} +(* endblock extrapackages *) + +(* block header *) + + \title{((title))} + \author{((author))} + \subtitle{((subtitle))} + (* if version!="unknown" *) + \version{((version))} + (* endif *) + \mail{((mail))} + \web{((web))} + \picture{((picture))} + \picturecopyright{((picturecopyright))} + \footer{((footer))} + \lang{((mainlang))} + + \newindex{titleidx}{((filename))_title} + \newauthorindex{authidx}{((filename))_auth} + + (* for prefix in titleprefixwords *) + \titleprefixwords{((prefix))} + (* endfor*) + (* for key in titleprefixkeys *) + (* for word in authwords.key *) + (* if key=="after" *) + \authbyword{((word))} + (* else *) + \auth((key))word{((word))} + (* endif *) + (* endfor *) + (* endfor*) + + \graphicspath{{((datadir))/img/}} + \pagestyle{empty} +(* endblock header *) + +(* block preface *) + \definecolor{SongNumberBgColor}{HTML}{((songnumberbgcolor))} + \definecolor{NoteBgColor}{HTML}{((notebgcolor))} + \definecolor{IndexBgColor}{HTML}{((indexbgcolor))} + + \renewcommand{\snumbgcolor}{SongNumberBgColor} + \renewcommand{\notebgcolor}{NoteBgColor} + \renewcommand{\idxbgcolor}{IndexBgColor} + + (* if mainlang==english *) + \showindex{Songs Index}{titleidx} + \showindex{Authors Index}{authidx} + (* else *) + \showindex{Index des chansons}{titleidx} + \showindex{Index des auteurs}{authidx} + (* endif *) + (* if mainlang==french *) + \notenamesin{A}{B}{C}{D}{E}{F}{G} + \notenamesout{La}{Si}{Do}{Ré}{Mi}{Fa}{Sol} + (* endif *) + + % list of chords + \ifchorded + \phantomsection + (* if mainlang==english *) + \addcontentsline{toc}{section}{Chords list} + (* else *) + \addcontentsline{toc}{section}{Liste des accords} + (* endif *) +% \chords + \fi +(* endblock *) + +(* block content *) + \phantomsection + (* if mainlang==english *) + \addcontentsline{toc}{section}{Songs list} + (* else *) + \addcontentsline{toc}{section}{Liste des chansons} + (* endif *) + + \begin{songs}{titleidx,authidx} + (* for song in songlist.songs *) + \input{((song.path))} + (* endfor *) + \end{songs} +(* endblock *) diff --git a/songbook_core/data/templates/default.tmpl b/songbook_core/data/templates/default.tmpl deleted file mode 100644 index d1e16803..00000000 --- a/songbook_core/data/templates/default.tmpl +++ /dev/null @@ -1,152 +0,0 @@ -% Copyright (C) 2009-2010 Romain Goffe, Alexandre Dupas -% Copyright (C) 2008 Kevin W. Hamlen -% -% This program is free software; you can redistribute it and/or -% modify it under the terms of the GNU General Public License -% as published by the Free Software Foundation; either version 2 -% of the License, or (at your option) any later version. -% -% This program is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with this program; if not, write to the Free Software -% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -% MA 02110-1301, USA. -% -% The latest version of this program can be obtained from -% http://songs.sourceforge.net. -% -% Modified to serve personnal purposes. Newer versions can be -% obtained from http://www.lohrun.net. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Template parameters -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%:[ -%%: {"name":"title", "description":"Title", "default":"Recueil de chansons pour guitare", "mandatory":true}, -%%: {"name":"author", "description":"Author", "default":"The Songbook Team", "mandatory":true}, -%%: {"name":"booktype", "description":"Type", "type":"enum", "values":["chorded","lyric"], "default":"chorded", "mandatory":true}, -%%: {"name":"lang", "description":"Language", "default":"english"}, -%%: {"name":"instruments", "description":"Instruments", "type":"flag", "values":["guitar","ukulele"], "join":",", "mandatory":true, "default":["guitar"]}, -%%: {"name":"bookoptions", "description":"Options", "type":"flag", "values":["diagram","importantdiagramonly","lilypond","pictures","tabs","repeatchords","onesongperpage"], "join":",", "mandatory":true, "default":["diagram","pictures"]}, -%%: {"name":"version", "description":"Version", "default":"unknown"}, -%%: {"name":"subtitle", "description":"Subtitle"}, -%%: {"name":"web", "description":"Web", "default":"http://www.patacrep.com"}, -%%: {"name":"mail", "description":"Email", "default":"crep@team-on-fire.com"}, -%%: {"name":"picture", "description":"Picture", "type":"file", "default":"treble_a"}, -%%: {"name":"picturecopyright", "description":"Copyright", "default":"Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"}, -%%: {"name":"footer", "description":"Footer", "default":"\\begin{flushright}Generated using Songbook (\\url{http://www.patacrep.com})\\end{flushright}"}, -%%: {"name":"mainfontsize", "description":"Font Size", "type":"font", "default":"10"}, -%%: {"name":"songnumberbgcolor", "description":"Number Shade", "type":"color", "default":"#D1E4AE"}, -%%: {"name":"notebgcolor", "description":"Note Shade", "type":"color", "default":"#D1E4AE"}, -%%: {"name":"indexbgcolor", "description":"Index Shade", "type":"color", "default":"#D1E4AE"}, -%%: {"name":"titleprefixwords", "description":"Ignore some words in the beginning of song titles"}, -%%: {"name":"authwords", "descriptipn":"Set of options to process author string (LaTeX commands authsepword, authignoreword, authbyword)"}, -%%: {"name":"languages", "description":"List of languages used by songs", "default":""} -%%:] -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% begin document -\makeatletter\def\input@path{{tex/}} -\documentclass[\getbooktype,\getinstruments,\getbookoptions,\getmainfontsize]{crepbook} -\usepackage[utf8]{inputenc} -\usepackage[T1]{fontenc} -\usepackage{lmodern} - -\PassOptionsToPackage{\getlanguages}{babel} -\PassOptionsToPackage{\getlang}{babel} -\usepackage{babel} - -\title{\gettitle} -\author{\getauthor} -\subtitle{\getsubtitle} -\version{\getversion} -\mail{\getmail} -\web{\getweb} -\picture{\getpicture} -\picturecopyright{\getpicturecopyright} -\footer{\getfooter} -\lang{\getlang} - -\newindex{titleidx}{\getname_title} -\newauthorindex{authidx}{\getname_auth} - -\definecolor{SongNumberBgColor}{HTML}{\getsongnumberbgcolor} -\definecolor{NoteBgColor}{HTML}{\getnotebgcolor} -\definecolor{IndexBgColor}{HTML}{\getindexbgcolor} - -\renewcommand{\snumbgcolor}{SongNumberBgColor} -\renewcommand{\notebgcolor}{NoteBgColor} -\renewcommand{\idxbgcolor}{IndexBgColor} - -\gettitleprefixwords -\getauthwords - -\pagestyle{empty} - -% Customization of the page appearance -\usepackage[ - a4paper % paper size - ,includeheadfoot % include header and footer into text size - ,hmarginratio=1:1 % ratio between inner and outer margin (default) - ,outer=1.8cm % outer margin (right) - ,vmarginratio=1:1 % ratio between top and bottom margin - ,bmargin=1.3cm % bottom margin -% ,bindingoffset=1.7cm % space reserved to bound pages together - ]{geometry} - -\usepackage{chords} - -\begin{document} - -% translate default title -\IfStrEq{\gettitle}{Recueil de chansons pour guitare}{ - \IfStrEq{\getlang}{english}{\title{Patacrep songbook}}{} -}{} - -\maketitle - -% indexes -\IfStrEq{\getlang}{english}{ - \showindex{Songs Index}{titleidx} -}{ - \showindex{Index des chansons}{titleidx} -} -\IfStrEq{\getlang}{english}{ - \showindex{Authors Index}{authidx} -}{ - \showindex{Index des auteurs}{authidx} -} - -% chords notation -\IfStrEq{\getlang}{french}{ - \notenamesin{A}{B}{C}{D}{E}{F}{G} - \notenamesout{La}{Si}{Do}{Ré}{Mi}{Fa}{Sol} -}{} - -% list of chords -\ifchorded -\phantomsection -\IfStrEq{\getlang}{english}{ - \addcontentsline{toc}{section}{Chords list} -}{ - \addcontentsline{toc}{section}{Liste des accords} -} -\chords -\fi - -% songs -\phantomsection -\IfStrEq{\getlang}{english}{ - \addcontentsline{toc}{section}{Songs list} -}{ - \addcontentsline{toc}{section}{Liste des chansons} -} -\begin{songs}{titleidx,authidx} - \getsongslist -\end{songs} - -\end{document} -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% end document diff --git a/songbook_core/data/templates/layout.tex b/songbook_core/data/templates/layout.tex new file mode 100644 index 00000000..d046cfd8 --- /dev/null +++ b/songbook_core/data/templates/layout.tex @@ -0,0 +1,58 @@ +%% Automaticly generated document. +%% You may edit this file but all changes will be overwritten. +%% If you want to change this document, have a look at +%% the templating system. + +% This program is free software; you can redistribute it and/or +% modify it under the terms of the GNU General Public License +% as published by the Free Software Foundation; either version 2 +% of the License, or (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +% MA 02110-1301, USA. +% +% The latest version of this program can be obtained from +% https://github.com/patacrep/ + +% Copyright (C) 2014 The Songbook team (www.patacrep.com) + +\documentclass[((booktype)), + (* for option in bookoptions *)((option)), + (* endfor *) + (* for instrument in instruments *)((instrument)), + (* endfor *) + ((mainfontsize))pt]{crepbook} +(* block packages *) + \usepackage[utf8]{inputenc} + \usepackage[T1]{fontenc} + \usepackage{lmodern} + (* for lang in songlist.languages() *) + \PassOptionsToPackage{((lang))}{babel} + (* endfor *) + \usepackage{babel} + (* block extrapackages *) + (* endblock extrapackages *) +(* endblock packages *) + +(* block header *) +(* endblock header *) + +\begin{document} +\maketitle +(* block preface *) +(* endblock *) + +(* block content *) +(* endblock *) + +(* block postface *) +(* endblock *) +\end{document} +% End of file diff --git a/songbook_core/songs.py b/songbook_core/songs.py index f6ba4942..f0c8811b 100644 --- a/songbook_core/songs.py +++ b/songbook_core/songs.py @@ -126,14 +126,6 @@ class SongsList(object): for filename in glob.iglob(os.path.join(self._songdir, regexp)): self.append(filename) - def latex(self): - """Renvoie le code LaTeX nécessaire pour intégrer la liste de chansons. - """ - result = [r'\input{{{0}}}'.format(song.path.replace("\\", "/").strip()) - for song in self.songs] - result.append(r'\selectlanguage{%s}' % self._language) - return '\n'.join(result) - def languages(self): """Renvoie la liste des langues utilisées par les chansons""" return set().union(*[set(song.languages) for song in self.songs]) diff --git a/songbook_core/templates.py b/songbook_core/templates.py new file mode 100644 index 00000000..534fbd91 --- /dev/null +++ b/songbook_core/templates.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Template for .tex generation settings and utilities""" + +from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PackageLoader +import os +import re + +_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 + + +def _init_tex_env(datadir=''): + '''Start a new jinja2 environment for .tex creation''' + loader = ChoiceLoader([ + PackageLoader('songbook_core', 'data/templates'), + FileSystemLoader(os.path.join(datadir, 'templates')), + ]) + texenv = Environment(loader=loader) + texenv.block_start_string = '(*' + texenv.block_end_string = '*)' + texenv.variable_start_string = '((' + texenv.variable_end_string = '))' + texenv.comment_start_string = '(% comment %)' + texenv.comment_end_string = '(% endcomment %)' + texenv.filters['escape_tex'] = _escape_tex + texenv.trim_blocks = True + texenv.lstrip_blocks = True + return texenv + + +def render_tex(output, context, datadir=''): + '''Render a template into a .tex file + + Arguments: + - output: a file object to write the result + - context: all the data to populate the template + - datadir: location of the user-defined templates + ''' + env = _init_tex_env(datadir=datadir) + template = env.get_template(context['template']) + + content = template.render(**context) + output.write(content) + return None # TODO: gestion des erreurs