Browse Source

Merge branch 'master' into test_compilation

pull/120/head
Louis 9 years ago
parent
commit
c99469d557
  1. 3
      examples/example-all.sb
  2. 2
      examples/songs/greensleeves.sgc
  3. 2
      examples/songs/tests/chords.sgc
  4. 2
      examples/songs/tests/errors.sgc
  5. 6
      patacrep/build.py
  6. 8
      patacrep/content/song.py
  7. 2
      patacrep/data/templates/patacrep.tex
  8. 12
      patacrep/data/templates/songs.tex
  9. 14
      patacrep/files.py
  10. 23
      patacrep/latex/__init__.py
  11. 6
      patacrep/latex/ast.py
  12. 2
      patacrep/songbook/__main__.py
  13. 66
      patacrep/songs/__init__.py
  14. 120
      patacrep/songs/chordpro/__init__.py
  15. 4
      patacrep/songs/chordpro/ast.py
  16. 4
      patacrep/songs/chordpro/data/chordpro/song_header
  17. 5
      patacrep/songs/chordpro/data/html/content_image
  18. 7
      patacrep/songs/chordpro/data/html/content_metadata_cover
  19. 7
      patacrep/songs/chordpro/data/html/content_partition
  20. 8
      patacrep/songs/chordpro/data/html/content_verse
  21. 4
      patacrep/songs/chordpro/data/html/song_header
  22. 7
      patacrep/songs/chordpro/data/latex/content_image
  23. 5
      patacrep/songs/chordpro/data/latex/content_partition
  24. 11
      patacrep/songs/chordpro/data/latex/song
  25. 19
      patacrep/songs/convert/__main__.py
  26. 39
      patacrep/songs/latex/__init__.py
  27. 4
      patacrep/templates.py
  28. 61
      patacrep/utils.py
  29. 2
      test/test_chordpro/00.sgc
  30. 2
      test/test_chordpro/01.sgc
  31. 2
      test/test_chordpro/02.sgc
  32. 2
      test/test_chordpro/03.sgc
  33. 2
      test/test_chordpro/04.sgc
  34. 2
      test/test_chordpro/05.sgc
  35. 2
      test/test_chordpro/06.sgc
  36. 2
      test/test_chordpro/07.sgc
  37. 2
      test/test_chordpro/08.sgc
  38. 2
      test/test_chordpro/09.sgc
  39. 2
      test/test_chordpro/10.sgc
  40. 2
      test/test_chordpro/11.sgc
  41. 2
      test/test_chordpro/12.sgc
  42. 2
      test/test_chordpro/13.sgc
  43. 2
      test/test_chordpro/21.sgc
  44. 2
      test/test_chordpro/22.sgc
  45. 2
      test/test_chordpro/23.sgc
  46. 2
      test/test_chordpro/24.sgc
  47. 2
      test/test_chordpro/25.sgc
  48. 2
      test/test_chordpro/26.sgc
  49. 2
      test/test_chordpro/27.sgc
  50. 2
      test/test_chordpro/28.sgc
  51. 2
      test/test_chordpro/29.sgc
  52. 2
      test/test_chordpro/author_names.sgc
  53. 2
      test/test_chordpro/chords.sgc
  54. 2
      test/test_chordpro/customchords.sgc
  55. 0
      test/test_chordpro/datadir/img/traditionnel.png
  56. 0
      test/test_chordpro/datadir/scores/greensleeves.ly
  57. 2
      test/test_chordpro/greensleeves.sgc
  58. 2
      test/test_chordpro/greensleeves.source
  59. 4
      test/test_chordpro/greensleeves.tex
  60. 2
      test/test_chordpro/invalid_chord.sgc
  61. 2
      test/test_chordpro/invalid_customchord.sgc
  62. 1
      test/test_chordpro/lang.sgc
  63. 1
      test/test_chordpro/lang.source
  64. 12
      test/test_chordpro/metadata.sgc
  65. 14
      test/test_chordpro/metadata.source
  66. 10
      test/test_chordpro/metadata.tex
  67. 0
      test/test_chordpro/metadata_cover.png
  68. 0
      test/test_chordpro/metadata_image.png
  69. 0
      test/test_chordpro/metadata_lilypond.ly
  70. 51
      test/test_chordpro/newline.html
  71. 2
      test/test_chordpro/newline.sgc
  72. 2
      test/test_chordpro/nolyrics.sgc
  73. 22
      test/test_chordpro/test_parser.py
  74. 2
      test/test_chordpro/ukulelechords.sgc
  75. 3
      test/test_compilation/subdir.sb
  76. 13
      test/test_compilation/subdir.tex.control
  77. 0
      test/test_compilation/subdir_datadir/scores/datadir.ly
  78. 6
      test/test_compilation/subdir_datadir/songs/datadir.sg
  79. 6
      test/test_compilation/subdir_datadir/songs/datadir2.sg
  80. 0
      test/test_compilation/subdir_datadir2/scores/datadir2.ly
  81. 10
      test/test_compilation/test_compilation.py

3
examples/example-all.sb

