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/build.py b/patacrep/build.py index 44023e65..40ef74ff 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 c5455c92..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 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/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 a9cef025..129c8045 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -168,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 @@ -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 cc0807bb..c46a0880 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -1,9 +1,10 @@ """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 @@ -11,24 +12,13 @@ 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.""" @@ -42,10 +32,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 = { 'lang': self.lang, "titles": self.titles, @@ -56,9 +43,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 jinjaenv.filters['lang2babel'] = lang2babel @@ -70,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 @@ -79,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/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/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 d106c2e8..6f026c79 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 980fece2..8dd8d15b 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, 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 dictionary 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( @@ -50,7 +52,10 @@ class LatexSong(Song): BABEL_LANGUAGES[custom_lang] = language self.lang = custom_lang -SONG_PARSERS = { - 'is': LatexSong, - 'sg': LatexSong, - } +SONG_RENDERERS = { + "latex": { + 'is': Latex2LatexSong, + 'sg': Latex2LatexSong, + }, +} + 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/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.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/metadata.sgc b/test/test_chordpro/metadata.sgc index d48ded77..559f3dd3 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 02167238..7a623009 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..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={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/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(), )