Browse Source

Merge branch 'master' into authors

pull/110/head
Louis 9 years ago
parent
commit
958eb66c62
  1. 3
      patacrep/encoding.py
  2. 96
      patacrep/songs/__init__.py
  3. 62
      patacrep/songs/chordpro/__init__.py
  4. 14
      patacrep/songs/chordpro/ast.py
  5. 2
      patacrep/songs/chordpro/data/chordpro/content_image
  6. 1
      patacrep/songs/chordpro/data/chordpro/content_newline
  7. 2
      patacrep/songs/chordpro/data/chordpro/content_partition
  8. 6
      patacrep/songs/chordpro/data/chordpro/content_verse
  9. 36
      patacrep/songs/chordpro/data/chordpro/song
  10. 3
      patacrep/songs/chordpro/data/chordpro/song_body
  11. 34
      patacrep/songs/chordpro/data/chordpro/song_header
  12. 1
      patacrep/songs/chordpro/data/html/content_chord
  13. 6
      patacrep/songs/chordpro/data/html/content_chordlist
  14. 1
      patacrep/songs/chordpro/data/html/content_comment
  15. 31
      patacrep/songs/chordpro/data/html/content_define
  16. 3
      patacrep/songs/chordpro/data/html/content_define_list
  17. 3
      patacrep/songs/chordpro/data/html/content_error
  18. 1
      patacrep/songs/chordpro/data/html/content_guitar_comment
  19. 1
      patacrep/songs/chordpro/data/html/content_image
  20. 3
      patacrep/songs/chordpro/data/html/content_line
  21. 3
      patacrep/songs/chordpro/data/html/content_metadata_cover
  22. 1
      patacrep/songs/chordpro/data/html/content_newline
  23. 1
      patacrep/songs/chordpro/data/html/content_partition
  24. 1
      patacrep/songs/chordpro/data/html/content_space
  25. 5
      patacrep/songs/chordpro/data/html/content_tablature
  26. 6
      patacrep/songs/chordpro/data/html/content_verse
  27. 1
      patacrep/songs/chordpro/data/html/content_word
  28. 5
      patacrep/songs/chordpro/data/html/song
  29. 3
      patacrep/songs/chordpro/data/html/song_body
  30. 30
      patacrep/songs/chordpro/data/html/song_header
  31. 4
      patacrep/songs/chordpro/data/latex/content_define
  32. 2
      patacrep/songs/chordpro/data/latex/content_image
  33. 2
      patacrep/songs/chordpro/data/latex/content_partition
  34. 6
      patacrep/songs/chordpro/data/latex/song
  35. 3
      patacrep/songs/chordpro/data/latex/song_body
  36. 4
      patacrep/songs/latex/__init__.py
  37. 2
      test/test_chordpro/01.sgc
  38. 3
      test/test_chordpro/08.sgc
  39. 3
      test/test_chordpro/09.sgc
  40. 2
      test/test_chordpro/10.sgc
  41. 2
      test/test_chordpro/11.sgc
  42. 2
      test/test_chordpro/12.sgc
  43. 2
      test/test_chordpro/21.sgc
  44. 3
      test/test_chordpro/28.sgc
  45. 3
      test/test_chordpro/29.sgc
  46. 2
      test/test_chordpro/chords.sgc
  47. 12
      test/test_chordpro/greensleeves.sgc
  48. 2
      test/test_chordpro/invalid_chord.sgc
  49. 57
      test/test_chordpro/test_parser.py
  50. 5
      test/test_chordpro/ukulelechords.sgc
  51. 4
      test/test_chordpro/ukulelechords.source
  52. 14
      test/test_chordpro/ukulelechords.tex

3
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 set, use it as the encoding (do not guess).
""" """
if encoding is None: 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: else:
fileencoding = encoding fileencoding = encoding

96
patacrep/songs/__init__.py