@ -6,8 +6,9 @@
"pictures" "pictures"
], ],
"booktype" : "chorded", "booktype" : "chorded",
"datadir": ["datadir2"],
"template" : "patacrep.tex", "template" : "patacrep.tex",
"lang" : "french", "lang" : "fr",
"encoding": "utf8", "encoding": "utf8",
"authwords" : { "authwords" : {
"sep" : ["and", "et"] "sep" : ["and", "et"]

2
examples/songs/greensleeves.sgc

@ -1,4 +1,4 @@
{language : english} {lang : en}
{columns : 2} {columns : 2}
{ title : Greensleeves} { title : Greensleeves}
{subtitle: Test of the chordpro format} {subtitle: Test of the chordpro format}

2
examples/songs/tests/chords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{columns: 1} {columns: 1}
{title: Chords testing} {title: Chords testing}
{subtitle: Test of the chords specification and LaTeX translation} {subtitle: Test of the chords specification and LaTeX translation}

2
examples/songs/tests/errors.sgc

@ -1,4 +1,4 @@
{language : english} {lang : en}
{columns : 2} {columns : 2}
{ title : Error} { title : Error}
{subtitle: A chordpro file with many errors} {subtitle: A chordpro file with many errors}

6
patacrep/build.py

@ -29,7 +29,7 @@ GENERATED_EXTENSIONS = [
] ]
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
'template': "default.tex", 'template': "default.tex",
'lang': 'english', 'lang': 'en',
'content': [], 'content': [],
'titleprefixwords': [], 'titleprefixwords': [],
'encoding': None, 'encoding': None,
@ -110,8 +110,8 @@ class Songbook(object):
config['_song_plugins'] = files.load_plugins( config['_song_plugins'] = files.load_plugins(
datadirs=config.get('datadir', []), datadirs=config.get('datadir', []),
root_modules=['songs'], root_modules=['songs'],
keyword='SONG_PARSERS', keyword='SONG_RENDERERS',
) )['latex']
# Configuration set # Configuration set
config['render'] = content.render config['render'] = content.render

8
patacrep/content/song.py

@ -43,7 +43,7 @@ class SongRenderer(Content):
""").format( """).format(
separator="%"*80, separator="%"*80,
path=self.song.subpath, path=self.song.subpath,
song=self.song.render(output=context['filename'], output_format="latex"), song=self.song.render(output=context['filename']),
) )
#pylint: disable=unused-argument #pylint: disable=unused-argument
@ -60,8 +60,8 @@ def parse(keyword, argument, contentlist, config):
Return a list of Song() instances. Return a list of Song() instances.
""" """
plugins = config['_song_plugins'] plugins = config['_song_plugins']
if '_languages' not in config: if '_langs' not in config:
config['_languages'] = set() config['_langs'] = set()
songlist = [] songlist = []
for songdir in config['_songdir']: for songdir in config['_songdir']:
if contentlist: if contentlist:
@ -92,7 +92,7 @@ def parse(keyword, argument, contentlist, config):
datadir=songdir.datadir, datadir=songdir.datadir,
)) ))
songlist.append(renderer) songlist.append(renderer)
config["_languages"].update(renderer.song.languages) config["_langs"].add(renderer.song.lang)
if len(songlist) > before: if len(songlist) > before:
break break
if len(songlist) == before: if len(songlist) == before:

2
patacrep/data/templates/patacrep.tex

