Browse Source

[chordpro] More generic song renderer

pull/79/head
Louis 9 years ago
parent
commit
eba5d404d1
  1. 2
      patacrep/build.py
  2. 2
      patacrep/content/__init__.py
  3. 2
      patacrep/content/song.py
  4. 2
      patacrep/data/templates/songs.tex
  5. 15
      patacrep/songs/__init__.py
  6. 28
      patacrep/songs/chordpro/__init__.py
  7. 4
      patacrep/songs/chordpro/ast.py
  8. 0
      patacrep/songs/chordpro/data/latex/content_chord
  9. 0
      patacrep/songs/chordpro/data/latex/content_chordlist
  10. 0
      patacrep/songs/chordpro/data/latex/content_comment
  11. 0
      patacrep/songs/chordpro/data/latex/content_define
  12. 0
      patacrep/songs/chordpro/data/latex/content_error
  13. 0
      patacrep/songs/chordpro/data/latex/content_guitar_comment
  14. 0
      patacrep/songs/chordpro/data/latex/content_image
  15. 0
      patacrep/songs/chordpro/data/latex/content_line
  16. 0
      patacrep/songs/chordpro/data/latex/content_newline
  17. 0
      patacrep/songs/chordpro/data/latex/content_partition
  18. 0
      patacrep/songs/chordpro/data/latex/content_space
  19. 0
      patacrep/songs/chordpro/data/latex/content_verse
  20. 0
      patacrep/songs/chordpro/data/latex/content_word
  21. 0
      patacrep/songs/chordpro/data/latex/song
  22. 4
      patacrep/songs/latex/__init__.py
  23. 46
      patacrep/templates.py

2
patacrep/build.py

