import string atom_end = set('()"\'') | set(string.whitespace) def parse(sexp): stack, i, length = [[]], 0, len(sexp) while i < length: c = sexp[i] reading = type(stack[-1]) if reading == list: match c: case '(': stack.append([]) case ')': stack[-2].append(stack.pop()) if stack[-1][0] == ('quote',): stack[-2].append(stack.pop()) case '"': stack.append('') case "'": stack.append([('quote',)]) case _: if c in string.whitespace: pass else: stack.append((c,)) elif reading == str: if c == '"': stack[-2].append(stack.pop()) if stack[-1][0] == ('quote',): stack[-2].append(stack.pop()) elif c == '\\': i += 1 stack[-1] += sexp[i] else: stack[-1] += c elif reading == tuple: if c in atom_end: atom = stack.pop() if atom[0][0].isdigit(): stack[-1].append(eval(atom[0])) else: stack[-1].append(atom) if stack[-1][0] == ('quote',): stack[-2].append(stack.pop()) continue else: stack[-1] = ((stack[-1][0] + c),) i += 1 return stack.pop() def h(node, attributes, *children): res_attrs = '' if type(attributes) == dict: for key in attributes: res_attrs += f' {key}={attributes[key]}' else: children = [attributes] + list(children) res = f'<{node} {res_attrs}>' if type(children) in [tuple, list]: res += ''.join(children) elif type(children) == str: res += children res += f'' return res def lines(s: str): return [h('div', x) for x in s.split('\n')] def chord_line(sexp): res = '' for x in sexp: res += h('td', { 'class': 'g-Chord' }, x[0]) return res def chords(sexp): return h('section', { 'class': 'g-Section' }, h('h2', { 'class': 'g-Subtitle' }, 'Accords'), h('table', { 'class': 'g-Chords' }, *[h('tr', {}, chord_line(x)) for x in sexp])) part_names = { 'intro': 'Intro', 'verse': 'Couplet', 'chorus': 'Refrain', 'interlude': 'Interlude' } def lyrics(sexp): ys = [] for x in sexp: match x: case [(name)]: ys.append(h('section', { 'class': 'g-Section' }, h('div', { 'class': 'g-Lyrics__Part' }, h('h3', part_names[name[0]])))) case [(name), s]: ys.append(h('section', { 'class': 'g-Section' }, h('div', { 'class': 'g-Lyrics__Part' }, h('h3', part_names[name[0]]), h('div', { 'class': 'g-Lyrics__Paragraph' }, *lines(s))))) return h('section', { 'class': 'g-Section' }, h('h2', { 'class': 'g-Subtitle' }, 'Paroles'), h('div', { 'class': 'g-Lyrics' }, *ys)) def sexp_prop(sexp, name): return [x[1:] for x in sexp if x[0][0] == name][0] # ----------------- with open('ben-e-king-stand-by-me.lisp', 'r') as f: content = f.read() sexp = parse(content) title = sexp_prop(sexp, 'title')[0] subtitle = sexp_prop(sexp, 'from')[0] html = f''' {title} – {subtitle} ''' html += h('body', h('section', { 'class': 'g-Section' }, h('h1', { 'class': 'g-Title' }, title), h('div', { 'class': 'g-Author' }, subtitle)), chords(sexp_prop(sexp, 'chords')), lyrics(sexp_prop(sexp, 'lyrics'))) with open('ben-e-king-stand-by-me.html', 'w') as f: f.write(html) print('OK')