@ -34,7 +34,7 @@
}, },
"picture": {"description": {"english": "Cover picture", "french": "Image de couverture"}, "picture": {"description": {"english": "Cover picture", "french": "Image de couverture"},
"type": "file", "type": "file",
"default": {"default": "treble_a"} "default": {"default": "img/treble_a"}
}, },
"picturecopyright": {"description": {"english": "Copyright for the cover picture", "french": "Copyright pour l'image de couverture"}, "picturecopyright": {"description": {"english": "Copyright for the cover picture", "french": "Copyright pour l'image de couverture"},
"default": {"default": "Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"} "default": {"default": "Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"}

12
patacrep/data/templates/songs.tex

@ -52,7 +52,7 @@
"mandatory": true "mandatory": true
}, },
"lang": {"description": {"english": "Language", "french": "Langue"}, "lang": {"description": {"english": "Language", "french": "Langue"},
"default": {"english": "english", "french": "french"} "default": {"english": "en", "french": "fr"}
}, },
"titleprefixwords": {"description": {"english": "Ignore some words in the beginning of song titles", "titleprefixwords": {"description": {"english": "Ignore some words in the beginning of song titles",
"french": "Ignore des mots dans le classement des chansons"}, "french": "Ignore des mots dans le classement des chansons"},
@ -80,16 +80,16 @@
(* block songbookpreambule *) (* block songbookpreambule *)
(( super() )) (( super() ))
(* for lang in _languages -*) (* for lang in _langs -*)
\PassOptionsToPackage{((lang))}{babel} \PassOptionsToPackage{(( lang2babel(lang) ))}{babel}
(* endfor *) (* endfor *)
\usepackage[((lang))]{babel} \usepackage[(( lang2babel(lang) ))]{babel}
\lang{((lang))} \lang{(( lang2babel(lang) ))}
\usepackage{graphicx} \usepackage{graphicx}
\graphicspath{ % \graphicspath{ %
(* for dir in datadir *) (* for dir in datadir *)
{(( path2posix(dir) ))/img/} % {(( path2posix(dir) ))/} %
(* endfor *) (* endfor *)
} }

14
patacrep/files.py

@ -8,6 +8,8 @@ import posixpath
import re import re
import sys import sys
from patacrep import utils
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
def recursive_find(root_directory, extensions): def recursive_find(root_directory, extensions):
@ -104,7 +106,7 @@ def load_plugins(datadirs, root_modules, keyword):
- keys are the keywords ; - keys are the keywords ;
- values are functions triggered when this keyword is met. - values are functions triggered when this keyword is met.
""" """
plugins = {} plugins = utils.DictOfDict()
datadir_path = [ datadir_path = [
os.path.join(datadir, "python", *root_modules) os.path.join(datadir, "python", *root_modules)
@ -121,13 +123,5 @@ def load_plugins(datadirs, root_modules, keyword):
prefix="patacrep.{}.".format(".".join(root_modules)) prefix="patacrep.{}.".format(".".join(root_modules))
): ):
if hasattr(module, keyword): if hasattr(module, keyword):
for (key, value) in getattr(module, keyword).items(): plugins.update(getattr(module, keyword))
if key in plugins:
LOGGER.warning(
"File %s: Keyword '%s' is already used. Ignored.",
module.__file__,
key,
)
continue
plugins[key] = value
return plugins return plugins

23
patacrep/latex/__init__.py

@ -5,4 +5,27 @@ This module uses an LALR parser to try to parse LaTeX code. LaTeX language
will work on simple cases, but not on complex ones. will work on simple cases, but not on complex ones.
""" """
import logging
from collections import OrderedDict
from patacrep.latex.syntax import tex2plain, parse_song from patacrep.latex.syntax import tex2plain, parse_song
LOGGER = logging.getLogger(__name__)
BABEL_LANGUAGES = OrderedDict((
('fr', 'french'),
('en', 'english'),
('de', 'german'),
('es', 'spanish'),
('it', 'italian'),
('pt', 'portuguese'),
))
def lang2babel(lang):
"""Return the language used by babel, corresponding to the language code"""
try:
return BABEL_LANGUAGES[lang]
except KeyError:
available = ", ".join(BABEL_LANGUAGES.keys())
LOGGER.error('Unknown lang code: ' + lang + '. Supported: ' + available)
return 'english'

6
patacrep/latex/ast.py

@ -2,6 +2,8 @@
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
DEFAULT_LANGUAGE = "english"
class AST: class AST:
"""Base class for the tree.""" """Base class for the tree."""
# pylint: disable=no-init # pylint: disable=no-init
@ -16,7 +18,7 @@ class AST:
parsing. parsing.
""" """
cls.metadata = { cls.metadata = {
'@languages': set(), '@language': DEFAULT_LANGUAGE,
} }
class Expression(AST): class Expression(AST):
@ -44,7 +46,7 @@ class Command(AST):
self.optional = optional self.optional = optional
if name == r'\selectlanguage': if name == r'\selectlanguage':
self.metadata['@languages'] |= set(self.mandatory) self.metadata['@language'] = self.mandatory[0]
def __str__(self): def __str__(self):
if self.name in [r'\emph']: if self.name in [r'\emph']:

2
patacrep/songbook/__main__.py

@ -108,6 +108,8 @@ def main():
options = argument_parser(sys.argv[1:]) options = argument_parser(sys.argv[1:])
songbook_path = options.book[0] songbook_path = options.book[0]
if os.path.exists(songbook_path + ".sb") and not os.path.exists(songbook_path):
songbook_path += ".sb"
basename = os.path.basename(songbook_path)[:-3] basename = os.path.basename(songbook_path)[:-3]

66
patacrep/songs/__init__.py

@ -89,7 +89,7 @@ class Song:
"cached", "cached",
"data", "data",
"subpath", "subpath",
"languages", "lang",
"authors", "authors",
"_filehash", "_filehash",
"_version", "_version",
@ -131,6 +131,7 @@ class Song:
self.titles = [] self.titles = []
self.data = {} self.data = {}
self.cached = None self.cached = None
self.lang = None
self._parse(config) self._parse(config)
# Post processing of data # Post processing of data
@ -167,16 +168,12 @@ class Song:
def __repr__(self): def __repr__(self):
return repr((self.titles, self.data, self.fullpath)) return repr((self.titles, self.data, self.fullpath))
def render(self, output_format, output=None, *args, **kwargs): def render(self, output=None, *args, **kwargs):
"""Return the code rendering this song. """Return the code rendering this song.
Arguments: Arguments:
- output_format: Format of the output file (latex, chordpro...)
- output: Name of the output file, or `None` if irrelevant. - output: Name of the output file, or `None` if irrelevant.
""" """
method = "render_{}".format(output_format)
if hasattr(self, method):
return getattr(self, method)(output, *args, **kwargs)
raise NotImplementedError() raise NotImplementedError()
def _parse(self, config): # pylint: disable=no-self-use def _parse(self, config): # pylint: disable=no-self-use
@ -186,8 +183,7 @@ class Song:
- titles: the list of (raw) titles. This list will be processed to - titles: the list of (raw) titles. This list will be processed to
remove prefixes. remove prefixes.
- languages: the list of languages used in the song, as languages - lang: the main language of the song, as language code..
recognized by the LaTeX babel package.
- authors: the list of (raw) authors. This list will be processed to - authors: the list of (raw) authors. This list will be processed to
'clean' it (see function :func:`patacrep.authors.processauthors`). 'clean' it (see function :func:`patacrep.authors.processauthors`).
- data: song metadata. Used (among others) to sort the songs. - data: song metadata. Used (among others) to sort the songs.
@ -204,15 +200,24 @@ class Song:
if os.path.isdir(fullpath): if os.path.isdir(fullpath):
yield fullpath yield fullpath
def search_file(self, filename, extensions=None, directories=None): def search_datadir_file(self, filename, extensions=None, directories=None):
"""Search for a file name. """Search for a file name.
:param str filename: The name, as provided in the chordpro file (with or without extension). :param str filename: The name, as provided in the chordpro file (with or without extension).
:param list extensions: Possible extensions (with '.'). Default is no extension. :param list extensions: Possible extensions (with '.'). Default is no extension.
:param iterator directories: Other directories where to search for the file :param iterator directories: Other directories where to search for the file
The directory where the Song file is stored is added to the list. The directory where the Song file is stored is added to the list.
:return: A tuple `(datadir, filename, extension)` if file has been
Returns None if nothing found. found. It is guaranteed that `os.path.join(datadir,
filename+extension)` is a (relative or absolute) valid path to an
existing filename.
* `datadir` is the datadir in which the file has been found. Can be
the empty string.
* `filename` is the filename, relative to the datadir.
* `extension` is the extension that is to be appended to the
filename to get the real filename. Can be the empty string.
Raise `FileNotFoundError` if nothing found.
This function can also be used as a preprocessor for a renderer: for This function can also be used as a preprocessor for a renderer: for
instance, it can compile a file, place it in a temporary folder, and instance, it can compile a file, place it in a temporary folder, and
@ -224,27 +229,42 @@ class Song:
directories = self.config['datadir'] directories = self.config['datadir']
songdir = os.path.dirname(self.fullpath) songdir = os.path.dirname(self.fullpath)
for extension in extensions:
if os.path.isfile(os.path.join(songdir, filename + extension)):
return "", os.path.join(songdir, filename), extension
for directory in [songdir] + list(directories): for directory in directories:
for extension in extensions: for extension in extensions:
fullpath = os.path.join(directory, filename + extension) if os.path.isfile(os.path.join(directory, filename + extension)):
if os.path.isfile(fullpath): return directory, filename, extension
return os.path.abspath(fullpath)
return None raise FileNotFoundError(filename)
def search_file(self, filename, extensions=None, *, datadirs=None):
"""Return the path to a file present in a datadir.
Implementation is specific to each renderer, as:
- some renderers can preprocess files;
- some renderers can return the absolute path, other can return something else;
- etc.
"""
raise NotImplementedError()
def search_image(self, filename, none_if_not_found=False): def search_image(self, filename):
"""Search for an image file""" """Search for an image file"""
filepath = self.search_file( return self.search_file(
filename, filename,
['', '.jpg', '.png'], ['', '.jpg', '.png'],
self.get_datadirs('img'), datadirs=self.get_datadirs('img'),
) )
return filepath if none_if_not_found or filepath else filename
def search_partition(self, filename, none_if_not_found=False): def search_partition(self, filename):
"""Search for a lilypond file""" """Search for a lilypond file"""
filepath = self.search_file(filename, ['', '.ly']) return self.search_file(
return filepath if none_if_not_found or filepath else filename filename,
['', '.ly'],
datadirs=self.get_datadirs('scores'),
)
def unprefixed_title(title, prefixes): def unprefixed_title(title, prefixes):
"""Remove the first prefix of the list in the beginning of title (if any). """Remove the first prefix of the list in the beginning of title (if any).

120
patacrep/songs/chordpro/__init__.py

@ -1,33 +1,24 @@
"""Chordpro parser""" """Chordpro parser"""
from jinja2 import Environment, FileSystemLoader, contextfunction from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader
import jinja2 import jinja2
import logging
import os import os
import pkg_resources from pkg_resources import resource_filename
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 Renderer from patacrep.templates import Renderer
from patacrep.latex import lang2babel
LOGGER = logging.getLogger(__name__)
class ChordproSong(Song): class ChordproSong(Song):
"""Chordpros song parser.""" """Chordpro song parser"""
# pylint: disable=abstract-method
@staticmethod output_language = None
def iter_template_paths(templatedirs, output_format):
"""Iterate over paths in which templates are to be searched.
:param iterator templatedirs: Iterators of additional directories (the
default hard-coded template directory is returned last).
:param str output_format: Song output format, which is appended to
each directory.
"""
for directory in templatedirs:
yield os.path.join(directory, output_format)
yield os.path.join(
os.path.abspath(pkg_resources.resource_filename(__name__, 'data')),
output_format,
)
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."""
@ -35,18 +26,15 @@ class ChordproSong(Song):
song = parse_song(song.read(), self.fullpath) song = parse_song(song.read(), self.fullpath)
self.authors = song.authors self.authors = song.authors
self.titles = song.titles self.titles = song.titles
self.languages = song.get_data_argument('language', [self.config['lang']]) self.lang = song.get_data_argument('lang', self.config['lang'])
self.data = song.meta self.data = song.meta
self.cached = { self.cached = {
'song': song, 'song': song,
} }
def render(self, output_format, output=None, template="song", templatedirs=None): # pylint: disable=arguments-differ def render(self, output=None, template="song"): # pylint: disable=arguments-differ
if templatedirs is None:
templatedirs = []
context = { context = {
'language': self.languages[0], 'lang': self.lang,
"titles": self.titles, "titles": self.titles,
"authors": self.authors, "authors": self.authors,
"metadata": self.data, "metadata": self.data,
@ -55,11 +43,17 @@ class ChordproSong(Song):
"content": self.cached['song'].content, "content": self.cached['song'].content,
} }
jinjaenv = Environment(loader=FileSystemLoader( jinjaenv = Environment(loader=ChoiceLoader([
self.iter_template_paths(templatedirs, output_format) FileSystemLoader(
)) self.get_datadirs(os.path.join("templates", self.output_language))
),
FileSystemLoader(
os.path.join(resource_filename(__name__, 'data'), self.output_language)
),
]))
jinjaenv.filters['search_image'] = self.search_image jinjaenv.filters['search_image'] = self.search_image
jinjaenv.filters['search_partition'] = self.search_partition jinjaenv.filters['search_partition'] = self.search_partition
jinjaenv.filters['lang2babel'] = lang2babel
try: try:
return Renderer( return Renderer(
@ -68,7 +62,7 @@ class ChordproSong(Song):
jinjaenv=jinjaenv, jinjaenv=jinjaenv,
).template.render(context) ).template.render(context)
except jinja2.exceptions.TemplateNotFound: except jinja2.exceptions.TemplateNotFound:
raise NotImplementedError("Cannot convert to format '{}'.".format(output_format)) raise NotImplementedError("Cannot convert to format '{}'.".format(self.output_language))
@staticmethod @staticmethod
@contextfunction @contextfunction
@ -77,6 +71,72 @@ class ChordproSong(Song):
context.vars['content'] = content context.vars['content'] = content
return context.environment.get_template(content.template()).render(context) return context.environment.get_template(content.template()).render(context)
SONG_PARSERS = { class Chordpro2HtmlSong(ChordproSong):
'sgc': ChordproSong, """Render chordpro song to html code"""
output_language = "html"
def search_file(self, filename, extensions=None, *, datadirs=None):
try:
datadir, filename, extension = self.search_datadir_file(filename, extensions, datadirs)
return os.path.join(datadir, filename + extension)
except FileNotFoundError:
LOGGER.warning(
"Song '%s' (datadir '%s'): File '%s' not found.",
self.subpath, self.datadir, filename,
)
return None
class Chordpro2LatexSong(ChordproSong):
"""Render chordpro song to latex code"""
output_language = "latex"
def search_file(self, filename, extensions=None, *, datadirs=None):
_datadir, filename, _extension = self.search_datadir_file(
filename,
extensions,
datadirs,
)
return filename
def search_partition(self, filename):
try:
return os.path.join("scores", super().search_partition(filename))
except FileNotFoundError:
LOGGER.warning(
"Song '%s' (datadir '%s'): Score '%s' not found.",
self.subpath, self.datadir, filename,
)
return None
def search_image(self, filename):
try:
return os.path.join("img", super().search_image(filename))
except FileNotFoundError:
LOGGER.warning(
"Song '%s' (datadir '%s'): Image '%s' not found.",
self.subpath, self.datadir, filename,
)
return None
class Chordpro2ChordproSong(ChordproSong):
"""Render chordpro song to chordpro code"""
output_language = "chordpro"
def search_file(self, filename, extensions=None, *, datadirs=None):
# pylint: disable=unused-variable
return filename
SONG_RENDERERS = {
"latex": {
'sgc': Chordpro2LatexSong,
},
"html": {
'sgc': Chordpro2HtmlSong,
},
"chordpro": {
'sgc': Chordpro2ChordproSong,
},
} }

4
patacrep/songs/chordpro/ast.py

@ -31,6 +31,7 @@ DIRECTIVE_SHORTCUTS = {
"c": "comment", "c": "comment",
"gc": "guitar_comment", "gc": "guitar_comment",
"cover": "cov", "cover": "cov",
"language": "lang",
} }
def directive_name(text): def directive_name(text):
@ -181,7 +182,7 @@ class Song(AST):
- content: the song content, as a list of objects `foo` such that - content: the song content, as a list of objects `foo` such that
`foo.inline` is True. `foo.inline` is True.
- titles: The list of titles - titles: The list of titles
- language: The language (if set), None otherwise - lang: The language code (if set), None otherwise
- authors: The list of authors - authors: The list of authors
- meta: Every other metadata. - meta: Every other metadata.
""" """
@ -193,7 +194,6 @@ class Song(AST):
"artist": "add_author", "artist": "add_author",
"key": "add_key", "key": "add_key",
"define": "add_cumulative", "define": "add_cumulative",
"language": "add_cumulative",
} }
def __init__(self, filename): def __init__(self, filename):

4
patacrep/songs/chordpro/data/chordpro/song_header

@ -1,5 +1,5 @@
(* if language is defined -*) (* if lang is defined -*)
{language: (( language ))} {lang: (( lang ))}
(* endif *) (* endif *)
(* if metadata.columns is defined -*) (* if metadata.columns is defined -*)
{columns: (( metadata.columns ))} {columns: (( metadata.columns ))}

5
patacrep/songs/chordpro/data/html/content_image

@ -1 +1,6 @@
(* block image *)
(* set image = content.argument|search_image *)
(* if image *)
<img src="(( content.argument|search_image ))"> <img src="(( content.argument|search_image ))">
(* endif *)
(* endblock *)

7
patacrep/songs/chordpro/data/html/content_metadata_cover

@ -1,3 +1,8 @@
(* block cov *)
(* if 'cov' in metadata -*) (* if 'cov' in metadata -*)
<img src="(( metadata['cov'].argument|search_image ))"><br> (* set cov = metadata['cov'].argument|search_image *)
(* if cov *)
<img src="(( cov ))"><br>
(* endif *)
(* endif *) (* endif *)
(* endblock *)

7
patacrep/songs/chordpro/data/html/content_partition

@ -1 +1,6 @@
<a class="song-partition" href="(( content.argument|search_partition ))">((content.argument))</a> (* block partition *)
(* set partition = content.argument|search_partition *)
(* if partition *)
<a class="song-partition" href="(( partition ))">((content.argument))</a>
(* endif *)
(* endblock *)

8
patacrep/songs/chordpro/data/html/content_verse

@ -1,5 +1,11 @@
(* if content.directive() *)
<p class="directives">
(*- elif content.nolyrics -*)
<p class="nolyrics">
(*- else *)
<p class="(( content.type ))"> <p class="(( content.type ))">
(* for line in content.lines *) (*- endif *)
(* for line in content.lines -*)
(( render(line) )) (( render(line) ))
(*- if not loop.last *)<br> (*- if not loop.last *)<br>
(* endif *) (* endif *)

4
patacrep/songs/chordpro/data/html/song_header

@ -17,8 +17,8 @@
(* endif *) (* endif *)
(* endfor *) (* endfor *)
(* if language is defined -*) (* if lang is defined -*)
<span class="song-language">Language: (( language ))</span><br> <span class="song-language">Lang: (( lang ))</span><br>
(* endif *) (* endif *)
(* include 'content_metadata_cover' *) (* include 'content_metadata_cover' *)

7
patacrep/songs/chordpro/data/latex/content_image

@ -1 +1,6 @@
\image{(( content.argument|search_image ))} (* block image *)
(* set image = content.argument|search_image *)
(* if image *)
\image{(( image ))}
(*- endif *)
(*- endblock *)

5
patacrep/songs/chordpro/data/latex/content_partition

@ -1 +1,6 @@
(* block partition *)
(* set partition = content.argument|search_partition *)
(* if partition *)
\lilypond{ ((- content.argument|search_partition -)) } \lilypond{ ((- content.argument|search_partition -)) }
(*- endif -*)
(*- endblock -*)

11
patacrep/songs/chordpro/data/latex/song

@ -1,5 +1,5 @@
(* if language is defined -*) (* if lang is defined -*)
\selectlanguage{((language))} \selectlanguage{(( lang2babel(lang) ))}
(* endif *) (* endif *)
(*- if metadata.columns is defined *) (*- if metadata.columns is defined *)
@ -28,7 +28,12 @@
(* endif *) (* endif *)
(* endfor *) (* endfor *)
(* if 'cov' in metadata *) (* if 'cov' in metadata *)
cov={(( metadata["cov"].argument|search_image ))}, (* block cov *)
(* set cov = metadata["cov"].argument|search_image *)
(* if cov *)
cov={(( cov ))},
(* endif *)
(* endblock *)
(* endif *) (* endif *)
(* for key in metadata.morekeys *) (* for key in metadata.morekeys *)
(( key.keyword ))={(( key.argument ))}, (( key.keyword ))={(( key.argument ))},

19
patacrep/songs/convert/__main__.py

@ -36,22 +36,29 @@ if __name__ == "__main__":
dest = sys.argv[2] dest = sys.argv[2]
song_files = sys.argv[3:] song_files = sys.argv[3:]
song_parsers = files.load_plugins( renderers = files.load_plugins(
datadirs=DEFAULT_CONFIG.get('datadir', []), datadirs=DEFAULT_CONFIG.get('datadir', []),
root_modules=['songs'], root_modules=['songs'],
keyword='SONG_PARSERS', keyword='SONG_RENDERERS',
) )
if source not in song_parsers: if dest not in renderers:
LOGGER.error( LOGGER.error(
"Unknown file format '%s'. Available ones are %s.", "Unknown destination file format '%s'. Available ones are %s.",
source, source,
", ".join(["'{}'".format(key) for key in song_parsers.keys()]) ", ".join(["'{}'".format(key) for key in renderers.keys()])
)
sys.exit(1)
if source not in renderers[dest]:
LOGGER.error(
"Unknown source file format '%s'. Available ones are %s.",
source,
", ".join(["'{}'".format(key) for key in renderers[dest].keys()])
) )
sys.exit(1) sys.exit(1)
for file in song_files: for file in song_files:
song = song_parsers[source](file, DEFAULT_CONFIG) song = renderers[dest][source](file, DEFAULT_CONFIG)
try: try:
destname = "{}.{}".format(".".join(file.split(".")[:-1]), dest) destname = "{}.{}".format(".".join(file.split(".")[:-1]), dest)
if os.path.exists(destname): if os.path.exists(destname):

39
patacrep/songs/latex/__init__.py

@ -8,28 +8,30 @@ will work on simple cases, but not on complex ones.
import os import os
from patacrep import files, encoding from patacrep import files, encoding
from patacrep.latex import parse_song from patacrep.latex import parse_song, BABEL_LANGUAGES
from patacrep.songs import Song from patacrep.songs import Song
class LatexSong(Song): class Latex2LatexSong(Song):
"""LaTeX song parser.""" """Song written in LaTeX, rendered in LaTeX"""
# pylint: disable=abstract-method
def _parse(self, __config): def _parse(self, __config):
"""Parse content, and return the dictinory of song data.""" """Parse content, and return the dictionary of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song: with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
self.data = parse_song(song.read(), self.fullpath) self.data = parse_song(song.read(), self.fullpath)
self.titles = self.data['@titles'] self.titles = self.data['@titles']
del self.data['@titles'] del self.data['@titles']
self.languages = self.data['@languages'] self.set_lang(self.data['@language'])
del self.data['@languages'] del self.data['@language']
if "by" in self.data: if "by" in self.data:
self.authors = [self.data['by']] self.authors = [self.data['by']]
del self.data['by'] del self.data['by']
else: else:
self.authors = [] self.authors = []
def render_latex(self, output): def render(self, output):
"""Return the code rendering the song.""" """Return the code rendering the song."""
# pylint: disable=signature-differs
if output is None: if output is None:
raise ValueError(output) raise ValueError(output)
path = files.path2posix(files.relpath( path = files.path2posix(files.relpath(
@ -38,7 +40,22 @@ class LatexSong(Song):
)) ))
return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path)) return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path))
SONG_PARSERS = { def set_lang(self, language):
'is': LatexSong, """Set the language code"""
'sg': LatexSong, for lang, babel_language in BABEL_LANGUAGES.items():
} if language == babel_language:
self.lang = lang
return
# Add a custom language to the babel dictionary (language is not officially supported)
custom_lang = '_' + language
BABEL_LANGUAGES[custom_lang] = language
self.lang = custom_lang
SONG_RENDERERS = {
"latex": {
'is': Latex2LatexSong,
'sg': Latex2LatexSong,
},
}

