diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index baddb158..e76960ac 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -10,10 +10,8 @@ class ChordproSong(Song): """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) - print(self.data) + print(type(self.data), self.data) import sys; sys.exit(1) - self.titles = self.data['@titles'] - del self.data['@titles'] self.languages = self.data['@languages'] del self.data['@languages'] self.authors = self.data['by'] diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index 5eec6b1e..05affee6 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -1,71 +1,264 @@ # -*- coding: utf-8 -*- """Abstract Syntax Tree for ChordPro code.""" +import functools + +def _indent(string): + return "\n".join([" {}".format(line) for line in string.split('\n')]) + +INLINE_PROPERTIES = { + "lilypond", + "comment", + "guitar_comment", + "image", + } + +DIRECTIVE_SHORTCUTS = { + "t": "title", + "st": "subtitle", + "a": "album", + "by": "artist", + "c": "comment", + "gc": "guitar_comment", + } + +def directive_name(text): + if text in DIRECTIVE_SHORTCUTS: + return DIRECTIVE_SHORTCUTS[text] + return text + + class AST: - """Base class for the tree.""" - # pylint: disable=no-init - metadata = None + inline = False + +class Line(AST): + """A line is a sequence of (possibly truncated) words, spaces and chords.""" + + def __init__(self): + super().__init__() + self.line = [] + + def prepend(self, data): + self.line.insert(0, data) + return self + + def __str__(self): + return "".join([str(item) for item in self.line]) + + def strip(self): + while True: + if not self.line: + return self + if isinstance(self.line[0], Space): + del self.line[0] + continue + if isinstance(self.line[-1], Space): + del self.line[-1] + continue + return self + +class LineElement(AST): + """Something present on a line.""" + pass + +class Word(LineElement): + """A chunk of word.""" + + def __init__(self, value): + super().__init__() + self.value = value - @classmethod - def init_metadata(cls): - """Clear metadata + def __str__(self): + return self.value - As this attribute is a class attribute, it as to be reset at each new - parsing. - """ - cls.metadata = { - '@languages': set(), - } +class Space(LineElement): + """A space between words""" -class Expression(AST): - """ChordPro expression""" + def __init__(self): + super().__init__() + + def __str__(self): + return " " + +class Chord(LineElement): + """A chord.""" def __init__(self, value): super().__init__() - self.content = [value] + self.value = value + + def __str__(self): + return "[{}]".format(self.value) - def prepend(self, value): - """Add a value at the beginning of the content list.""" - if value is not None: - self.content.insert(0, value) +class Verse(AST): + """A verse (or bridge, or chorus)""" + type = "verse" + inline = True + + def __init__(self, block=None): + super().__init__() + self.lines = [] # TODO check block + + def prepend(self, data): + self.lines.insert(0, data) return self def __str__(self): - return "".join([str(item) for item in self.content]) - -class SongPart(AST): - """ChordPro start_of/end_of command - - {start_of_chorus}, {end_of_tab}, {eov} ... - """ - - class Type: - CHORUS = ("chorus", - "start_of_chorus", "end_of_chorus", - "soc", "eoc") - VERSE = ("verse", - "start_of_verse", "end_of_verse", - "sov", "eov") - BRIDGE = ("bridge", - "start_of_bridge", "end_of_bridge", - "sob", "eob") - TAB = ("tab", - "start_of_tab", "end_of_tab", - "sot", "eot") - - def __init__(self, name): - if "_" in name: - self.init_long_form(name) + return '{{start_of_{type}}}\n{content}\n{{end_of_{type}}}'.format( + type = self.type, + content = _indent("\n".join([str(line) for line in self.lines])), + ) + +class Chorus(Verse): + type = 'chorus' + +class Bridge(Verse): + type = 'bridge' + +class Song(AST): + """A song""" + + METADATA_TYPE = { + "title": "add_title", + "subtitle": "add_subtitle", + "language": "add_language", + "artist": "add_author", + } + + def __init__(self): + super().__init__() + self.content = [] + self.meta = [] + self._authors = [] + self._titles = [] + self._subtitles = [] + self._languages = set() + + def add(self, data): + if data is None: + if not (self.content and isinstance(self.content[0], Newline)): + self.content.insert(0, Newline()) + elif isinstance(data, Line): + if not (self.content and isinstance(self.content[0], Verse)): + self.content.insert(0, Verse()) + self.content[0].prepend(data.strip()) + elif data.inline: + self.content.insert(0, data) + elif isinstance(data, Directive): + name = directive_name(data.keyword) + if name in self.METADATA_TYPE: + getattr(self, self.METADATA_TYPE[name])(*data.as_tuple) + else: + self.meta.append(data) else: - self.init_short_form(name) + raise Exception() + return self + + def str_meta(self): + for title in self.titles: + yield "{{title: {}}}".format(title) + for language in sorted(self.languages): + yield "{{language: {}}}".format(language) + for author in self.authors: + yield "{{by: {}}}".format(author) + for key in sorted(self.meta): + yield str(key) def __str__(self): - return self.name + return ( + "\n".join(self.str_meta()).strip() + + + "\n========\n" + + + "\n".join([str(item) for item in self.content]).strip() + ) + + + def add_title(self, __ignored, title): + self._titles.insert(0, title) + + def add_subtitle(self, __ignored, title): + self._subtitles.insert(0, title) + + @property + def titles(self): + return self._titles + self._subtitles + + def add_author(self, __ignored, title): + self._authors.insert(0, title) - def init_short_form(self, name): - self.type = "" + @property + def authors(self): + return self._authors - def init_long_form(self, name): - self.type = "" + def add_language(self, __ignored, language): + self._languages.add(language) + + @property + def languages(self): + return self._languages + + + +class Newline(AST): + def __str__(self): + return "" + +@functools.total_ordering +class Directive(AST): + """A directive""" + + def __init__(self): + super().__init__() + self.keyword = "" + self.argument = None + @property + def keyword(self): + return self._keyword + + @property + def inline(self): + return self.keyword in INLINE_PROPERTIES + + @keyword.setter + def keyword(self, value): + self._keyword = value.strip() + + def __str__(self): + if self.argument is not None: + return "{{{}: {}}}".format( + self.keyword, + self.argument, + ) + else: + return "{{{}}}".format(self.keyword) + + @property + def as_tuple(self): + return (self.keyword, self.argument) + + def __eq__(self, other): + return self.as_tuple == other.as_tuple + + def __lt__(self, other): + return self.as_tuple < other.as_tuple + +class Tab(AST): + """Tablature""" + + inline = True + + def __init__(self): + super().__init__() + self.content = [] + + def prepend(self, data): + self.content.insert(0, data) + return self + + def __str__(self): + return '{{start_of_tab}}\n{}\n{{end_of_tab}}'.format( + _indent("\n".join(self.content)), + ) diff --git a/patacrep/songs/chordpro/lexer.py b/patacrep/songs/chordpro/lexer.py index f33d8f2e..726c24e4 100644 --- a/patacrep/songs/chordpro/lexer.py +++ b/patacrep/songs/chordpro/lexer.py @@ -7,16 +7,21 @@ LOGGER = logging.getLogger() #pylint: disable=invalid-name tokens = ( - #'LBRACKET', - #'RBRACKET', - 'CHORD', - 'LBRACE', - 'RBRACE', - 'NEWLINE', - #'COLON', - 'WORD', - 'SPACE', - #'NUMBER', + 'LBRACE', + 'RBRACE', + 'CHORD', + 'NEWLINE', + 'COLON', + 'WORD', + 'SPACE', + 'TEXT', + 'KEYWORD', + 'SOC', + 'EOC', + 'SOB', + 'EOB', + 'SOT', + 'EOT', ) class ChordProLexer: @@ -26,14 +31,51 @@ class ChordProLexer: states = ( ('chord', 'exclusive'), + ('directive', 'exclusive'), + ('directiveargument', 'exclusive'), + ('tablature', 'exclusive'), ) - t_LBRACE = r'{' - t_RBRACE = r'}' t_SPACE = r'[ \t]+' - #t_COLON = r':' + t_chord_CHORD = r'[A-G7#m]+' # TODO This can be refined + t_directive_SPACE = r'[ \t]+' + t_directive_KEYWORD = r'[a-zA-Z_]+' + t_directiveargument_TEXT = r'[^}]+' + + def t_SOC(self, token): + r'{(soc|start_of_chorus)}' + return token + def t_EOC(self, token): + r'{(eoc|end_of_chorus)}' + return token + + def t_SOB(self, token): + r'{(sob|start_of_bridge)}' + return token + + def t_EOB(self, token): + r'{(eob|end_of_bridge)}' + return token + + def t_SOT(self, token): + r'{(sot|start_of_tab)}' + self.lexer.push_state('tablature') + return token + + def t_tablature_EOT(self, token): + r'{(eot|end_of_tab)}' + self.lexer.pop_state() + return token + + def t_tablature_SPACE(self, token): + r'[ \t]+' + return token + + t_tablature_TEXT = r'[^\n]+' + t_tablature_NEWLINE = r'\n' + def __init__(self): self.__class__.lexer = lex.lex(module=self) @@ -51,7 +93,7 @@ class ChordProLexer: @staticmethod def t_WORD(token): - r'[^\n\][\t ]+' + r'[^{}\n\][\t ]+' return token def t_LBRACKET(self, token): @@ -62,11 +104,26 @@ class ChordProLexer: r'\]' self.lexer.pop_state() - #@staticmethod - #def t_NUMBER(token): - # r'[0-9]+' - # token.value = int(token.value) - # return token + def t_LBRACE(self, token): + r'{' + self.lexer.push_state('directive') + return token + + def t_directive_RBRACE(self, token): + r'}' + self.lexer.pop_state() + return token + + def t_directiveargument_RBRACE(self, token): + r'}' + self.lexer.pop_state() + self.lexer.pop_state() + return token + + def t_directive_COLON(self, token): + r':' + self.lexer.push_state('directiveargument') + return token @staticmethod def t_error(token): @@ -79,3 +136,19 @@ class ChordProLexer: """Manage errors""" LOGGER.error("Illegal character '{}' in chord..".format(token.value[0])) token.lexer.skip(1) + + @staticmethod + def t_tablature_error(token): + """Manage errors""" + LOGGER.error("Illegal character '{}' in tablature..".format(token.value[0])) + token.lexer.skip(1) + + @staticmethod + def t_directive_error(token): + """Manage errors""" + LOGGER.error("Illegal character '{}' in directive..".format(token.value[0])) + token.lexer.skip(1) + + @staticmethod + def t_directiveargument_error(token): + return t_directive_error(token) diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 91470ba5..32fa2c42 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -4,9 +4,10 @@ import logging import ply.yacc as yacc -from patacrep.songs.chordpro.lexer import tokens, ChordProLexer -from patacrep.songs.chordpro import ast from patacrep.errors import SongbookError +from patacrep.songs.chordpro import ast +from patacrep.songs.chordpro import ast +from patacrep.songs.chordpro.lexer import tokens, ChordProLexer LOGGER = logging.getLogger() @@ -24,6 +25,8 @@ class ParsingError(SongbookError): class Parser: """ChordPro parser class""" + start = "song" + def __init__(self, filename=None): self.tokens = tokens self.filename = filename @@ -39,7 +42,7 @@ class Parser: def p_error(self, token): """Manage parsing errors.""" - if token: # TODO remove this test + if token: LOGGER.error("Error in file {}, line {}:{}.".format( str(self.filename), token.lineno, @@ -52,85 +55,213 @@ class Parser: """song : block song | empty """ + #if isinstance(symbols[1], str): if len(symbols) == 2: - symbols[0] = ('song') + symbols[0] = ast.Song() else: - symbols[0] = ('song', symbols[1], symbols[2]) + symbols[0] = symbols[2].add(symbols[1]) + + #@staticmethod + #def p_song_next(symbols): + # """song_next : block song_next + # | empty + # """ + # if len(symbols) == 2: + # symbols[0] = ast.Song() + # else: + # symbols[0] = symbols[2].add(symbols[1]) @staticmethod def p_block(symbols): - """block : directive NEWLINE newlines - | stanza NEWLINE newlines + """block : SPACE block + | directive NEWLINE + | line NEWLINE + | chorus NEWLINE + | tab NEWLINE + | bridge NEWLINE + | NEWLINE """ - symbols[0] = ('block', symbols[1]) + if len(symbols) == 3 and isinstance(symbols[1], str): + symbols[0] = symbols[2] + elif (symbols[1] is None) or (len(symbols) == 2): + symbols[0] = None + else: + symbols[0] = symbols[1] @staticmethod - def p_newlines(symbols): - """newlines : NEWLINE newlines - | empty""" - symbols[0] = ('newlines') + def p_maybespace(symbols): + """maybespace : SPACE + | empty + """ + symbols[0] = None + + #@staticmethod + #def p_newlines(symbols): + # """newlines : NEWLINE newlines + # | empty""" + # symbols[0] = ('newlines') @staticmethod def p_directive(symbols): - """directive : LBRACE WORD RBRACE""" - symbols[0] = ('directive', symbols[1]) + """directive : LBRACE KEYWORD directive_next RBRACE + | LBRACE SPACE KEYWORD directive_next RBRACE + """ + if len(symbols) == 5: + symbols[3].keyword = symbols[2] + symbols[0] = symbols[3] + else: + symbols[4].keyword = symbols[3] + symbols[0] = symbols[4] + + @staticmethod + def p_directive_next(symbols): + """directive_next : SPACE COLON TEXT + | COLON TEXT + | empty + """ + symbols[0] = ast.Directive() + if len(symbols) == 3: + symbols[0].argument = symbols[2].strip() + elif len(symbols) == 4: + symbols[0].argument = symbols[3].strip() @staticmethod def p_line(symbols): - """line : WORD line_next - | CHORD line_next - | SPACE line_next + """line : word line_next + | chord line_next """ - symbols[0] = ('line', symbols[1], symbols[2]) + symbols[0] = symbols[2].prepend(symbols[1]) @staticmethod def p_line_next(symbols): - """line_next : WORD line_next - | SPACE line_next - | CHORD line_next + """line_next : word line_next + | space line_next + | chord line_next | empty """ if len(symbols) == 2: - symbols[0] = ('line-next') + symbols[0] = ast.Line() + else: + symbols[0] = symbols[2].prepend(symbols[1]) + + @staticmethod + def p_word(symbols): + """word : WORD""" + symbols[0] = ast.Word(symbols[1]) + + @staticmethod + def p_space(symbols): + """space : SPACE""" + symbols[0] = ast.Space() + + @staticmethod + def p_chord(symbols): + """chord : CHORD""" + symbols[0] = ast.Chord(symbols[1]) + + #@staticmethod + #def p_verse(symbols): + # """verse : line NEWLINE verse_next + # """ + # symbols[0] = symbols[3].prepend(symbols[1]) + + #@staticmethod + #def p_verse_next(symbols): + # """verse_next : line NEWLINE verse_next + # | empty + # """ + # if len(symbols) == 2: + # symbols[0] = ast.Verse() + # else: + # symbols[0] = symbols[3].prepend(symbols[1]) + + @staticmethod + def p_chorus(symbols): + """chorus : SOC maybespace NEWLINE chorus_content EOC maybespace + """ + symbols[0] = symbols[4] + + @staticmethod + def p_chorus_content(symbols): + """chorus_content : line NEWLINE chorus_content + | SPACE chorus_content + | empty + """ + if len(symbols) == 2: + symbols[0] = ast.Chorus() + elif len(symbols) == 3: + symbols[0] = symbols[2] else: - symbols[0] = ('line-next', symbols[1], symbols[2]) + symbols[0] = symbols[3].prepend(symbols[1]) @staticmethod - def p_stanza(symbols): - """stanza : line NEWLINE stanza_next + def p_bridge(symbols): + """bridge : SOB maybespace NEWLINE bridge_content EOB maybespace """ - symbols[0] = ('stanza', symbols[1], symbols[3]) + symbols[0] = symbols[4] @staticmethod - def p_stanza_next(symbols): - """stanza_next : line NEWLINE stanza_next - | empty + def p_bridge_content(symbols): + """bridge_content : line NEWLINE bridge_content + | SPACE bridge_content + | empty """ if len(symbols) == 2: - symbols[0] = ('stanza-next') + symbols[0] = ast.Bridge() + elif len(symbols) == 3: + symbols[0] = symbols[2] else: - symbols[0] = ('stanza-next', symbols[1], symbols[3]) + symbols[0] = symbols[3].prepend(symbols[1]) + #@staticmethod - #def p_braces(symbols): - # """braces : LBRACE expression COLON expression RBRACE""" - # symbols[0] = symbols[2] + #def p_bridge_next(symbols): + # """bridge_next : line NEWLINE bridge_next + # | empty + # """ + # if len(symbols) == 2: + # symbols[0] = ast.Bridge() + # else: + # symbols[0] = symbols[3].prepend(symbols[1]) + + @staticmethod + def p_tab(symbols): + """tab : SOT maybespace NEWLINE tab_content EOT maybespace + """ + symbols[0] = symbols[4] + + @staticmethod + def p_tab_content(symbols): + """tab_content : NEWLINE tab_content + | TEXT tab_content + | SPACE tab_content + | empty + """ + if len(symbols) == 2: + symbols[0] = ast.Tab() + else: + if symbols[1].strip(): + symbols[2].prepend(symbols[1]) + symbols[0] = symbols[2] @staticmethod def p_empty(symbols): """empty :""" symbols[0] = None - #@staticmethod - #def p_comment(symbols): - # """comment : COMMENT""" - # symbols[0] = ('comment', symbols[1]) - - +def lex_song(content): + # TODO delete + lex = ChordProLexer().lexer + lex.input(content) + while 1: + tok = lex.token() + if not tok: break + print(tok) def parse_song(content, filename=None): """Parse song and return its metadata.""" return yacc.yacc(module=Parser(filename)).parse( content, + debug=0, lexer=ChordProLexer().lexer, ) diff --git a/patacrep/songs/chordpro/test/00.sgc b/patacrep/songs/chordpro/test/00.sgc new file mode 100644 index 00000000..e69de29b diff --git a/patacrep/songs/chordpro/test/00.txt b/patacrep/songs/chordpro/test/00.txt new file mode 100644 index 00000000..dbbd9c50 --- /dev/null +++ b/patacrep/songs/chordpro/test/00.txt @@ -0,0 +1 @@ +======== diff --git a/patacrep/songs/chordpro/test/01.sgc b/patacrep/songs/chordpro/test/01.sgc new file mode 100644 index 00000000..8ea8d2a2 --- /dev/null +++ b/patacrep/songs/chordpro/test/01.sgc @@ -0,0 +1 @@ +A verse line diff --git a/patacrep/songs/chordpro/test/01.txt b/patacrep/songs/chordpro/test/01.txt new file mode 100644 index 00000000..84cf4364 --- /dev/null +++ b/patacrep/songs/chordpro/test/01.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + A verse line +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/02.sgc b/patacrep/songs/chordpro/test/02.sgc new file mode 100644 index 00000000..270bc746 --- /dev/null +++ b/patacrep/songs/chordpro/test/02.sgc @@ -0,0 +1 @@ +{title : A directive} diff --git a/patacrep/songs/chordpro/test/02.txt b/patacrep/songs/chordpro/test/02.txt new file mode 100644 index 00000000..f3fbd9e9 --- /dev/null +++ b/patacrep/songs/chordpro/test/02.txt @@ -0,0 +1,2 @@ +{title: A directive} +======== diff --git a/patacrep/songs/chordpro/test/03.sgc b/patacrep/songs/chordpro/test/03.sgc new file mode 100644 index 00000000..fd40910d --- /dev/null +++ b/patacrep/songs/chordpro/test/03.sgc @@ -0,0 +1,4 @@ + + + + diff --git a/patacrep/songs/chordpro/test/03.txt b/patacrep/songs/chordpro/test/03.txt new file mode 100644 index 00000000..dbbd9c50 --- /dev/null +++ b/patacrep/songs/chordpro/test/03.txt @@ -0,0 +1 @@ +======== diff --git a/patacrep/songs/chordpro/test/04.sgc b/patacrep/songs/chordpro/test/04.sgc new file mode 100644 index 00000000..6a266eea --- /dev/null +++ b/patacrep/songs/chordpro/test/04.sgc @@ -0,0 +1,3 @@ +{soc} +A one line chorus +{eoc} diff --git a/patacrep/songs/chordpro/test/04.txt b/patacrep/songs/chordpro/test/04.txt new file mode 100644 index 00000000..3336d565 --- /dev/null +++ b/patacrep/songs/chordpro/test/04.txt @@ -0,0 +1,4 @@ +======== +{start_of_chorus} + A one line chorus +{end_of_chorus} diff --git a/patacrep/songs/chordpro/test/05.sgc b/patacrep/songs/chordpro/test/05.sgc new file mode 100644 index 00000000..9472ebcb --- /dev/null +++ b/patacrep/songs/chordpro/test/05.sgc @@ -0,0 +1,3 @@ +{sob} +A one line bridge +{eob} diff --git a/patacrep/songs/chordpro/test/05.txt b/patacrep/songs/chordpro/test/05.txt new file mode 100644 index 00000000..b2f99be2 --- /dev/null +++ b/patacrep/songs/chordpro/test/05.txt @@ -0,0 +1,4 @@ +======== +{start_of_bridge} + A one line bridge +{end_of_bridge} diff --git a/patacrep/songs/chordpro/test/06.sgc b/patacrep/songs/chordpro/test/06.sgc new file mode 100644 index 00000000..90a2b559 --- /dev/null +++ b/patacrep/songs/chordpro/test/06.sgc @@ -0,0 +1 @@ +# A comment diff --git a/patacrep/songs/chordpro/test/06.txt b/patacrep/songs/chordpro/test/06.txt new file mode 100644 index 00000000..dbbd9c50 --- /dev/null +++ b/patacrep/songs/chordpro/test/06.txt @@ -0,0 +1 @@ +======== diff --git a/patacrep/songs/chordpro/test/07.sgc b/patacrep/songs/chordpro/test/07.sgc new file mode 100644 index 00000000..fd183eff --- /dev/null +++ b/patacrep/songs/chordpro/test/07.sgc @@ -0,0 +1,3 @@ +{sot} +A tab +{eot} diff --git a/patacrep/songs/chordpro/test/07.txt b/patacrep/songs/chordpro/test/07.txt new file mode 100644 index 00000000..7fb8bc64 --- /dev/null +++ b/patacrep/songs/chordpro/test/07.txt @@ -0,0 +1,4 @@ +======== +{start_of_tab} + A tab +{end_of_tab} diff --git a/patacrep/songs/chordpro/test/08.sgc b/patacrep/songs/chordpro/test/08.sgc new file mode 100644 index 00000000..b51646ca --- /dev/null +++ b/patacrep/songs/chordpro/test/08.sgc @@ -0,0 +1,10 @@ + + + +# comment +# comment + + +A lot of new lines + +# comment diff --git a/patacrep/songs/chordpro/test/08.txt b/patacrep/songs/chordpro/test/08.txt new file mode 100644 index 00000000..92c183b1 --- /dev/null +++ b/patacrep/songs/chordpro/test/08.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + A lot of new lines +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/09.sgc b/patacrep/songs/chordpro/test/09.sgc new file mode 100644 index 00000000..b831f230 --- /dev/null +++ b/patacrep/songs/chordpro/test/09.sgc @@ -0,0 +1,15 @@ + + + +# comment +# comment + + +A lot of new lines + +# comment + +{title: and a directive} + +# comment + diff --git a/patacrep/songs/chordpro/test/09.txt b/patacrep/songs/chordpro/test/09.txt new file mode 100644 index 00000000..b7669e62 --- /dev/null +++ b/patacrep/songs/chordpro/test/09.txt @@ -0,0 +1,5 @@ +{title: and a directive} +======== +{start_of_verse} + A lot of new lines +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/10.sgc b/patacrep/songs/chordpro/test/10.sgc new file mode 100644 index 00000000..6519ab80 --- /dev/null +++ b/patacrep/songs/chordpro/test/10.sgc @@ -0,0 +1 @@ +A line[A] with a chord diff --git a/patacrep/songs/chordpro/test/10.txt b/patacrep/songs/chordpro/test/10.txt new file mode 100644 index 00000000..b96d8637 --- /dev/null +++ b/patacrep/songs/chordpro/test/10.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + A line[A] with a chord +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/11.sgc b/patacrep/songs/chordpro/test/11.sgc new file mode 100644 index 00000000..fc3d697d --- /dev/null +++ b/patacrep/songs/chordpro/test/11.sgc @@ -0,0 +1 @@ +A line ending with a chord[A] diff --git a/patacrep/songs/chordpro/test/11.txt b/patacrep/songs/chordpro/test/11.txt new file mode 100644 index 00000000..2a9eaf17 --- /dev/null +++ b/patacrep/songs/chordpro/test/11.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + A line ending with a chord[A] +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/12.sgc b/patacrep/songs/chordpro/test/12.sgc new file mode 100644 index 00000000..a9583451 --- /dev/null +++ b/patacrep/songs/chordpro/test/12.sgc @@ -0,0 +1 @@ +[A]A line starting with a chord diff --git a/patacrep/songs/chordpro/test/12.txt b/patacrep/songs/chordpro/test/12.txt new file mode 100644 index 00000000..83c11625 --- /dev/null +++ b/patacrep/songs/chordpro/test/12.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + [A]A line starting with a chord +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/13.sgc b/patacrep/songs/chordpro/test/13.sgc new file mode 100644 index 00000000..6cc3dfbc --- /dev/null +++ b/patacrep/songs/chordpro/test/13.sgc @@ -0,0 +1,5 @@ +{sot} + A table + wit many # weir [ + [ symbols +{eot} diff --git a/patacrep/songs/chordpro/test/13.txt b/patacrep/songs/chordpro/test/13.txt new file mode 100644 index 00000000..447f67dd --- /dev/null +++ b/patacrep/songs/chordpro/test/13.txt @@ -0,0 +1,6 @@ +======== +{start_of_tab} + A table + wit many # weir [ + [ symbols +{end_of_tab} diff --git a/patacrep/songs/chordpro/test/21.sgc b/patacrep/songs/chordpro/test/21.sgc new file mode 100644 index 00000000..c01b31ad --- /dev/null +++ b/patacrep/songs/chordpro/test/21.sgc @@ -0,0 +1 @@ + A verse line diff --git a/patacrep/songs/chordpro/test/21.txt b/patacrep/songs/chordpro/test/21.txt new file mode 100644 index 00000000..84cf4364 --- /dev/null +++ b/patacrep/songs/chordpro/test/21.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + A verse line +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/22.sgc b/patacrep/songs/chordpro/test/22.sgc new file mode 100644 index 00000000..425fd584 --- /dev/null +++ b/patacrep/songs/chordpro/test/22.sgc @@ -0,0 +1 @@ + {title : A directive} diff --git a/patacrep/songs/chordpro/test/22.txt b/patacrep/songs/chordpro/test/22.txt new file mode 100644 index 00000000..f3fbd9e9 --- /dev/null +++ b/patacrep/songs/chordpro/test/22.txt @@ -0,0 +1,2 @@ +{title: A directive} +======== diff --git a/patacrep/songs/chordpro/test/23.sgc b/patacrep/songs/chordpro/test/23.sgc new file mode 100644 index 00000000..ce5ed9dd --- /dev/null +++ b/patacrep/songs/chordpro/test/23.sgc @@ -0,0 +1,4 @@ + + + + diff --git a/patacrep/songs/chordpro/test/23.txt b/patacrep/songs/chordpro/test/23.txt new file mode 100644 index 00000000..dbbd9c50 --- /dev/null +++ b/patacrep/songs/chordpro/test/23.txt @@ -0,0 +1 @@ +======== diff --git a/patacrep/songs/chordpro/test/24.sgc b/patacrep/songs/chordpro/test/24.sgc new file mode 100644 index 00000000..a929a754 --- /dev/null +++ b/patacrep/songs/chordpro/test/24.sgc @@ -0,0 +1,3 @@ + {soc} + A one line chorus + {eoc} diff --git a/patacrep/songs/chordpro/test/24.txt b/patacrep/songs/chordpro/test/24.txt new file mode 100644 index 00000000..3336d565 --- /dev/null +++ b/patacrep/songs/chordpro/test/24.txt @@ -0,0 +1,4 @@ +======== +{start_of_chorus} + A one line chorus +{end_of_chorus} diff --git a/patacrep/songs/chordpro/test/25.sgc b/patacrep/songs/chordpro/test/25.sgc new file mode 100644 index 00000000..45efba59 --- /dev/null +++ b/patacrep/songs/chordpro/test/25.sgc @@ -0,0 +1,3 @@ + {sob} + A one line bridge + {eob} diff --git a/patacrep/songs/chordpro/test/25.txt b/patacrep/songs/chordpro/test/25.txt new file mode 100644 index 00000000..b2f99be2 --- /dev/null +++ b/patacrep/songs/chordpro/test/25.txt @@ -0,0 +1,4 @@ +======== +{start_of_bridge} + A one line bridge +{end_of_bridge} diff --git a/patacrep/songs/chordpro/test/26.sgc b/patacrep/songs/chordpro/test/26.sgc new file mode 100644 index 00000000..372b96b4 --- /dev/null +++ b/patacrep/songs/chordpro/test/26.sgc @@ -0,0 +1 @@ + # A comment diff --git a/patacrep/songs/chordpro/test/26.txt b/patacrep/songs/chordpro/test/26.txt new file mode 100644 index 00000000..dbbd9c50 --- /dev/null +++ b/patacrep/songs/chordpro/test/26.txt @@ -0,0 +1 @@ +======== diff --git a/patacrep/songs/chordpro/test/27.sgc b/patacrep/songs/chordpro/test/27.sgc new file mode 100644 index 00000000..bfa0cf0e --- /dev/null +++ b/patacrep/songs/chordpro/test/27.sgc @@ -0,0 +1,3 @@ + {sot} + A tab + {eot} diff --git a/patacrep/songs/chordpro/test/27.txt b/patacrep/songs/chordpro/test/27.txt new file mode 100644 index 00000000..7fb8bc64 --- /dev/null +++ b/patacrep/songs/chordpro/test/27.txt @@ -0,0 +1,4 @@ +======== +{start_of_tab} + A tab +{end_of_tab} diff --git a/patacrep/songs/chordpro/test/28.sgc b/patacrep/songs/chordpro/test/28.sgc new file mode 100644 index 00000000..69f980b3 --- /dev/null +++ b/patacrep/songs/chordpro/test/28.sgc @@ -0,0 +1,10 @@ + + + + # comment +# comment + + + A lot of new lines + +# comment diff --git a/patacrep/songs/chordpro/test/28.txt b/patacrep/songs/chordpro/test/28.txt new file mode 100644 index 00000000..92c183b1 --- /dev/null +++ b/patacrep/songs/chordpro/test/28.txt @@ -0,0 +1,4 @@ +======== +{start_of_verse} + A lot of new lines +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/29.sgc b/patacrep/songs/chordpro/test/29.sgc new file mode 100644 index 00000000..830e0177 --- /dev/null +++ b/patacrep/songs/chordpro/test/29.sgc @@ -0,0 +1,15 @@ + + + +# comment +# comment + + + A lot of new lines + +# comment + + {title: and a directive} + +# comment + diff --git a/patacrep/songs/chordpro/test/29.txt b/patacrep/songs/chordpro/test/29.txt new file mode 100644 index 00000000..b7669e62 --- /dev/null +++ b/patacrep/songs/chordpro/test/29.txt @@ -0,0 +1,5 @@ +{title: and a directive} +======== +{start_of_verse} + A lot of new lines +{end_of_verse} diff --git a/patacrep/songs/chordpro/test/30.sgc b/patacrep/songs/chordpro/test/30.sgc new file mode 100644 index 00000000..ee67061c --- /dev/null +++ b/patacrep/songs/chordpro/test/30.sgc @@ -0,0 +1 @@ + [A]A line starting with a chord diff --git a/patacrep/songs/chordpro/test/__init__.py b/patacrep/songs/chordpro/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/patacrep/songs/chordpro/test/greensleeves.sgc b/patacrep/songs/chordpro/test/greensleeves.sgc new file mode 100644 index 00000000..1dee93ff --- /dev/null +++ b/patacrep/songs/chordpro/test/greensleeves.sgc @@ -0,0 +1,44 @@ +{language : english} +{columns : 2} +{subtitle : Un sous titre} +{ title : Greensleeves} +{title : Un autre sous-titre} +{artist: Traditionnel} +{cover : traditionnel } +{album :Angleterre} + +{partition : greensleeves.ly} + + +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} + [C]Green[B]sleeves was [G]all my joy + [Am]Greensleeves was [E]my delight + [C]Greensleeves was my [G]heart of gold + And [Am]who but [E]Ladie [Am]Greensleeves +{end_of_chorus} + +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 + +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 + +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 + + +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/patacrep/songs/chordpro/test/greensleeves.txt b/patacrep/songs/chordpro/test/greensleeves.txt new file mode 100644 index 00000000..38564149 --- /dev/null +++ b/patacrep/songs/chordpro/test/greensleeves.txt @@ -0,0 +1,51 @@ +{title: Greensleeves} +{title: Un autre sous-titre} +{title: Un sous titre} +{language: english} +{by: Traditionnel} +{album: Angleterre} +{columns: 2} +{cover: 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} + +{start_of_chorus} + [C]Green[B]sleeves was [G]all my joy + [Am]Greensleeves was [E]my delight + [C]Greensleeves was my [G]heart of gold + And [Am]who but [E]Ladie [Am]Greensleeves +{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} + +{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} + +{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} + +{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} diff --git a/patacrep/songs/chordpro/test/metadata.sgc b/patacrep/songs/chordpro/test/metadata.sgc new file mode 100644 index 00000000..c892faf0 --- /dev/null +++ b/patacrep/songs/chordpro/test/metadata.sgc @@ -0,0 +1,20 @@ +{subtitle: Subtitle3} +{title: Title} +{title: Subtitle1} +{subtitle: Subtitle4} +{t: Subtitle2} +{st: Subtitle5} +{language: french} +{language: english} +{by: Author1} +{artist: Author2} +{album: Albom} +{copyright: Copyright} +{cover: Cover} +{vcover: VCover} +{capo: Capo} +{foo: Foo} +{comment: Comment} +{guitar_comment: GuitarComment} +{image: Image} +{lilypond: Lilypond} diff --git a/patacrep/songs/chordpro/test/metadata.txt b/patacrep/songs/chordpro/test/metadata.txt new file mode 100644 index 00000000..8d73f4b8 --- /dev/null +++ b/patacrep/songs/chordpro/test/metadata.txt @@ -0,0 +1,21 @@ +{title: Title} +{title: Subtitle1} +{title: Subtitle2} +{title: Subtitle3} +{title: Subtitle4} +{title: Subtitle5} +{language: english} +{language: french} +{by: Author1} +{by: Author2} +{album: Albom} +{capo: Capo} +{copyright: Copyright} +{cover: Cover} +{foo: Foo} +{vcover: VCover} +======== +{comment: Comment} +{guitar_comment: GuitarComment} +{image: Image} +{lilypond: Lilypond} diff --git a/patacrep/songs/chordpro/test/test_parser.py b/patacrep/songs/chordpro/test/test_parser.py new file mode 100644 index 00000000..85ad4108 --- /dev/null +++ b/patacrep/songs/chordpro/test/test_parser.py @@ -0,0 +1,36 @@ +import glob +import os +import unittest + +from patacrep.songs.chordpro import syntax as chordpro + +class ParserTestCase(unittest.TestCase): + + def test_txt(self): + for txt in sorted(glob.glob(os.path.join( + os.path.dirname(__file__), + '*.txt', + ))): + basename = txt[:-len('.txt')] + with open("{}.sgc".format(basename), 'r', encoding='utf8') as sourcefile: + with open("{}.txt".format(basename), 'r', encoding='utf8') as expectfile: + #print(os.path.basename(sourcefile.name)) + #with open("{}.txt.diff".format(basename), 'w', encoding='utf8') as difffile: + # difffile.write( + # str(chordpro.parse_song( + # sourcefile.read(), + # os.path.basename(sourcefile.name), + # )).strip() + # ) + # sourcefile.seek(0) + self.assertMultiLineEqual( + str(chordpro.parse_song( + sourcefile.read(), + os.path.basename(sourcefile.name), + )).strip(), + expectfile.read().strip(), + ) + + def test_tex(self): + # TODO + pass