Browse Source

[chordpro] `{define: FOO}` directive are now meta information. Generated AST changed too.

pull/79/head
Louis 9 years ago
parent
commit
6afb5fd8e0
  1. 14
      patacrep/songs/chordpro/__init__.py
  2. 288
      patacrep/songs/chordpro/ast.py
  3. 12
      patacrep/songs/chordpro/data/latex/song.tex
  4. 22
      patacrep/songs/chordpro/syntax.py
  5. 2
      patacrep/songs/chordpro/test/00.txt
  6. 2
      patacrep/songs/chordpro/test/01.txt
  7. 2
      patacrep/songs/chordpro/test/02.txt
  8. 2
      patacrep/songs/chordpro/test/03.txt
  9. 2
      patacrep/songs/chordpro/test/04.txt
  10. 2
      patacrep/songs/chordpro/test/05.txt
  11. 2
      patacrep/songs/chordpro/test/06.txt
  12. 2
      patacrep/songs/chordpro/test/07.txt
  13. 2
      patacrep/songs/chordpro/test/08.txt
  14. 2
      patacrep/songs/chordpro/test/09.txt
  15. 2
      patacrep/songs/chordpro/test/10.txt
  16. 2
      patacrep/songs/chordpro/test/11.txt
  17. 2
      patacrep/songs/chordpro/test/12.txt
  18. 2
      patacrep/songs/chordpro/test/13.txt
  19. 2
      patacrep/songs/chordpro/test/21.txt
  20. 2
      patacrep/songs/chordpro/test/22.txt
  21. 2
      patacrep/songs/chordpro/test/23.txt
  22. 2
      patacrep/songs/chordpro/test/24.txt
  23. 2
      patacrep/songs/chordpro/test/25.txt
  24. 2
      patacrep/songs/chordpro/test/26.txt
  25. 2
      patacrep/songs/chordpro/test/27.txt
  26. 2
      patacrep/songs/chordpro/test/28.txt
  27. 2
      patacrep/songs/chordpro/test/29.txt
  28. 2
      patacrep/songs/chordpro/test/chords.txt
  29. 2
      patacrep/songs/chordpro/test/customchords.txt
  30. 6
      patacrep/songs/chordpro/test/greensleeves.txt
  31. 8
      patacrep/songs/chordpro/test/metadata.txt
  32. 4
      patacrep/songs/chordpro/test/test_parser.py

14
patacrep/songs/chordpro/__init__.py

@ -22,28 +22,30 @@ class ChordproSong(Song):
song = parse_song(song.read(), self.fullpath) song = parse_song(song.read(), self.fullpath)
self.authors = song.authors self.authors = song.authors
self.titles = song.titles self.titles = song.titles
self.languages = song.get_directives('language') self.languages = song.get_data_argument('language', [self.config['lang']])
self.data = dict([meta.as_tuple for meta in song.meta]) self.data = song.meta
self.cached = { self.cached = {
'song': song, 'song': song,
} }
def tex(self, output): def tex(self, output):
context = { context = {
'language': self.cached['song'].get_directive('language', self.config['lang']), 'language': self.config.get(
'columns': self.cached['song'].get_directive('columns', 1), 'lang',
self.cached['song'].get_data_argument('language', 'english'),
),
#'columns': self.cached['song'].get_data_argument('columns', 1),
"path": files.relpath(self.fullpath, os.path.dirname(output)), "path": files.relpath(self.fullpath, os.path.dirname(output)),
"titles": r"\\".join(self.titles), "titles": r"\\".join(self.titles),
"authors": ", ".join(["{} {}".format(name[1], name[0]) for name in self.authors]), "authors": ", ".join(["{} {}".format(name[1], name[0]) for name in self.authors]),
"metadata": self.data, "metadata": self.data,
"beginsong": self.cached['song'].meta_beginsong(),
"render": self.render_tex, "render": self.render_tex,
} }
self.texenv = Environment(loader=FileSystemLoader(os.path.join( self.texenv = Environment(loader=FileSystemLoader(os.path.join(
os.path.abspath(pkg_resources.resource_filename(__name__, 'data')), os.path.abspath(pkg_resources.resource_filename(__name__, 'data')),
'latex' 'latex'
))) )))
return self.render_tex(context, self.cached['song'].content, template="chordpro.tex") return self.render_tex(context, self.cached['song'].content, template="song.tex")
@contextfunction @contextfunction
def render_tex(self, context, content, template=None): def render_tex(self, context, content, template=None):