4
patacrep/templates.py

@ -9,6 +9,7 @@ import re
import json import json
from patacrep import errors, files from patacrep import errors, files
from patacrep.latex import lang2babel
import patacrep.encoding import patacrep.encoding
_LATEX_SUBS = ( _LATEX_SUBS = (
@ -84,6 +85,7 @@ class Renderer:
self.jinjaenv.trim_blocks = True self.jinjaenv.trim_blocks = True
self.jinjaenv.lstrip_blocks = True self.jinjaenv.lstrip_blocks = True
self.jinjaenv.globals["path2posix"] = files.path2posix self.jinjaenv.globals["path2posix"] = files.path2posix
self.jinjaenv.globals["lang2babel"] = lang2babel
self.template = self.jinjaenv.get_template(template) self.template = self.jinjaenv.get_template(template)
@ -153,7 +155,7 @@ class TexBookRenderer(Renderer):
variable = default["default"] variable = default["default"]
elif "en" in default: elif "en" in default:
variable = default["en"] variable = default["en"]
elif len(default > 0): elif len(default):
variable = default.popitem()[1] variable = default.popitem()[1]
else: else:
variable = None variable = None

61
patacrep/utils.py

@ -0,0 +1,61 @@
"""Some utility functions"""
from collections import UserDict
class DictOfDict(UserDict):
"""Dictionary, with a recursive :meth:`update` method.
By "recursive", we mean: if `self.update(other)` is called, and for some
key both `self[key]` and `other[key]` are dictionary, then `self[key]` is
not replaced by `other[key]`, but instead is updated. This is done
recursively (that is, `self[foo][bar][baz]` is updated with
`other[foo][bar][baz]`, if the corresponding objects are dictionaries).
>>> ordinal = DictOfDict({
... "francais": {
... 1: "premier",
... 2: "deuxieme",
... },
... "english": {
... 1: "first",
... },
... })
>>> ordinal.update({
... "francais": {
... 2: "second",
... 3: "troisieme",
... },
... "espanol": {
... 1: "primero",
... },
... })
>>> ordinal == {
... "francais": {
... 1: "premier",
... 2: "second",
... 3: "troisieme",
... },
... "english": {
... 1: "first",
... },
... "espanol": {
... 1: "primero",
... },
... }
True
"""
def update(self, other):
# pylint: disable=arguments-differ
self._update(self, other)
@staticmethod
def _update(left, right):
"""Equivalent to `left.update(right)`, with recursive update."""
for key in right:
if key not in left:
left[key] = right[key]
elif isinstance(left[key], dict) and isinstance(right[key], dict):
DictOfDict._update(left[key], right[key])
else:
left[key] = right[key]

2
test/test_chordpro/00.sgc

@ -1 +1 @@
{language: english} {lang: en}

2
test/test_chordpro/01.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A verse line A verse line

2
test/test_chordpro/02.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
{title: A directive} {title: A directive}

2
test/test_chordpro/03.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

2
test/test_chordpro/04.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_chorus} {start_of_chorus}
A one line chorus A one line chorus

2
test/test_chordpro/05.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_bridge} {start_of_bridge}
A one line bridge A one line bridge

