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: class Mdoc2Man
46: ANGLE = 1
47: OPTION = 2
48: PAREN = 3
49:
50: RE_PUNCT = /^[!"'),\.\/:;>\?\]`]$/
51:
52: def initialize
53: @name = @date = @id = nil
54: @refauthors = @reftitle = @refissue = @refdate = @refopt = nil
55:
56: @optlist = 0
57: @oldoptlist = 0
58: @nospace = 0
59: @enum = 0
60: @synopsis = true
61: @reference = false
62: @ext = false
63: @extopt = false
64: @literal = false
65: end
66:
67: def mdoc2man(i, o)
68: i.each { |line|
69: if /^\./ !~ line
70: o.print line
71: o.print ".br\n" if @literal
72: next
73: end
74:
75: line.slice!(0, 1)
76:
77: next if /\\"/ =~ line
78:
79: line = parse_macro(line) and o.print line
80: }
81:
82: initialize
83: end
84:
85: def parse_macro(line)
86: words = line.split
87: retval = ''
88:
89: quote = []
90: dl = false
91:
92: while word = words.shift
93: case word
94: when RE_PUNCT
95: while q = quote.pop
96: case q
97: when OPTION
98: retval << ']'
99: when PAREN
100: retval << ')'
101: when ANGLE
102: retval << '>'
103: end
104: end
105: retval << word
106: next
107: when 'Li', 'Pf'
108: @nospace = 1
109: next
110: when 'Xo'
111: @ext = true
112: retval << ' ' unless retval.empty? || /[\n ]\z/ =~ retval
113: next
114: when 'Xc'
115: @ext = false
116: retval << "\n" unless @extopt
117: break
118: when 'Bd'
119: @literal = true if words[0] == '-literal'
120: retval << "\n"
121: break
122: when 'Ed'
123: @literal = false
124: break
125: when 'Ns'
126: @nospace = 1 if @nospace == 0
127: retval.chomp!(' ')
128: next
129: when 'No'
130: retval.chomp!(' ')
131: retval << words.shift
132: next
133: when 'Dq'
134: retval << '``'
135: begin
136: retval << words.shift << ' '
137: end until words.empty? || RE_PUNCT =~ words[0]
138: retval.chomp!(' ')
139: retval << '\'\''
140: @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
141: next
142: when 'Sq', 'Ql'
143: retval << '`' << words.shift << '\''
144: @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
145: next
146:
147:
148:
149: when 'Oo'
150:
151: @extopt = true
152: @nospace = 1 if @nospace == 0
153: retval << '['
154: next
155: when 'Oc'
156: @extopt = false
157: retval << ']'
158: next
159: when 'Ao'
160: @nospace = 1 if @nospace == 0
161: retval << '<'
162: next
163: when 'Ac'
164: retval << '>'
165: next
166: end
167:
168: retval << ' ' if @nospace == 0 && !(retval.empty? || /[\n ]\z/ =~ retval)
169: @nospace = 0 if @nospace == 1
170:
171: case word
172: when 'Dd'
173: @date = words.join(' ')
174: return nil
175: when 'Dt'
176: if words.size >= 2 && words[1] == '""' &&
177: /^(.*)\(([0-9])\)$/ =~ words[0]
178: words[0] = $1
179: words[1] = $2
180: end
181: @id = words.join(' ')
182: return nil
183: when 'Os'
184: retval << '.TH ' << @id << ' "' << @date << '" "' <<
185: words.join(' ') << '"'
186: break
187: when 'Sh'
188: retval << '.SH'
189: @synopsis = (words[0] == 'SYNOPSIS')
190: next
191: when 'Xr'
192: retval << '\\fB' << words.shift <<
193: '\\fP(' << words.shift << ')' << words.shift
194: break
195: when 'Rs'
196: @refauthors = []
197: @reftitle = ''
198: @refissue = ''
199: @refdate = ''
200: @refopt = ''
201: @reference = true
202: break
203: when 'Re'
204: retval << "\n"
205:
206:
207: while @refauthors.size > 1
208: retval << @refauthors.shift << ', '
209: end
210: retval << 'and ' unless retval.empty?
211: retval << @refauthors.shift
212:
213:
214: retval << ', \\fI' << @reftitle << '\\fP'
215:
216:
217: retval << ', ' << @refissue unless @refissue.empty?
218:
219:
220: retval << ', ' << @refdate unless @refdate.empty?
221:
222:
223: retval << ', ' << @refopt unless @refopt.empty?
224:
225: retval << ".\n"
226:
227: @reference = false
228: break
229: when 'An'
230: next
231: when 'Dl'
232: retval << ".nf\n" << '\\& '
233: dl = true
234: next
235: when 'Ux'
236: retval << "UNIX"
237: next
238: end
239:
240: if @reference
241: case word
242: when '%A'
243: @refauthors.unshift(words.join(' '))
244: break
245: when '%T'
246: @reftitle = words.join(' ')
247: @reftitle.sub!(/^"/, '')
248: @reftitle.sub!(/"$/, '')
249: break
250: when '%N'
251: @refissue = words.join(' ')
252: break
253: when '%D'
254: @refdate = words.join(' ')
255: break
256: when '%O'
257: @refopt = words.join(' ')
258: break
259: end
260: end
261:
262: case word
263: when 'Nm'
264: name = words.empty? ? @name : words.shift
265: @name ||= name
266: retval << ".br\n" if @synopsis
267: retval << "\\fB" << name << "\\fP"
268: @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
269: next
270: when 'Nd'
271: retval << '\\-'
272: next
273: when 'Fl'
274: retval << '\\fB\\-' << words.shift << '\\fP'
275: @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
276: next
277: when 'Ar'
278: retval << '\\fI'
279: if words.empty?
280: retval << 'file ...\\fP'
281: else
282: retval << words.shift << '\\fP'
283: while words[0] == '|'
284: retval << ' ' << words.shift << ' \\fI' << words.shift << '\\fP'
285: end
286: @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
287: next
288: end
289: when 'Cm'
290: retval << '\\fB' << words.shift << '\\fP'
291: while RE_PUNCT =~ words[0]
292: retval << words.shift
293: end
294: next
295: when 'Op'
296: quote << OPTION
297: @nospace = 1 if @nospace == 0
298: retval << '['
299:
300: next
301: when 'Aq'
302: quote << ANGLE
303: @nospace = 1 if @nospace == 0
304: retval << '<'
305:
306: next
307: when 'Pp'
308: retval << "\n"
309: next
310: when 'Ss'
311: retval << '.SS'
312: next
313: end
314:
315: if word == 'Pa' && !quote.include?(OPTION)
316: retval << '\\fI'
317: retval << '\\&' if /^\./ =~ words[0]
318: retval << words.shift << '\\fP'
319: while RE_PUNCT =~ words[0]
320: retval << words.shift
321: end
322:
323: next
324: end
325:
326: case word
327: when 'Dv'
328: retval << '.BR'
329: next
330: when 'Em', 'Ev'
331: retval << '.IR'
332: next
333: when 'Pq'
334: retval << '('
335: @nospace = 1
336: quote << PAREN
337: next
338: when 'Sx', 'Sy'
339: retval << '.B ' << words.join(' ')
340: break
341: when 'Ic'
342: retval << '\\fB'
343: until words.empty? || RE_PUNCT =~ words[0]
344: case words[0]
345: when 'Op'
346: words.shift
347: retval << '['
348: words.push(words.pop + ']')
349: next
350: when 'Aq'
351: words.shift
352: retval << '<'
353: words.push(words.pop + '>')
354: next
355: when 'Ar'
356: words.shift
357: retval << '\\fI' << words.shift << '\\fP'
358: else
359: retval << words.shift
360: end
361:
362: retval << ' ' if @nospace == 0
363: end
364:
365: retval.chomp!(' ')
366: retval << '\\fP'
367: retval << words.shift unless words.empty?
368: break
369: when 'Bl'
370: @oldoptlist = @optlist
371:
372: case words[0]
373: when '-bullet'
374: @optlist = 1
375: when '-enum'
376: @optlist = 2
377: @enum = 0
378: when '-tag'
379: @optlist = 3
380: when '-item'
381: @optlist = 4
382: end
383:
384: break
385: when 'El'
386: @optlist = @oldoptlist
387: next
388: end
389:
390: if @optlist != 0 && word == 'It'
391: case @optlist
392: when 1
393:
394: retval << '.IP \\(bu'
395: when 2
396:
397: @enum += 1
398: retval << '.IP ' << @enum << '.'
399: when 3
400:
401: retval << ".TP\n"
402: case words[0]
403: when 'Pa', 'Ev'
404: words.shift
405: retval << '.B'
406: end
407: when 4
408:
409: retval << ".IP\n"
410: end
411:
412: next
413: end
414:
415: case word
416: when 'Sm'
417: case words[0]
418: when 'off'
419: @nospace = 2
420: when 'on'
421:
422: @nospace = 0
423: end
424: words.shift
425: next
426: end
427:
428: retval << word
429: end
430:
431: return nil if retval == '.'
432:
433: retval.sub!(/\A\.([^a-zA-Z])/, "\\1")
434:
435:
436: while q = quote.pop
437: case q
438: when OPTION
439: retval << ']'
440: when PAREN
441: retval << ')'
442: when ANGLE
443: retval << '>'
444: end
445: end
446:
447:
448:
449: retval << ' ' unless !@ext || @extopt || / $/ =~ retval
450:
451: retval << "\n" unless @ext || @extopt || retval.empty? || /\n\z/ =~ retval
452:
453: retval << ".fi\n" if dl
454:
455: return retval
456: end
457:
458: def self.mdoc2man(i, o)
459: new.mdoc2man(i, o)
460: end
461: end
462:
463: if $0 == __FILE__
464: Mdoc2Man.mdoc2man(ARGF, STDOUT)
465: end