diff --git a/patacrep/encoding.py b/patacrep/encoding.py index b8bdc8e6..fd58fc2f 100644 --- a/patacrep/encoding.py +++ b/patacrep/encoding.py @@ -16,7 +16,8 @@ def open_read(filename, mode='r', encoding=None): If `encoding` is set, use it as the encoding (do not guess). """ if encoding is None: - fileencoding = chardet.detect(open(filename, 'rb').read())['encoding'] + with open(filename, 'rb') as file: + fileencoding = chardet.detect(file.read())['encoding'] else: fileencoding = encoding diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index fbf02e36..297a993e 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -128,7 +128,7 @@ class Song: self.titles = [] self.data = {} self.cached = None - self.parse(config) + self._parse(config) # Post processing of data self.datadir = datadir @@ -165,19 +165,19 @@ class Song: def __repr__(self): return repr((self.titles, self.data, self.fullpath)) - def render(self, output, output_format): + def render(self, output_format, output=None, *args, **kwargs): """Return the code rendering this song. Arguments: - - output: Name of the output file. - 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) + return getattr(self, method)(output, *args, **kwargs) raise NotImplementedError() - def parse(self, config): # pylint: disable=no-self-use + def _parse(self, config): # pylint: disable=no-self-use """Parse song. It set the following attributes: @@ -194,6 +194,56 @@ class Song: """ raise NotImplementedError() + def get_datadirs(self, subdir=None): + """Return an iterator of existing datadirs (with eventually a subdir) + """ + for directory in self.config['datadir']: + fullpath = os.path.join(directory, subdir) + if os.path.isdir(fullpath): + yield fullpath + + def search_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. + + 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 + return the path to the compiled file. + """ + if extensions is None: + extensions = [''] + if directories is None: + directories = self.config['datadir'] + + songdir = os.path.dirname(self.fullpath) + + 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 + + def search_image(self, filename, none_if_not_found=False): + """Search for an image file""" + filepath = self.search_file( + filename, + ['', '.jpg', '.png'], + 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): + """Search for a lilypond file""" + filepath = self.search_file(filename, ['', '.ly']) + return filepath if none_if_not_found or filepath else filename + def unprefixed_title(title, prefixes): """Remove the first prefix of the list in the beginning of title (if any). """ @@ -202,39 +252,3 @@ def unprefixed_title(title, prefixes): if match: return match.group(2) return title - -def search_image(image, chordprofile, config): - """Return the file name of an image, so that LaTeX will find it. - - :param str image: The name, as provided in the chordpro file. - :param str chordprofile: The name of the file including this image. - :param dict config: Songbook configuration dictionary. - - The image can be: - - - in the same directory as the including song file; - - in the same directory as the main LaTeX file; - - in some of the `DATADIR/img` directories. - - If image is not found, the `image` argument is returned. - """ - # Image is in the same folder as its song - texdir = os.path.dirname(chordprofile) - if os.path.exists(os.path.join(texdir, image)): - return os.path.join(texdir, image) - - # Image is in the same directory as the main tex file - rootdir = os.path.dirname(os.path.join( - os.getcwd(), - config['filename'], - )) - if os.path.exists(os.path.join(rootdir, image)): - return image - - # Image is in a datadir - for directory in config['datadir']: - if os.path.exists(os.path.join(directory, 'img', image)): - return os.path.join(directory, 'img', image) - - # Could not find image - return image diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index ae37b79b..6d4c0e39 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -5,7 +5,7 @@ import pkg_resources import os from patacrep import encoding, files -from patacrep.songs import Song, search_image +from patacrep.songs import Song from patacrep.songs.chordpro.syntax import parse_song from patacrep.templates import Renderer @@ -14,9 +14,24 @@ class ChordproSong(Song): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.jinjaenv = None - def parse(self, config): + @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, + ) + + def _parse(self, config): """Parse content, and return the dictionary of song data.""" with encoding.open_read(self.fullpath, encoding=self.encoding) as song: song = parse_song(song.read(), self.fullpath) @@ -28,42 +43,39 @@ class ChordproSong(Song): 'song': song, } - def render(self, output, output_format): + def render(self, output_format, output=None, template="song", templatedirs=None): # pylint: disable=arguments-differ + if templatedirs is None: + templatedirs = [] + context = { 'language': self.languages[0], - "path": files.relpath(self.fullpath, os.path.dirname(output)), "titles": self.titles, "authors": self.authors, "metadata": self.data, "render": self._render_ast, "config": self.config, + "content": self.cached['song'].content, } - self.jinjaenv = Environment(loader=FileSystemLoader(os.path.join( - os.path.abspath(pkg_resources.resource_filename(__name__, 'data')), - output_format, - ))) - self.jinjaenv.filters['search_image'] = search_image - return self._render_ast( - context, - self.cached['song'].content, - template="song", - ) - @contextfunction - def _render_ast(self, context, content, template=None): - """Render ``content``.""" - if isinstance(context, dict): - context['content'] = content - else: - context.vars['content'] = content - if template is None: - template = content.template() + jinjaenv = Environment(loader=FileSystemLoader( + self.iter_template_paths(templatedirs, output_format) + )) + jinjaenv.filters['search_image'] = self.search_image + jinjaenv.filters['search_partition'] = self.search_partition + return Renderer( template=template, encoding='utf8', - jinjaenv=self.jinjaenv, + jinjaenv=jinjaenv, ).template.render(context) + @staticmethod + @contextfunction + def _render_ast(context, content): + """Render ``content``.""" + context.vars['content'] = content + return context.environment.get_template(content.template()).render(context) + SONG_PARSERS = { 'sgc': ChordproSong, } diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index 5cffbb86..0227d457 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -39,6 +39,10 @@ class OrderedLifoDict: def __getitem__(self, key): return self._values[key] + def get(self, key, default=None): + """Same as :meth:`dict.get`.""" + return self._values.get(key, default) + def _indent(string): """Return and indented version of argument.""" return "\n".join([" {}".format(line) for line in string.split('\n')]) @@ -149,6 +153,11 @@ class Chord(AST): # pylint: disable=too-many-arguments self.chord = chord + @property + def pretty_chord(self): + """Return the chord with nicer (utf8) alteration""" + return self.chord.replace('b', '♭').replace('#', '♯') + class Verse(AST): """A verse (or bridge, or chorus)""" _template = "verse" @@ -337,6 +346,11 @@ class Define(Directive): self.fingers = fingers # Can be None super().__init__("define", None) + @property + def pretty_key(self): + """Return the key with nicer (utf8) alteration""" + return self.key.chord.replace('&', '♭').replace('#', '♯') + def __str__(self): return None diff --git a/patacrep/songs/chordpro/data/chordpro/content_image b/patacrep/songs/chordpro/data/chordpro/content_image index 58e7f904..4f9bf06c 100644 --- a/patacrep/songs/chordpro/data/chordpro/content_image +++ b/patacrep/songs/chordpro/data/chordpro/content_image @@ -1 +1 @@ -{image: (( content.argument ))} +{image: (( content.argument|search_image ))} diff --git a/patacrep/songs/chordpro/data/chordpro/content_newline b/patacrep/songs/chordpro/data/chordpro/content_newline index 139597f9..8b137891 100644 --- a/patacrep/songs/chordpro/data/chordpro/content_newline +++ b/patacrep/songs/chordpro/data/chordpro/content_newline @@ -1,2 +1 @@ - diff --git a/patacrep/songs/chordpro/data/chordpro/content_partition b/patacrep/songs/chordpro/data/chordpro/content_partition index 362c4f64..43b8761e 100644 --- a/patacrep/songs/chordpro/data/chordpro/content_partition +++ b/patacrep/songs/chordpro/data/chordpro/content_partition @@ -1 +1 @@ -{partition: ((content.argument))} +{partition: ((content.argument|search_partition))} diff --git a/patacrep/songs/chordpro/data/chordpro/content_verse b/patacrep/songs/chordpro/data/chordpro/content_verse index 465e7697..cd562cea 100644 --- a/patacrep/songs/chordpro/data/chordpro/content_verse +++ b/patacrep/songs/chordpro/data/chordpro/content_verse @@ -1,5 +1,11 @@ +(*- if content.type != 'verse' -*) {start_of_(( content.type ))} (* for line in content.lines *) (( render(line) )) (* endfor *) {end_of_(( content.type ))} +(* else -*) + (*- for line in content.lines -*) + (( render(line) )) + (* endfor -*) +(*- endif -*) diff --git a/patacrep/songs/chordpro/data/chordpro/song b/patacrep/songs/chordpro/data/chordpro/song index 92f474ee..3baa8c22 100644 --- a/patacrep/songs/chordpro/data/chordpro/song +++ b/patacrep/songs/chordpro/data/chordpro/song @@ -1,35 +1,3 @@ -(* if language is defined -*) - {language: (( language ))} -(* endif *) -(* if metadata.columns is defined -*) - {columns: (( metadata.columns ))} -(* endif *) -(* if metadata.capo is defined -*) - {capo: (( metadata.capo ))} -(* endif *) +(* include 'song_header' *) -(*- for title in titles -*) - {title: (( title ))} -(* endfor -*) - -(*- for author in authors -*) - {artist: (( author[1] )) (( author[0] ))} -(* endfor *) - -(*- for key in ['album', 'copyright', 'cov', 'tag'] *) - (* if key in metadata -*) - {(( key )): (( metadata[key] ))} - (* endif *) -(* endfor *) - -(*- for key in metadata.keys -*) - {key: (( key.keyword )): (( key.argument ))} -(* endfor *) - -(*- for chord in metadata['define'] *) - ((- render(chord) )) -(* endfor *) - -(* for item in content -*) - (( render(item) )) -(* endfor *) +(* include 'song_body' *) diff --git a/patacrep/songs/chordpro/data/chordpro/song_body b/patacrep/songs/chordpro/data/chordpro/song_body new file mode 100644 index 00000000..1b7159bf --- /dev/null +++ b/patacrep/songs/chordpro/data/chordpro/song_body @@ -0,0 +1,3 @@ +(* for item in content -*) + (( render(item) )) +(* endfor *) diff --git a/patacrep/songs/chordpro/data/chordpro/song_header b/patacrep/songs/chordpro/data/chordpro/song_header new file mode 100644 index 00000000..f111271f --- /dev/null +++ b/patacrep/songs/chordpro/data/chordpro/song_header @@ -0,0 +1,34 @@ +(* if language is defined -*) + {language: (( language ))} +(* endif *) +(* if metadata.columns is defined -*) + {columns: (( metadata.columns ))} +(* endif *) +(* if metadata.capo is defined -*) + {capo: (( metadata.capo ))} +(* endif *) + +(*- for title in titles -*) + {title: (( title ))} +(* endfor -*) + +(*- for author in authors -*) + {artist: (( author[1] )) (( author[0] ))} +(* endfor *) + +(*- for key in ['album', 'copyright', 'tag'] *) + (* if key in metadata -*) + {(( key )): (( metadata[key] ))} + (* endif *) +(* endfor *) +(* if 'cov' in metadata -*) + {(( 'cov' )): (( metadata['cov'].argument|search_image ))} +(* endif *) + +(*- for key in metadata.keys -*) + {key: (( key.keyword )): (( key.argument ))} +(* endfor *) + +(*- for chord in metadata['define'] *) + ((- render(chord) )) +(* endfor *) diff --git a/patacrep/songs/chordpro/data/html/content_chord b/patacrep/songs/chordpro/data/html/content_chord new file mode 100644 index 00000000..baa97f7e --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_chord @@ -0,0 +1 @@ +((- content.pretty_chord -)) diff --git a/patacrep/songs/chordpro/data/html/content_chordlist b/patacrep/songs/chordpro/data/html/content_chordlist new file mode 100644 index 00000000..ad3fc4d0 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_chordlist @@ -0,0 +1,6 @@ + + (*- for chord in content.chords -*) + (* if not loop.first *) (* endif -*) + (( render(chord) -)) + (* endfor -*) + diff --git a/patacrep/songs/chordpro/data/html/content_comment b/patacrep/songs/chordpro/data/html/content_comment new file mode 100644 index 00000000..d9dfd2f1 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_comment @@ -0,0 +1 @@ +
(( content.argument ))
diff --git a/patacrep/songs/chordpro/data/html/content_define b/patacrep/songs/chordpro/data/html/content_define new file mode 100644 index 00000000..4998f0bd --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_define @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/patacrep/songs/chordpro/data/html/content_define_list b/patacrep/songs/chordpro/data/html/content_define_list new file mode 100644 index 00000000..923f37c2 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_define_list @@ -0,0 +1,3 @@ +(*- for chord in metadata['define'] *) + ((- render(chord) )) +(* endfor *) diff --git a/patacrep/songs/chordpro/data/html/content_error b/patacrep/songs/chordpro/data/html/content_error new file mode 100644 index 00000000..bbdb95fd --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_error @@ -0,0 +1,3 @@ + +ERROR : Template not found for "(( content.__class__.__name__ ))". See the logs for details. + diff --git a/patacrep/songs/chordpro/data/html/content_guitar_comment b/patacrep/songs/chordpro/data/html/content_guitar_comment new file mode 100644 index 00000000..770cc6b0 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_guitar_comment @@ -0,0 +1 @@ +
(( content.argument ))
diff --git a/patacrep/songs/chordpro/data/html/content_image b/patacrep/songs/chordpro/data/html/content_image new file mode 100644 index 00000000..0da4bcfe --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_image @@ -0,0 +1 @@ + diff --git a/patacrep/songs/chordpro/data/html/content_line b/patacrep/songs/chordpro/data/html/content_line new file mode 100644 index 00000000..03ca80b7 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_line @@ -0,0 +1,3 @@ +(* for item in content.line -*) + (( render(item) )) +(*- endfor *) diff --git a/patacrep/songs/chordpro/data/html/content_metadata_cover b/patacrep/songs/chordpro/data/html/content_metadata_cover new file mode 100644 index 00000000..96fc7718 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_metadata_cover @@ -0,0 +1,3 @@ +(* if 'cov' in metadata -*) +
+(* endif *) \ No newline at end of file diff --git a/patacrep/songs/chordpro/data/html/content_newline b/patacrep/songs/chordpro/data/html/content_newline new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_newline @@ -0,0 +1 @@ + diff --git a/patacrep/songs/chordpro/data/html/content_partition b/patacrep/songs/chordpro/data/html/content_partition new file mode 100644 index 00000000..1075cced --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_partition @@ -0,0 +1 @@ +((content.argument)) diff --git a/patacrep/songs/chordpro/data/html/content_space b/patacrep/songs/chordpro/data/html/content_space new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_space @@ -0,0 +1 @@ + diff --git a/patacrep/songs/chordpro/data/html/content_tablature b/patacrep/songs/chordpro/data/html/content_tablature new file mode 100644 index 00000000..35cfd425 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_tablature @@ -0,0 +1,5 @@ +
+  (* for content in content.content *)
+    ((- content ))
+  (* endfor *)
+
diff --git a/patacrep/songs/chordpro/data/html/content_verse b/patacrep/songs/chordpro/data/html/content_verse new file mode 100644 index 00000000..eab4d361 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_verse @@ -0,0 +1,6 @@ +