2
test/test_chordpro/06.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

2
test/test_chordpro/07.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_tab} {start_of_tab}
A tab A tab

2
test/test_chordpro/08.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
A lot of new lines A lot of new lines

2
test/test_chordpro/09.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{title: and a directive} {title: and a directive}

2
test/test_chordpro/10.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A line[A] with a chord A line[A] with a chord

2
test/test_chordpro/11.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A line ending with a chord[A] A line ending with a chord[A]

2
test/test_chordpro/12.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
[A]A line starting with a chord [A]A line starting with a chord

2
test/test_chordpro/13.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_tab} {start_of_tab}
A table A table

2
test/test_chordpro/21.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
A verse line A verse line

2
test/test_chordpro/22.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
{title: A directive} {title: A directive}

2
test/test_chordpro/23.sgc

@ -1 +1 @@
{language: english} {lang: en}

2
test/test_chordpro/24.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_chorus} {start_of_chorus}
A one line chorus A one line chorus

2
test/test_chordpro/25.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_bridge} {start_of_bridge}
A one line bridge A one line bridge

2
test/test_chordpro/26.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

2
test/test_chordpro/27.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{start_of_tab} {start_of_tab}
A tab A tab

2
test/test_chordpro/28.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
A lot of new lines A lot of new lines

2
test/test_chordpro/29.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{title: and a directive} {title: and a directive}

