summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/songproj/song2tex.py
blob: dc32da2b3faaa2ddbd13a432b613c906bf992379 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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()