@ -128,7 +128,7 @@ class Song:
self.titles = [] self.titles = []
self.data = {} self.data = {}
self.cached = None self.cached = None
self.parse(config) self._parse(config)
# Post processing of data # Post processing of data
self.datadir = datadir self.datadir = datadir
@ -165,19 +165,19 @@ class Song:
def __repr__(self): def __repr__(self):
return repr((self.titles, self.data, self.fullpath)) return repr((self.titles, self.data, self.fullpath))
def render(self, output, output_format): def render(self, output_format, output=None, *args, **kwargs):
"""Return the code rendering this song. """Return the code rendering this song.
Arguments: Arguments:
- output: Name of the output file.
- output_format: Format of the output file (latex, chordpro...) - output_format: Format of the output file (latex, chordpro...)
- output: Name of the output file, or `None` if irrelevant.
""" """
method = "render_{}".format(output_format) method = "render_{}".format(output_format)
if hasattr(self, method): if hasattr(self, method):
return getattr(self, method)(output) return getattr(self, method)(output, *args, **kwargs)
raise NotImplementedError() raise NotImplementedError()
def parse(self, config): # pylint: disable=no-self-use def _parse(self, config): # pylint: disable=no-self-use
"""Parse song. """Parse song.
It set the following attributes: It set the following attributes:
@ -194,47 +194,61 @@ class Song:
""" """
raise NotImplementedError() raise NotImplementedError()
def unprefixed_title(title, prefixes): def get_datadirs(self, subdir=None):
"""Remove the first prefix of the list in the beginning of title (if any). """Return an iterator of existing datadirs (with eventually a subdir)
""" """
for prefix in prefixes: for directory in self.config['datadir']:
match = re.compile(r"^(%s)\b\s*(.*)$" % prefix, re.LOCALE).match(title) fullpath = os.path.join(directory, subdir)
if match: if os.path.isdir(fullpath):
return match.group(2) yield fullpath
return title
def search_image(image, chordprofile, config): def search_file(self, filename, extensions=None, directories=None):
"""Return the file name of an image, so that LaTeX will find it. """Search for a file name.
:param str image: The name, as provided in the chordpro file. :param str filename: The name, as provided in the chordpro file (with or without extension).
:param str chordprofile: The name of the file including this image. :param list extensions: Possible extensions (with '.'). Default is no extension.
:param dict config: Songbook configuration dictionary. :param iterator directories: Other directories where to search for the file
The directory where the Song file is stored is added to the list.
The image can be: Returns None if nothing found.
- in the same directory as the including song file; This function can also be used as a preprocessor for a renderer: for
- in the same directory as the main LaTeX file; instance, it can compile a file, place it in a temporary folder, and
- in some of the `DATADIR/img` directories. return the path to the compiled file.
If image is not found, the `image` argument is returned.
""" """
# Image is in the same folder as its song if extensions is None:
texdir = os.path.dirname(chordprofile) extensions = ['']
if os.path.exists(os.path.join(texdir, image)): if directories is None:
return os.path.join(texdir, image) directories = self.config['datadir']
# Image is in the same directory as the main tex file songdir = os.path.dirname(self.fullpath)
rootdir = os.path.dirname(os.path.join(
os.getcwd(), for directory in [songdir] + list(directories):
config['filename'], for extension in extensions:
)) fullpath = os.path.join(directory, filename + extension)
if os.path.exists(os.path.join(rootdir, image)): if os.path.isfile(fullpath):
return image 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
# Image is in a datadir def search_partition(self, filename, none_if_not_found=False):
for directory in config['datadir']: """Search for a lilypond file"""
if os.path.exists(os.path.join(directory, 'img', image)): filepath = self.search_file(filename, ['', '.ly'])
return os.path.join(directory, 'img', image) return filepath if none_if_not_found or filepath else filename
# Could not find image def unprefixed_title(title, prefixes):
return image """Remove the first prefix of the list in the beginning of title (if any).
"""
for prefix in prefixes:
match = re.compile(r"^(%s)\b\s*(.*)$" % prefix, re.LOCALE).match(title)
if match:
return match.group(2)
return title

62
patacrep/songs/chordpro/__init__.py

