|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
r"""PlasTeX module to deal with chords commands of the songs LaTeX package
|
|
|
|
|
|
|
|
Chords are set using commands like \[C]. This package parses those commands.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from plasTeX import Command, Environment, Macro
|
|
|
|
from plasTeX.Base.LaTeX.Math import BeginDisplayMath
|
|
|
|
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
# Count the number of levels of 'verse' environment: IN_VERSE==1 means that we
|
|
|
|
# are in a 'verse' environment; IN_VERSE==2 means that we are in two included
|
|
|
|
# 'verse' environment, and so on.
|
|
|
|
IN_VERSE = 0
|
|
|
|
|
|
|
|
def wrap_displaymath(cls):
|
|
|
|
"""Decorator to store the depth of 'verse' environment
|
|
|
|
|
|
|
|
In the invoke() method classes, global variable IN_VERSE indicates the
|
|
|
|
number of 'verse' (or 'chorus' or 'verse*') environment we are in.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# pylint: disable=no-init,too-few-public-methods
|
|
|
|
class DisplayMath(cls):
|
|
|
|
"""Wrapper to LaTeX environment updating IN_VERSE"""
|
|
|
|
blockType = True
|
|
|
|
# pylint: disable=super-on-old-class,global-statement,no-member
|
|
|
|
def invoke(self, tex):
|
|
|
|
"""Wrapper to invoke() to update global variable IN_VERSE."""
|
|
|
|
global IN_VERSE
|
|
|
|
if self.macroMode == Macro.MODE_BEGIN:
|
|
|
|
IN_VERSE += 1
|
|
|
|
else:
|
|
|
|
IN_VERSE -= 1
|
|
|
|
super(DisplayMath, self).invoke(tex)
|
|
|
|
return DisplayMath
|
|
|
|
|
|
|
|
# pylint: disable=too-many-public-methods
|
|
|
|
@wrap_displaymath
|
|
|
|
class Verse(Environment):
|
|
|
|
"""LaTeX 'verse' environment"""
|
|
|
|
macroName = 'verse'
|
|
|
|
|
|
|
|
# pylint: disable=too-many-public-methods
|
|
|
|
@wrap_displaymath
|
|
|
|
class VerseStar(Environment):
|
|
|
|
"""LaTeX 'verse*' environment"""
|
|
|
|
macroName = 'verse*'
|
|
|
|
|
|
|
|
# pylint: disable=too-many-public-methods
|
|
|
|
@wrap_displaymath
|
|
|
|
class Chorus(Environment):
|
|
|
|
"""LaTeX 'chorus' environment"""
|
|
|
|
macroName = 'chorus'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Chord(Command):
|
|
|
|
"""Beginning of a chord notation"""
|
|
|
|
macroName = 'chord'
|
|
|
|
macroMode = Command.MODE_NONE
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(Chord, self).__init__(*args, **kwargs)
|
|
|
|
self.chord = ""
|
|
|
|
|
|
|
|
@property
|
|
|
|
def source(self):
|
|
|
|
"""Return chord LaTeX code."""
|
|
|
|
return r'\[{}]'.format(self.chord)
|
|
|
|
|
|
|
|
class BeginChordOrDisplayMath(BeginDisplayMath):
|
|
|
|
r"""Wrapper to BeginDisplayMath
|
|
|
|
|
|
|
|
In a 'verse' (or 'verse*' or 'chorus') environment, the '\[' macro
|
|
|
|
displays a chord. Otherwise, it corresponds to the usual LaTeX math mode.
|
|
|
|
This class calls the right method, depending on the inclusion of this
|
|
|
|
macro in a verse environment.
|
|
|
|
"""
|
|
|
|
macroName = '['
|
|
|
|
|
|
|
|
def digest(self, tokens):
|
|
|
|
"""Consume the tokens corresponding to the arguments of this macro"""
|
|
|
|
if IN_VERSE:
|
|
|
|
for item in tokens:
|
|
|
|
if item.nodeType == item.TEXT_NODE and item.nodeValue == ']':
|
|
|
|
break
|
|
|
|
self.appendChild(item)
|
|
|
|
else:
|
|
|
|
return super(BeginChordOrDisplayMath, self).digest(tokens)
|
|
|
|
|
|
|
|
def invoke(self, tex):
|
|
|
|
"""Process this macro"""
|
|
|
|
if IN_VERSE:
|
|
|
|
chord = Chord()
|
|
|
|
|
|
|
|
for token in tex:
|
|
|
|
if token.nodeType == token.TEXT_NODE and token.nodeValue == ']':
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if token.nodeName == '#text':
|
|
|
|
chord.chord += str(token)
|
|
|
|
elif token.nodeName == "active::&":
|
|
|
|
chord.chord += '&'
|
|
|
|
else:
|
|
|
|
LOGGER.warning((
|
|
|
|
"{}: Unexpected character '{}' in chord "
|
|
|
|
"argument. Continuing anyway.").format(
|
|
|
|
tex.filename,
|
|
|
|
token.source,
|
|
|
|
))
|
|
|
|
break
|
|
|
|
|
|
|
|
return [chord]
|
|
|
|
else:
|
|
|
|
return super(BeginChordOrDisplayMath, self).invoke(tex)
|