1
# -*- test-case-name: twisted.lore.test.test_man2lore -*-
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
man2lore: Converts man page source (i.e. groff) into lore-compatible html.
8
This is nasty and hackish (and doesn't support lots of real groff), but is good
9
enough for converting fairly simple man pages.
14
quoteRE = re.compile('"(.*?)"')
19
text = text.replace('<', '<').replace('>', '>')
20
text = quoteRE.sub('<q>\\1</q>', text)
26
if s[0] == s[-1] == '"':
32
class ManConverter(object):
34
Convert a man page to the Lore format.
36
@ivar tp: State variable for handling text inside a C{TP} token. It can
37
take values from 0 to 3:
38
- 0: when outside of a C{TP} token.
39
- 1: once a C{TP} token has been encountered. If the previous value
40
was 0, a definition list is started. Then, at the first line of
41
text, a definition term is started.
42
- 2: when the first line after the C{TP} token has been handled.
43
The definition term is closed, and a definition is started with
44
the next line of text.
45
- 3: when the first line as definition data has been handled.
54
def convert(self, inf, outf):
55
self.write = outf.write
57
for line in inf.readlines():
58
if line.rstrip() and line.rstrip()[-1] == '\\':
59
longline += line.rstrip()[:-1] + ' '
62
line = longline + line
64
self.lineReceived(line)
66
self.write('</body>\n</html>\n')
70
def lineReceived(self, line):
72
f = getattr(self, 'macro_' + line[1:3].rstrip().upper(), None)
79
def continueReceived(self, cont):
83
f = getattr(self, 'macro_' + cont[:2].rstrip().upper(), None)
91
if self.state != 'regular':
92
self.write('</%s>' % self.state)
94
self.write('</dd>\n\n')
97
self.write('</dl>\n\n')
100
self.write('</p>\n\n')
105
if not self.tp and not self.para:
110
def macro_TH(self, line):
112
'<?xml version="1.0"?>\n'
113
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n'
114
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
115
self.write('<html><head>\n')
116
parts = [stripQuotes(x) for x in line.split(' ', 2)] + ['', '']
117
title, manSection = parts[:2]
118
self.write('<title>%s.%s</title>' % (title, manSection))
119
self.write('</head>\n<body>\n\n')
120
self.write('<h1>%s.%s</h1>\n\n' % (title, manSection))
125
def macro_SH(self, line):
129
self.text(stripQuotes(line))
132
self.write('</h2>\n\n')
135
def macro_B(self, line):
137
words[0] = '\\fB' + words[0] + '\\fR '
138
self.text(' '.join(words))
141
def macro_NM(self, line):
144
self.text(self.name + ' ')
147
def macro_NS(self, line):
148
parts = line.split(' Ns ')
155
self.continueReceived(l)
158
def macro_OO(self, line):
160
self.continueReceived(line)
163
def macro_OC(self, line):
165
self.continueReceived(line)
168
def macro_OP(self, line):
170
self.continueReceived(line)
174
def macro_FL(self, line):
176
self.text('\\fB-%s\\fR' % parts[0])
177
self.continueReceived(' '.join(parts[1:]))
180
def macro_AR(self, line):
182
self.text('\\fI %s\\fR' % parts[0])
183
self.continueReceived(' '.join(parts[1:]))
186
def macro_PP(self, line):
190
def macro_IC(self, line):
191
cmd = line.split(' ', 1)[0]
192
args = line[line.index(cmd) + len(cmd):]
193
args = args.split(' ')
197
if arg.lower() == "ar":
198
text += " \\fU%s\\fR" % (args.pop(0),)
199
elif arg.lower() == "op":
201
text += " [\\fU%s\\fR]" % (args.pop(0),)
206
def macro_TP(self, line):
208
Handle C{TP} token: start a definition list if it's first token, or
209
close previous definition data.
212
self.write('</dd>\n\n')
220
def macro_BL(self, line):
225
def macro_EL(self, line):
229
self.write('</dl>\n\n')
233
def macro_IT(self, line):
237
self.continueReceived(line)
240
def text(self, line):
242
Handle a line of text without detected token.
250
bits = line.split('\\')
251
self.write(escape(bits[0]))
254
self.write('<em>' + escape(bit[2:]))
256
elif bit[:2] == 'fB':
257
self.write('<strong>' + escape(bit[2:]))
258
self.state = 'strong'
259
elif bit[:2] == 'fR':
260
self.write('</%s>' % self.state)
261
self.write(escape(bit[2:]))
262
self.state = 'regular'
263
elif bit[:2] == 'fU':
264
# fU doesn't really exist, but it helps us to manage underlined
266
self.write('<u>' + escape(bit[2:]))
268
elif bit[:3] == '(co':
269
self.write('©' + escape(bit[3:]))
271
self.write(escape(bit))
281
class ProcessingFunctionFactory:
283
def generate_lore(self, d, filenameGenerator=None):
284
ext = d.get('ext', '.html')
285
return lambda file,_: ManConverter().convert(open(file),
286
open(os.path.splitext(file)[0]+ext, 'w'))
290
factory = ProcessingFunctionFactory()
293
if __name__ == '__main__':
295
mc = ManConverter().convert(open(sys.argv[1]), sys.stdout)