Browse Source

Tex rendere works

pull/70/head
Louis 10 years ago
committed by Luthaf
parent
commit
b9d459ec24
  1. 13
      patacrep/authors.py
  2. 4
      patacrep/build.py
  3. 1
      patacrep/data/examples/example-all.sb
  4. 13
      patacrep/data/examples/songs/greensleeves.sgc
  5. 45
      patacrep/songs/__init__.py
  6. 47
      patacrep/songs/chordpro/__init__.py
  7. 198
      patacrep/songs/chordpro/ast.py
  8. 27
      patacrep/songs/chordpro/data/latex/chordpro.tex
  9. 1
      patacrep/songs/chordpro/data/latex/content_chord.tex
  10. 1
      patacrep/songs/chordpro/data/latex/content_comment.tex
  11. 3
      patacrep/songs/chordpro/data/latex/content_error.tex
  12. 1
      patacrep/songs/chordpro/data/latex/content_guitar_comment.tex
  13. 1
      patacrep/songs/chordpro/data/latex/content_image.tex
  14. 1
      patacrep/songs/chordpro/data/latex/content_line.tex
  15. 2
      patacrep/songs/chordpro/data/latex/content_newline.tex
  16. 1
      patacrep/songs/chordpro/data/latex/content_partition.tex
  17. 1
      patacrep/songs/chordpro/data/latex/content_space.tex
  18. 7
      patacrep/songs/chordpro/data/latex/content_verse.tex
  19. 1
      patacrep/songs/chordpro/data/latex/content_word.tex
  20. 27
      patacrep/songs/chordpro/lexer.py
  21. 22
      patacrep/songs/chordpro/syntax.py
  22. 1
      patacrep/songs/chordpro/test/__init__.py
  23. 7
      patacrep/songs/chordpro/test/greensleeves.txt
  24. 4
      patacrep/songs/chordpro/test/metadata.sgc
  25. 14
      patacrep/songs/chordpro/test/metadata.txt
  26. 76
      patacrep/songs/chordpro/test/test_parser.py
  27. 2
      patacrep/songs/latex/__init__.py
  28. 40
      patacrep/templates.py
  29. 26
      patacrep/test.py
  30. 3
      setup.py

13
patacrep/authors.py

@ -225,9 +225,12 @@ def processauthors(authors_string, after=None, ignore=None, sep=None):
)
]
def process_listauthors(authors_list):
def process_listauthors(authors_list, after=None, ignore=None, sep=None):
"""Process a list of authors, and return the list of resulting authors."""
return sum([
processauthors(string)
for string in authors_list
])
authors = []
for sublist in [
processauthors(string, after, ignore, sep)
for string in authors_list
]:
authors.extend(sublist)
return authors

4
patacrep/build.py

