From 942818902be9f3ea479d90a780c76394712adf30 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 18 Oct 2015 20:34:38 +0200 Subject: [PATCH 01/11] [WIP] Create a `DictOfDict` class --- patacrep/utils.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 patacrep/utils.py diff --git a/patacrep/utils.py b/patacrep/utils.py new file mode 100644 index 00000000..f61b447d --- /dev/null +++ b/patacrep/utils.py @@ -0,0 +1,51 @@ +from collections import UserDict + +class DictOfDict(UserDict): + """Dictionary, with a smart :meth:`update` method. + + By "smart", 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. + + >>> 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): + for key in other: + if key not in self: + self[key] = other[key] + elif isinstance(self[key], dict) and isinstance(other[key], dict): + self[key].update(other[key]) + else: + self[key] = other[key] From 32da0ffd48a02f80f0f7f85a0f3174b77574cbbd Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 19 Oct 2015 17:14:44 +0200 Subject: [PATCH 02/11] `DictOfDict.update()` is now recursive --- patacrep/utils.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/patacrep/utils.py b/patacrep/utils.py index f61b447d..3a69fd25 100644 --- a/patacrep/utils.py +++ b/patacrep/utils.py @@ -1,11 +1,15 @@ +"""Some utility functions""" + from collections import UserDict class DictOfDict(UserDict): - """Dictionary, with a smart :meth:`update` method. + """Dictionary, with a recursive :meth:`update` method. - By "smart", 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. + 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": { @@ -42,10 +46,15 @@ class DictOfDict(UserDict): """ def update(self, other): - for key in other: - if key not in self: - self[key] = other[key] - elif isinstance(self[key], dict) and isinstance(other[key], dict): - self[key].update(other[key]) + # pylint: disable=arguments-differ + self._update(self, other) + + def _update(self, 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): + self._update(left[key], right[key]) else: - self[key] = other[key] + left[key] = right[key] From 51d4562901e6f6d4ca02660fc14211bdec778275 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 19 Oct 2015 23:50:24 +0200 Subject: [PATCH 03/11] Create one Song class per couple parser/renderer Instead of one song class per parser, which renders several formats --- patacrep/build.py | 4 +- patacrep/content/song.py | 2 +- patacrep/files.py | 14 +-- patacrep/songs/__init__.py | 62 ++++++++----- patacrep/songs/chordpro/__init__.py | 90 +++++++++++++------ .../songs/chordpro/data/html/content_image | 5 ++ .../chordpro/data/html/content_metadata_cover | 9 +- .../chordpro/data/html/content_partition | 7 +- .../songs/chordpro/data/latex/content_image | 7 +- .../chordpro/data/latex/content_partition | 5 ++ patacrep/songs/chordpro/data/latex/song | 7 +- patacrep/songs/convert/__main__.py | 19 ++-- patacrep/songs/latex/__init__.py | 18 ++-- .../test_chordpro/datadir/img/greensleeves.ly | 0 .../datadir/img/traditionnel.png | 0 test/test_chordpro/greensleeves.tex | 2 +- test/test_chordpro/metadata.sgc | 6 +- test/test_chordpro/metadata.source | 6 +- test/test_chordpro/metadata.tex | 6 +- test/test_chordpro/metadata_cover.png | 0 test/test_chordpro/metadata_image.png | 0 test/test_chordpro/metadata_lilypond.ly | 0 test/test_chordpro/test_parser.py | 19 +++- 23 files changed, 194 insertions(+), 94 deletions(-) create mode 100644 test/test_chordpro/datadir/img/greensleeves.ly create mode 100644 test/test_chordpro/datadir/img/traditionnel.png create mode 100644 test/test_chordpro/metadata_cover.png create mode 100644 test/test_chordpro/metadata_image.png create mode 100644 test/test_chordpro/metadata_lilypond.ly diff --git a/patacrep/build.py b/patacrep/build.py index 492f30fc..8151f797 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -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..d6bff200 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 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/songs/__init__.py b/patacrep/songs/__init__.py index 68d5a960..05862287 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -167,16 +167,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 @@ -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 @@ -223,28 +228,43 @@ class Song: if directories is None: directories = self.config['datadir'] + for directory in directories: + for extension in extensions: + if os.path.isfile(os.path.join(directory, filename + extension)): + return directory, filename, extension + 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 extension in extensions: - fullpath = os.path.join(directory, filename + extension) - if os.path.isfile(fullpath): - 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. - def search_image(self, filename, none_if_not_found=False): + 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): """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('img'), + ) 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..4f33a084 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -1,9 +1,9 @@ """Chordpro parser""" -from jinja2 import Environment, FileSystemLoader, contextfunction +from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader import jinja2 import os -import pkg_resources +from pkg_resources import resource_filename from patacrep import encoding, files from patacrep.songs import Song @@ -11,23 +11,10 @@ from patacrep.songs.chordpro.syntax import parse_song from patacrep.templates import Renderer 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.""" @@ -41,10 +28,7 @@ class ChordproSong(Song): '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], "titles": self.titles, @@ -55,9 +39,14 @@ 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 @@ -68,7 +57,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 +66,51 @@ 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, extensions = self.search_datadir_file(filename, extensions, datadirs) + return os.path.join(datadir, filename + extensions) + except FileNotFoundError: + return None + +class Chordpro2LatexSong(ChordproSong): + """Render chordpro song to latex code""" + + output_language = "latex" + + def search_file(self, filename, extensions=None, *, datadirs=None): + try: + _datadir, filename, _extension = self.search_datadir_file( + filename, + extensions, + datadirs, + ) + return filename + except FileNotFoundError: + 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/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..0da23623 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 = metadatad['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/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..050cf66b 100644 --- a/patacrep/songs/chordpro/data/latex/song +++ b/patacrep/songs/chordpro/data/latex/song @@ -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..565b4067 100644 --- a/patacrep/songs/latex/__init__.py +++ b/patacrep/songs/latex/__init__.py @@ -11,8 +11,9 @@ from patacrep import files, encoding from patacrep.latex import parse_song 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.""" @@ -28,8 +29,9 @@ class LatexSong(Song): 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,9 @@ class LatexSong(Song): )) return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path)) -SONG_PARSERS = { - 'is': LatexSong, - 'sg': LatexSong, - } +SONG_RENDERERS = { + "latex": { + 'is': Latex2LatexSong, + 'sg': Latex2LatexSong, + }, +} diff --git a/test/test_chordpro/datadir/img/greensleeves.ly b/test/test_chordpro/datadir/img/greensleeves.ly new file mode 100644 index 00000000..e69de29b 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/greensleeves.tex b/test/test_chordpro/greensleeves.tex index a0b602c0..b7186121 100644 --- a/test/test_chordpro/greensleeves.tex +++ b/test/test_chordpro/greensleeves.tex @@ -7,7 +7,7 @@ Un sous titre}[ by={ Traditionnel }, album={Angleterre}, - cov={traditionnel}, + cov={traditionnel}, ] \cover diff --git a/test/test_chordpro/metadata.sgc b/test/test_chordpro/metadata.sgc index 979c1388..e270b729 100644 --- a/test/test_chordpro/metadata.sgc +++ b/test/test_chordpro/metadata.sgc @@ -10,13 +10,13 @@ {artist: Author2} {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..05031dd2 100644 --- a/test/test_chordpro/metadata.source +++ b/test/test_chordpro/metadata.source @@ -10,12 +10,12 @@ {artist: Author2} {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..46368580 100644 --- a/test/test_chordpro/metadata.tex +++ b/test/test_chordpro/metadata.tex @@ -11,7 +11,7 @@ Subtitle5}[ Author2 }, album={Album}, copyright={Copyright}, - cov={Cover}, + cov={test/test_chordpro/metadata_cover}, foo={Foo}, ] @@ -19,8 +19,8 @@ Subtitle5}[ \textnote{Comment} \musicnote{GuitarComment} -\lilypond{Lilypond} -\image{Image} +\lilypond{test/test_chordpro/metadata_lilypond} +\image{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/test_parser.py b/test/test_chordpro/test_parser.py index 20cafd38..6ce2a83a 100644 --- a/test/test_chordpro/test_parser.py +++ b/test/test_chordpro/test_parser.py @@ -5,9 +5,10 @@ import glob import os import unittest +from pkg_resources import resource_filename +from patacrep import files from patacrep.build import DEFAULT_CONFIG -from patacrep.songs.chordpro import ChordproSong from .. import disable_logging @@ -26,11 +27,22 @@ class FileTestMeta(type): def __init__(cls, name, bases, nmspc): super().__init__(name, bases, nmspc) + # 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( os.path.dirname(__file__), '*.source', ))): - base = source[:-len(".source")] + base = os.path.relpath(source, os.getcwd())[:-len(".source")] for dest in LANGUAGES: destname = "{}.{}".format(base, dest) if not os.path.exists(destname): @@ -55,9 +67,8 @@ class FileTestMeta(type): chordproname = "{}.source".format(base) with disable_logging(): self.assertMultiLineEqual( - ChordproSong(chordproname, DEFAULT_CONFIG).render( + self.song_plugins[LANGUAGES[dest]]['sgc'](chordproname, self.config).render( output=chordproname, - output_format=LANGUAGES[dest], ).strip(), expectfile.read().strip(), ) From a7d7a03bb63433e416315750a89226f0de9a2680 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Wed, 21 Oct 2015 14:17:27 +0200 Subject: [PATCH 04/11] Use static method for recursive update of DictOfDict --- patacrep/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/patacrep/utils.py b/patacrep/utils.py index 3a69fd25..6d9eab4c 100644 --- a/patacrep/utils.py +++ b/patacrep/utils.py @@ -49,12 +49,13 @@ class DictOfDict(UserDict): # pylint: disable=arguments-differ self._update(self, other) - def _update(self, left, right): + @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): - self._update(left[key], right[key]) + DictOfDict._update(left[key], right[key]) else: left[key] = right[key] From 93b0fdde2456d4e1c40d42fa4566340e8d058cf4 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 21 Oct 2015 19:01:18 +0200 Subject: [PATCH 05/11] Files located in the same directory as the song have priority over other files --- patacrep/songs/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index 05862287..a9f083d5 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -228,16 +228,16 @@ class Song: if directories is None: directories = self.config['datadir'] - for directory in directories: - for extension in extensions: - if os.path.isfile(os.path.join(directory, filename + extension)): - return directory, filename, extension - 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 directories: + for extension in extensions: + 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): From 1773163d37d82099d149b3087a81adee76aa3b18 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 21 Oct 2015 19:10:07 +0200 Subject: [PATCH 06/11] Added a warning when files could not be found --- patacrep/songs/chordpro/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 4f33a084..39c8825c 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -2,6 +2,7 @@ from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader import jinja2 +import logging import os from pkg_resources import resource_filename @@ -10,6 +11,8 @@ from patacrep.songs import Song from patacrep.songs.chordpro.syntax import parse_song from patacrep.templates import Renderer +LOGGER = logging.getLogger(__name__) + class ChordproSong(Song): """Chordpro song parser""" # pylint: disable=abstract-method @@ -76,6 +79,7 @@ class Chordpro2HtmlSong(ChordproSong): datadir, filename, extensions = self.search_datadir_file(filename, extensions, datadirs) return os.path.join(datadir, filename + extensions) except FileNotFoundError: + LOGGER.warning("Song '%s' (datadir '%s'): File '%s' not found.", self.subpath, self.datadir, filename) return None class Chordpro2LatexSong(ChordproSong): @@ -92,6 +96,7 @@ class Chordpro2LatexSong(ChordproSong): ) return filename except FileNotFoundError: + LOGGER.warning("Song '%s' (datadir '%s'): File '%s' not found.", self.subpath, self.datadir, filename) return None class Chordpro2ChordproSong(ChordproSong): From 1876b5b5b4887101d3b9f03688e6588873efbcfa Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 21 Oct 2015 20:36:12 +0200 Subject: [PATCH 07/11] Merge master --- examples/.gitignore | 1 + examples/example-all.sb | 3 ++- examples/songs/greensleeves.sgc | 2 +- examples/songs/tests/chords.sgc | 2 +- examples/songs/tests/errors.sgc | 2 +- patacrep/build.py | 2 +- patacrep/content/song.py | 6 ++--- patacrep/data/templates/songs.tex | 10 ++++---- patacrep/latex/__init__.py | 23 +++++++++++++++++++ patacrep/latex/ast.py | 6 +++-- patacrep/songbook.py | 2 ++ patacrep/songs/__init__.py | 6 ++--- patacrep/songs/chordpro/__init__.py | 6 +++-- patacrep/songs/chordpro/ast.py | 4 ++-- .../songs/chordpro/data/chordpro/song_header | 4 ++-- patacrep/songs/chordpro/data/html/song_header | 4 ++-- patacrep/songs/chordpro/data/latex/song | 4 ++-- patacrep/songs/latex/__init__.py | 20 ++++++++++++---- patacrep/templates.py | 4 +++- test/test_chordpro/00.sgc | 2 +- test/test_chordpro/01.sgc | 2 +- test/test_chordpro/02.sgc | 2 +- test/test_chordpro/03.sgc | 2 +- test/test_chordpro/04.sgc | 2 +- test/test_chordpro/05.sgc | 2 +- test/test_chordpro/06.sgc | 2 +- test/test_chordpro/07.sgc | 2 +- test/test_chordpro/08.sgc | 2 +- test/test_chordpro/09.sgc | 2 +- test/test_chordpro/10.sgc | 2 +- test/test_chordpro/11.sgc | 2 +- test/test_chordpro/12.sgc | 2 +- test/test_chordpro/13.sgc | 2 +- test/test_chordpro/21.sgc | 2 +- test/test_chordpro/22.sgc | 2 +- test/test_chordpro/23.sgc | 2 +- test/test_chordpro/24.sgc | 2 +- test/test_chordpro/25.sgc | 2 +- test/test_chordpro/26.sgc | 2 +- test/test_chordpro/27.sgc | 2 +- test/test_chordpro/28.sgc | 2 +- test/test_chordpro/29.sgc | 2 +- test/test_chordpro/author_names.sgc | 2 +- test/test_chordpro/chords.sgc | 2 +- test/test_chordpro/customchords.sgc | 2 +- test/test_chordpro/greensleeves.sgc | 2 +- test/test_chordpro/greensleeves.source | 2 +- test/test_chordpro/invalid_chord.sgc | 2 +- test/test_chordpro/invalid_customchord.sgc | 2 +- test/test_chordpro/lang.sgc | 1 + test/test_chordpro/lang.source | 1 + test/test_chordpro/metadata.sgc | 2 +- test/test_chordpro/metadata.source | 4 ++-- test/test_chordpro/newline.html | 2 +- test/test_chordpro/newline.sgc | 2 +- test/test_chordpro/nolyrics.sgc | 2 +- test/test_chordpro/ukulelechords.sgc | 2 +- 57 files changed, 117 insertions(+), 70 deletions(-) create mode 100644 test/test_chordpro/lang.sgc create mode 100644 test/test_chordpro/lang.source diff --git a/examples/.gitignore b/examples/.gitignore index 8c36c429..5b3a77ab 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1 +1,2 @@ /.cache +/*tex 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 8151f797..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, diff --git a/patacrep/content/song.py b/patacrep/content/song.py index d6bff200..480583fa 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -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/songs.tex b/patacrep/data/templates/songs.tex index bdfcf7bb..957e610e 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,11 +80,11 @@ (* 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{ % 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.py b/patacrep/songbook.py index 71340cae..e0e3979e 100644 --- a/patacrep/songbook.py +++ b/patacrep/songbook.py @@ -105,6 +105,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 a9f083d5..e8a91fa3 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 @@ -182,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. diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 39c8825c..c7d749b3 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -10,6 +10,7 @@ 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__) @@ -25,7 +26,7 @@ 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, @@ -33,7 +34,7 @@ class ChordproSong(Song): 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, @@ -52,6 +53,7 @@ class ChordproSong(Song): ])) jinjaenv.filters['search_image'] = self.search_image jinjaenv.filters['search_partition'] = self.search_partition + jinjaenv.filters['lang2babel'] = lang2babel try: return Renderer( 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/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/song b/patacrep/songs/chordpro/data/latex/song index 050cf66b..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 *) diff --git a/patacrep/songs/latex/__init__.py b/patacrep/songs/latex/__init__.py index 565b4067..d787707b 100644 --- a/patacrep/songs/latex/__init__.py +++ b/patacrep/songs/latex/__init__.py @@ -8,7 +8,7 @@ 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 Latex2LatexSong(Song): @@ -16,13 +16,13 @@ class Latex2LatexSong(Song): # 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'] @@ -40,6 +40,18 @@ class Latex2LatexSong(Song): )) return r'\import{{{}/}}{{{}}}'.format(os.path.dirname(path), os.path.basename(path)) + 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, 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/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/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/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 e270b729..559f3dd3 100644 --- a/test/test_chordpro/metadata.sgc +++ b/test/test_chordpro/metadata.sgc @@ -1,4 +1,4 @@ -{language: french} +{lang: fr} {capo: Capo} {title: Title} {title: Subtitle1} diff --git a/test/test_chordpro/metadata.source b/test/test_chordpro/metadata.source index 05031dd2..7a623009 100644 --- a/test/test_chordpro/metadata.source +++ b/test/test_chordpro/metadata.source @@ -4,8 +4,8 @@ {subtitle: Subtitle4} {t: Subtitle2} {st: Subtitle5} -{language: french} -{language: english} +{lang: en} +{lang: fr} {by: Author1} {artist: Author2} {album: Album} diff --git a/test/test_chordpro/newline.html b/test/test_chordpro/newline.html index 1fbb02a3..aca33fa6 100644 --- a/test/test_chordpro/newline.html +++ b/test/test_chordpro/newline.html @@ -1,4 +1,4 @@ -Language: english
+Lang: en
diff --git a/test/test_chordpro/newline.sgc b/test/test_chordpro/newline.sgc index 72bdc1a2..e9bf0c62 100644 --- a/test/test_chordpro/newline.sgc +++ b/test/test_chordpro/newline.sgc @@ -1,4 +1,4 @@ -{language: english} +{lang: en} This is a verse With a new line diff --git a/test/test_chordpro/nolyrics.sgc b/test/test_chordpro/nolyrics.sgc index fd59c884..2ad06b03 100644 --- a/test/test_chordpro/nolyrics.sgc +++ b/test/test_chordpro/nolyrics.sgc @@ -1,4 +1,4 @@ -{language: english} +{lang: en} A chorus [A]with lyrics [Emaj3]maj et nombre diff --git a/test/test_chordpro/ukulelechords.sgc b/test/test_chordpro/ukulelechords.sgc index 3b37123d..bbf4aa81 100644 --- a/test/test_chordpro/ukulelechords.sgc +++ b/test/test_chordpro/ukulelechords.sgc @@ -1,4 +1,4 @@ -{language: english} +{lang: en} {define: G frets 0 2 3 2} {define: D7 frets 2 2 2 3 fingers 1 1 1 2} {define: G frets 3 2 0 0 0 3} From 2207a1f354ae61102d2bab496299275e52d1def8 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 21 Oct 2015 21:04:04 +0200 Subject: [PATCH 08/11] pylint --- patacrep/songs/chordpro/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index c7d749b3..9e06f788 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -81,7 +81,10 @@ class Chordpro2HtmlSong(ChordproSong): datadir, filename, extensions = self.search_datadir_file(filename, extensions, datadirs) return os.path.join(datadir, filename + extensions) except FileNotFoundError: - LOGGER.warning("Song '%s' (datadir '%s'): File '%s' not found.", self.subpath, self.datadir, filename) + LOGGER.warning( + "Song '%s' (datadir '%s'): File '%s' not found.", + self.subpath, self.datadir, filename, + ) return None class Chordpro2LatexSong(ChordproSong): @@ -98,7 +101,10 @@ class Chordpro2LatexSong(ChordproSong): ) return filename except FileNotFoundError: - LOGGER.warning("Song '%s' (datadir '%s'): File '%s' not found.", self.subpath, self.datadir, filename) + LOGGER.warning( + "Song '%s' (datadir '%s'): File '%s' not found.", + self.subpath, self.datadir, filename, + ) return None class Chordpro2ChordproSong(ChordproSong): From 15bcda8901cf50726490d0aa9d799b4744073683 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 21 Oct 2015 23:22:22 +0200 Subject: [PATCH 09/11] typo --- patacrep/songs/chordpro/__init__.py | 4 ++-- patacrep/songs/chordpro/data/html/content_metadata_cover | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 9e06f788..c5b80888 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -78,8 +78,8 @@ class Chordpro2HtmlSong(ChordproSong): def search_file(self, filename, extensions=None, *, datadirs=None): try: - datadir, filename, extensions = self.search_datadir_file(filename, extensions, datadirs) - return os.path.join(datadir, filename + extensions) + 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.", diff --git a/patacrep/songs/chordpro/data/html/content_metadata_cover b/patacrep/songs/chordpro/data/html/content_metadata_cover index 0da23623..ef980a0a 100644 --- a/patacrep/songs/chordpro/data/html/content_metadata_cover +++ b/patacrep/songs/chordpro/data/html/content_metadata_cover @@ -1,6 +1,6 @@ (* block cov *) (* if 'cov' in metadata -*) - (* set cov = metadatad['cov'].argument|search_image *) + (* set cov = metadata['cov'].argument|search_image *) (* if cov *)
(* endif *) From 1f6801b54bf3c3bc7ac368524aa44d6b9f56b9da Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 22 Oct 2015 00:15:01 +0200 Subject: [PATCH 10/11] Sub-datadir (img and score) must be explicitely set --- examples/datadir2/{img => scores}/datadir2.ly | 0 examples/{img => scores}/datadir.ly | 0 examples/songs/subdir/datadir.sg | 6 ++--- examples/songs/subdir/datadir2.sg | 6 ++--- patacrep/data/templates/patacrep.tex | 2 +- patacrep/data/templates/songs.tex | 2 +- patacrep/songs/__init__.py | 2 +- patacrep/songs/chordpro/__init__.py | 25 ++++++++++++++----- 8 files changed, 28 insertions(+), 15 deletions(-) rename examples/datadir2/{img => scores}/datadir2.ly (100%) rename examples/{img => scores}/datadir.ly (100%) diff --git a/examples/datadir2/img/datadir2.ly b/examples/datadir2/scores/datadir2.ly similarity index 100% rename from examples/datadir2/img/datadir2.ly rename to examples/datadir2/scores/datadir2.ly diff --git a/examples/img/datadir.ly b/examples/scores/datadir.ly similarity index 100% rename from examples/img/datadir.ly rename to examples/scores/datadir.ly diff --git a/examples/songs/subdir/datadir.sg b/examples/songs/subdir/datadir.sg index 18e92c3d..933e208b 100644 --- a/examples/songs/subdir/datadir.sg +++ b/examples/songs/subdir/datadir.sg @@ -1,10 +1,10 @@ \beginsong{Image included from datadir\\\LaTeX} - [cov={datadir}] + [cov={img/datadir}] \cover - \lilypond{datadir.ly} + \lilypond{scores/datadir.ly} - \image{datadir} + \image{img/datadir} \endsong diff --git a/examples/songs/subdir/datadir2.sg b/examples/songs/subdir/datadir2.sg index f5bad860..6a805725 100644 --- a/examples/songs/subdir/datadir2.sg +++ b/examples/songs/subdir/datadir2.sg @@ -1,10 +1,10 @@ \beginsong{Image included from a different datadir\\\LaTeX} - [cov={datadir2}] + [cov={img/datadir2}] \cover - \lilypond{datadir2.ly} + \lilypond{scores/datadir2.ly} - \image{datadir2} + \image{img/datadir2} \endsong 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 957e610e..741b5d39 100644 --- a/patacrep/data/templates/songs.tex +++ b/patacrep/data/templates/songs.tex @@ -89,7 +89,7 @@ \usepackage{graphicx} \graphicspath{ % (* for dir in datadir *) - {(( path2posix(dir) ))/img/} % + {(( path2posix(dir) ))/} % (* endfor *) } diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index e8a91fa3..129c8045 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -263,7 +263,7 @@ class Song: return self.search_file( filename, ['', '.ly'], - datadirs=self.get_datadirs('img'), + datadirs=self.get_datadirs('scores'), ) def unprefixed_title(title, prefixes): diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index c5b80888..c46a0880 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -93,16 +93,29 @@ class Chordpro2LatexSong(ChordproSong): 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: - _datadir, filename, _extension = self.search_datadir_file( - filename, - extensions, - datadirs, + 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 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'): File '%s' not found.", + "Song '%s' (datadir '%s'): Image '%s' not found.", self.subpath, self.datadir, filename, ) return None From 34c04cd179b3ae5fd0e3f392be3e9eba32d5bd96 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 22 Oct 2015 00:22:56 +0200 Subject: [PATCH 11/11] Fix tests --- test/test_chordpro/datadir/{img => scores}/greensleeves.ly | 0 test/test_chordpro/greensleeves.tex | 4 ++-- test/test_chordpro/metadata.tex | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename test/test_chordpro/datadir/{img => scores}/greensleeves.ly (100%) diff --git a/test/test_chordpro/datadir/img/greensleeves.ly b/test/test_chordpro/datadir/scores/greensleeves.ly similarity index 100% rename from test/test_chordpro/datadir/img/greensleeves.ly rename to test/test_chordpro/datadir/scores/greensleeves.ly diff --git a/test/test_chordpro/greensleeves.tex b/test/test_chordpro/greensleeves.tex index b7186121..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/metadata.tex b/test/test_chordpro/metadata.tex index 46368580..a3a36676 100644 --- a/test/test_chordpro/metadata.tex +++ b/test/test_chordpro/metadata.tex @@ -11,7 +11,7 @@ Subtitle5}[ Author2 }, album={Album}, copyright={Copyright}, - cov={test/test_chordpro/metadata_cover}, + cov={img/test/test_chordpro/metadata_cover}, foo={Foo}, ] @@ -19,8 +19,8 @@ Subtitle5}[ \textnote{Comment} \musicnote{GuitarComment} -\lilypond{test/test_chordpro/metadata_lilypond} -\image{test/test_chordpro/metadata_image} +\lilypond{scores/test/test_chordpro/metadata_lilypond} +\image{img/test/test_chordpro/metadata_image}