@ -5,7 +5,7 @@ import pkg_resources
import os import os
from patacrep import encoding, files 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.songs.chordpro.syntax import parse_song
from patacrep.templates import Renderer from patacrep.templates import Renderer
@ -14,9 +14,24 @@ class ChordproSong(Song):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*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.""" """Parse content, and return the dictionary of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song: with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
song = parse_song(song.read(), self.fullpath) song = parse_song(song.read(), self.fullpath)
@ -28,42 +43,39 @@ class ChordproSong(Song):
'song': 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 = { context = {
'language': self.languages[0], 'language': self.languages[0],
"path": files.relpath(self.fullpath, os.path.dirname(output)),
"titles": self.titles, "titles": self.titles,
"authors": self.authors, "authors": self.authors,
"metadata": self.data, "metadata": self.data,
"render": self._render_ast, "render": self._render_ast,
"config": self.config, "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 jinjaenv = Environment(loader=FileSystemLoader(
def _render_ast(self, context, content, template=None): self.iter_template_paths(templatedirs, output_format)
"""Render ``content``.""" ))
if isinstance(context, dict): jinjaenv.filters['search_image'] = self.search_image
context['content'] = content jinjaenv.filters['search_partition'] = self.search_partition
else:
context.vars['content'] = content
if template is None:
template = content.template()
return Renderer( return Renderer(
template=template, template=template,
encoding='utf8', encoding='utf8',
jinjaenv=self.jinjaenv, jinjaenv=jinjaenv,
).template.render(context) ).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 = { SONG_PARSERS = {
'sgc': ChordproSong, 'sgc': ChordproSong,
} }

14
patacrep/songs/chordpro/ast.py

@ -39,6 +39,10 @@ class OrderedLifoDict:
def __getitem__(self, key): def __getitem__(self, key):
return self._values[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): def _indent(string):
"""Return and indented version of argument.""" """Return and indented version of argument."""
return "\n".join([" {}".format(line) for line in string.split('\n')]) return "\n".join([" {}".format(line) for line in string.split('\n')])
@ -149,6 +153,11 @@ class Chord(AST):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
self.chord = chord self.chord = chord
@property
def pretty_chord(self):
"""Return the chord with nicer (utf8) alteration"""
return self.chord.replace('b', '').replace('#', '')
class Verse(AST): class Verse(AST):
"""A verse (or bridge, or chorus)""" """A verse (or bridge, or chorus)"""
_template = "verse" _template = "verse"
@ -337,6 +346,11 @@ class Define(Directive):
self.fingers = fingers # Can be None self.fingers = fingers # Can be None
super().__init__("define", 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): def __str__(self):
return None return None

2
patacrep/songs/chordpro/data/chordpro/content_image

@ -1 +1 @@
{image: (( content.argument ))} {image: (( content.argument|search_image ))}

1
patacrep/songs/chordpro/data/chordpro/content_newline

@ -1,2 +1 @@

2
patacrep/songs/chordpro/data/chordpro/content_partition

@ -1 +1 @@
{partition: ((content.argument))} {partition: ((content.argument|search_partition))}

6
patacrep/songs/chordpro/data/chordpro/content_verse

@ -1,5 +1,11 @@
(*- if content.type != 'verse' -*)
{start_of_(( content.type ))} {start_of_(( content.type ))}
(* for line in content.lines *) (* for line in content.lines *)
(( render(line) )) (( render(line) ))
(* endfor *) (* endfor *)
{end_of_(( content.type ))} {end_of_(( content.type ))}
(* else -*)
(*- for line in content.lines -*)
(( render(line) ))
(* endfor -*)
(*- endif -*)

36
patacrep/songs/chordpro/data/chordpro/song

@ -1,35 +1,3 @@
(* if language is defined -*) (* include 'song_header' *)
{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 -*) (* include 'song_body' *)
{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 *)

3
patacrep/songs/chordpro/data/chordpro/song_body

@ -0,0 +1,3 @@
(* for item in content -*)
(( render(item) ))
(* endfor *)

34
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 *)

1
patacrep/songs/chordpro/data/html/content_chord

@ -0,0 +1 @@
((- content.pretty_chord -))

6
patacrep/songs/chordpro/data/html/content_chordlist

@ -0,0 +1,6 @@
<span class="chord">
(*- for chord in content.chords -*)
(* if not loop.first *) (* endif -*)
(( render(chord) -))
(* endfor -*)
</span>

1
patacrep/songs/chordpro/data/html/content_comment

@ -0,0 +1 @@
<div class="comment">(( content.argument ))</div>

31
patacrep/songs/chordpro/data/html/content_define

