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
102
103
104
105
106
107
|
#!/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+( |[-./] ?)')
re_txt_ext = re.compile(r'\.te?xt$')
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 ' {}'.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 ' {}'.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 ' {}'.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 '\n\\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 '\n\\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 '\n\\end{refrain}\n\n'
elif state == State.COUPLET:
yield '\n\\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'), nargs='?')
args = parser.parse_args(args)
if args.outfile is None:
try:
args.outfile = open(re_txt_ext.sub('', args.infile.name) + '.tex', 'w')
except AttributeError:
parser.error("the following argument is required: outfile")
for line in parse_song(args.infile, args.refrain, args.couplet):
args.outfile.write(line)
if __name__ == "__main__":
main()
|