diff --git a/examples/example-all.sb b/examples/example-all.sb
index dba08cc0..8f5d80a6 100644
--- a/examples/example-all.sb
+++ b/examples/example-all.sb
@@ -6,8 +6,9 @@
"pictures"
],
"booktype" : "chorded",
+"datadir": ["datadir2"],
"template" : "patacrep.tex",
-"lang" : "french",
+"lang" : "fr",
"encoding": "utf8",
"authwords" : {
"sep" : ["and", "et"]
diff --git a/examples/songs/greensleeves.sgc b/examples/songs/greensleeves.sgc
index e7a6d7cb..c1295968 100644
--- a/examples/songs/greensleeves.sgc
+++ b/examples/songs/greensleeves.sgc
@@ -1,4 +1,4 @@
-{language : english}
+{lang : en}
{columns : 2}
{ title : Greensleeves}
{subtitle: Test of the chordpro format}
diff --git a/examples/songs/tests/chords.sgc b/examples/songs/tests/chords.sgc
index 8779a140..1539387b 100644
--- a/examples/songs/tests/chords.sgc
+++ b/examples/songs/tests/chords.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{columns: 1}
{title: Chords testing}
{subtitle: Test of the chords specification and LaTeX translation}
diff --git a/examples/songs/tests/errors.sgc b/examples/songs/tests/errors.sgc
index 56cb61b7..3b682f4c 100644
--- a/examples/songs/tests/errors.sgc
+++ b/examples/songs/tests/errors.sgc
@@ -1,4 +1,4 @@
-{language : english}
+{lang : en}
{columns : 2}
{ title : Error}
{subtitle: A chordpro file with many errors}
diff --git a/patacrep/build.py b/patacrep/build.py
index 492f30fc..40ef74ff 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -29,7 +29,7 @@ GENERATED_EXTENSIONS = [
]
DEFAULT_CONFIG = {
'template': "default.tex",
- 'lang': 'english',
+ 'lang': 'en',
'content': [],
'titleprefixwords': [],
'encoding': None,
@@ -110,8 +110,8 @@ class Songbook(object):
config['_song_plugins'] = files.load_plugins(
datadirs=config.get('datadir', []),
root_modules=['songs'],
- keyword='SONG_PARSERS',
- )
+ keyword='SONG_RENDERERS',
+ )['latex']
# Configuration set
config['render'] = content.render
diff --git a/patacrep/content/song.py b/patacrep/content/song.py
index 9101425d..480583fa 100755
--- a/patacrep/content/song.py
+++ b/patacrep/content/song.py
@@ -43,7 +43,7 @@ class SongRenderer(Content):
""").format(
separator="%"*80,
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
@@ -60,8 +60,8 @@ def parse(keyword, argument, contentlist, config):
Return a list of Song() instances.
"""
plugins = config['_song_plugins']
- if '_languages' not in config:
- config['_languages'] = set()
+ if '_langs' not in config:
+ config['_langs'] = set()
songlist = []
for songdir in config['_songdir']:
if contentlist:
@@ -92,7 +92,7 @@ def parse(keyword, argument, contentlist, config):
datadir=songdir.datadir,
))
songlist.append(renderer)
- config["_languages"].update(renderer.song.languages)
+ config["_langs"].add(renderer.song.lang)
if len(songlist) > before:
break
if len(songlist) == before:
diff --git a/patacrep/data/templates/patacrep.tex b/patacrep/data/templates/patacrep.tex
index 96367078..50dacf5e 100644
--- a/patacrep/data/templates/patacrep.tex
+++ b/patacrep/data/templates/patacrep.tex
@@ -34,7 +34,7 @@
},
"picture": {"description": {"english": "Cover picture", "french": "Image de couverture"},
"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"},
"default": {"default": "Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"}
diff --git a/patacrep/data/templates/songs.tex b/patacrep/data/templates/songs.tex
index bdfcf7bb..741b5d39 100644
--- a/patacrep/data/templates/songs.tex
+++ b/patacrep/data/templates/songs.tex
@@ -52,7 +52,7 @@
"mandatory": true
},
"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",
"french": "Ignore des mots dans le classement des chansons"},
@@ -80,16 +80,16 @@
(* block songbookpreambule *)
(( super() ))
-(* for lang in _languages -*)
- \PassOptionsToPackage{((lang))}{babel}
+(* for lang in _langs -*)
+ \PassOptionsToPackage{(( lang2babel(lang) ))}{babel}
(* endfor *)
-\usepackage[((lang))]{babel}
-\lang{((lang))}
+\usepackage[(( lang2babel(lang) ))]{babel}
+\lang{(( lang2babel(lang) ))}
\usepackage{graphicx}
\graphicspath{ %
(* for dir in datadir *)
- {(( path2posix(dir) ))/img/} %
+ {(( path2posix(dir) ))/} %
(* endfor *)
}
diff --git a/patacrep/files.py b/patacrep/files.py
index e77c3ab2..1116c8b0 100644
--- a/patacrep/files.py
+++ b/patacrep/files.py
@@ -8,6 +8,8 @@ import posixpath
import re
import sys
+from patacrep import utils
+
LOGGER = logging.getLogger(__name__)
def recursive_find(root_directory, extensions):
@@ -104,7 +106,7 @@ def load_plugins(datadirs, root_modules, keyword):
- keys are the keywords ;
- values are functions triggered when this keyword is met.
"""
- plugins = {}
+ plugins = utils.DictOfDict()
datadir_path = [
os.path.join(datadir, "python", *root_modules)
@@ -121,13 +123,5 @@ def load_plugins(datadirs, root_modules, keyword):
prefix="patacrep.{}.".format(".".join(root_modules))
):
if hasattr(module, keyword):
- for (key, value) in getattr(module, keyword).items():
- if key in plugins:
- LOGGER.warning(
- "File %s: Keyword '%s' is already used. Ignored.",
- module.__file__,
- key,
- )
- continue
- plugins[key] = value
+ plugins.update(getattr(module, keyword))
return plugins
diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py
index 2c6db73a..d01cfbe5 100644
--- a/patacrep/latex/__init__.py
+++ b/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.
"""
+import logging
+from collections import OrderedDict
+
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'
diff --git a/patacrep/latex/ast.py b/patacrep/latex/ast.py
index c4763ddf..46017fda 100644
--- a/patacrep/latex/ast.py
+++ b/patacrep/latex/ast.py
@@ -2,6 +2,8 @@
# pylint: disable=too-few-public-methods
+DEFAULT_LANGUAGE = "english"
+
class AST:
"""Base class for the tree."""
# pylint: disable=no-init
@@ -16,7 +18,7 @@ class AST:
parsing.
"""
cls.metadata = {
- '@languages': set(),
+ '@language': DEFAULT_LANGUAGE,
}
class Expression(AST):
@@ -44,7 +46,7 @@ class Command(AST):
self.optional = optional
if name == r'\selectlanguage':
- self.metadata['@languages'] |= set(self.mandatory)
+ self.metadata['@language'] = self.mandatory[0]
def __str__(self):
if self.name in [r'\emph']:
diff --git a/patacrep/songbook/__main__.py b/patacrep/songbook/__main__.py
index 8ce858a5..5713b4a9 100644
--- a/patacrep/songbook/__main__.py
+++ b/patacrep/songbook/__main__.py
@@ -108,6 +108,8 @@ def main():
options = argument_parser(sys.argv[1:])
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]
diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py
index 68d5a960..129c8045 100644
--- a/patacrep/songs/__init__.py
+++ b/patacrep/songs/__init__.py
@@ -89,7 +89,7 @@ class Song:
"cached",
"data",
"subpath",
- "languages",
+ "lang",
"authors",
"_filehash",
"_version",
@@ -131,6 +131,7 @@ class Song:
self.titles = []
self.data = {}
self.cached = None
+ self.lang = None
self._parse(config)
# Post processing of data
@@ -167,16 +168,12 @@ class Song:
def __repr__(self):
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.
Arguments:
- - output_format: Format of the output file (latex, chordpro...)
- 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()
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
remove prefixes.
- - languages: the list of languages used in the song, as languages
- recognized by the LaTeX babel package.
+ - lang: the main language of the song, as language code..
- 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.
@@ -204,15 +200,24 @@ class Song:
if os.path.isdir(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.
: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 iterator directories: Other directories where to search for the file
The directory where the Song file is stored is added to the list.
-
- Returns None if nothing found.
+ :return: A tuple `(datadir, filename, extension)` if file has been
+ 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
instance, it can compile a file, place it in a temporary folder, and
@@ -224,27 +229,42 @@ class Song:
directories = self.config['datadir']
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:
- fullpath = os.path.join(directory, filename + extension)
- if os.path.isfile(fullpath):
- return os.path.abspath(fullpath)
- return None
+ if os.path.isfile(os.path.join(directory, filename + extension)):
+ return directory, filename, extension
+
+ 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"""
- filepath = self.search_file(
+ return self.search_file(
filename,
['', '.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"""
- filepath = self.search_file(filename, ['', '.ly'])
- return filepath if none_if_not_found or filepath else filename
+ return self.search_file(
+ filename,
+ ['', '.ly'],
+ datadirs=self.get_datadirs('scores'),
+ )
def unprefixed_title(title, prefixes):
"""Remove the first prefix of the list in the beginning of title (if any).
diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py
index a874628c..c46a0880 100644
--- a/patacrep/songs/chordpro/__init__.py
+++ b/patacrep/songs/chordpro/__init__.py
@@ -1,33 +1,24 @@
"""Chordpro parser"""
-from jinja2 import Environment, FileSystemLoader, contextfunction
+from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader
import jinja2
+import logging
import os
-import pkg_resources
+from pkg_resources import resource_filename
from patacrep import encoding, files
from patacrep.songs import Song
from patacrep.songs.chordpro.syntax import parse_song
from patacrep.templates import Renderer
+from patacrep.latex import lang2babel
+
+LOGGER = logging.getLogger(__name__)
class ChordproSong(Song):
- """Chordpros song parser."""
+ """Chordpro song parser"""
+ # pylint: disable=abstract-method
- @staticmethod
- 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,
- )
+ output_language = None
def _parse(self, config):
"""Parse content, and return the dictionary of song data."""
@@ -35,18 +26,15 @@ class ChordproSong(Song):
song = parse_song(song.read(), self.fullpath)
self.authors = song.authors
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.cached = {
'song': song,
}
- def render(self, output_format, output=None, template="song", templatedirs=None): # pylint: disable=arguments-differ
- if templatedirs is None:
- templatedirs = []
-
+ def render(self, output=None, template="song"): # pylint: disable=arguments-differ
context = {
- 'language': self.languages[0],
+ 'lang': self.lang,
"titles": self.titles,
"authors": self.authors,
"metadata": self.data,
@@ -55,11 +43,17 @@ class ChordproSong(Song):
"content": self.cached['song'].content,
}
- jinjaenv = Environment(loader=FileSystemLoader(
- self.iter_template_paths(templatedirs, output_format)
- ))
+ jinjaenv = Environment(loader=ChoiceLoader([
+ 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_partition'] = self.search_partition
+ jinjaenv.filters['lang2babel'] = lang2babel
try:
return Renderer(
@@ -68,7 +62,7 @@ class ChordproSong(Song):
jinjaenv=jinjaenv,
).template.render(context)
except jinja2.exceptions.TemplateNotFound:
- raise NotImplementedError("Cannot convert to format '{}'.".format(output_format))
+ raise NotImplementedError("Cannot convert to format '{}'.".format(self.output_language))
@staticmethod
@contextfunction
@@ -77,6 +71,72 @@ class ChordproSong(Song):
context.vars['content'] = content
return context.environment.get_template(content.template()).render(context)
-SONG_PARSERS = {
- 'sgc': ChordproSong,
+class Chordpro2HtmlSong(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,
+ },
}
diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py
index 18805e9b..9213ed37 100644
--- a/patacrep/songs/chordpro/ast.py
+++ b/patacrep/songs/chordpro/ast.py
@@ -31,6 +31,7 @@ DIRECTIVE_SHORTCUTS = {
"c": "comment",
"gc": "guitar_comment",
"cover": "cov",
+ "language": "lang",
}
def directive_name(text):
@@ -181,7 +182,7 @@ class Song(AST):
- 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
+ - lang: The language code (if set), None otherwise
- authors: The list of authors
- meta: Every other metadata.
"""
@@ -193,7 +194,6 @@ class Song(AST):
"artist": "add_author",
"key": "add_key",
"define": "add_cumulative",
- "language": "add_cumulative",
}
def __init__(self, filename):
diff --git a/patacrep/songs/chordpro/data/chordpro/song_header b/patacrep/songs/chordpro/data/chordpro/song_header
index ac5d8329..2ffb1d30 100644
--- a/patacrep/songs/chordpro/data/chordpro/song_header
+++ b/patacrep/songs/chordpro/data/chordpro/song_header
@@ -1,5 +1,5 @@
-(* if language is defined -*)
- {language: (( language ))}
+(* if lang is defined -*)
+ {lang: (( lang ))}
(* endif *)
(* if metadata.columns is defined -*)
{columns: (( metadata.columns ))}
diff --git a/patacrep/songs/chordpro/data/html/content_image b/patacrep/songs/chordpro/data/html/content_image
index 0da4bcfe..bf46a95f 100644
--- a/patacrep/songs/chordpro/data/html/content_image
+++ b/patacrep/songs/chordpro/data/html/content_image
@@ -1 +1,6 @@
+(* block image *)
+(* set image = content.argument|search_image *)
+(* if image *)
+(* endif *)
+(* endblock *)
diff --git a/patacrep/songs/chordpro/data/html/content_metadata_cover b/patacrep/songs/chordpro/data/html/content_metadata_cover
index 96fc7718..ef980a0a 100644
--- a/patacrep/songs/chordpro/data/html/content_metadata_cover
+++ b/patacrep/songs/chordpro/data/html/content_metadata_cover
@@ -1,3 +1,8 @@
+(* block cov *)
(* if 'cov' in metadata -*)
-
-(* endif *)
\ No newline at end of file
+ (* set cov = metadata['cov'].argument|search_image *)
+ (* if cov *)
+
+ (* endif *)
+(* endif *)
+(* endblock *)
diff --git a/patacrep/songs/chordpro/data/html/content_partition b/patacrep/songs/chordpro/data/html/content_partition
index 1075cced..b43318f8 100644
--- a/patacrep/songs/chordpro/data/html/content_partition
+++ b/patacrep/songs/chordpro/data/html/content_partition
@@ -1 +1,6 @@
-((content.argument))
+(* block partition *)
+(* set partition = content.argument|search_partition *)
+(* if partition *)
+((content.argument))
+(* endif *)
+(* endblock *)
diff --git a/patacrep/songs/chordpro/data/html/content_verse b/patacrep/songs/chordpro/data/html/content_verse
index 6fe21f6b..2b3e6fe3 100644
--- a/patacrep/songs/chordpro/data/html/content_verse
+++ b/patacrep/songs/chordpro/data/html/content_verse
@@ -1,5 +1,11 @@
+(* if content.directive() *)
+
+(*- elif content.nolyrics -*) +
+(*- else *)
- (* for line in content.lines *)
+(*- endif *)
+ (* for line in content.lines -*)
(( render(line) ))
(*- if not loop.last *)
(* endif *)
diff --git a/patacrep/songs/chordpro/data/html/song_header b/patacrep/songs/chordpro/data/html/song_header
index 963ef686..bd401864 100644
--- a/patacrep/songs/chordpro/data/html/song_header
+++ b/patacrep/songs/chordpro/data/html/song_header
@@ -17,8 +17,8 @@
(* endif *)
(* endfor *)
-(* if language is defined -*)
- Language: (( language ))
+(* if lang is defined -*)
+ Lang: (( lang ))
(* endif *)
(* include 'content_metadata_cover' *)
diff --git a/patacrep/songs/chordpro/data/latex/content_image b/patacrep/songs/chordpro/data/latex/content_image
index e4c2befb..ac97404b 100644
--- a/patacrep/songs/chordpro/data/latex/content_image
+++ b/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 *)
diff --git a/patacrep/songs/chordpro/data/latex/content_partition b/patacrep/songs/chordpro/data/latex/content_partition
index 4ce134a1..a3c35a3e 100644
--- a/patacrep/songs/chordpro/data/latex/content_partition
+++ b/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 -)) }
+(*- endif -*)
+(*- endblock -*)
diff --git a/patacrep/songs/chordpro/data/latex/song b/patacrep/songs/chordpro/data/latex/song
index 6f51d673..6f026c79 100644
--- a/patacrep/songs/chordpro/data/latex/song
+++ b/patacrep/songs/chordpro/data/latex/song
@@ -1,5 +1,5 @@
-(* if language is defined -*)
- \selectlanguage{((language))}
+(* if lang is defined -*)
+ \selectlanguage{(( lang2babel(lang) ))}
(* endif *)
(*- if metadata.columns is defined *)
@@ -28,7 +28,12 @@
(* endif *)
(* endfor *)
(* 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 *)
(* for key in metadata.morekeys *)
(( key.keyword ))={(( key.argument ))},
diff --git a/patacrep/songs/convert/__main__.py b/patacrep/songs/convert/__main__.py
index 51786875..6b40dca4 100644
--- a/patacrep/songs/convert/__main__.py
+++ b/patacrep/songs/convert/__main__.py
@@ -36,22 +36,29 @@ if __name__ == "__main__":
dest = sys.argv[2]
song_files = sys.argv[3:]
- song_parsers = files.load_plugins(
+ renderers = files.load_plugins(
datadirs=DEFAULT_CONFIG.get('datadir', []),
root_modules=['songs'],
- keyword='SONG_PARSERS',
+ keyword='SONG_RENDERERS',
)
- if source not in song_parsers:
+ if dest not in renderers:
LOGGER.error(
- "Unknown file format '%s'. Available ones are %s.",
+ "Unknown destination file format '%s'. Available ones are %s.",
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)
for file in song_files:
- song = song_parsers[source](file, DEFAULT_CONFIG)
+ song = renderers[dest][source](file, DEFAULT_CONFIG)
try:
destname = "{}.{}".format(".".join(file.split(".")[:-1]), dest)
if os.path.exists(destname):
diff --git a/patacrep/songs/latex/__init__.py b/patacrep/songs/latex/__init__.py
index 6ac54c81..8dd8d15b 100644
--- a/patacrep/songs/latex/__init__.py
+++ b/patacrep/songs/latex/__init__.py
@@ -8,28 +8,30 @@ will work on simple cases, but not on complex ones.
import os
from patacrep import files, encoding
-from patacrep.latex import parse_song
+from patacrep.latex import parse_song, BABEL_LANGUAGES
from patacrep.songs import Song
-class LatexSong(Song):
- """LaTeX song parser."""
+class Latex2LatexSong(Song):
+ """Song written in LaTeX, rendered in LaTeX"""
+ # pylint: disable=abstract-method
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:
self.data = parse_song(song.read(), self.fullpath)
self.titles = self.data['@titles']
del self.data['@titles']
- self.languages = self.data['@languages']
- del self.data['@languages']
+ self.set_lang(self.data['@language'])
+ del self.data['@language']
if "by" in self.data:
self.authors = [self.data['by']]
del self.data['by']
else:
self.authors = []
- def render_latex(self, output):
+ def render(self, output):
"""Return the code rendering the song."""
+ # pylint: disable=signature-differs
if output is None:
raise ValueError(output)
path = files.path2posix(files.relpath(
@@ -38,7 +40,22 @@ class LatexSong(Song):
))
return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path))
-SONG_PARSERS = {
- 'is': LatexSong,
- 'sg': LatexSong,
- }
+ def set_lang(self, language):
+ """Set the language code"""
+ 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,
+ },
+}
+
diff --git a/patacrep/templates.py b/patacrep/templates.py
index 4b81c08d..6a799295 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -9,6 +9,7 @@ import re
import json
from patacrep import errors, files
+from patacrep.latex import lang2babel
import patacrep.encoding
_LATEX_SUBS = (
@@ -84,6 +85,7 @@ class Renderer:
self.jinjaenv.trim_blocks = True
self.jinjaenv.lstrip_blocks = True
self.jinjaenv.globals["path2posix"] = files.path2posix
+ self.jinjaenv.globals["lang2babel"] = lang2babel
self.template = self.jinjaenv.get_template(template)
@@ -153,7 +155,7 @@ class TexBookRenderer(Renderer):
variable = default["default"]
elif "en" in default:
variable = default["en"]
- elif len(default > 0):
+ elif len(default):
variable = default.popitem()[1]
else:
variable = None
diff --git a/patacrep/utils.py b/patacrep/utils.py
new file mode 100644
index 00000000..6d9eab4c
--- /dev/null
+++ b/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]
diff --git a/test/test_chordpro/00.sgc b/test/test_chordpro/00.sgc
index 3e2185b7..768ee9ee 100644
--- a/test/test_chordpro/00.sgc
+++ b/test/test_chordpro/00.sgc
@@ -1 +1 @@
-{language: english}
+{lang: en}
diff --git a/test/test_chordpro/01.sgc b/test/test_chordpro/01.sgc
index 25d6a677..6cf1934e 100644
--- a/test/test_chordpro/01.sgc
+++ b/test/test_chordpro/01.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
A verse line
diff --git a/test/test_chordpro/02.sgc b/test/test_chordpro/02.sgc
index 2a8629e0..365a1b9b 100644
--- a/test/test_chordpro/02.sgc
+++ b/test/test_chordpro/02.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
{title: A directive}
diff --git a/test/test_chordpro/03.sgc b/test/test_chordpro/03.sgc
index 341c9bd4..6468f5e9 100644
--- a/test/test_chordpro/03.sgc
+++ b/test/test_chordpro/03.sgc
@@ -1,2 +1,2 @@
-{language: english}
+{lang: en}
diff --git a/test/test_chordpro/04.sgc b/test/test_chordpro/04.sgc
index e9c2a952..d25f38a8 100644
--- a/test/test_chordpro/04.sgc
+++ b/test/test_chordpro/04.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_chorus}
A one line chorus
diff --git a/test/test_chordpro/05.sgc b/test/test_chordpro/05.sgc
index 9bc7f016..cf6a654b 100644
--- a/test/test_chordpro/05.sgc
+++ b/test/test_chordpro/05.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_bridge}
A one line bridge
diff --git a/test/test_chordpro/06.sgc b/test/test_chordpro/06.sgc
index 341c9bd4..6468f5e9 100644
--- a/test/test_chordpro/06.sgc
+++ b/test/test_chordpro/06.sgc
@@ -1,2 +1,2 @@
-{language: english}
+{lang: en}
diff --git a/test/test_chordpro/07.sgc b/test/test_chordpro/07.sgc
index b05a536f..1362cdeb 100644
--- a/test/test_chordpro/07.sgc
+++ b/test/test_chordpro/07.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_tab}
A tab
diff --git a/test/test_chordpro/08.sgc b/test/test_chordpro/08.sgc
index 9a9f0566..bfcb44dd 100644
--- a/test/test_chordpro/08.sgc
+++ b/test/test_chordpro/08.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
A lot of new lines
diff --git a/test/test_chordpro/09.sgc b/test/test_chordpro/09.sgc
index 193db1b0..b252de69 100644
--- a/test/test_chordpro/09.sgc
+++ b/test/test_chordpro/09.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{title: and a directive}
diff --git a/test/test_chordpro/10.sgc b/test/test_chordpro/10.sgc
index e7537b3b..431ddd11 100644
--- a/test/test_chordpro/10.sgc
+++ b/test/test_chordpro/10.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
A line[A] with a chord
diff --git a/test/test_chordpro/11.sgc b/test/test_chordpro/11.sgc
index 3610f67c..350f195d 100644
--- a/test/test_chordpro/11.sgc
+++ b/test/test_chordpro/11.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
A line ending with a chord[A]
diff --git a/test/test_chordpro/12.sgc b/test/test_chordpro/12.sgc
index 029ccad4..946d7add 100644
--- a/test/test_chordpro/12.sgc
+++ b/test/test_chordpro/12.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
[A]A line starting with a chord
diff --git a/test/test_chordpro/13.sgc b/test/test_chordpro/13.sgc
index aa6f6603..1000c0db 100644
--- a/test/test_chordpro/13.sgc
+++ b/test/test_chordpro/13.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_tab}
A table
diff --git a/test/test_chordpro/21.sgc b/test/test_chordpro/21.sgc
index 25d6a677..6cf1934e 100644
--- a/test/test_chordpro/21.sgc
+++ b/test/test_chordpro/21.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
A verse line
diff --git a/test/test_chordpro/22.sgc b/test/test_chordpro/22.sgc
index 2a8629e0..365a1b9b 100644
--- a/test/test_chordpro/22.sgc
+++ b/test/test_chordpro/22.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
{title: A directive}
diff --git a/test/test_chordpro/23.sgc b/test/test_chordpro/23.sgc
index 3e2185b7..768ee9ee 100644
--- a/test/test_chordpro/23.sgc
+++ b/test/test_chordpro/23.sgc
@@ -1 +1 @@
-{language: english}
+{lang: en}
diff --git a/test/test_chordpro/24.sgc b/test/test_chordpro/24.sgc
index e9c2a952..d25f38a8 100644
--- a/test/test_chordpro/24.sgc
+++ b/test/test_chordpro/24.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_chorus}
A one line chorus
diff --git a/test/test_chordpro/25.sgc b/test/test_chordpro/25.sgc
index 9bc7f016..cf6a654b 100644
--- a/test/test_chordpro/25.sgc
+++ b/test/test_chordpro/25.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_bridge}
A one line bridge
diff --git a/test/test_chordpro/26.sgc b/test/test_chordpro/26.sgc
index 341c9bd4..6468f5e9 100644
--- a/test/test_chordpro/26.sgc
+++ b/test/test_chordpro/26.sgc
@@ -1,2 +1,2 @@
-{language: english}
+{lang: en}
diff --git a/test/test_chordpro/27.sgc b/test/test_chordpro/27.sgc
index b05a536f..1362cdeb 100644
--- a/test/test_chordpro/27.sgc
+++ b/test/test_chordpro/27.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{start_of_tab}
A tab
diff --git a/test/test_chordpro/28.sgc b/test/test_chordpro/28.sgc
index 9a9f0566..bfcb44dd 100644
--- a/test/test_chordpro/28.sgc
+++ b/test/test_chordpro/28.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
A lot of new lines
diff --git a/test/test_chordpro/29.sgc b/test/test_chordpro/29.sgc
index 193db1b0..b252de69 100644
--- a/test/test_chordpro/29.sgc
+++ b/test/test_chordpro/29.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{title: and a directive}
diff --git a/test/test_chordpro/author_names.sgc b/test/test_chordpro/author_names.sgc
index 36a39230..b1756cc3 100644
--- a/test/test_chordpro/author_names.sgc
+++ b/test/test_chordpro/author_names.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{title: Title}
{artist: The Beatles}
{artist: Oasis}
diff --git a/test/test_chordpro/chords.sgc b/test/test_chordpro/chords.sgc
index 040dcf07..983e03ee 100644
--- a/test/test_chordpro/chords.sgc
+++ b/test/test_chordpro/chords.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
[A]Simple
[Bb]Bémol
diff --git a/test/test_chordpro/customchords.sgc b/test/test_chordpro/customchords.sgc
index 3357bb96..dd42e025 100644
--- a/test/test_chordpro/customchords.sgc
+++ b/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: 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 - -}
diff --git a/test/test_chordpro/datadir/img/traditionnel.png b/test/test_chordpro/datadir/img/traditionnel.png
new file mode 100644
index 00000000..e69de29b
diff --git a/test/test_chordpro/datadir/scores/greensleeves.ly b/test/test_chordpro/datadir/scores/greensleeves.ly
new file mode 100644
index 00000000..e69de29b
diff --git a/test/test_chordpro/greensleeves.sgc b/test/test_chordpro/greensleeves.sgc
index 389f14d1..7ff25a58 100644
--- a/test/test_chordpro/greensleeves.sgc
+++ b/test/test_chordpro/greensleeves.sgc
@@ -1,4 +1,4 @@
-{language: english}
+{lang: en}
{columns: 2}
{title: Greensleeves}
{title: Un autre sous-titre}
diff --git a/test/test_chordpro/greensleeves.source b/test/test_chordpro/greensleeves.source
index 1dee93ff..3801418d 100644
--- a/test/test_chordpro/greensleeves.source
+++ b/test/test_chordpro/greensleeves.source
@@ -1,4 +1,4 @@
-{language : english}
+{lang : en}
{columns : 2}
{subtitle : Un sous titre}
{ title : Greensleeves}
diff --git a/test/test_chordpro/greensleeves.tex b/test/test_chordpro/greensleeves.tex
index a0b602c0..f582b0c8 100644
--- a/test/test_chordpro/greensleeves.tex
+++ b/test/test_chordpro/greensleeves.tex
@@ -7,14 +7,14 @@ Un sous titre}[
by={
Traditionnel },
album={Angleterre},
- cov={traditionnel},
+ cov={img/traditionnel},
]
\cover
-\lilypond{greensleeves.ly}
+\lilypond{scores/greensleeves.ly}
diff --git a/test/test_chordpro/invalid_chord.sgc b/test/test_chordpro/invalid_chord.sgc
index acb5444f..3d6319be 100644
--- a/test/test_chordpro/invalid_chord.sgc
+++ b/test/test_chordpro/invalid_chord.sgc
@@ -1,3 +1,3 @@
-{language: english}
+{lang: en}
This is invalid.
diff --git a/test/test_chordpro/invalid_customchord.sgc b/test/test_chordpro/invalid_customchord.sgc
index 341c9bd4..6468f5e9 100644
--- a/test/test_chordpro/invalid_customchord.sgc
+++ b/test/test_chordpro/invalid_customchord.sgc
@@ -1,2 +1,2 @@
-{language: english}
+{lang: en}
diff --git a/test/test_chordpro/lang.sgc b/test/test_chordpro/lang.sgc
new file mode 100644
index 00000000..be2eff89
--- /dev/null
+++ b/test/test_chordpro/lang.sgc
@@ -0,0 +1 @@
+{lang: fr}
\ No newline at end of file
diff --git a/test/test_chordpro/lang.source b/test/test_chordpro/lang.source
new file mode 100644
index 00000000..c9a4c305
--- /dev/null
+++ b/test/test_chordpro/lang.source
@@ -0,0 +1 @@
+{language: fr}
diff --git a/test/test_chordpro/metadata.sgc b/test/test_chordpro/metadata.sgc
index 979c1388..22c59f59 100644
--- a/test/test_chordpro/metadata.sgc
+++ b/test/test_chordpro/metadata.sgc
@@ -1,22 +1,22 @@
-{language: french}
+{lang: fr}
{capo: Capo}
-{title: Title}
+{title: Je t'ai Manqué}
{title: Subtitle1}
{title: Subtitle2}
{title: Subtitle3}
{title: Subtitle4}
{title: Subtitle5}
{artist: Author1}
-{artist: Author2}
+{artist: Texte de Jean Richepin, chanté par Georges Brassens}
{album: Album}
{copyright: Copyright}
-{cov: Cover}
+{cov: metadata_cover}
{key: foo: Foo}
{comment: Comment}
{guitar_comment: GuitarComment}
-{partition: Lilypond}
-{image: Image}
+{partition: metadata_lilypond}
+{image: metadata_image}
Foo
diff --git a/test/test_chordpro/metadata.source b/test/test_chordpro/metadata.source
index 65a2359a..b9c41cc6 100644
--- a/test/test_chordpro/metadata.source
+++ b/test/test_chordpro/metadata.source
@@ -1,21 +1,21 @@
{subtitle: Subtitle3}
-{title: Title}
+{title: Je t'ai Manqué}
{title: Subtitle1}
{subtitle: Subtitle4}
{t: Subtitle2}
{st: Subtitle5}
-{language: french}
-{language: english}
+{lang: en}
+{lang: fr}
{by: Author1}
-{artist: Author2}
+{artist: Texte de Jean Richepin, chanté par Georges Brassens}
{album: Album}
{copyright: Copyright}
-{cover: Cover}
+{cover: metadata_cover}
{capo: Capo}
{key: foo: Foo}
{comment: Comment}
{guitar_comment: GuitarComment}
-{partition: Lilypond}
-{image: Image}
+{partition: metadata_lilypond}
+{image: metadata_image}
Foo
diff --git a/test/test_chordpro/metadata.tex b/test/test_chordpro/metadata.tex
index fa29ec3c..d2a2273a 100644
--- a/test/test_chordpro/metadata.tex
+++ b/test/test_chordpro/metadata.tex
@@ -1,6 +1,6 @@
\selectlanguage{french}
-\beginsong{Title\\
+\beginsong{Je t'ai Manqué\\
Subtitle1\\
Subtitle2\\
Subtitle3\\
@@ -8,10 +8,10 @@ Subtitle4\\
Subtitle5}[
by={
Author1,
- Author2 },
+ Texte de Jean Richepin, chanté par Georges Brassens },
album={Album},
copyright={Copyright},
- cov={Cover},
+ cov={img/test/test_chordpro/metadata_cover},
foo={Foo},
]
@@ -19,8 +19,8 @@ Subtitle5}[
\textnote{Comment}
\musicnote{GuitarComment}
-\lilypond{Lilypond}
-\image{Image}
+\lilypond{scores/test/test_chordpro/metadata_lilypond}
+\image{img/test/test_chordpro/metadata_image}
diff --git a/test/test_chordpro/metadata_cover.png b/test/test_chordpro/metadata_cover.png
new file mode 100644
index 00000000..e69de29b
diff --git a/test/test_chordpro/metadata_image.png b/test/test_chordpro/metadata_image.png
new file mode 100644
index 00000000..e69de29b
diff --git a/test/test_chordpro/metadata_lilypond.ly b/test/test_chordpro/metadata_lilypond.ly
new file mode 100644
index 00000000..e69de29b
diff --git a/test/test_chordpro/newline.html b/test/test_chordpro/newline.html
index 1fbb02a3..5540af64 100644
--- a/test/test_chordpro/newline.html
+++ b/test/test_chordpro/newline.html
@@ -1,50 +1,43 @@
-Language: english
+
+Lang: en
- This is a verse
- With a new line
-
- The second part of the verse
- Is this line
+
This is a verse
+With a new line
+
+The second part of the verse
+Is this line
- Here is a new line at the end
-
+
Here is a new line at the end
+
- Foo bar +
Foo bar
-
-
- And a new line
- At the beginning
+
+And a new line
+At the beginning
- New lines can also
-
- Be in chorus
+
New lines can also
+
+Be in chorus
- New lines can also
-
- Be in bridges
+
New lines can also
+
+Be in bridges
- New lines can also
-
- Be surrounded by spaces
+
New lines can also
+
+Be surrounded by spaces
- New lines cannot +
New lines cannot
-