@ -10,7 +10,7 @@ from subprocess import Popen, PIPE, call
from patacrep import __DATADIR__, authors, content, errors, files
from patacrep.index import process_sxd
from patacrep.templates import TexRenderer
from patacrep.templates import TexBookRenderer
from patacrep.songs import DataSubpath
LOGGER = logging.getLogger(__name__)
@ -87,7 +87,7 @@ class Songbook(object):
# Updating configuration
config = DEFAULT_CONFIG.copy()
config.update(self.config)
renderer = TexRenderer(
renderer = TexBookRenderer(
config['template'],
config['datadir'],
config['lang'],

1
patacrep/data/examples/example-all.sb

@ -7,6 +7,7 @@
],
"booktype" : "chorded",
"lang" : "french",
"encoding": "utf8",
"authwords" : {
"sep" : ["and", "et"]
},

13
patacrep/data/examples/songs/greensleeves.sgc

@ -1,7 +1,9 @@
{language : english}
{columns : 2}
{ title : Greensleeves}
{ title : Un sous titre}
{artist: Traditionnel}
{artist: Prénom Nom}
{cover : traditionnel }
{album :Angleterre}
@ -10,7 +12,7 @@
A[Am]las, my love, ye [G]do me wrong
To [Am]cast me oft dis[E]curteously
And [Am]{I have} loved [G]you so long
And [Am]I have loved [G]you so long
De[Am]lighting [E]in your [Am]companie
{start_of_chorus}
@ -35,8 +37,15 @@ The [Am]cloth so fine as [E]fine might be
I [Am]gave thee jewels [G]for thy chest
And [Am]all this [E]cost I [Am]spent on thee
{c:test of comment}
{gc: test of guitar comment}
{image: traditionnel}
Thy [Am]smock of silke, both [G]faire and white
With [Am]gold embrodered [E]gorgeously
Thy [Am]peticote of [G]sendall right
And [Am]this I [E]bought thee [Am]gladly
And [Am]this I [E]bought thee [Am]gladly

45
patacrep/songs/__init__.py

@ -8,7 +8,7 @@ import os
import pickle
import re
from patacrep.authors import processauthors
from patacrep.authors import process_listauthors
from patacrep import files, encoding
LOGGER = logging.getLogger(__name__)
@ -130,13 +130,12 @@ class Song(Content):
))
# Data extraction from the latex song
self.data = self.DEFAULT_DATA
self.data['@path'] = self.fullpath
self.data.update(self.parse(
encoding.open_read(self.fullpath).read()
))
self.titles = self.data['@titles']
self.languages = self.data['@languages']
self.titles = []
self.data = {}
self.cached = None
self.parse(config)
# Post processing of data
self.datadir = datadir
self.unprefixed_titles = [
unprefixed_title(
@ -146,17 +145,12 @@ class Song(Content):
for title
in self.titles
]
self.subpath = subpath
self.authors = processauthors(
self.authors = process_listauthors(
self.authors,
**config["_compiled_authwords"]
)
# Cache management
#: Special attribute to allow plugins to store cached data
self.cached = None
self._version = self.CACHE_VERSION
self._write_cache()
@ -181,11 +175,24 @@ class Song(Content):
Arguments:
- output: Name of the output file.
"""
return NotImplementedError()
def parse(self, content): # pylint: disable=no-self-use, unused-argument
"""Parse song, and return a dictionary of its data."""
return NotImplementedError()
raise NotImplementedError()
def parse(self, config): # pylint: disable=no-self-use
"""Parse song.
It set the following attributes:
- titles: the list of (raw) titles. This list will be processed to
remove prefixes.
- languages: the list of languages used in the song, as languages
recognized by the LaTeX babel package.
- authors: the list of (raw) authors. This list will be processed to
'clean' it (see function :func:`patacrep.authors.processauthors`).
- data: song metadata. Used (among others) to sort the songs.
- cached: additional data that will be cached. Thus, data stored in
this attribute must be picklable.
"""
raise NotImplementedError()
def unprefixed_title(title, prefixes):
"""Remove the first prefix of the list in the beginning of title (if any).

47
patacrep/songs/chordpro/__init__.py

@ -1,21 +1,50 @@
"""Chordpro parser"""
from patacrep import encoding
from jinja2 import Environment, FileSystemLoader
import pkg_resources
import os
from patacrep import encoding, files
from patacrep.songs import Song
from patacrep.songs.chordpro.syntax import parse_song
from patacrep.templates import TexRenderer
class ChordproSong(Song):
"""Chordpros song parser."""
def parse(self):
def parse(self, config):
"""Parse content, and return the dictinory of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
self.data = parse_song(song.read(), self.fullpath)
print(type(self.data), self.data)
import sys; sys.exit(1)
self.languages = self.data['@languages']
del self.data['@languages']
self.authors = self.data['by']
del self.data['by']
song = parse_song(song.read(), self.fullpath)
self.authors = song.authors
self.titles = song.titles
self.languages = song.get_directives('language')
self.data = dict([meta.as_tuple for meta in song.meta])
self.cached = {
'song': song,
}
#: Main language
self.language = song.get_directive('language', config['lang'])
def tex(self, output):
context = {
'language': self.language,
'columns': self.cached['song'].get_directive('columns', 1),
"path": files.relpath(self.fullpath, os.path.dirname(output)),
"titles": r"\\".join(self.titles),
"authors": ", ".join(["{} {}".format(name[1], name[0]) for name in self.authors]),
"metadata": self.data,
"beginsong": self.cached['song'].meta_beginsong(),
"content": self.cached['song'].content,
}
return TexRenderer(
template="chordpro.tex",
encoding='utf8',
texenv=Environment(loader=FileSystemLoader(os.path.join(
os.path.abspath(pkg_resources.resource_filename(__name__, 'data')),
'latex'
))),
).template.render(context)
SONG_PARSERS = {
'sgc': ChordproSong,

198
patacrep/songs/chordpro/ast.py

@ -1,18 +1,37 @@
# -*- coding: utf-8 -*-
"""Abstract Syntax Tree for ChordPro code."""
# pylint: disable=too-few-public-methods
import functools
import logging
import os
LOGGER = logging.getLogger()
def _indent(string):
"""Return and indented version of argument."""
return "\n".join([" {}".format(line) for line in string.split('\n')])
#: List of properties that are to be displayed in the flow of the song (not as
#: metadata at the beginning or end of song.
INLINE_PROPERTIES = {
"lilypond",
"partition",
"comment",
"guitar_comment",
"image",
}
#: List of properties that are listed in the `\beginsong` LaTeX directive.
BEGINSONG_PROPERTIES = {
"album",
"copyright",
"cov",
"vcov",
"tag",
}
#: Some directive have alternative names. For instance `{title: Foo}` and `{t:
#: Foo}` are equivalent.
DIRECTIVE_SHORTCUTS = {
"t": "title",
"st": "subtitle",
@ -20,25 +39,40 @@ DIRECTIVE_SHORTCUTS = {
"by": "artist",
"c": "comment",
"gc": "guitar_comment",
"cover": "cov",
"vcover": "vcov",
}
def directive_name(text):
if text in DIRECTIVE_SHORTCUTS:
return DIRECTIVE_SHORTCUTS[text]
return text
"""Return name of the directive, considering eventual shortcuts."""
return DIRECTIVE_SHORTCUTS.get(text, text)
class AST:
"""Generic object representing elements of the song."""
_template = None
inline = False
def template(self, extension):
"""Return the template to be used to render this object."""
if self._template is None:
LOGGER.warning("No template defined for {}.".format(self.__class__))
base = "error"
else:
base = self._template
return "content_{}.{}".format(base, extension)
class Line(AST):
"""A line is a sequence of (possibly truncated) words, spaces and chords."""
_template = "line"
def __init__(self):
super().__init__()
self.line = []
def prepend(self, data):
"""Add an object at the beginning of line."""
self.line.insert(0, data)
return self
@ -46,6 +80,7 @@ class Line(AST):
return "".join([str(item) for item in self.line])
def strip(self):
"""Remove spaces at the beginning and end of line."""
while True:
if not self.line:
return self
@ -63,6 +98,7 @@ class LineElement(AST):
class Word(LineElement):
"""A chunk of word."""
_template = "word"
def __init__(self, value):
super().__init__()
@ -73,6 +109,7 @@ class Word(LineElement):
class Space(LineElement):
"""A space between words"""
_template = "space"
def __init__(self):
super().__init__()
@ -83,6 +120,8 @@ class Space(LineElement):
class Chord(LineElement):
"""A chord."""
_template = "chord"
def __init__(self, value):
super().__init__()
self.value = value
@ -92,59 +131,95 @@ class Chord(LineElement):
class Verse(AST):
"""A verse (or bridge, or chorus)"""
_template = "verse"
type = "verse"
inline = True
def __init__(self, block=None):
def __init__(self):
super().__init__()
self.lines = [] # TODO check block
self.lines = []
def prepend(self, data):
"""Add data at the beginning of verse."""
self.lines.insert(0, data)
return self
def __str__(self):
return '{{start_of_{type}}}\n{content}\n{{end_of_{type}}}'.format(
type = self.type,
content = _indent("\n".join([str(line) for line in self.lines])),
type=self.type,
content=_indent("\n".join([str(line) for line in self.lines])),
)
class Chorus(Verse):
"""Chorus"""
type = 'chorus'
class Bridge(Verse):
"""Bridge"""
type = 'bridge'
class Song(AST):
"""A song"""
r"""A song
Attributes:
- content: the song content, as a list of objects `foo` such that
`foo.inline` is True.
- titles: The list of titles
- language: The language (if set), None otherwise
- authors: The list of authors
- meta_beginsong: The list of directives that are to be set in the
`\beginsong{}` LaTeX directive.
- meta: Every other metadata.
"""
#: Some directives are added to the song using special methods.
METADATA_TYPE = {
"title": "add_title",
"subtitle": "add_subtitle",
"language": "add_language",
"artist": "add_author",
"key": "add_key",
}
def __init__(self):
#: Some directives have to be processed before being considered.
PROCESS_DIRECTIVE = {
"cov": "_process_relative",
"partition": "_process_relative",
"image": "_process_relative",
}
def __init__(self, filename):
super().__init__()
self.content = []
self.meta = []
self._authors = []
self._titles = []
self._subtitles = []
self._languages = set()
self._keys = []
self.filename = filename
def add(self, data):
"""Add an element to the song"""
if isinstance(data, Directive):
# Some directives are preprocessed
name = directive_name(data.keyword)
if name in self.PROCESS_DIRECTIVE:
data = getattr(self, self.PROCESS_DIRECTIVE[name])(data)
if data is None:
# New line
if not (self.content and isinstance(self.content[0], Newline)):
self.content.insert(0, Newline())
elif isinstance(data, Line):
# Add a new line, maybe in the current verse.
if not (self.content and isinstance(self.content[0], Verse)):
self.content.insert(0, Verse())
self.content[0].prepend(data.strip())
elif data.inline:
# Add an object in the content of the song.
self.content.insert(0, data)
elif isinstance(data, Directive):
# Add a metadata directive. Some of them are added using special
# methods listed in ``METADATA_TYPE``.
name = directive_name(data.keyword)
if name in self.METADATA_TYPE:
getattr(self, self.METADATA_TYPE[name])(*data.as_tuple)
@ -155,12 +230,13 @@ class Song(AST):
return self
def str_meta(self):
"""Return an iterator over *all* metadata, as strings."""
for title in self.titles:
yield "{{title: {}}}".format(title)
for language in sorted(self.languages):
yield "{{language: {}}}".format(language)
for author in self.authors:
yield "{{by: {}}}".format(author)
for key in sorted(self.keys):
yield "{{key: {}}}".format(str(key))
for key in sorted(self.meta):
yield str(key)
@ -175,32 +251,84 @@ class Song(AST):
def add_title(self, __ignored, title):
"""Add a title"""
self._titles.insert(0, title)
def add_subtitle(self, __ignored, title):
"""Add a subtitle"""
self._subtitles.insert(0, title)
@property
def titles(self):
"""Return the list of titles (and subtitles)."""
return self._titles + self._subtitles
def add_author(self, __ignored, title):
"""Add an auhor."""
self._authors.insert(0, title)
@property
def authors(self):
"""Return the list of (raw) authors."""
return self._authors
def add_language(self, __ignored, language):
self._languages.add(language)
def get_directive(self, key, default=None):
"""Return the first directive with a given key."""
for directive in self.meta:
if directive.keyword == directive_name(key):
return directive.argument
return default
def get_directives(self, key):
"""Return the list of directives with a given key."""
values = []
for directive in self.meta:
if directive.keyword == directive_name(key):
values.append(directive.argument)
return values
def add_key(self, __ignored, argument):
"""Add a new {key: foo: bar} directive."""
key, *argument = argument.split(":")
self._keys.append(Directive(
key.strip(),
":".join(argument).strip(),
))
@property
def languages(self):
return self._languages
def keys(self):
"""Return the list of keys.
That is, directive that where given of the form ``{key: foo: bar}``.
"""
return self._keys
def meta_beginsong(self):
r"""Return the meta information to be put in \beginsong."""
for directive in BEGINSONG_PROPERTIES:
if self.get_directive(directive) is not None:
yield (directive, self.get_directive(directive))
for (key, value) in self.keys:
yield (key, value)
def _process_relative(self, directive):
"""Return the directive, in which the argument is given relative to file
This argument is expected to be a path (as a string).
"""
return Directive(
directive.keyword,
os.path.join(
os.path.dirname(self.filename),
directive.argument,
),
)
class Newline(AST):
"""New line"""
_template = "newline"
def __str__(self):
return ""
@ -208,22 +336,38 @@ class Newline(AST):
class Directive(AST):
"""A directive"""
def __init__(self):
def __init__(self, keyword="", argument=None):
super().__init__()
self.keyword = ""
self.argument = None
self._keyword = None
self.keyword = keyword
self.argument = argument
@property
def _template(self):
"""Name of the template to use to render this keyword.
This only applies if ``self.inline == True``
"""
return self.keyword
@property
def keyword(self):
"""Keyword of the directive."""
return self._keyword
@property
def inline(self):
"""True iff this directive is to be rendered in the flow on the song.
"""
return self.keyword in INLINE_PROPERTIES
@keyword.setter
def keyword(self, value):
self._keyword = value.strip()
"""self.keyword setter
Replace keyword by its canonical name if it is a shortcut.
"""
self._keyword = directive_name(value.strip())
def __str__(self):
if self.argument is not None:
@ -236,6 +380,7 @@ class Directive(AST):
@property
def as_tuple(self):
"""Return the directive as a tuple."""
return (self.keyword, self.argument)
def __eq__(self, other):
@ -254,6 +399,7 @@ class Tab(AST):
self.content = []
def prepend(self, data):
"""Add an element at the beginning of content."""
self.content.insert(0, data)
return self

27
patacrep/songs/chordpro/data/latex/chordpro.tex

@ -0,0 +1,27 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ((path))
(* if language is defined *)
\selectlanguage{((language))}
(* endif *)
\songcolumns{((metadata.columns))}
\beginsong{((titles))}[
by={((authors))},
(* for (key, argument) in beginsong *)
((key))={((argument))},
(* endfor *)
]
(* if (metadata.cov is defined) or (metadata.vcov is defined) *)
\cover
(* endif *)
(* for content in content *)
(* include content.template("tex") *)
(* endfor *)
\endsong
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

1
patacrep/songs/chordpro/data/latex/content_chord.tex

@ -0,0 +1 @@
\[(( content.value ))]

1
patacrep/songs/chordpro/data/latex/content_comment.tex

@ -0,0 +1 @@
\textnote{(( content.argument ))}

3
patacrep/songs/chordpro/data/latex/content_error.tex

@ -0,0 +1,3 @@
ERROR : Template not found for \verb+(( content.__class__))+. See the logs for details.

1
patacrep/songs/chordpro/data/latex/content_guitar_comment.tex

@ -0,0 +1 @@
\musicnote{(( content.argument ))}

1
patacrep/songs/chordpro/data/latex/content_image.tex

@ -0,0 +1 @@
\image{(( content.argument ))}

1
patacrep/songs/chordpro/data/latex/content_line.tex

@ -0,0 +1 @@
(* for content in content.line *)(* include content.template("tex") *)(* endfor *)

2
patacrep/songs/chordpro/data/latex/content_newline.tex

@ -0,0 +1,2 @@

1
patacrep/songs/chordpro/data/latex/content_partition.tex

@ -0,0 +1 @@
\lilypond{((content.argument))}

1
patacrep/songs/chordpro/data/latex/content_space.tex

@ -0,0 +1 @@

7
patacrep/songs/chordpro/data/latex/content_verse.tex

@ -0,0 +1,7 @@
\begin{(( content.type ))}
(* for content in content.lines *)
(* include content.template("tex") *)
(* endfor *)
\end{(( content.type ))}

1
patacrep/songs/chordpro/data/latex/content_word.tex

@ -0,0 +1 @@
(( content.value ))

27
patacrep/songs/chordpro/lexer.py

@ -26,6 +26,7 @@ tokens = (
class ChordProLexer:
"""ChordPro Lexer class"""
# pylint: disable=too-many-public-methods
tokens = tokens
@ -44,18 +45,23 @@ class ChordProLexer:
t_directive_KEYWORD = r'[a-zA-Z_]+'
t_directiveargument_TEXT = r'[^}]+'
def t_SOC(self, token):
@staticmethod
def t_SOC(token):
r'{(soc|start_of_chorus)}'
return token
def t_EOC(self, token):
@staticmethod
def t_EOC(token):
r'{(eoc|end_of_chorus)}'
return token
def t_SOB(self, token):
@staticmethod
def t_SOB(token):
r'{(sob|start_of_bridge)}'
return token
def t_EOB(self, token):
@staticmethod
def t_EOB(token):
r'{(eob|end_of_bridge)}'
return token
@ -69,7 +75,8 @@ class ChordProLexer:
self.lexer.pop_state()
return token
def t_tablature_SPACE(self, token):
@staticmethod
def t_tablature_SPACE(token):
r'[ \t]+'
return token
@ -96,11 +103,11 @@ class ChordProLexer:
r'[^{}\n\][\t ]+'
return token
def t_LBRACKET(self, token):
def t_LBRACKET(self, __token):
r'\['
self.lexer.push_state('chord')
def t_chord_RBRACKET(self, token):
def t_chord_RBRACKET(self, __token):
r'\]'
self.lexer.pop_state()
@ -149,6 +156,6 @@ class ChordProLexer:
LOGGER.error("Illegal character '{}' in directive..".format(token.value[0]))
token.lexer.skip(1)
@staticmethod
def t_directiveargument_error(token):
return t_directive_error(token)
def t_directiveargument_error(self, token):
"""Manage errors"""
return self.t_directive_error(token)

22
patacrep/songs/chordpro/syntax.py

@ -6,7 +6,6 @@ import ply.yacc as yacc
from patacrep.errors import SongbookError
from patacrep.songs.chordpro import ast
from patacrep.songs.chordpro import ast
from patacrep.songs.chordpro.lexer import tokens, ChordProLexer
LOGGER = logging.getLogger()
@ -50,14 +49,13 @@ class Parser:
)
)
@staticmethod
def p_song(symbols):
def p_song(self, symbols):
"""song : block song
| empty
"""
#if isinstance(symbols[1], str):
if len(symbols) == 2:
symbols[0] = ast.Song()
symbols[0] = ast.Song(self.filename)
else:
symbols[0] = symbols[2].add(symbols[1])
@ -249,19 +247,13 @@ class Parser:
"""empty :"""
symbols[0] = None
def lex_song(content):
# TODO delete
lex = ChordProLexer().lexer
lex.input(content)
while 1:
tok = lex.token()
if not tok: break
print(tok)
def parse_song(content, filename=None):
"""Parse song and return its metadata."""
return yacc.yacc(module=Parser(filename)).parse(
content,
return yacc.yacc(
module=Parser(filename),
debug=0,
write_tables=0,
).parse(
content,
lexer=ChordProLexer().lexer,
)

1
patacrep/songs/chordpro/test/__init__.py

@ -0,0 +1 @@
"""Test for chordpro parser"""

7
patacrep/songs/chordpro/test/greensleeves.txt

@ -1,13 +1,14 @@
{title: Greensleeves}
{title: Un autre sous-titre}
{title: Un sous titre}
{language: english}
{by: Traditionnel}
{album: Angleterre}
{columns: 2}
{cover: traditionnel}
{partition: greensleeves.ly}
{cov: DIRNAME/traditionnel}
{language: english}
========
{partition: DIRNAME/greensleeves.ly}
{start_of_verse}
A[Am]las, my love, ye [G]do me wrong
To [Am]cast me oft dis[E]curteously

4
patacrep/songs/chordpro/test/metadata.sgc

@ -13,8 +13,8 @@
{cover: Cover}
{vcover: VCover}
{capo: Capo}
{foo: Foo}
{key: foo: Foo}
{comment: Comment}
{guitar_comment: GuitarComment}
{image: Image}
{lilypond: Lilypond}
{partition: Lilypond}

14
patacrep/songs/chordpro/test/metadata.txt

@ -4,18 +4,18 @@
{title: Subtitle3}
{title: Subtitle4}
{title: Subtitle5}
{language: english}
{language: french}
{by: Author1}
{by: Author2}
{key: {foo: Foo}}
{album: Albom}
{capo: Capo}
{copyright: Copyright}
{cover: Cover}
{foo: Foo}
{vcover: VCover}
{cov: DIRNAME/Cover}
{language: english}
{language: french}
{vcov: VCover}
========
{comment: Comment}
{guitar_comment: GuitarComment}
{image: Image}
{lilypond: Lilypond}
{image: DIRNAME/Image}
{partition: DIRNAME/Lilypond}

76
patacrep/songs/chordpro/test/test_parser.py

@ -1,36 +1,56 @@
"""Tests for the chordpro parser."""
# pylint: disable=too-few-public-methods
import glob
import os
import unittest
from patacrep.songs.chordpro import syntax as chordpro
class ParserTestCase(unittest.TestCase):
class ParserTxtRenderer(unittest.TestCase):
"""Test parser, and renderer as a txt file."""
maxDiff = None
def __init__(self, methodname="runTest", basename=None):
super().__init__(methodname)
self.basename = basename
def shortDescription(self):
return "Parsing file '{}.txt'.".format(self.basename)
def runTest(self):
"""Test txt output (default, debug output)."""
# pylint: disable=invalid-name
if self.basename is None:
return
with open("{}.sgc".format(self.basename), 'r', encoding='utf8') as sourcefile:
with open("{}.txt".format(self.basename), 'r', encoding='utf8') as expectfile:
#print(os.path.basename(sourcefile.name))
#with open("{}.txt.diff".format(self.basename), 'w', encoding='utf8') as difffile:
# difffile.write(
# str(chordpro.parse_song(
# sourcefile.read(),
# os.path.basename(sourcefile.name),
# )).strip()
# )
# sourcefile.seek(0)
self.assertMultiLineEqual(
str(chordpro.parse_song(
sourcefile.read(),
os.path.abspath(sourcefile.name),
)).strip(),
expectfile.read().strip().replace("DIRNAME", os.path.dirname(self.basename)),
)
def test_txt(self):
for txt in sorted(glob.glob(os.path.join(
os.path.dirname(__file__),
'*.txt',
def load_tests(__loader, tests, __pattern):
"""Load several tests given test files present in the directory."""
# Load all txt files as tests
for txt in sorted(glob.glob(os.path.join(
os.path.dirname(__file__),
'*.txt',
))):
basename = txt[:-len('.txt')]
with open("{}.sgc".format(basename), 'r', encoding='utf8') as sourcefile:
with open("{}.txt".format(basename), 'r', encoding='utf8') as expectfile:
#print(os.path.basename(sourcefile.name))
#with open("{}.txt.diff".format(basename), 'w', encoding='utf8') as difffile:
# difffile.write(
# str(chordpro.parse_song(
# sourcefile.read(),
# os.path.basename(sourcefile.name),
# )).strip()
# )
# sourcefile.seek(0)
self.assertMultiLineEqual(
str(chordpro.parse_song(
sourcefile.read(),
os.path.basename(sourcefile.name),
)).strip(),
expectfile.read().strip(),
)
def test_tex(self):
# TODO
pass
tests.addTest(ParserTxtRenderer(basename=txt[:-len('.txt')]))
return tests

2
patacrep/songs/latex/__init__.py

@ -14,7 +14,7 @@ from patacrep.songs import Song
class LatexSong(Song):
"""LaTeX song parser."""
def parse(self, content):
def parse(self, __config):
"""Parse content, and return the dictinory of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
self.data = parse_song(song.read(), self.fullpath)

40
patacrep/templates.py

@ -64,9 +64,29 @@ def _escape_tex(value):
return newval
class TexRenderer(object):
class TexRenderer:
"""Render a template to a LaTeX file."""
def __init__(self, template, texenv, encoding=None):
self.encoding = encoding
self.texenv = texenv
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 = '(% comment %)'
self.texenv.comment_end_string = '(% endcomment %)'
self.texenv.line_comment_prefix = '%!'
self.texenv.filters['escape_tex'] = _escape_tex
self.texenv.trim_blocks = True
self.texenv.lstrip_blocks = True
self.texenv.globals["path2posix"] = files.path2posix
self.template = self.texenv.get_template(template)
class TexBookRenderer(TexRenderer):
"""Tex renderer for the whole songbook"""
def __init__(self, template, datadirs, lang, encoding=None):
'''Start a new jinja2 environment for .tex creation.
@ -78,29 +98,15 @@ class TexRenderer(object):
- encoding: if set, encoding of the template.
'''
self.lang = lang
self.encoding = encoding
# Load templates in filesystem ...
loaders = [FileSystemLoader(os.path.join(datadir, 'templates'))
for datadir in datadirs]
self.texenv = Environment(
texenv = Environment(
loader=ChoiceLoader(loaders),
extensions=[VariablesExtension],
)
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 = '(% comment %)'
self.texenv.comment_end_string = '(% endcomment %)'
self.texenv.line_comment_prefix = '%!'
self.texenv.filters['escape_tex'] = _escape_tex
self.texenv.trim_blocks = True
self.texenv.lstrip_blocks = True
self.texenv.globals["path2posix"] = files.path2posix
try:
self.template = self.texenv.get_template(template)
super().__init__(template, texenv, encoding)
except TemplateNotFound as exception:
# Only works if all loaders are FileSystemLoader().
paths = [

26
patacrep/test.py

@ -0,0 +1,26 @@
"""Tests"""
import doctest
import os
import unittest
import patacrep
def suite():
"""Return a TestSuite object, to test whole `patacrep` package.
Both unittest and doctest are tested.
"""
test_loader = unittest.defaultTestLoader
return test_loader.discover(os.path.dirname(__file__))
def load_tests(__loader, tests, __pattern):
"""Load tests (unittests and doctests)."""
# Loading doctests
tests.addTests(doctest.DocTestSuite(patacrep))
# Unittests are loaded by default
return tests
if __name__ == "__main__":
unittest.TextTestRunner().run(suite())

3
setup.py

@ -38,6 +38,7 @@ setup(
"Programming Language :: Python :: 3.4",
"Topic :: Utilities",
],
platforms=["GNU/Linux", "Windows", "MacOsX"]
platforms=["GNU/Linux", "Windows", "MacOsX"],
test_suite="patacrep.test.suite",
long_description = open("README.rst", "r").read(),
)

Loading…
Cancel
Save