@ -0,0 +1,31 @@
<div(( " " -))
class="chord-diagram"(( " " -))
data-shift="
(*- if content.basefret -*)
((content.basefret))
(*- else -*)
0
(*- endif *)
((- '" ' -))
data-frets="
(*- for fret in content.frets -*)
(* if fret is none -*)
x
(*- else -*)
(( fret -))
(* endif -*)
(* endfor -*)
((- '" ' -))
(* if content.fingers -*)
data-fingers="
(*- for finger in content.fingers -*)
(* if finger is none -*)
-
(*- else -*)
(( finger -))
(* endif -*)
(* endfor -*)
((- '" ' -))
(* endif -*)
data-name="(( content.pretty_key ))"(( " " -))
></div>

3
patacrep/songs/chordpro/data/html/content_define_list

@ -0,0 +1,3 @@
(*- for chord in metadata['define'] *)
((- render(chord) ))
(* endfor *)

3
patacrep/songs/chordpro/data/html/content_error

@ -0,0 +1,3 @@
ERROR : Template not found for "(( content.__class__.__name__ ))". See the logs for details.

1
patacrep/songs/chordpro/data/html/content_guitar_comment

@ -0,0 +1 @@
<div class="guitar_comment">(( content.argument ))</div>

1
patacrep/songs/chordpro/data/html/content_image

@ -0,0 +1 @@
<img src="(( content.argument|search_image ))">

3
patacrep/songs/chordpro/data/html/content_line

@ -0,0 +1,3 @@
(* for item in content.line -*)
(( render(item) ))
(*- endfor *)

3
patacrep/songs/chordpro/data/html/content_metadata_cover

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

1
patacrep/songs/chordpro/data/html/content_newline

@ -0,0 +1 @@

1
patacrep/songs/chordpro/data/html/content_partition

@ -0,0 +1 @@
<a class="song-partition" href="(( content.argument|search_partition ))">((content.argument))</a>

1
patacrep/songs/chordpro/data/html/content_space

@ -0,0 +1 @@

5
patacrep/songs/chordpro/data/html/content_tablature

@ -0,0 +1,5 @@
<pre class="tablature">
(* for content in content.content *)
((- content ))
(* endfor *)
</pre>

6
patacrep/songs/chordpro/data/html/content_verse

@ -0,0 +1,6 @@
<p class="(( content.type ))">
(*- for line in content.lines -*)
(* if not loop.first *)<br>(* endif -*)
(( render(line) ))
(* endfor -*)
</p>

1
patacrep/songs/chordpro/data/html/content_word

@ -0,0 +1 @@
(( content.value ))

5
patacrep/songs/chordpro/data/html/song

@ -0,0 +1,5 @@
(* include 'song_header' *)
<div class="song_content">
(* include 'song_body' *)
</div>

3
patacrep/songs/chordpro/data/html/song_body

@ -0,0 +1,3 @@
(* for item in content -*)
(( render(item) ))
(* endfor *)

30
patacrep/songs/chordpro/data/html/song_header

@ -0,0 +1,30 @@
(*- for title in titles -*)
(* if loop.first *)
<h1 class="song-title">(( title ))</h1>
(* else *)
<h2 class="song-title">(( title ))</h2>
(* endif *)
(* endfor -*)
(*- for author in authors -*)
<h2 class="song-artist">(( author[1] )) (( author[0] ))</h2>
(* endfor *)
(*- for key in ['album', 'copyright', 'tag', 'columns', 'capo'] *)
(* if key in metadata -*)
<span class="song-(( key ))">(( key|capitalize )): (( metadata[key] ))</span><br/>
(* endif *)
(* endfor *)
(* if language is defined -*)
<span class="song-language">Language: (( language ))</span><br>
(* endif *)
(* include 'content_metadata_cover' *)
(*- for key in metadata.keys -*)
{key: (( key.keyword )): (( key.argument ))}
(* endfor *)
(* include 'content_define_list' *)

4
patacrep/songs/chordpro/data/latex/content_define

