summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/songproj/song2tex.py
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/songproj/song2tex.py')
-rwxr-xr-xmacros/latex/contrib/songproj/song2tex.py101
1 files changed, 101 insertions, 0 deletions
diff --git a/macros/latex/contrib/songproj/song2tex.py b/macros/latex/contrib/songproj/song2tex.py
new file mode 100755
index 0000000000..dc32da2b3f
--- /dev/null
+++ b/macros/latex/contrib/songproj/song2tex.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+
+import argparse
+import enum
+import re
+
+
+# The following regexp match the typical markers of refrain and couplet:
+re_refrain = re.compile(r'(R(efrain)?|C(horus)?)( |[-./] ?)', re.IGNORECASE)
+re_couplet = re.compile(r'\d+( |[-./] ?)')
+
+
+def match_rest(regexp, s):
+ """Match the start of string s against the given regexp:
+ * if it matches, return the non-matching part;
+ * it it does not matches, return None."""
+ if (m := regexp.match(s)) is not None:
+ return s[m.end():]
+ else:
+ return None
+
+
+class State(enum.Enum):
+ """A state indicates in what context we are while parsing a song."""
+ LIMBO = 0 # Not in the refrain or in a couplet
+ REFRAIN = 1 # In the refrain
+ COUPLET = 2 # In a couplet
+
+
+def parse_song(lines, re_refrain, re_couplet):
+ # At the beginning of the song, we are not in the refrain or in a couplet.
+ state = State.LIMBO
+ # This variable will be used to record the longest line seen so far.
+ longest = ''
+ for line in lines:
+ line = line.rstrip()
+ if state == State.LIMBO:
+ # We are not in the refrain or in a couplet. Depending on what we find:
+ if (rest := match_rest(re_refrain, line)) is not None:
+ # when a refrain start marker is found, enter the refrain.
+ yield '\\begin{refrain}\n'
+ state = State.REFRAIN
+ line = rest
+ yield ' {} \\\\\n'.format(line)
+ elif (rest := match_rest(re_couplet, line)) is not None:
+ # when a couplet start marker is found, enter a couplet.
+ yield '\\begin{couplet}\n'
+ state = State.COUPLET
+ line = rest
+ yield ' {} \\\\\n'.format(line)
+ elif line != '':
+ # when a non-empty line is found, also enter a couplet as all.
+ yield '\\begin{couplet}\n'
+ state = State.COUPLET
+ yield ' {} \\\\\n'.format(line)
+ elif state == State.REFRAIN:
+ # We are in the refrain. Depending on what we find:
+ if line != '':
+ # when a non-empty line is found, stay in the refrain.
+ yield ' {} \\\\\n'.format(line)
+ else:
+ # when an empty line is found, leave the refrain.
+ yield '\\end{refrain}\n\n'
+ state = State.LIMBO
+ elif state == State.COUPLET:
+ # We are in a couplet. Depending on what we find:
+ if line != '':
+ # when a non-empty line is found, stay in the couplet.
+ yield ' {} \\\\\n'.format(line)
+ else:
+ # when an empty line is found, leave the couplet.
+ yield '\\end{couplet}\n\n'
+ state = State.LIMBO
+ # No matter what we found, we have a song line, possibly empty, that we
+ # have to check to see if it could not be the new longest line seen so far.
+ if len(line) > len(longest):
+ longest = line
+ # The song text file is now finished. We may still be in the refrain or in a
+ # couplet, that we have to close.
+ if state == State.REFRAIN:
+ yield '\\end{refrain}\n\n'
+ elif state == State.COUPLET:
+ yield '\\end{couplet}\n\n'
+ # Now print the longest line of the entire song.
+ yield '\\longest{{{}}}\n'.format(longest)
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description="Convert song text to LaTeX songproj markup")
+ parser.add_argument("--refrain", '-r', type=re.compile, default=re_refrain, metavar="REGEXP",
+ help="specify the refrain marker (regexp, default to \"{}\")".format(re_refrain.pattern))
+ parser.add_argument("--couplet", '-c', type=re.compile, default=re_couplet, metavar="REGEXP",
+ help="specify the couplet marker (regexp, default \"{}\")".format(re_couplet.pattern))
+ parser.add_argument("infile", help="input song text file", type=argparse.FileType('r'))
+ parser.add_argument("outfile", help="output LaTeX file", type=argparse.FileType('w'))
+ args = parser.parse_args(args)
+ for line in parse_song(args.infile, args.refrain, args.couplet):
+ args.outfile.write(line)
+
+if __name__ == "__main__":
+ main()