* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / mdoc2man.rb
blob910b2e57451828b437938c81b701874e8f2ca282
1 #!/usr/bin/env ruby
2 ###
3 ### mdoc2man - mdoc to man converter
4 ###
5 ### Quick usage:  mdoc2man.rb < mdoc_manpage.8 > man_manpage.8
6 ###
7 ### Ported from Perl by Akinori MUSHA.
8 ###
9 ###  Copyright (c) 2001 University of Illinois Board of Trustees
10 ###  Copyright (c) 2001 Mark D. Roth
11 ###  Copyright (c) 2002, 2003 Akinori MUSHA
12 ###  All rights reserved.
13 ### 
14 ###  Redistribution and use in source and binary forms, with or without
15 ###  modification, are permitted provided that the following conditions
16 ###  are met:
17 ###  1. Redistributions of source code must retain the above copyright
18 ###     notice, this list of conditions and the following disclaimer.
19 ###  2. Redistributions in binary form must reproduce the above copyright
20 ###     notice, this list of conditions and the following disclaimer in the
21 ###     documentation and/or other materials provided with the distribution.
22 ###  3. All advertising materials mentioning features or use of this software
23 ###     must display the following acknowledgement:
24 ###     This product includes software developed by the University of
25 ###     Illinois at Urbana, and their contributors.
26 ###  4. The University nor the names of their
27 ###     contributors may be used to endorse or promote products derived from
28 ###     this software without specific prior written permission.
29 ### 
30 ###  THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND
31 ###  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 ###  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 ###  ARE DISCLAIMED.  IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE
34 ###  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 ###  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 ###  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 ###  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 ###  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 ###  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 ###  SUCH DAMAGE.
41 ###
42 ### $Id$
43 ###
45 class Mdoc2Man
46   ANGLE = 1
47   OPTION = 2
48   PAREN = 3
50   RE_PUNCT = /^[!"'),\.\/:;>\?\]`]$/
52   def initialize
53     @name = @date = @id = nil
54     @refauthors = @reftitle = @refissue = @refdate = @refopt = nil
56     @optlist = 0                ### 1 = bullet, 2 = enum, 3 = tag, 4 = item
57     @oldoptlist = 0
58     @nospace = 0                ### 0, 1, 2
59     @enum = 0
60     @synopsis = true
61     @reference = false
62     @ext = false
63     @extopt = false
64     @literal = false
65   end
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
75       line.slice!(0, 1)
77       next if /\\"/ =~ line
79       line = parse_macro(line) and o.print line
80     }
82     initialize
83   end
85   def parse_macro(line)
86     words = line.split
87     retval = ''
89     quote = []
90     dl = false
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         # when  'Ic'
147         #   retval << '\\fB' << words.shift << '\\fP'
148         #   next
149       when 'Oo'
150         #retval << "[\\c\n"
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
168       retval << ' ' if @nospace == 0 && !(retval.empty? || /[\n ]\z/ =~ retval)
169       @nospace = 0 if @nospace == 1
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"
206         # authors
207         while @refauthors.size > 1
208           retval << @refauthors.shift << ', '
209         end
210         retval << 'and ' unless retval.empty?
211         retval << @refauthors.shift
213         # title 
214         retval << ', \\fI' << @reftitle << '\\fP'
216         # issue
217         retval << ', ' << @refissue unless @refissue.empty?
219         # date
220         retval << ', ' << @refdate unless @refdate.empty?
222         # optional info
223         retval << ', ' << @refopt unless @refopt.empty?
225         retval << ".\n"
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
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
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         # words.push(words.pop + ']')
300         next
301       when 'Aq'
302         quote << ANGLE
303         @nospace = 1 if @nospace == 0
304         retval << '<'
305         # words.push(words.pop + '>')
306         next
307       when 'Pp'
308         retval << "\n"
309         next
310       when 'Ss'
311         retval << '.SS'
312         next
313       end
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         # @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
323         next
324       end
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
362           retval << ' ' if @nospace == 0
363         end
365         retval.chomp!(' ')
366         retval << '\\fP'
367         retval << words.shift unless words.empty?
368         break
369       when 'Bl'
370         @oldoptlist = @optlist
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
384         break
385       when 'El'
386         @optlist = @oldoptlist
387         next
388       end
390       if @optlist != 0 && word == 'It'
391         case @optlist
392         when 1
393           # bullets
394           retval << '.IP \\(bu'
395         when 2
396           # enum
397           @enum += 1
398           retval << '.IP ' << @enum << '.'
399         when 3
400           # tags
401           retval << ".TP\n"
402           case words[0]
403           when 'Pa', 'Ev'
404             words.shift
405             retval << '.B'
406           end
407         when 4
408           # item
409           retval << ".IP\n"
410         end
412         next
413       end
415       case word
416       when 'Sm'
417         case words[0]
418         when 'off'
419           @nospace = 2
420         when 'on'
421           # retval << "\n"
422           @nospace = 0
423         end
424         words.shift
425         next
426       end
428       retval << word
429     end
431     return nil if retval == '.'
433     retval.sub!(/\A\.([^a-zA-Z])/, "\\1")
434     # retval.chomp!(' ')
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
447     # retval << ' ' unless @nospace == 0 || retval.empty? || /\n\z/ =~ retval
449     retval << ' ' unless !@ext || @extopt || / $/ =~ retval
451     retval << "\n" unless @ext || @extopt || retval.empty? || /\n\z/ =~ retval
453     retval << ".fi\n" if dl
455     return retval
456   end
458   def self.mdoc2man(i, o)
459     new.mdoc2man(i, o)
460   end
463 if $0 == __FILE__
464   Mdoc2Man.mdoc2man(ARGF, STDOUT)