From d3f68f109be716f337a272276195f0d603395c90 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 7 Aug 2021 01:08:01 -0700 Subject: [PATCH] Support additional markers in the chordpro file, and move to div-based HTML --- src/chordpro/base.py | 40 ++++++++++++++++++++++++++++++++-- src/chordpro/constants.py | 1 + src/chordpro/renderers/html.py | 36 ++++++++++++++++++++---------- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/chordpro/base.py b/src/chordpro/base.py index 8df1bf0..58da5d8 100644 --- a/src/chordpro/base.py +++ b/src/chordpro/base.py @@ -1,7 +1,7 @@ from hyphen import Hyphenator -from chordpro.constants import DIRECTIVE, CHORD_WORD, CHORUS_MARKER, KNOWN_DIRECTIVES, \ - START_OF, END_OF +from chordpro.constants import BRIDGE_MARKER, CHORD_WORD, CHORUS_MARKER, DIRECTIVE, END_OF, \ + KNOWN_DIRECTIVES, START_OF, VERSE_MARKER HYPHEN_CACHE = {} SYLLABLE_EXCEPTIONS = { @@ -159,6 +159,18 @@ class Verse(object): return match is not None return match.group(1) == type_ + @staticmethod + def is_verse_marker(line): + return line.strip().startswith('{verse') + + @staticmethod + def get_verse_from_marker(line): + match = VERSE_MARKER.match(line) + if not match: + return None + if len(match.groups()) > 1: + return match.group(2) + @staticmethod def is_chorus_marker(line): return line.strip().startswith('{chorus') @@ -171,6 +183,18 @@ class Verse(object): if len(match.groups()) > 1: return match.group(2) + @staticmethod + def is_bridge_marker(line): + return line.strip().startswith('{bridge') + + @staticmethod + def get_bridge_from_marker(line): + match = BRIDGE_MARKER.match(line) + if not match: + return None + if len(match.groups()) > 1: + return match.group(2) + class Metadata(object): @@ -214,9 +238,21 @@ class Song(object): current_verse = None elif is_verse: current_verse.add_line(line.strip()) + elif Verse.is_verse_marker(line): + verse_name = Verse.get_verse_from_marker(line) + for verse in self.verses[::-1]: + if verse.title == verse_name or verse.type_ == "verse": + self.verse_order.append(verse) + break elif Verse.is_chorus_marker(line): chorus_name = Verse.get_chorus_from_marker(line) for verse in self.verses[::-1]: if verse.title == chorus_name or verse.type_ == "chorus": self.verse_order.append(verse) break + elif Verse.is_bridge_marker(line): + bridge_name = Verse.get_bridge_from_marker(line) + for verse in self.verses[::-1]: + if verse.title == bridge_name or verse.type_ == "bridge": + self.verse_order.append(verse) + break diff --git a/src/chordpro/constants.py b/src/chordpro/constants.py index 3916a4f..5a02ded 100644 --- a/src/chordpro/constants.py +++ b/src/chordpro/constants.py @@ -35,5 +35,6 @@ START_OF = re.compile(r'\{start_of_(' + '|'.join(KNOWN_VERSE_TYPES) + r')(: *(.* END_OF = re.compile(r'\{end_of_(' + '|'.join(KNOWN_VERSE_TYPES) + r')\}') CHORUS_MARKER = re.compile(r'\{chorus(: *(.*?))?\}') VERSE_MARKER = re.compile(r'\{verse: *(.*?)\}') +BRIDGE_MARKER = re.compile(r'\{bridge: *(.*?)\}') CHORD_WORD = re.compile(r'(.*?)\[(.*?)\]') CHORD = re.compile(r'\[(.*?)\]') diff --git a/src/chordpro/renderers/html.py b/src/chordpro/renderers/html.py index e8af233..812395e 100644 --- a/src/chordpro/renderers/html.py +++ b/src/chordpro/renderers/html.py @@ -1,9 +1,16 @@ import os -CHORD = '{}' -SYLLB = '{}' -LINE = '{}{}
' +# Tables -- don't work when rendered to PDF +# CHORD = '{}' +# SYLLB = '{}' +# LINE = '{}{}
' + +# Divs +CHORD = '
{}
' +SYLLB = '
{}
' +WRAPP = '
{}
' +LINE = '
{}
' STANZA = '''

{verse_name}

@@ -16,6 +23,9 @@ HTML = ''' {title} @@ -403,26 +413,30 @@ def generate_option_styles(options): return styles -def render(song, options=None): +def render(song, options=None, extra_styles=None): """Render a song to HTML""" styles = options and generate_option_styles(options) or [] + if extra_styles: + styles.append(extra_styles) rendered_verses = [] for verse in song.verse_order: rendered_lines = [] for line in verse.lines: - rendered_chords = [] rendered_syllables = [] for word_counter, word in enumerate(line.words): is_last_word = (word_counter + 1) == len(line.words) for syll_counter, syllable in enumerate(word.syllables): is_last_syllable = (syll_counter + 1) == len(word.syllables) - rendered_chords.append(CHORD.format(syllable.chord or ' ')) - rendered_syllables.append(SYLLB.format( - syllable.syllable + (' ' if is_last_syllable and not is_last_word else ''))) - rendered_lines.append(LINE.format(''.join(rendered_chords), ''.join(rendered_syllables))) + rendered_chord = CHORD.format(syllable.chord or ' ') + rendered_syllable = SYLLB.format( + (syllable.syllable or ' ') + + (' ' if is_last_syllable and not is_last_word else '')) + rendered_syllables.append(WRAPP.format(rendered_chord + rendered_syllable)) + rendered_lines.append(LINE.format(''.join(rendered_syllables))) rendered_verses.append(STANZA.format(verse_type=verse.type_ or '', verse_name=verse.title, verse_body='\n'.join(rendered_lines))) title = song.metadata.get('title') or 'Song' - metadata = TITLE.format(title=title, composer=song.metadata.get('artist') or song.metadata.get('composer') or '') + metadata = TITLE.format(title=title, composer=song.metadata.get('artist') or + song.metadata.get('composer') or '') body = metadata + '\n' + '\n'.join(rendered_verses) return HTML.format(title=title, body=body, styles=os.linesep.join(styles))