288
patacrep/songs/chordpro/ast.py

@ -2,12 +2,41 @@
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
import functools
import logging import logging
import os import os
LOGGER = logging.getLogger() LOGGER = logging.getLogger()
class OrderedLifoDict:
"""Ordered (LIFO) dictionary.
Mimics the :class:`dict` dictionary, with:
- dictionary is ordered: the order the keys are kept (as with
:class:`collections.OrderedDict`), excepted that:
- LIFO: the last item is reterned first when iterating.
"""
def __init__(self):
self._keys = []
self._values = {}
def values(self):
"""Same as :meth:`dict.values`."""
for key in self:
yield self._values[key]
def __iter__(self):
yield from self._keys
def __setitem__(self, key, value):
if key not in self._keys:
self._keys.insert(0, key)
self._values[key] = value
def __getitem__(self, key):
return self._values[key]
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')])
@ -21,15 +50,6 @@ INLINE_PROPERTIES = {
"image", "image",
} }
#: List of properties that are listed in the `\beginsong` LaTeX directive.
BEGINSONG_PROPERTIES = {
"album",
"copyright",
"cov",
"vcov",
"tag",
}
#: Some directive have alternative names. For instance `{title: Foo}` and `{t: #: Some directive have alternative names. For instance `{title: Foo}` and `{t:
#: Foo}` are equivalent. #: Foo}` are equivalent.
DIRECTIVE_SHORTCUTS = { DIRECTIVE_SHORTCUTS = {
@ -62,6 +82,10 @@ class AST:
base = self._template base = self._template
return "content_{}.{}".format(base, extension) return "content_{}.{}".format(base, extension)
def chordpro(self):
"""Return the chordpro string corresponding to this object."""
raise NotImplementedError()
class Line(AST): class Line(AST):
"""A line is a sequence of (possibly truncated) words, spaces and chords.""" """A line is a sequence of (possibly truncated) words, spaces and chords."""
@ -76,8 +100,8 @@ class Line(AST):
self.line.insert(0, data) self.line.insert(0, data)
return self return self
def __str__(self): def chordpro(self):
return "".join([str(item) for item in self.line]) return "".join([item.chordpro() for item in self.line])
def strip(self): def strip(self):
"""Remove spaces at the beginning and end of line.""" """Remove spaces at the beginning and end of line."""
@ -94,6 +118,7 @@ class Line(AST):
class LineElement(AST): class LineElement(AST):
"""Something present on a line.""" """Something present on a line."""
# pylint: disable=abstract-method
pass pass
class Word(LineElement): class Word(LineElement):
@ -104,7 +129,7 @@ class Word(LineElement):
super().__init__() super().__init__()
self.value = value self.value = value
def __str__(self): def chordpro(self):
return self.value return self.value
class Space(LineElement): class Space(LineElement):
@ -114,7 +139,7 @@ class Space(LineElement):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def __str__(self): def chordpro(self):
return " " return " "
class ChordList(LineElement): class ChordList(LineElement):
@ -124,9 +149,9 @@ class ChordList(LineElement):
def __init__(self, *chords): def __init__(self, *chords):
self.chords = chords self.chords = chords
def __str__(self): def chordpro(self):
return "[{}]".format(" ".join( return "[{}]".format(" ".join(
[str(chord) for chord in self.chords] [chord.chordpro() for chord in self.chords]
)) ))
class Chord(AST): class Chord(AST):
@ -151,7 +176,7 @@ class Chord(AST):
self.basskey = basskey self.basskey = basskey
self.bassalteration = bassalteration self.bassalteration = bassalteration
def __str__(self): def chordpro(self):
text = "" text = ""
text += self.key text += self.key
if self.alteration is not None: if self.alteration is not None:
@ -166,50 +191,6 @@ class Chord(AST):
text += self.bassalteration text += self.bassalteration
return text return text
class Define(AST):
"""A chord definition.
Attributes:
.. attribute:: key
The key, as a :class:`Chord` object.
.. attribute:: basefret
The base fret, as an integer. Can be `None` if no base fret is defined.
.. attribute:: frets
The list of frets, as a list of integers, or `None`, if this fret is not to be played.
.. attribute:: fingers
The list of fingers to use on frets, as a list of integers, or `None`
if no information is given (this string is not played, or is played
open). Can be `None` if not defined.
"""
_template = "define"
inline = True
def __init__(self, key, basefret, frets, fingers):
self.key = key
self.basefret = basefret # Can be None
self.frets = frets
self.fingers = fingers # Can be None
def __str__(self):
text = str(self.key)
if self.basefret is not None:
text += " base-fret " + str(self.basefret)
text += " frets"
for fret in self.frets:
if fret is None:
text += " x"
else:
text += " " + str(fret)
if self.fingers:
text += " fingers"
for finger in self.fingers:
if finger is None:
text += " -"
else:
text += " " + str(finger)
return "{{define: {}}}".format(text)
class Verse(AST): class Verse(AST):
"""A verse (or bridge, or chorus)""" """A verse (or bridge, or chorus)"""
_template = "verse" _template = "verse"
@ -225,10 +206,10 @@ class Verse(AST):
self.lines.insert(0, data) self.lines.insert(0, data)
return self return self
def __str__(self): def chordpro(self):
return '{{start_of_{type}}}\n{content}\n{{end_of_{type}}}'.format( return '{{start_of_{type}}}\n{content}\n{{end_of_{type}}}'.format(
type=self.type, type=self.type,
content=_indent("\n".join([str(line) for line in self.lines])), content=_indent("\n".join([line.chordpro() for line in self.lines])),
) )
class Chorus(Verse): class Chorus(Verse):
@ -248,17 +229,17 @@ class Song(AST):
- titles: The list of titles - titles: The list of titles
- language: The language (if set), None otherwise - language: The language (if set), None otherwise
- authors: The list of authors - authors: The list of authors
- meta_beginsong: The list of directives that are to be set in the
`\beginsong{}` LaTeX directive.
- meta: Every other metadata. - meta: Every other metadata.
""" """
#: Some directives are added to the song using special methods. #: Some directives are added to the song using special methods.
METADATA_TYPE = { METADATA_ADD = {
"title": "add_title", "title": "add_title",
"subtitle": "add_subtitle", "subtitle": "add_subtitle",
"artist": "add_author", "artist": "add_author",
"key": "add_key", "key": "add_key",
"define": "add_cumulative",
"language": "add_cumulative",
} }
#: Some directives have to be processed before being considered. #: Some directives have to be processed before being considered.
@ -271,7 +252,7 @@ class Song(AST):
def __init__(self, filename): def __init__(self, filename):
super().__init__() super().__init__()
self.content = [] self.content = []
self.meta = [] self.meta = OrderedLifoDict()
self._authors = [] self._authors = []
self._titles = [] self._titles = []
self._subtitles = [] self._subtitles = []
@ -282,9 +263,8 @@ class Song(AST):
"""Add an element to the song""" """Add an element to the song"""
if isinstance(data, Directive): if isinstance(data, Directive):
# Some directives are preprocessed # Some directives are preprocessed
name = directive_name(data.keyword) if data.keyword in self.PROCESS_DIRECTIVE:
if name in self.PROCESS_DIRECTIVE: data = getattr(self, self.PROCESS_DIRECTIVE[data.keyword])(data)
data = getattr(self, self.PROCESS_DIRECTIVE[name])(data)
if data is None: if data is None:
# New line # New line
@ -300,12 +280,11 @@ class Song(AST):
self.content.insert(0, data) self.content.insert(0, data)
elif isinstance(data, Directive): elif isinstance(data, Directive):
# Add a metadata directive. Some of them are added using special # Add a metadata directive. Some of them are added using special
# methods listed in ``METADATA_TYPE``. # methods listed in ``METADATA_ADD``.
name = directive_name(data.keyword) if data.keyword in self.METADATA_ADD:
if name in self.METADATA_TYPE: getattr(self, self.METADATA_ADD[data.keyword])(data)
getattr(self, self.METADATA_TYPE[name])(*data.as_tuple)
else: else:
self.meta.append(data) self.meta[data.keyword] = data
else: else:
raise Exception() raise Exception()
return self return self
@ -316,62 +295,71 @@ class Song(AST):
yield "{{title: {}}}".format(title) yield "{{title: {}}}".format(title)
for author in self.authors: for author in self.authors:
yield "{{by: {}}}".format(author) yield "{{by: {}}}".format(author)
for key in sorted(self.keys): for key in self.keys:
yield "{{key: {}}}".format(str(key)) yield "{{key: {}}}".format(key.chordpro())
for key in sorted(self.meta): for value in self.meta.values():
yield str(key) if isinstance(value, list):
yield "\n".join([item.chordpro() for item in value])
else:
yield value.chordpro()
def __str__(self): def chordpro(self):
return ( return (
"\n".join(self.str_meta()).strip() "\n".join(self.str_meta()).strip()
+ +
"\n========\n" "\n\n"
+ +
"\n".join([str(item) for item in self.content]).strip() "\n".join([item.chordpro() for item in self.content]).strip()
) )
def add_title(self, __ignored, title): def add_title(self, data):
"""Add a title""" """Add a title"""
self._titles.insert(0, title) self._titles.insert(0, data.argument)
def add_cumulative(self, data):
"""Add a cumulative argument into metadata"""
if data.keyword not in self.meta:
self.meta[data.keyword] = []
self.meta[data.keyword].insert(0, data)
def add_subtitle(self, __ignored, title): def get_data_argument(self, keyword, default):
"""Return `self.meta[keyword].argument`.
Return `default` if `self.meta[keyword]` does not exist.
If `self.meta[keyword]` is a list, return the list of `item.argument`
for each item in the list.
"""
if keyword not in self.meta:
return default
if isinstance(self.meta[keyword], list):
return [item.argument for item in self.meta[keyword]]
else:
return self.meta[keyword].argument
def add_subtitle(self, data):
"""Add a subtitle""" """Add a subtitle"""
self._subtitles.insert(0, title) self._subtitles.insert(0, data.argument)
@property @property
def titles(self): def titles(self):
"""Return the list of titles (and subtitles).""" """Return the list of titles (and subtitles)."""
return self._titles + self._subtitles return self._titles + self._subtitles
def add_author(self, __ignored, title): def add_author(self, data):
"""Add an auhor.""" """Add an auhor."""
self._authors.insert(0, title) self._authors.insert(0, data.argument)
@property @property
def authors(self): def authors(self):
"""Return the list of (raw) authors.""" """Return the list of (raw) authors."""
return self._authors return self._authors
def get_directive(self, key, default=None): def add_key(self, data):
"""Return the first directive with a given key."""
for directive in self.meta:
if directive.keyword == directive_name(key):
return directive.argument
return default
def get_directives(self, key):
"""Return the list of directives with a given key."""
values = []
for directive in self.meta:
if directive.keyword == directive_name(key):
values.append(directive.argument)
return values
def add_key(self, __ignored, argument):
"""Add a new {key: foo: bar} directive.""" """Add a new {key: foo: bar} directive."""
key, *argument = argument.split(":") key, *argument = data.argument.split(":")
self._keys.append(Directive( self._keys.insert(0, Directive(
key.strip(), key.strip(),
":".join(argument).strip(), ":".join(argument).strip(),
)) ))
@ -384,15 +372,6 @@ class Song(AST):
""" """
return self._keys return self._keys
def meta_beginsong(self):
r"""Return the meta information to be put in \beginsong."""
for directive in BEGINSONG_PROPERTIES:
if self.get_directive(directive) is not None:
yield (directive, self.get_directive(directive))
for (key, value) in self.keys:
yield (key, value)
def _process_relative(self, directive): def _process_relative(self, directive):
"""Return the directive, in which the argument is given relative to file """Return the directive, in which the argument is given relative to file
@ -410,17 +389,15 @@ class Newline(AST):
"""New line""" """New line"""
_template = "newline" _template = "newline"
def __str__(self): def chordpro(self):
return "" return ""
@functools.total_ordering
class Directive(AST): class Directive(AST):
"""A directive""" """A directive"""
def __init__(self, keyword="", argument=None): def __init__(self, keyword, argument=None):
super().__init__() super().__init__()
self._keyword = None self.keyword = directive_name(keyword.strip())
self.keyword = keyword
self.argument = argument self.argument = argument
@property @property
@ -431,26 +408,13 @@ class Directive(AST):
""" """
return self.keyword return self.keyword
@property
def keyword(self):
"""Keyword of the directive."""
return self._keyword
@property @property
def inline(self): def inline(self):
"""True iff this directive is to be rendered in the flow on the song. """True iff this directive is to be rendered in the flow on the song.
""" """
return self.keyword in INLINE_PROPERTIES return self.keyword in INLINE_PROPERTIES
@keyword.setter def chordpro(self):
def keyword(self, value):
"""self.keyword setter
Replace keyword by its canonical name if it is a shortcut.
"""
self._keyword = directive_name(value.strip())
def __str__(self):
if self.argument is not None: if self.argument is not None:
return "{{{}: {}}}".format( return "{{{}: {}}}".format(
self.keyword, self.keyword,
@ -459,16 +423,54 @@ class Directive(AST):
else: else:
return "{{{}}}".format(self.keyword) return "{{{}}}".format(self.keyword)
@property def __str__(self):
def as_tuple(self): return self.argument
"""Return the directive as a tuple."""
return (self.keyword, self.argument) class Define(Directive):
"""A chord definition.
Attributes:
.. attribute:: key
The key, as a :class:`Chord` object.
.. attribute:: basefret
The base fret, as an integer. Can be `None` if no base fret is defined.
.. attribute:: frets
The list of frets, as a list of integers, or `None`, if this fret is not to be played.
.. attribute:: fingers
The list of fingers to use on frets, as a list of integers, or `None`
if no information is given (this string is not played, or is played
open). Can be `None` if not defined.
"""
def __eq__(self, other): def __init__(self, key, basefret, frets, fingers):
return self.as_tuple == other.as_tuple self.key = key
self.basefret = basefret # Can be None
self.frets = frets
self.fingers = fingers # Can be None
super().__init__("define", None)
def chordpro(self):
text = self.key.chordpro()
if self.basefret is not None:
text += " base-fret " + str(self.basefret)
text += " frets"
for fret in self.frets:
if fret is None:
text += " x"
else:
text += " " + str(fret)
if self.fingers:
text += " fingers"
for finger in self.fingers:
if finger is None:
text += " -"
else:
text += " " + str(finger)
return "{{define: {}}}".format(text)
def __lt__(self, other): def __str__(self):
return self.as_tuple < other.as_tuple return None
class Tab(AST): class Tab(AST):
"""Tablature""" """Tablature"""
@ -484,7 +486,7 @@ class Tab(AST):
self.content.insert(0, data) self.content.insert(0, data)
return self return self
def __str__(self): def chordpro(self):
return '{{start_of_tab}}\n{}\n{{end_of_tab}}'.format( return '{{start_of_tab}}\n{}\n{{end_of_tab}}'.format(
_indent("\n".join(self.content)), _indent("\n".join(self.content)),
) )

12
patacrep/songs/chordpro/data/latex/chordpro.tex → patacrep/songs/chordpro/data/latex/song.tex

@ -6,12 +6,14 @@
(* if language is defined -*) (* if language is defined -*)
\selectlanguage{((language))} \selectlanguage{((language))}
(* endif *) (* endif *)
\songcolumns{((metadata.columns))} \songcolumns{(( metadata.columns ))}
\beginsong{((titles))}[ \beginsong{((titles))}[
by={((authors))}, by={((authors))},
(* for (key, argument) in beginsong *) (* for key in ['album', 'copyright', 'cov', 'vcov', 'tag'] *)
((key))={((argument))}, (* if key in metadata *)
(( key ))={(( metadata[key] ))},
(* endif *)
(* endfor *) (* endfor *)
] ]
@ -19,6 +21,10 @@
\cover \cover
(* endif -*) (* endif -*)
(* for chord in metadata['define'] *)
(( render(chord) ))
(* endfor *)
(* for item in content -*) (* for item in content -*)
(( render(item) )) (( render(item) ))
(* endfor *) (* endfor *)

22
patacrep/songs/chordpro/syntax.py

@ -130,12 +130,13 @@ class ChordproParser(Parser):
| LBRACE SPACE KEYWORD directive_next RBRACE | LBRACE SPACE KEYWORD directive_next RBRACE
""" """
if len(symbols) == 5: if len(symbols) == 5:
symbols[3].keyword = symbols[2] keyword = symbols[2]
symbols[0] = symbols[3] argument = symbols[3]
else: else:
symbols[4].keyword = symbols[3] keyword = symbols[3]
symbols[0] = symbols[4] argument = symbols[4]
if symbols[0].keyword == 'define':
if keyword == "define":
match = re.compile( match = re.compile(
r""" r"""
(?P<key>[^\ ]*)\ * (?P<key>[^\ ]*)\ *
@ -144,12 +145,14 @@ class ChordproParser(Parser):
(fingers\ *(?P<fingers>(([0-4-])\ *)*))? (fingers\ *(?P<fingers>(([0-4-])\ *)*))?
""", """,
re.VERBOSE re.VERBOSE
).match(symbols[0].argument) ).match(argument)
if match is None: if match is None:
TODO TODO
symbols[0] = _parse_define(**match.groupdict()) symbols[0] = _parse_define(**match.groupdict())
else:
symbols[0] = ast.Directive(keyword, argument)
@staticmethod @staticmethod
def p_directive_next(symbols): def p_directive_next(symbols):
@ -157,11 +160,12 @@ class ChordproParser(Parser):
| COLON TEXT | COLON TEXT
| empty | empty
""" """
symbols[0] = ast.Directive()
if len(symbols) == 3: if len(symbols) == 3:
symbols[0].argument = symbols[2].strip() symbols[0] = symbols[2].strip()
elif len(symbols) == 4: elif len(symbols) == 4:
symbols[0].argument = symbols[3].strip() symbols[0] = symbols[3].strip()
else:
symbols[0] = None
@staticmethod @staticmethod
def p_line(symbols): def p_line(symbols):

2
patacrep/songs/chordpro/test/00.txt

@ -1 +1 @@
========

2
patacrep/songs/chordpro/test/01.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
A verse line A verse line
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/02.txt

@ -1,2 +1,2 @@
{title: A directive} {title: A directive}
========

2
patacrep/songs/chordpro/test/03.txt

@ -1 +1 @@
========

2
patacrep/songs/chordpro/test/04.txt

@ -1,4 +1,4 @@
========
{start_of_chorus} {start_of_chorus}
A one line chorus A one line chorus
{end_of_chorus} {end_of_chorus}

2
patacrep/songs/chordpro/test/05.txt

@ -1,4 +1,4 @@
========
{start_of_bridge} {start_of_bridge}
A one line bridge A one line bridge
{end_of_bridge} {end_of_bridge}

2
patacrep/songs/chordpro/test/06.txt

@ -1 +1 @@
========

2
patacrep/songs/chordpro/test/07.txt

@ -1,4 +1,4 @@
========
{start_of_tab} {start_of_tab}
A tab A tab
{end_of_tab} {end_of_tab}

2
patacrep/songs/chordpro/test/08.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/09.txt

@ -1,5 +1,5 @@
{title: and a directive} {title: and a directive}
========
{start_of_verse} {start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/10.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
A line[A] with a chord A line[A] with a chord
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/11.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
A line ending with a chord[A] A line ending with a chord[A]
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/12.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
[A]A line starting with a chord [A]A line starting with a chord
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/13.txt

@ -1,4 +1,4 @@
========
{start_of_tab} {start_of_tab}
A table A table
wit many # weir [ wit many # weir [

2
patacrep/songs/chordpro/test/21.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
A verse line A verse line
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/22.txt

@ -1,2 +1,2 @@
{title: A directive} {title: A directive}
========

2
patacrep/songs/chordpro/test/23.txt

@ -1 +1 @@
========

2
patacrep/songs/chordpro/test/24.txt

@ -1,4 +1,4 @@
========
{start_of_chorus} {start_of_chorus}
A one line chorus A one line chorus
{end_of_chorus} {end_of_chorus}

2
patacrep/songs/chordpro/test/25.txt

@ -1,4 +1,4 @@
========
{start_of_bridge} {start_of_bridge}
A one line bridge A one line bridge
{end_of_bridge} {end_of_bridge}

2
patacrep/songs/chordpro/test/26.txt

@ -1 +1 @@
========

2
patacrep/songs/chordpro/test/27.txt

@ -1,4 +1,4 @@
========
{start_of_tab} {start_of_tab}
A tab A tab
{end_of_tab} {end_of_tab}

2
patacrep/songs/chordpro/test/28.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/29.txt

@ -1,5 +1,5 @@
{title: and a directive} {title: and a directive}
========
{start_of_verse} {start_of_verse}
A lot of new lines A lot of new lines
{end_of_verse} {end_of_verse}

2
patacrep/songs/chordpro/test/chords.txt

@ -1,4 +1,4 @@
========
{start_of_verse} {start_of_verse}
[A]Simple [A]Simple
[Bb]Bémol [Bb]Bémol

2
patacrep/songs/chordpro/test/customchords.txt

@ -1,3 +1,3 @@
========
{define: E4 base-fret 7 frets 0 1 3 3 x x} {define: E4 base-fret 7 frets 0 1 3 3 x x}
{define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} {define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -}

6
patacrep/songs/chordpro/test/greensleeves.txt

@ -2,11 +2,11 @@
{title: Un autre sous-titre} {title: Un autre sous-titre}
{title: Un sous titre} {title: Un sous titre}
{by: Traditionnel} {by: Traditionnel}
{album: Angleterre} {language: english}
{columns: 2} {columns: 2}
{cov: DIRNAME/traditionnel} {cov: DIRNAME/traditionnel}
{language: english} {album: Angleterre}
========
{partition: DIRNAME/greensleeves.ly} {partition: DIRNAME/greensleeves.ly}
{start_of_verse} {start_of_verse}

8
patacrep/songs/chordpro/test/metadata.txt

@ -7,14 +7,14 @@
{by: Author1} {by: Author1}
{by: Author2} {by: Author2}
{key: {foo: Foo}} {key: {foo: Foo}}
{language: french}
{language: english}
{album: Albom} {album: Albom}
{capo: Capo}
{copyright: Copyright} {copyright: Copyright}
{cov: DIRNAME/Cover} {cov: DIRNAME/Cover}
{language: english}
{language: french}
{vcov: VCover} {vcov: VCover}
======== {capo: Capo}
{comment: Comment} {comment: Comment}
{guitar_comment: GuitarComment} {guitar_comment: GuitarComment}
{image: DIRNAME/Image} {image: DIRNAME/Image}

4
patacrep/songs/chordpro/test/test_parser.py

@ -29,10 +29,10 @@ class ParserTxtRenderer(unittest.TestCase):
with open("{}.sgc".format(self.basename), 'r', encoding='utf8') as sourcefile: with open("{}.sgc".format(self.basename), 'r', encoding='utf8') as sourcefile:
with open("{}.txt".format(self.basename), 'r', encoding='utf8') as expectfile: with open("{}.txt".format(self.basename), 'r', encoding='utf8') as expectfile:
self.assertMultiLineEqual( self.assertMultiLineEqual(
str(chordpro.parse_song( chordpro.parse_song(
sourcefile.read(), sourcefile.read(),
os.path.abspath(sourcefile.name), os.path.abspath(sourcefile.name),
)).strip(), ).chordpro().strip(),
expectfile.read().strip().replace("DIRNAME", os.path.dirname(self.basename)), expectfile.read().strip().replace("DIRNAME", os.path.dirname(self.basename)),
) )

Loading…
Cancel
Save