2
test/test_chordpro/author_names.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{title: Title} {title: Title}
{artist: The Beatles} {artist: The Beatles}
{artist: Oasis} {artist: Oasis}

2
test/test_chordpro/chords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
[A]Simple [A]Simple
[Bb]Bémol [Bb]Bémol

2
test/test_chordpro/customchords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{define: E4 base-fret 7 frets 0 1 3 3 x x} {define: E4 base-fret 7 frets 0 1 3 3 x x}
{define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} {define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -}
{define: E5/A* base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} {define: E5/A* base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -}

0
test/test_chordpro/datadir/img/traditionnel.png

0
test/test_chordpro/datadir/scores/greensleeves.ly

2
test/test_chordpro/greensleeves.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{columns: 2} {columns: 2}
{title: Greensleeves} {title: Greensleeves}
{title: Un autre sous-titre} {title: Un autre sous-titre}

2
test/test_chordpro/greensleeves.source

@ -1,4 +1,4 @@
{language : english} {lang : en}
{columns : 2} {columns : 2}
{subtitle : Un sous titre} {subtitle : Un sous titre}
{ title : Greensleeves} { title : Greensleeves}

4
test/test_chordpro/greensleeves.tex

@ -7,14 +7,14 @@ Un sous titre}[
by={ by={
Traditionnel }, Traditionnel },
album={Angleterre}, album={Angleterre},
cov={traditionnel}, cov={img/traditionnel},
] ]
\cover \cover
\lilypond{greensleeves.ly} \lilypond{scores/greensleeves.ly}