+ (*- for line in content.lines -*) + (* if not loop.first *)
(* endif -*) + (( render(line) )) + (* endfor -*) +

diff --git a/patacrep/songs/chordpro/data/html/content_word b/patacrep/songs/chordpro/data/html/content_word new file mode 100644 index 00000000..d9dd7a30 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/content_word @@ -0,0 +1 @@ +(( content.value )) diff --git a/patacrep/songs/chordpro/data/html/song b/patacrep/songs/chordpro/data/html/song new file mode 100644 index 00000000..e774cdff --- /dev/null +++ b/patacrep/songs/chordpro/data/html/song @@ -0,0 +1,5 @@ +(* include 'song_header' *) + +
+(* include 'song_body' *) +
diff --git a/patacrep/songs/chordpro/data/html/song_body b/patacrep/songs/chordpro/data/html/song_body new file mode 100644 index 00000000..1b7159bf --- /dev/null +++ b/patacrep/songs/chordpro/data/html/song_body @@ -0,0 +1,3 @@ +(* for item in content -*) + (( render(item) )) +(* endfor *) diff --git a/patacrep/songs/chordpro/data/html/song_header b/patacrep/songs/chordpro/data/html/song_header new file mode 100644 index 00000000..d85adbd8 --- /dev/null +++ b/patacrep/songs/chordpro/data/html/song_header @@ -0,0 +1,30 @@ +(*- for title in titles -*) + (* if loop.first *) +

