+import os
+
+CHORD = '
{}
'
+SYLLB = '
{}
'
+WRAPP = '
{}
'
+LINE = '
{}
'
+STANZA = '''
+{verse_name}
+
{verse_body}
'''
-TITLE = '
'
+METADATA = '''
'''
+TITLE = '
{title}
'
+ARTIST = '
As performed by {artist}
'
+COMPOSER = '
Written by {composer}
'
+KEY_CAPO = '
{key_capo}
'
+KEY = '
Key: {key}
'
+CAPO = '
Capo: {capo}
'
+COPYRIGHT = '
{copyright}
'
+FOOTER = ''
HTML = '''
{title}
@@ -24,39 +40,457 @@ HTML = '''
{body}
'''
-STYLES = {
- 'verse_name_bold': '.verse-name { font-weight: bold !important; }',
- 'verse_upper': '.verse-name { text-transform: uppercase; }',
- 'chord_bold': '.chord { font-weight: bold; }'
+STYLE_KEYS = [
+ 'font',
+ 'size',
+ 'is_bold',
+ 'is_centered',
+ 'is_upper',
+ 'indent'
+]
+HTML_OPTIONS = {
+ 'default_font': {
+ 'description': 'The default font to use.',
+ 'type': str,
+ 'default': None,
+ 'group': 'default'
+ },
+ 'default_size': {
+ 'description': 'The default size to use.',
+ 'type': int,
+ 'default': 12,
+ 'group': 'default'
+ },
+ 'title_font': {
+ 'description': 'The font for the title',
+ 'type': str,
+ 'default': None,
+ 'group': 'title'
+ },
+ 'title_size': {
+ 'description': 'The size of the title in pt',
+ 'type': int,
+ 'default': 14,
+ 'group': 'title'
+ },
+ 'title_is_bold': {
+ 'description': 'Set to True to make the title bold',
+ 'type': bool,
+ 'default': True,
+ 'group': 'title'
+ },
+ 'title_is_centered': {
+ 'description': 'Center the title on the page',
+ 'type': bool,
+ 'default': False,
+ 'group': 'title'
+ },
+ 'title_is_upper': {
+ 'description': 'Force the title to be uppercase',
+ 'type': bool,
+ 'default': False,
+ 'group': 'title'
+ },
+ 'composer_font': {
+ 'description': 'The font for the composer',
+ 'type': str,
+ 'default': None,
+ 'group': 'composer'
+ },
+ 'composer_size': {
+ 'description': 'The size of the composer in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'composer'
+ },
+ 'composer_is_bold': {
+ 'description': 'Set to True to make the composer bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'composer'
+ },
+ 'composer_is_centered': {
+ 'description': 'Center the composer on the page',
+ 'type': bool,
+ 'default': False,
+ 'group': 'composer'
+ },
+ 'composer_is_upper': {
+ 'description': 'Force the composer to be uppercase',
+ 'type': bool,
+ 'default': False,
+ 'group': 'composer'
+ },
+ 'artist_font': {
+ 'description': 'The font for the artist',
+ 'type': str,
+ 'default': None,
+ 'group': 'artist'
+ },
+ 'artist_size': {
+ 'description': 'The size of the artist in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'artist'
+ },
+ 'artist_is_bold': {
+ 'description': 'Set to True to make the artist bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'artist'
+ },
+ 'artist_is_centered': {
+ 'description': 'Center the artist on the page',
+ 'type': bool,
+ 'default': False,
+ 'group': 'artist'
+ },
+ 'artist_is_upper': {
+ 'description': 'Force the artist to be uppercase',
+ 'type': bool,
+ 'default': False,
+ 'group': 'artist'
+ },
+ 'copyright_font': {
+ 'description': 'The font for the copyright',
+ 'type': str,
+ 'default': None,
+ 'group': 'copyright'
+ },
+ 'copyright_size': {
+ 'description': 'The size of the copyright in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'copyright'
+ },
+ 'copyright_is_bold': {
+ 'description': 'Set to True to make the copyright bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'copyright'
+ },
+ 'copyright_is_upper': {
+ 'description': 'Force the copyright to be uppercase',
+ 'type': bool,
+ 'default': False,
+ 'group': 'copyright'
+ },
+ 'stanza_font': {
+ 'description': 'The font for the stanzas',
+ 'type': str,
+ 'default': None,
+ 'group': 'stanza'
+ },
+ 'stanza_size': {
+ 'description': 'The size of the stanza in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'stanza'
+ },
+ 'stanza_is_bold': {
+ 'description': 'Set to True to make the stanza bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'stanza'
+ },
+ 'stanza_indent': {
+ 'description': 'How much to indent the stanza',
+ 'type': int,
+ 'default': 4,
+ 'group': 'stanza'
+ },
+ 'stanza_heading_font': {
+ 'description': 'The font for the stanza headings',
+ 'type': str,
+ 'default': None,
+ 'group': 'stanza_heading'
+ },
+ 'stanza_heading_size': {
+ 'description': 'The size of the stanza heading in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'stanza_heading'
+ },
+ 'stanza_heading_is_bold': {
+ 'description': 'Set to True to make the stanza heading bold',
+ 'type': bool,
+ 'default': True,
+ 'group': 'stanza_heading'
+ },
+ 'stanza_heading_is_upper': {
+ 'description': 'Force the stanza heading to be uppercase',
+ 'type': bool,
+ 'default': True,
+ 'group': 'stanza_heading'
+ },
+ 'verse_font': {
+ 'description': 'The font for the verses',
+ 'type': str,
+ 'default': None,
+ 'group': 'verse'
+ },
+ 'verse_size': {
+ 'description': 'The size of the verse in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'verse'
+ },
+ 'verse_is_bold': {
+ 'description': 'Set to True to make the verse bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'verse'
+ },
+ 'verse_indent': {
+ 'description': 'How much to indent the verse',
+ 'type': int,
+ 'default': 4,
+ 'group': 'verse'
+ },
+ 'verse_heading_font': {
+ 'description': 'The font for the verse headings',
+ 'type': str,
+ 'default': None,
+ 'group': 'verse_heading'
+ },
+ 'verse_heading_size': {
+ 'description': 'The size of the verse heading in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'verse_heading'
+ },
+ 'verse_heading_is_bold': {
+ 'description': 'Set to True to make the verse heading bold',
+ 'type': bool,
+ 'default': True,
+ 'group': 'verse_heading'
+ },
+ 'verse_heading_is_upper': {
+ 'description': 'Force the verse heading to be uppercase',
+ 'type': bool,
+ 'default': True,
+ 'group': 'verse_heading'
+ },
+ 'chorus_font': {
+ 'description': 'The font for the choruses',
+ 'type': str,
+ 'default': None,
+ 'group': 'chorus'
+ },
+ 'chorus_size': {
+ 'description': 'The size of the chorus in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'chorus'
+ },
+ 'chorus_is_bold': {
+ 'description': 'Set to True to make the chorus bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'chorus'
+ },
+ 'chorus_indent': {
+ 'description': 'How much to indent the chorus',
+ 'type': int,
+ 'default': 4,
+ 'group': 'chorus'
+ },
+ 'chorus_heading_font': {
+ 'description': 'The font for the chorus headings',
+ 'type': str,
+ 'default': None,
+ 'group': 'chorus_heading'
+ },
+ 'chorus_heading_size': {
+ 'description': 'The size of the chorus heading in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'chorus_heading'
+ },
+ 'chorus_heading_is_bold': {
+ 'description': 'Set to True to make the chorus heading bold',
+ 'type': bool,
+ 'default': True,
+ 'group': 'chorus_heading'
+ },
+ 'chorus_heading_is_upper': {
+ 'description': 'Force the chorus heading to be uppercase',
+ 'type': bool,
+ 'default': True,
+ 'group': 'chorus_heading'
+ },
+ 'bridge_font': {
+ 'description': 'The font for the bridge',
+ 'type': str,
+ 'default': None,
+ 'group': 'bridge'
+ },
+ 'bridge_size': {
+ 'description': 'The size of the bridge in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'bridge'
+ },
+ 'bridge_is_bold': {
+ 'description': 'Set to True to make the bridge bold',
+ 'type': bool,
+ 'default': False,
+ 'group': 'bridge'
+ },
+ 'bridge_indent': {
+ 'description': 'How much to indent the bridge',
+ 'type': int,
+ 'default': 4,
+ 'group': 'bridge'
+ },
+ 'bridge_heading_font': {
+ 'description': 'The font for the bridge headings',
+ 'type': str,
+ 'default': None,
+ 'group': 'bridge_heading'
+ },
+ 'bridge_heading_size': {
+ 'description': 'The size of the bridge heading in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'bridge_heading'
+ },
+ 'bridge_heading_is_bold': {
+ 'description': 'Set to True to make the bridge heading bold',
+ 'type': bool,
+ 'default': True,
+ 'group': 'bridge_heading'
+ },
+ 'bridge_heading_is_upper': {
+ 'description': 'Force the bridge heading to be uppercase',
+ 'type': bool,
+ 'default': True,
+ 'group': 'bridge_heading'
+ },
+ 'chord_font': {
+ 'description': 'The font for the chord',
+ 'type': str,
+ 'default': None,
+ 'group': 'chord'
+ },
+ 'chord_size': {
+ 'description': 'The size of the chord in pt',
+ 'type': int,
+ 'default': 12,
+ 'group': 'chord'
+ },
+ 'chord_is_bold': {
+ 'description': 'Set to True to make the chord bold',
+ 'type': bool,
+ 'default': True,
+ 'group': 'chord'
+ },
+ 'chord_is_centered': {
+ 'description': 'Make chords centered over syllables',
+ 'type': bool,
+ 'default': True,
+ 'group': 'chord'
+ }
}
+_OPTION_GROUPS = []
-def render(song, verse_upper=False, verse_name_bold=False, chord_bold=False):
+def get_options(group=None):
+ """Return the options supported by the HTML renderer"""
+ if not group:
+ return HTML_OPTIONS
+ else:
+ return {key: value for key, value in HTML_OPTIONS.items() if value['group'] == group}
+
+
+def get_option_groups():
+ if not _OPTION_GROUPS:
+ for option in HTML_OPTIONS.values():
+ if option['group'] not in _OPTION_GROUPS:
+ _OPTION_GROUPS.append(option['group'])
+ return _OPTION_GROUPS
+
+
+def make_style(name, font=None, size=None, is_bold=None, is_centered=None, is_upper=None, indent=None):
+ """Build a CSS style"""
+ styles = []
+ if font:
+ styles.append(' font-family: \'{font}\';'.format(font=font))
+ if size:
+ styles.append(' font-size: {size}pt;'.format(size=size))
+ if is_bold:
+ styles.append(' font-weight: bold !important;')
+ if is_centered:
+ styles.append(' text-align: center;')
+ if is_upper:
+ styles.append(' text-transform: uppercase;')
+ if indent:
+ styles.append(' margin-left: {indent}rem;'.format(indent=indent))
+ if styles:
+ if name == 'default':
+ styles.insert(0, 'html, body, * {')
+ else:
+ styles.insert(0, '.{name} {{'.format(name=name))
+ styles.append('}')
+ return os.linesep.join(styles)
+
+
+def generate_option_styles(options):
+ styles = []
+ for group in get_option_groups():
+ kwargs = {}
+ for key in STYLE_KEYS:
+ name = '{group}_{key}'.format(group=group, key=key)
+ if name not in HTML_OPTIONS:
+ continue
+ option = options.get(name, HTML_OPTIONS[name]['default'])
+ if option:
+ kwargs[key] = option
+ styles.append(make_style(group.replace('_', '-'), **kwargs))
+ return styles
+
+
+def render(song, options=None, extra_styles=None):
"""Render a song to HTML"""
+ nl = os.linesep
+ 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_verses.append(VERSE.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, artist=song.metadata.get('artist') or song.metadata.get('composer') or '')
- body = metadata + '\n' + '\n'.join(rendered_verses)
- styles = ''
- if verse_name_bold:
- styles += STYLES['verse_name_bold'] + '\n'
- if verse_upper:
- styles += STYLES['verse_upper'] + '\n'
- if chord_bold:
- styles += STYLES['chord_bold'] + '\n'
- return HTML.format(title=title, body=body, styles=styles)
+ 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=nl.join(rendered_lines)))
+ metadata_lines = []
+ if song.metadata.get('key') or song.metadata.get('capo'):
+ key_lines = []
+ if song.metadata.get('key'):
+ key_lines.append(KEY.format(key=song.metadata.get('key')))
+ if song.metadata.get('capo'):
+ key_lines.append(CAPO.format(capo=song.metadata.get('capo')))
+ if key_lines:
+ metadata_lines.append(KEY_CAPO.format(key_capo=''.join(key_lines)))
+ metadata_lines.append(TITLE.format(title=song.metadata.get('title') or 'Song'))
+ if song.metadata.get('composer'):
+ metadata_lines.append(COMPOSER.format(composer=song.metadata.get('composer')))
+ if song.metadata.get('artist'):
+ metadata_lines.append(ARTIST.format(artist=song.metadata.get('artist')))
+ metadata = METADATA.format(metadata=nl.join(metadata_lines))
+ footer = ''
+ # if song.metadata.get('copyright'):
+ # copyright = COPYRIGHT.format(copyright=song.metadata.get('copyright'))
+ # footer = FOOTER.format(footer=copyright)
+ body = metadata + nl + nl.join(rendered_verses) + nl + footer
+ return HTML.format(title=song.metadata.get('title') or 'Song', body=body, styles=nl.join(styles))