2
test/test_chordpro/invalid_chord.sgc

@ -1,3 +1,3 @@
{language: english} {lang: en}
This is invalid. This is invalid.

2
test/test_chordpro/invalid_customchord.sgc

@ -1,2 +1,2 @@
{language: english} {lang: en}

1
test/test_chordpro/lang.sgc

@ -0,0 +1 @@
{lang: fr}

1
test/test_chordpro/lang.source

@ -0,0 +1 @@
{language: fr}

12
test/test_chordpro/metadata.sgc

@ -1,22 +1,22 @@
{language: french} {lang: fr}
{capo: Capo} {capo: Capo}
{title: Title} {title: Je t'ai Manqué}
{title: Subtitle1} {title: Subtitle1}
{title: Subtitle2} {title: Subtitle2}
{title: Subtitle3} {title: Subtitle3}
{title: Subtitle4} {title: Subtitle4}
{title: Subtitle5} {title: Subtitle5}
{artist: Author1} {artist: Author1}
{artist: Author2} {artist: Texte de Jean Richepin, chanté par Georges Brassens}
{album: Album} {album: Album}
{copyright: Copyright} {copyright: Copyright}
{cov: Cover} {cov: metadata_cover}
{key: foo: Foo} {key: foo: Foo}
{comment: Comment} {comment: Comment}
{guitar_comment: GuitarComment} {guitar_comment: GuitarComment}
{partition: Lilypond} {partition: metadata_lilypond}
{image: Image} {image: metadata_image}
Foo Foo

14
test/test_chordpro/metadata.source

@ -1,21 +1,21 @@
{subtitle: Subtitle3} {subtitle: Subtitle3}
{title: Title} {title: Je t'ai Manqué}
{title: Subtitle1} {title: Subtitle1}
{subtitle: Subtitle4} {subtitle: Subtitle4}
{t: Subtitle2} {t: Subtitle2}
{st: Subtitle5} {st: Subtitle5}
{language: french} {lang: en}
{language: english} {lang: fr}
{by: Author1} {by: Author1}
{artist: Author2} {artist: Texte de Jean Richepin, chanté par Georges Brassens}
{album: Album} {album: Album}
{copyright: Copyright} {copyright: Copyright}
{cover: Cover} {cover: metadata_cover}
{capo: Capo} {capo: Capo}
{key: foo: Foo} {key: foo: Foo}
{comment: Comment} {comment: Comment}
{guitar_comment: GuitarComment} {guitar_comment: GuitarComment}
{partition: Lilypond} {partition: metadata_lilypond}
{image: Image} {image: metadata_image}
Foo Foo

10
test/test_chordpro/metadata.tex

@ -1,6 +1,6 @@
\selectlanguage{french} \selectlanguage{french}
\beginsong{Title\\ \beginsong{Je t'ai Manqué\\
Subtitle1\\ Subtitle1\\
Subtitle2\\ Subtitle2\\
Subtitle3\\ Subtitle3\\
@ -8,10 +8,10 @@ Subtitle4\\
Subtitle5}[ Subtitle5}[
by={ by={
Author1, Author1,
Author2 }, Texte de Jean Richepin, chanté par Georges Brassens },
album={Album}, album={Album},
copyright={Copyright}, copyright={Copyright},
cov={Cover}, cov={img/test/test_chordpro/metadata_cover},
foo={Foo}, foo={Foo},
] ]
@ -19,8 +19,8 @@ Subtitle5}[
\textnote{Comment} \textnote{Comment}
\musicnote{GuitarComment} \musicnote{GuitarComment}
\lilypond{Lilypond} \lilypond{scores/test/test_chordpro/metadata_lilypond}
\image{Image} \image{img/test/test_chordpro/metadata_image}

0
test/test_chordpro/metadata_cover.png

0
test/test_chordpro/metadata_image.png

0
test/test_chordpro/metadata_lilypond.ly

51
test/test_chordpro/newline.html

@ -1,50 +1,43 @@
<span class="song-language">Language: english</span><br>
<span class="song-language">Lang: en</span><br>
<div class="song_content"> <div class="song_content">
<p class="verse"> <p class="verse">This is a verse<br>
This is a verse<br> With a new line<br>
With a new line<br> <br>
<br> The second part of the verse<br>
The second part of the verse<br> Is this line
Is this line
</p> </p>
<p class="verse"> <p class="verse">Here is a new line at the end<br>
Here is a new line at the end<br>
</p> </p>
<p class="verse"> <p class="verse">Foo bar
Foo bar
</p> </p>
<p class="verse"> <p class="verse"><br>
<br> And a new line<br>
And a new line<br> At the beginning
At the beginning
</p> </p>
<p class="chorus"> <p class="chorus">New lines can also<br>
New lines can also<br> <br>
<br> Be in chorus
Be in chorus
</p> </p>
<p class="bridge"> <p class="bridge">New lines can also<br>
New lines can also<br> <br>
<br> Be in bridges
Be in bridges
</p> </p>
<p class="verse"> <p class="verse">New lines can also<br>
New lines can also<br> <br>
<br> Be surrounded by spaces
Be surrounded by spaces
</p> </p>
<p class="verse"> <p class="verse">New lines cannot
New lines cannot
</p> </p>
</div> </div>

2
test/test_chordpro/newline.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
This is a verse This is a verse
With a new line With a new line

2
test/test_chordpro/nolyrics.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
A chorus [A]with lyrics A chorus [A]with lyrics
[Emaj3]maj et nombre [Emaj3]maj et nombre

22
test/test_chordpro/test_parser.py

@ -5,9 +5,11 @@
import glob import glob
import os import os
import unittest import unittest
from pkg_resources import resource_filename
from patacrep import files
from patacrep.build import DEFAULT_CONFIG from patacrep.build import DEFAULT_CONFIG
from patacrep.songs.chordpro import ChordproSong from patacrep.encoding import open_read
from .. import disable_logging from .. import disable_logging
from .. import dynamic # pylint: disable=unused-import from .. import dynamic # pylint: disable=unused-import
@ -34,11 +36,22 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
@classmethod @classmethod
def _iter_testmethods(cls): def _iter_testmethods(cls):
"""Iterate over song files to test.""" """Iterate over song files to test."""
# Setting datadir
cls.config = DEFAULT_CONFIG
if 'datadir' not in cls.config:
cls.config['datadir'] = []
cls.config['datadir'].append(resource_filename(__name__, 'datadir'))
cls.song_plugins = files.load_plugins(
datadirs=cls.config['datadir'],
root_modules=['songs'],
keyword='SONG_RENDERERS',
)
for source in sorted(glob.glob(os.path.join( for source in sorted(glob.glob(os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'*.source', '*.source',
))): ))):
base = source[:-len(".source")] base = os.path.relpath(source, os.getcwd())[:-len(".source")]
for dest in LANGUAGES: for dest in LANGUAGES:
destname = "{}.{}".format(base, dest) destname = "{}.{}".format(base, dest)
if not os.path.exists(destname): if not os.path.exists(destname):
@ -58,13 +71,12 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
if base is None or dest is None: if base is None or dest is None:
return return
destname = "{}.{}".format(base, dest) destname = "{}.{}".format(base, dest)
with open(destname, 'r', encoding='utf8') as expectfile: with open_read(destname) as expectfile:
chordproname = "{}.source".format(base) chordproname = "{}.source".format(base)
with disable_logging(): with disable_logging():
self.assertMultiLineEqual( self.assertMultiLineEqual(
ChordproSong(chordproname, DEFAULT_CONFIG).render( self.song_plugins[LANGUAGES[dest]]['sgc'](chordproname, self.config).render(
output=chordproname, output=chordproname,
output_format=LANGUAGES[dest],
).strip(), ).strip(),
expectfile.read().strip(), expectfile.read().strip(),
) )

2
test/test_chordpro/ukulelechords.sgc

@ -1,4 +1,4 @@
{language: english} {lang: en}
{define: G frets 0 2 3 2} {define: G frets 0 2 3 2}
{define: D7 frets 2 2 2 3 fingers 1 1 1 2} {define: D7 frets 2 2 2 3 fingers 1 1 1 2}
{define: G frets 3 2 0 0 0 3} {define: G frets 3 2 0 0 0 3}

3
test/test_compilation/subdir.sb

@ -3,5 +3,6 @@
"lilypond", "lilypond",
"pictures" "pictures"
], ],
"datadir": ["subdir_datadir", "subdir_datadir2"] "datadir": ["subdir_datadir", "subdir_datadir2"],
"lang": "en"
} }