(( title ))

+ (* else *) +

(( title ))

+ (* endif *) +(* endfor -*) + +(*- for author in authors -*) +

(( author[1] )) (( author[0] ))

+(* endfor *) + + +(*- for key in ['album', 'copyright', 'tag', 'columns', 'capo'] *) + (* if key in metadata -*) + (( key|capitalize )): (( metadata[key] ))
+ (* endif *) +(* endfor *) + +(* if language is defined -*) + Language: (( language ))
+(* endif *) + +(* include 'content_metadata_cover' *) + +(*- for key in metadata.keys -*) + {key: (( key.keyword )): (( key.argument ))} +(* endfor *) + +(* include 'content_define_list' *) diff --git a/patacrep/songs/chordpro/data/latex/content_define b/patacrep/songs/chordpro/data/latex/content_define index ca899ff3..d1294b3a 100644 --- a/patacrep/songs/chordpro/data/latex/content_define +++ b/patacrep/songs/chordpro/data/latex/content_define @@ -1,4 +1,8 @@ +(*- if content.frets|length == 4 -*) +\utab{ +(*- else -*) \gtab{ +(*- endif -*) ((- render(content.key) -)) }{ (*- if content.basefret -*) diff --git a/patacrep/songs/chordpro/data/latex/content_image b/patacrep/songs/chordpro/data/latex/content_image index bd132ce6..e4c2befb 100644 --- a/patacrep/songs/chordpro/data/latex/content_image +++ b/patacrep/songs/chordpro/data/latex/content_image @@ -1 +1 @@ -\image{(( content.argument|search_image(path, config) ))} +\image{(( content.argument|search_image ))} diff --git a/patacrep/songs/chordpro/data/latex/content_partition b/patacrep/songs/chordpro/data/latex/content_partition index bcb92a0b..4ce134a1 100644 --- a/patacrep/songs/chordpro/data/latex/content_partition +++ b/patacrep/songs/chordpro/data/latex/content_partition @@ -1 +1 @@ -\lilypond{ ((- content.argument|search_image(path, config) -)) } +\lilypond{ ((- content.argument|search_partition -)) } diff --git a/patacrep/songs/chordpro/data/latex/song b/patacrep/songs/chordpro/data/latex/song index 4cb04412..2568f7f7 100644 --- a/patacrep/songs/chordpro/data/latex/song +++ b/patacrep/songs/chordpro/data/latex/song @@ -28,7 +28,7 @@ (* endif *) (* endfor *) (* if 'cov' in metadata *) - cov={(( metadata["cov"].argument|search_image(path, config) ))}, + cov={(( metadata["cov"].argument|search_image ))}, (* endif *) (* for key in metadata.keys *) (( key.keyword ))={(( key.argument ))}, @@ -43,8 +43,6 @@ (( render(chord) )) (* endfor *) -(* for item in content -*) - (( render(item) )) -(* endfor *) +(* include 'song_body' *) \endsong diff --git a/patacrep/songs/chordpro/data/latex/song_body b/patacrep/songs/chordpro/data/latex/song_body new file mode 100644 index 00000000..1b7159bf --- /dev/null +++ b/patacrep/songs/chordpro/data/latex/song_body @@ -0,0 +1,3 @@ +(* for item in content -*) + (( render(item) )) +(* endfor *) diff --git a/patacrep/songs/latex/__init__.py b/patacrep/songs/latex/__init__.py index a0d9c945..6ac54c81 100644 --- a/patacrep/songs/latex/__init__.py +++ b/patacrep/songs/latex/__init__.py @@ -14,7 +14,7 @@ from patacrep.songs import Song class LatexSong(Song): """LaTeX song parser.""" - def parse(self, __config): + def _parse(self, __config): """Parse content, and return the dictinory of song data.""" with encoding.open_read(self.fullpath, encoding=self.encoding) as song: self.data = parse_song(song.read(), self.fullpath) @@ -30,6 +30,8 @@ class LatexSong(Song): def render_latex(self, output): """Return the code rendering the song.""" + if output is None: + raise ValueError(output) path = files.path2posix(files.relpath( self.fullpath, os.path.dirname(output) diff --git a/test/test_chordpro/01.sgc b/test/test_chordpro/01.sgc index ae7eabc4..25d6a677 100644 --- a/test/test_chordpro/01.sgc +++ b/test/test_chordpro/01.sgc @@ -1,5 +1,3 @@ {language: english} -{start_of_verse} - A verse line -{end_of_verse} +A verse line diff --git a/test/test_chordpro/08.sgc b/test/test_chordpro/08.sgc index 61224d41..9a9f0566 100644 --- a/test/test_chordpro/08.sgc +++ b/test/test_chordpro/08.sgc @@ -1,7 +1,4 @@ {language: english} - -{start_of_verse} - A lot of new lines -{end_of_verse} +A lot of new lines diff --git a/test/test_chordpro/09.sgc b/test/test_chordpro/09.sgc index 942a91e1..193db1b0 100644 --- a/test/test_chordpro/09.sgc +++ b/test/test_chordpro/09.sgc @@ -2,7 +2,4 @@ {title: and a directive} - -{start_of_verse} - A lot of new lines -{end_of_verse} +A lot of new lines diff --git a/test/test_chordpro/10.sgc b/test/test_chordpro/10.sgc index 23bce4fa..e7537b3b 100644 --- a/test/test_chordpro/10.sgc +++ b/test/test_chordpro/10.sgc @@ -1,5 +1,3 @@ {language: english} -{start_of_verse} - A line[A] with a chord -{end_of_verse} +A line[A] with a chord diff --git a/test/test_chordpro/11.sgc b/test/test_chordpro/11.sgc index 79d44702..3610f67c 100644 --- a/test/test_chordpro/11.sgc +++ b/test/test_chordpro/11.sgc @@ -1,5 +1,3 @@ {language: english} -{start_of_verse} - A line ending with a chord[A] -{end_of_verse} +A line ending with a chord[A] diff --git a/test/test_chordpro/12.sgc b/test/test_chordpro/12.sgc index 6afb5bf8..029ccad4 100644 --- a/test/test_chordpro/12.sgc +++ b/test/test_chordpro/12.sgc @@ -1,5 +1,3 @@ {language: english} -{start_of_verse} - [A]A line starting with a chord -{end_of_verse} +[A]A line starting with a chord diff --git a/test/test_chordpro/21.sgc b/test/test_chordpro/21.sgc index ae7eabc4..25d6a677 100644 --- a/test/test_chordpro/21.sgc +++ b/test/test_chordpro/21.sgc @@ -1,5 +1,3 @@ {language: english} -{start_of_verse} - A verse line -{end_of_verse} +A verse line diff --git a/test/test_chordpro/28.sgc b/test/test_chordpro/28.sgc index 61224d41..9a9f0566 100644 --- a/test/test_chordpro/28.sgc +++ b/test/test_chordpro/28.sgc @@ -1,7 +1,4 @@ {language: english} - -{start_of_verse} - A lot of new lines -{end_of_verse} +A lot of new lines diff --git a/test/test_chordpro/29.sgc b/test/test_chordpro/29.sgc index 942a91e1..193db1b0 100644 --- a/test/test_chordpro/29.sgc +++ b/test/test_chordpro/29.sgc @@ -2,7 +2,4 @@ {title: and a directive} - -{start_of_verse} - A lot of new lines -{end_of_verse} +A lot of new lines diff --git a/test/test_chordpro/chords.sgc b/test/test_chordpro/chords.sgc index 942a0223..040dcf07 100644 --- a/test/test_chordpro/chords.sgc +++ b/test/test_chordpro/chords.sgc @@ -1,19 +1,17 @@ {language: english} -{start_of_verse} - [A]Simple - [Bb]Bémol - [C#]Dièse - [Adim]dim - [Dmaj]maj - [Em3]m chiffre - [G4]Nombre - [Emaj3]maj et nombre - [Absus8]bémol, sus et nombre - [A/A]Deux notes - [F/Fb]Deux notes, bémol - [B/C#]Deux notes, dièse - [Ab B#/A]Plusieurs notes à la suite - [E5/A*]Avec une étoile - [B#+8]Avec un plus -{end_of_verse} +[A]Simple +[Bb]Bémol +[C#]Dièse +[Adim]dim +[Dmaj]maj +[Em3]m chiffre +[G4]Nombre +[Emaj3]maj et nombre +[Absus8]bémol, sus et nombre +[A/A]Deux notes +[F/Fb]Deux notes, bémol +[B/C#]Deux notes, dièse +[Ab B#/A]Plusieurs notes à la suite +[E5/A*]Avec une étoile +[B#+8]Avec un plus diff --git a/test/test_chordpro/greensleeves.sgc b/test/test_chordpro/greensleeves.sgc index bd0ddd71..1f0ea7f3 100644 --- a/test/test_chordpro/greensleeves.sgc +++ b/test/test_chordpro/greensleeves.sgc @@ -8,16 +8,12 @@ {cov: traditionnel} - {partition: greensleeves.ly} - -{start_of_verse} - A[Am]las, my love, ye [G]do me wrong - To [Am]cast me oft dis[E]curteously - And [Am]I have loved [G]you so long - De[Am]lighting [E]in your [Am]companie -{end_of_verse} +A[Am]las, my love, ye [G]do me wrong +To [Am]cast me oft dis[E]curteously +And [Am]I have loved [G]you so long +De[Am]lighting [E]in your [Am]companie {start_of_chorus} @@ -28,33 +24,25 @@ {end_of_chorus} -{start_of_verse} - I [Am]have been ready [G]at your hand - To [Am]grant what ever [E]you would crave - I [Am]have both waged [G]life and land - Your [Am]love and [E]good will [Am]for to have -{end_of_verse} +I [Am]have been ready [G]at your hand +To [Am]grant what ever [E]you would crave +I [Am]have both waged [G]life and land +Your [Am]love and [E]good will [Am]for to have -{start_of_verse} - I [Am]bought thee kerchers [G]to thy head - That [Am]were wrought fine and [E]gallantly - I [Am]kept thee both at [G]boord and bed - Which [Am]cost my [E]purse well [Am]favouredly -{end_of_verse} +I [Am]bought thee kerchers [G]to thy head +That [Am]were wrought fine and [E]gallantly +I [Am]kept thee both at [G]boord and bed +Which [Am]cost my [E]purse well [Am]favouredly -{start_of_verse} - I [Am]bought thee peticotes [G]of the best - The [Am]cloth so fine as [E]fine might be - I [Am]gave thee jewels [G]for thy chest - And [Am]all this [E]cost I [Am]spent on thee -{end_of_verse} +I [Am]bought thee peticotes [G]of the best +The [Am]cloth so fine as [E]fine might be +I [Am]gave thee jewels [G]for thy chest +And [Am]all this [E]cost I [Am]spent on thee -{start_of_verse} - Thy [Am]smock of silke, both [G]faire and white - With [Am]gold embrodered [E]gorgeously - Thy [Am]peticote of [G]sendall right - And [Am]this I [E]bought thee [Am]gladly -{end_of_verse} +Thy [Am]smock of silke, both [G]faire and white +With [Am]gold embrodered [E]gorgeously +Thy [Am]peticote of [G]sendall right +And [Am]this I [E]bought thee [Am]gladly diff --git a/test/test_chordpro/invalid_chord.sgc b/test/test_chordpro/invalid_chord.sgc index 7ec4fd65..acb5444f 100644 --- a/test/test_chordpro/invalid_chord.sgc +++ b/test/test_chordpro/invalid_chord.sgc @@ -1,5 +1,3 @@ {language: english} -{start_of_verse} - This is invalid. -{end_of_verse} +This is invalid. diff --git a/test/test_chordpro/test_parser.py b/test/test_chordpro/test_parser.py index 2bdd15aa..8f857156 100644 --- a/test/test_chordpro/test_parser.py +++ b/test/test_chordpro/test_parser.py @@ -16,22 +16,15 @@ LANGUAGES = { 'sgc': 'chordpro', } -class TestParsingRendering(unittest.TestCase): - """Test parsing and rendering""" +class FileTestMeta(type): + """Metaclass that creates on-the-fly test function according to files. - maxDiff = None + See the :class:`FileTest` documentation for more information. + """ - def test_all(self): - """Test of chorpro parser, and several renderers. + def __init__(cls, name, bases, nmspc): + super().__init__(name, bases, nmspc) - For any given `foo.source`, it is parsed as a chordpro file, and - should be rendered as `foo.sgc` with the chordpro renderer, and - `foo.tex` with the latex renderer. - """ - config = DEFAULT_CONFIG.copy() - config.update({ - 'encoding': 'utf8', - }) for source in sorted(glob.glob(os.path.join( os.path.dirname(__file__), '*.source', @@ -41,15 +34,47 @@ class TestParsingRendering(unittest.TestCase): destname = "{}.{}".format(base, dest) if not os.path.exists(destname): continue - with open(destname, 'r', encoding='utf8') as expectfile: - chordproname = "{}.source".format(base) - config['filename'] = chordproname - with disable_logging(): - with self.subTest(base=os.path.basename(base), format=dest): - self.assertMultiLineEqual( - ChordproSong(None, chordproname, config).render( - output=chordproname, - output_format=LANGUAGES[dest], - ).strip(), - expectfile.read().strip(), - ) + setattr( + cls, + "test_{}_{}".format(os.path.basename(base), dest), + cls._create_test(base, dest), + ) + + @staticmethod + def _create_test(base, dest): + """Return a function testing that `base` compilation in `dest` format. + """ + + def test_parse_render(self): + """Test that `base` is correctly parsed and rendered.""" + if base is None or dest is None: + return + destname = "{}.{}".format(base, dest) + with open(destname, 'r', encoding='utf8') as expectfile: + chordproname = "{}.source".format(base) + with disable_logging(): + self.assertMultiLineEqual( + ChordproSong(None, chordproname, DEFAULT_CONFIG).render( + output=chordproname, + output_format=LANGUAGES[dest], + ).strip(), + expectfile.read().strip(), + ) + + test_parse_render.__doc__ = ( + "Test that '{base}' is correctly parsed and rendererd into '{format}' format." + ).format(base=os.path.basename(base), format=dest) + return test_parse_render + +class FileTest(unittest.TestCase, metaclass=FileTestMeta): + """Test of chorpro parser, and several renderers. + + For any given `foo.source`, it is parsed as a chordpro file, and should be + rendered as `foo.sgc` with the chordpro renderer, and `foo.tex` with the + latex renderer. + + This class does nothing by itself, but its metaclass populates it with test + methods testing parser and renderers. + """ + + maxDiff = None diff --git a/test/test_chordpro/ukulelechords.sgc b/test/test_chordpro/ukulelechords.sgc new file mode 100644 index 00000000..3b37123d --- /dev/null +++ b/test/test_chordpro/ukulelechords.sgc @@ -0,0 +1,5 @@ +{language: english} +{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} +{define: A#+2 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} diff --git a/test/test_chordpro/ukulelechords.source b/test/test_chordpro/ukulelechords.source new file mode 100644 index 00000000..e21837f6 --- /dev/null +++ b/test/test_chordpro/ukulelechords.source @@ -0,0 +1,4 @@ +{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} +{define: A#+2 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} diff --git a/test/test_chordpro/ukulelechords.tex b/test/test_chordpro/ukulelechords.tex new file mode 100644 index 00000000..57c247e9 --- /dev/null +++ b/test/test_chordpro/ukulelechords.tex @@ -0,0 +1,14 @@ +\selectlanguage{english} + +\beginsong{}[ + by={ + }, +] + +\utab{G}{0232} +\utab{D7}{2223:1112} +\gtab{G}{320003} +\gtab{A#+2}{7:0133XX:012300} + + +\endsong