@ -1,4 +1,8 @@
(*- if content.frets|length == 4 -*)
\utab{
(*- else -*)
\gtab{ \gtab{
(*- endif -*)
((- render(content.key) -)) ((- render(content.key) -))
}{ }{
(*- if content.basefret -*) (*- if content.basefret -*)

2
patacrep/songs/chordpro/data/latex/content_image

@ -1 +1 @@
\image{(( content.argument|search_image(path, config) ))} \image{(( content.argument|search_image ))}

2
patacrep/songs/chordpro/data/latex/content_partition

@ -1 +1 @@
\lilypond{ ((- content.argument|search_image(path, config) -)) } \lilypond{ ((- content.argument|search_partition -)) }

6
patacrep/songs/chordpro/data/latex/song

@ -28,7 +28,7 @@
(* endif *) (* endif *)
(* endfor *) (* endfor *)
(* if 'cov' in metadata *) (* if 'cov' in metadata *)
cov={(( metadata["cov"].argument|search_image(path, config) ))}, cov={(( metadata["cov"].argument|search_image ))},
(* endif *) (* endif *)
(* for key in metadata.keys *) (* for key in metadata.keys *)
(( key.keyword ))={(( key.argument ))}, (( key.keyword ))={(( key.argument ))},
@ -43,8 +43,6 @@
(( render(chord) )) (( render(chord) ))
(* endfor *) (* endfor *)
(* for item in content -*) (* include 'song_body' *)
(( render(item) ))
(* endfor *)
\endsong \endsong

3
patacrep/songs/chordpro/data/latex/song_body

@ -0,0 +1,3 @@
(* for item in content -*)
(( render(item) ))
(* endfor *)

4
patacrep/songs/latex/__init__.py

@ -14,7 +14,7 @@ from patacrep.songs import Song
class LatexSong(Song): class LatexSong(Song):
"""LaTeX song parser.""" """LaTeX song parser."""
def parse(self, __config): def _parse(self, __config):
"""Parse content, and return the dictinory of song data.""" """Parse content, and return the dictinory of song data."""
with encoding.open_read(self.fullpath, encoding=self.encoding) as song: with encoding.open_read(self.fullpath, encoding=self.encoding) as song:
self.data = parse_song(song.read(), self.fullpath) self.data = parse_song(song.read(), self.fullpath)
@ -30,6 +30,8 @@ class LatexSong(Song):
def render_latex(self, output): def render_latex(self, output):
"""Return the code rendering the song.""" """Return the code rendering the song."""
if output is None:
raise ValueError(output)
path = files.path2posix(files.relpath( path = files.path2posix(files.relpath(
self.fullpath, self.fullpath,
os.path.dirname(output) os.path.dirname(output)

2
test/test_chordpro/01.sgc

@ -1,5 +1,3 @@
{language: english} {language: english}
{start_of_verse}
A verse line A verse line
{end_of_verse}

3
test/test_chordpro/08.sgc

@ -1,7 +1,4 @@
{language: english} {language: english}
{start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse}

3
test/test_chordpro/09.sgc

@ -2,7 +2,4 @@
{title: and a directive} {title: and a directive}
{start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse}

2
test/test_chordpro/10.sgc

@ -1,5 +1,3 @@
{language: english} {language: english}
{start_of_verse}
A line[A] with a chord A line[A] with a chord
{end_of_verse}

2
test/test_chordpro/11.sgc

@ -1,5 +1,3 @@
{language: english} {language: english}
{start_of_verse}
A line ending with a chord[A] A line ending with a chord[A]
{end_of_verse}

2
test/test_chordpro/12.sgc

@ -1,5 +1,3 @@
{language: english} {language: english}
{start_of_verse}
[A]A line starting with a chord [A]A line starting with a chord
{end_of_verse}

2
test/test_chordpro/21.sgc

@ -1,5 +1,3 @@
{language: english} {language: english}
{start_of_verse}
A verse line A verse line
{end_of_verse}

3
test/test_chordpro/28.sgc

@ -1,7 +1,4 @@
{language: english} {language: english}
{start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse}

3
test/test_chordpro/29.sgc

@ -2,7 +2,4 @@
{title: and a directive} {title: and a directive}
{start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse}

2
test/test_chordpro/chords.sgc

@ -1,6 +1,5 @@
{language: english} {language: english}
{start_of_verse}
[A]Simple [A]Simple
[Bb]Bémol [Bb]Bémol
[C#]Dièse [C#]Dièse
@ -16,4 +15,3 @@
[Ab B#/A]Plusieurs notes à la suite [Ab B#/A]Plusieurs notes à la suite
[E5/A*]Avec une étoile [E5/A*]Avec une étoile
[B#+8]Avec un plus [B#+8]Avec un plus
{end_of_verse}

12
test/test_chordpro/greensleeves.sgc

@ -8,16 +8,12 @@
{cov: traditionnel} {cov: traditionnel}
{partition: greensleeves.ly} {partition: greensleeves.ly}
{start_of_verse}
A[Am]las, my love, ye [G]do me wrong A[Am]las, my love, ye [G]do me wrong
To [Am]cast me oft dis[E]curteously To [Am]cast me oft dis[E]curteously
And [Am]I have loved [G]you so long And [Am]I have loved [G]you so long
De[Am]lighting [E]in your [Am]companie De[Am]lighting [E]in your [Am]companie
{end_of_verse}
{start_of_chorus} {start_of_chorus}
@ -28,33 +24,25 @@
{end_of_chorus} {end_of_chorus}
{start_of_verse}
I [Am]have been ready [G]at your hand I [Am]have been ready [G]at your hand
To [Am]grant what ever [E]you would crave To [Am]grant what ever [E]you would crave
I [Am]have both waged [G]life and land I [Am]have both waged [G]life and land
Your [Am]love and [E]good will [Am]for to have Your [Am]love and [E]good will [Am]for to have
{end_of_verse}
{start_of_verse}
I [Am]bought thee kerchers [G]to thy head I [Am]bought thee kerchers [G]to thy head
That [Am]were wrought fine and [E]gallantly That [Am]were wrought fine and [E]gallantly
I [Am]kept thee both at [G]boord and bed I [Am]kept thee both at [G]boord and bed
Which [Am]cost my [E]purse well [Am]favouredly Which [Am]cost my [E]purse well [Am]favouredly
{end_of_verse}
{start_of_verse}
I [Am]bought thee peticotes [G]of the best I [Am]bought thee peticotes [G]of the best
The [Am]cloth so fine as [E]fine might be The [Am]cloth so fine as [E]fine might be
I [Am]gave thee jewels [G]for thy chest I [Am]gave thee jewels [G]for thy chest
And [Am]all this [E]cost I [Am]spent on thee And [Am]all this [E]cost I [Am]spent on thee
{end_of_verse}
{start_of_verse}
Thy [Am]smock of silke, both [G]faire and white Thy [Am]smock of silke, both [G]faire and white
With [Am]gold embrodered [E]gorgeously With [Am]gold embrodered [E]gorgeously
Thy [Am]peticote of [G]sendall right Thy [Am]peticote of [G]sendall right
And [Am]this I [E]bought thee [Am]gladly And [Am]this I [E]bought thee [Am]gladly
{end_of_verse}

2
test/test_chordpro/invalid_chord.sgc

@ -1,5 +1,3 @@
{language: english} {language: english}
{start_of_verse}
This is invalid. This is invalid.
{end_of_verse}

57
test/test_chordpro/test_parser.py

@ -16,22 +16,15 @@ LANGUAGES = {
'sgc': 'chordpro', 'sgc': 'chordpro',
} }
class TestParsingRendering(unittest.TestCase): class FileTestMeta(type):
"""Test parsing and rendering""" """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): def __init__(cls, name, bases, nmspc):
"""Test of chorpro parser, and several renderers. 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( for source in sorted(glob.glob(os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'*.source', '*.source',
@ -41,15 +34,47 @@ class TestParsingRendering(unittest.TestCase):
destname = "{}.{}".format(base, dest) destname = "{}.{}".format(base, dest)
if not os.path.exists(destname): if not os.path.exists(destname):
continue continue
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: with open(destname, 'r', encoding='utf8') as expectfile:
chordproname = "{}.source".format(base) chordproname = "{}.source".format(base)
config['filename'] = chordproname
with disable_logging(): with disable_logging():
with self.subTest(base=os.path.basename(base), format=dest):
self.assertMultiLineEqual( self.assertMultiLineEqual(
ChordproSong(None, chordproname, config).render( ChordproSong(None, chordproname, DEFAULT_CONFIG).render(
output=chordproname, output=chordproname,
output_format=LANGUAGES[dest], output_format=LANGUAGES[dest],
).strip(), ).strip(),
expectfile.read().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

5
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 - -}

4
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 - -}

14
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
Loading…
Cancel
Save