13
test/test_compilation/subdir.tex.control

@ -36,6 +36,7 @@ guitar,
\PassOptionsToPackage{french}{babel} \PassOptionsToPackage{french}{babel}
\PassOptionsToPackage{english}{babel}
\usepackage[english]{babel} \usepackage[english]{babel}
\lang{english} \lang{english}
@ -100,15 +101,15 @@ guitar,
Chordpro}[ Chordpro}[
by={ by={
}, },
cov={/home/louis/projets/patacrep/patacrep/test/test_compilation/subdir_datadir2/img/datadir2.png}, cov={img/datadir2.png},
] ]
\cover \cover
\lilypond{datadir2.ly} \lilypond{scores/datadir2.ly}
\image{/home/louis/projets/patacrep/patacrep/test/test_compilation/subdir_datadir2/img/datadir2.png} \image{img/datadir2.png}
\endsong \endsong
@ -149,15 +150,15 @@ Chordpro}[
Chordpro}[ Chordpro}[
by={ by={
}, },
cov={/home/louis/projets/patacrep/patacrep/test/test_compilation/subdir_datadir/img/datadir.png}, cov={img/datadir.png},
] ]
\cover \cover
\lilypond{datadir.ly} \lilypond{scores/datadir.ly}
\image{/home/louis/projets/patacrep/patacrep/test/test_compilation/subdir_datadir/img/datadir.png} \image{img/datadir.png}
\endsong \endsong

0
test/test_compilation/subdir_datadir/img/datadir.ly → test/test_compilation/subdir_datadir/scores/datadir.ly

6
test/test_compilation/subdir_datadir/songs/datadir.sg

@ -1,10 +1,10 @@
\beginsong{Image included from datadir\\\LaTeX} \beginsong{Image included from datadir\\\LaTeX}
[cov={datadir}] [cov={img/datadir}]
\cover \cover
\lilypond{datadir.ly} \lilypond{scores/datadir.ly}
\image{datadir} \image{img/datadir}
\endsong \endsong

6
test/test_compilation/subdir_datadir/songs/datadir2.sg

@ -1,10 +1,10 @@
\beginsong{Image included from a different datadir\\\LaTeX} \beginsong{Image included from a different datadir\\\LaTeX}
[cov={datadir2}] [cov={img/datadir2}]
\cover \cover
\lilypond{datadir2.ly} \lilypond{scores/datadir2.ly}
\image{datadir2} \image{img/datadir2}
\endsong \endsong

0
test/test_compilation/subdir_datadir2/img/datadir2.ly → test/test_compilation/subdir_datadir2/scores/datadir2.ly

10
test/test_compilation/test_compilation.py

@ -7,6 +7,8 @@ import os
import subprocess import subprocess
import unittest import unittest
from patacrep.encoding import open_read
from .. import dynamic # pylint: disable=unused-import from .. import dynamic # pylint: disable=unused-import
@ -57,11 +59,11 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
# Check generated tex # Check generated tex
control = "{}.tex.control".format(base) control = "{}.tex.control".format(base)
tex = "{}.tex".format(base) tex = "{}.tex".format(base)
with open(control, 'r', encoding='utf8') as expectfile: with open_read(control) as expectfile:
with open(tex, 'r', encoding='utf8') as latexfile: with open_read(tex) as latexfile:
self.assertMultiLineEqual( self.assertMultiLineEqual(
latexfile.read(), latexfile.read().strip(),
expectfile.read(), expectfile.read().strip(),
) )
# Check compilation # Check compilation

Loading…
Cancel
Save