@ -113,7 +113,7 @@ class Songbook(object):
) )
# Configuration set # Configuration set
config['render_content'] = content.render_content config['render'] = content.render
config['content'] = content.process_content( config['content'] = content.process_content(
config.get('content', []), config.get('content', []),
config, config,

2
patacrep/content/__init__.py

@ -131,7 +131,7 @@ class ContentError(SongbookError):
return "Content: {}: {}".format(self.keyword, self.message) return "Content: {}: {}".format(self.keyword, self.message)
@jinja2.contextfunction @jinja2.contextfunction
def render_content(context, content): def render(context, content):
"""Render the content of the songbook as a LaTeX code. """Render the content of the songbook as a LaTeX code.
Arguments: Arguments:

2
patacrep/content/song.py

@ -34,7 +34,7 @@ class SongRenderer(Content):
def render(self, context): def render(self, context):
"""Return the string that will render the song.""" """Return the string that will render the song."""
return self.song.tex(output=context['filename']) return self.song.render(output=context['filename'], output_format="latex")
#pylint: disable=unused-argument #pylint: disable=unused-argument
def parse(keyword, argument, contentlist, config): def parse(keyword, argument, contentlist, config):

2
patacrep/data/templates/songs.tex

@ -108,6 +108,6 @@
\phantomsection \phantomsection
\addcontentsline{toc}{section}{\songlistname} \addcontentsline{toc}{section}{\songlistname}
((render_content(content) )) (( render(content) ))
(* endblock *) (* endblock *)

15
patacrep/songs/__init__.py

@ -10,7 +10,6 @@ import re
from patacrep.authors import process_listauthors from patacrep.authors import process_listauthors
from patacrep import files, encoding from patacrep import files, encoding
from patacrep.content import Content
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -63,20 +62,20 @@ class DataSubpath(object):
return self return self
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class Song(Content): class Song:
"""Song (or song metadata) """Song (or song metadata)
This class represents a song, bound to a file. This class represents a song, bound to a file.
- It can parse the file given in arguments. - It can parse the file given in arguments.
- It can render the song as some LaTeX code. - It can render the song as some code (LaTeX, chordpro, depending on subclasses implemetation).
- Its content is cached, so that if the file has not been changed, the - Its content is cached, so that if the file has not been changed, the
file is not parsed again. file is not parsed again.
This class is inherited by classes implementing song management for This class is inherited by classes implementing song management for
several file formats. Those subclasses must implement: several file formats. Those subclasses must implement:
- `parse()` to parse the file; - `parse()` to parse the file;
- `render()` to render the song as LaTeX code. - `render()` to render the song as code.
""" """
# Version format of cached song. Increment this number if we update # Version format of cached song. Increment this number if we update
@ -166,12 +165,16 @@ class Song(Content):
def __repr__(self): def __repr__(self):
return repr((self.titles, self.data, self.fullpath)) return repr((self.titles, self.data, self.fullpath))
def tex(self, output): # pylint: disable=no-self-use, unused-argument def render(self, output, output_format):
"""Return the LaTeX code rendering this song. """Return the code rendering this song.
Arguments: Arguments:
- output: Name of the output file. - output: Name of the output file.
- output_format: Format of the output file (latex, chordpro...)
""" """
method = "render_{}".format(output_format)
if hasattr(self, method):
return getattr(self, method)(output)
raise NotImplementedError() raise NotImplementedError()
def parse(self, config): # pylint: disable=no-self-use def parse(self, config): # pylint: disable=no-self-use

28
patacrep/songs/chordpro/__init__.py

@ -7,14 +7,14 @@ import os
from patacrep import encoding, files from patacrep import encoding, files
from patacrep.songs import Song from patacrep.songs import Song
from patacrep.songs.chordpro.syntax import parse_song from patacrep.songs.chordpro.syntax import parse_song
from patacrep.templates import TexRenderer from patacrep.templates import Renderer
class ChordproSong(Song): class ChordproSong(Song):
"""Chordpros song parser.""" """Chordpros song parser."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.texenv = None self.jinjaenv = None
def parse(self, config): def parse(self, config):
"""Parse content, and return the dictionary of song data.""" """Parse content, and return the dictionary of song data."""
@ -28,7 +28,7 @@ class ChordproSong(Song):
'song': song, 'song': song,
} }
def tex(self, output): def render(self, output, output_format):
context = { context = {
'language': self.config.get( 'language': self.config.get(
'lang', 'lang',
@ -38,27 +38,31 @@ class ChordproSong(Song):
"titles": self.titles, "titles": self.titles,
"authors": self.authors, "authors": self.authors,
"metadata": self.data, "metadata": self.data,
"render": self.render_tex, "render": self._render_ast,
} }
self.texenv = Environment(loader=FileSystemLoader(os.path.join( self.jinjaenv = Environment(loader=FileSystemLoader(os.path.join(
os.path.abspath(pkg_resources.resource_filename(__name__, 'data')), os.path.abspath(pkg_resources.resource_filename(__name__, 'data')),
'latex' output_format,
))) )))
return self.render_tex(context, self.cached['song'].content, template="song.tex") return self._render_ast(
context,
self.cached['song'].content,
template="song",
)
@contextfunction @contextfunction
def render_tex(self, context, content, template=None): def _render_ast(self, context, content, template=None):
"""Render ``content`` as tex.""" """Render ``content``."""
if isinstance(context, dict): if isinstance(context, dict):
context['content'] = content context['content'] = content
else: else:
context.vars['content'] = content context.vars['content'] = content
if template is None: if template is None:
template = content.template('tex') template = content.template()
return TexRenderer( return Renderer(
template=template, template=template,
encoding='utf8', encoding='utf8',
texenv=self.texenv, jinjaenv=self.jinjaenv,
).template.render(context) ).template.render(context)
SONG_PARSERS = { SONG_PARSERS = {

4
patacrep/songs/chordpro/ast.py

@ -73,14 +73,14 @@ class AST:
_template = None _template = None
inline = False inline = False
def template(self, extension): def template(self):
"""Return the template to be used to render this object.""" """Return the template to be used to render this object."""
if self._template is None: if self._template is None:
LOGGER.warning("No template defined for {}.".format(self.__class__)) LOGGER.warning("No template defined for {}.".format(self.__class__))
base = "error" base = "error"
else: else:
base = self._template base = self._template
return "content_{}.{}".format(base, extension) return "content_{}".format(base)
def chordpro(self): def chordpro(self):
"""Return the chordpro string corresponding to this object.""" """Return the chordpro string corresponding to this object."""

0
patacrep/songs/chordpro/data/latex/content_chord.tex → patacrep/songs/chordpro/data/latex/content_chord

0
patacrep/songs/chordpro/data/latex/content_chordlist.tex → patacrep/songs/chordpro/data/latex/content_chordlist

0
patacrep/songs/chordpro/data/latex/content_comment.tex → patacrep/songs/chordpro/data/latex/content_comment

0
patacrep/songs/chordpro/data/latex/content_define.tex → patacrep/songs/chordpro/data/latex/content_define

0
patacrep/songs/chordpro/data/latex/content_error.tex → patacrep/songs/chordpro/data/latex/content_error

0
patacrep/songs/chordpro/data/latex/content_guitar_comment.tex → patacrep/songs/chordpro/data/latex/content_guitar_comment

0
patacrep/songs/chordpro/data/latex/content_image.tex → patacrep/songs/chordpro/data/latex/content_image

0
patacrep/songs/chordpro/data/latex/content_line.tex → patacrep/songs/chordpro/data/latex/content_line

0
patacrep/songs/chordpro/data/latex/content_newline.tex → patacrep/songs/chordpro/data/latex/content_newline

0
patacrep/songs/chordpro/data/latex/content_partition.tex → patacrep/songs/chordpro/data/latex/content_partition

0
patacrep/songs/chordpro/data/latex/content_space.tex → patacrep/songs/chordpro/data/latex/content_space

0
patacrep/songs/chordpro/data/latex/content_verse.tex → patacrep/songs/chordpro/data/latex/content_verse

0
patacrep/songs/chordpro/data/latex/content_word.tex → patacrep/songs/chordpro/data/latex/content_word

0
patacrep/songs/chordpro/data/latex/song.tex → patacrep/songs/chordpro/data/latex/song

4
patacrep/songs/latex/__init__.py

@ -25,8 +25,8 @@ class LatexSong(Song):
self.authors = [self.data['by']] self.authors = [self.data['by']]
del self.data['by'] del self.data['by']
def tex(self, output): def render_latex(self, output):
"""Return the LaTeX code rendering the song.""" """Return the code rendering the song."""
return r'\input{{{}}}'.format(files.path2posix( return r'\input{{{}}}'.format(files.path2posix(
files.relpath( files.relpath(
self.fullpath, self.fullpath,

46
patacrep/templates.py

@ -66,28 +66,28 @@ def _escape_tex(value):
return newval return newval
class TexRenderer: class Renderer:
"""Render a template to a LaTeX file.""" """Render a template to a LaTeX file."""
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def __init__(self, template, texenv, encoding=None): def __init__(self, template, jinjaenv, encoding=None):
self.encoding = encoding self.encoding = encoding
self.texenv = texenv self.jinjaenv = jinjaenv
self.texenv.block_start_string = '(*' self.jinjaenv.block_start_string = '(*'
self.texenv.block_end_string = '*)' self.jinjaenv.block_end_string = '*)'
self.texenv.variable_start_string = '((' self.jinjaenv.variable_start_string = '(('
self.texenv.variable_end_string = '))' self.jinjaenv.variable_end_string = '))'
self.texenv.comment_start_string = '(% comment %)' self.jinjaenv.comment_start_string = '(% comment %)'
self.texenv.comment_end_string = '(% endcomment %)' self.jinjaenv.comment_end_string = '(% endcomment %)'
self.texenv.line_comment_prefix = '%!' self.jinjaenv.line_comment_prefix = '%!'
self.texenv.filters['escape_tex'] = _escape_tex self.jinjaenv.filters['escape_tex'] = _escape_tex
self.texenv.trim_blocks = True self.jinjaenv.trim_blocks = True
self.texenv.lstrip_blocks = True self.jinjaenv.lstrip_blocks = True
self.texenv.globals["path2posix"] = files.path2posix self.jinjaenv.globals["path2posix"] = files.path2posix
self.template = self.texenv.get_template(template) self.template = self.jinjaenv.get_template(template)
class TexBookRenderer(TexRenderer): class TexBookRenderer(Renderer):
"""Tex renderer for the whole songbook""" """Tex renderer for the whole songbook"""
def __init__(self, template, datadirs, lang, encoding=None): def __init__(self, template, datadirs, lang, encoding=None):
@ -106,17 +106,17 @@ class TexBookRenderer(TexRenderer):
FileSystemLoader(os.path.join(datadir, 'templates')) FileSystemLoader(os.path.join(datadir, 'templates'))
for datadir in datadirs for datadir in datadirs
] ]
texenv = Environment( jinjaenv = Environment(
loader=ChoiceLoader(loaders), loader=ChoiceLoader(loaders),
extensions=[VariablesExtension], extensions=[VariablesExtension],
) )
try: try:
super().__init__(template, texenv, encoding) super().__init__(template, jinjaenv, encoding)
except TemplateNotFound as exception: except TemplateNotFound as exception:
# Only works if all loaders are FileSystemLoader(). # Only works if all loaders are FileSystemLoader().
paths = [ paths = [
item item
for loader in self.texenv.loader.loaders for loader in self.jinjaenv.loader.loaders
for item in loader.searchpath for item in loader.searchpath
] ]
raise errors.TemplateError( raise errors.TemplateError(
@ -199,13 +199,13 @@ class TexBookRenderer(TexRenderer):
""" """
subvariables = {} subvariables = {}
templatename = self.texenv.get_template(template).filename templatename = self.jinjaenv.get_template(template).filename
with patacrep.encoding.open_read( with patacrep.encoding.open_read(
templatename, templatename,
encoding=self.encoding encoding=self.encoding
) as template_file: ) as template_file:
content = template_file.read() content = template_file.read()
subtemplates = list(find_templates(self.texenv.parse(content))) subtemplates = list(find_templates(self.jinjaenv.parse(content)))
match = re.findall(_VARIABLE_REGEXP, content) match = re.findall(_VARIABLE_REGEXP, content)
if match: if match:
for var in match: for var in match:

Loading…
Cancel
Save