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. 4
      test/test_chordpro/01.sgc
  38. 5
      test/test_chordpro/08.sgc
  39. 5
      test/test_chordpro/09.sgc
  40. 4
      test/test_chordpro/10.sgc
  41. 4
      test/test_chordpro/11.sgc
  42. 4
      test/test_chordpro/12.sgc
  43. 4
      test/test_chordpro/21.sgc
  44. 5
      test/test_chordpro/28.sgc
  45. 5
      test/test_chordpro/29.sgc
  46. 32
      test/test_chordpro/chords.sgc
  47. 52
      test/test_chordpro/greensleeves.sgc
  48. 4
      test/test_chordpro/invalid_chord.sgc
  49. 75
      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 None:
fileencoding = chardet.detect(open(filename, 'rb').read())['encoding']
with open(filename, 'rb') as file:
fileencoding = chardet.detect(file.read())['encoding']
else:
fileencoding = encoding

96
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

62
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,
}

14
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

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 ))}
(* for line in content.lines *)
(( render(line) ))
(* endfor *)
{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 -*)
{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' *)

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{
(*- endif -*)
((- render(content.key) -))
}{
(*- 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 *)
(* 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

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):
"""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)

4
test/test_chordpro/01.sgc

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

5
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

5
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

4
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

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

4
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

4
test/test_chordpro/21.sgc

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

5
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

5
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

32
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

52
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

4
test/test_chordpro/invalid_chord.sgc

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

75
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

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