mdoc2man.awk revision 323134
1#!/usr/bin/awk
2#
3# $Id: mdoc2man.awk,v 1.9 2009/10/24 00:52:42 dtucker Exp $
4#
5# Version history:
6#  v4+ Adapted for OpenSSH Portable (see cvs Id and history)
7#  v3, I put the program under a proper license
8#      Dan Nelson <dnelson@allantgroup.com> added .An, .Aq and fixed a typo
9#  v2, fixed to work on GNU awk --posix and MacOS X
10#  v1, first attempt, didn't work on MacOS X
11#
12# Copyright (c) 2003 Peter Stuge <stuge-mdoc2man@cdy.org>
13#
14# Permission to use, copy, modify, and distribute this software for any
15# purpose with or without fee is hereby granted, provided that the above
16# copyright notice and this permission notice appear in all copies.
17#
18# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
20# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
21# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
23# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
24# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25
26
27BEGIN {
28  optlist=0
29  oldoptlist=0
30  nospace=0
31  synopsis=0
32  reference=0
33  block=0
34  ext=0
35  extopt=0
36  literal=0
37  prenl=0
38  breakw=0
39  line=""
40}
41
42function wtail() {
43  retval=""
44  while(w<nwords) {
45    if(length(retval))
46      retval=retval OFS
47    retval=retval words[++w]
48  }
49  return retval
50}
51
52function add(str) {
53  for(;prenl;prenl--)
54    line=line "\n"
55  line=line str
56}
57
58! /^\./ {
59  for(;prenl;prenl--)
60    print ""
61  print
62  if(literal)
63    print ".br"
64  next
65}
66
67/^\.\\"/ { next }
68
69{
70  option=0
71  parens=0
72  angles=0
73  sub("^\\.","")
74  nwords=split($0,words)
75  for(w=1;w<=nwords;w++) {
76    skip=0
77    if(match(words[w],"^Li|Pf$")) {
78      skip=1
79    } else if(match(words[w],"^Xo$")) {
80      skip=1
81      ext=1
82      if(length(line)&&!(match(line," $")||prenl))
83	add(OFS)
84    } else if(match(words[w],"^Xc$")) {
85      skip=1
86      ext=0
87      if(!extopt)
88	prenl++
89      w=nwords
90    } else if(match(words[w],"^Bd$")) {
91      skip=1
92      if(match(words[w+1],"-literal")) {
93	literal=1
94	prenl++
95	w=nwords
96      }
97    } else if(match(words[w],"^Ed$")) {
98      skip=1
99      literal=0
100    } else if(match(words[w],"^Ns$")) {
101      skip=1
102      if(!nospace)
103	nospace=1
104      sub(" $","",line)
105    } else if(match(words[w],"^No$")) {
106      skip=1
107      sub(" $","",line)
108      add(words[++w])
109    } else if(match(words[w],"^Dq$")) {
110      skip=1
111      add("``")
112      add(words[++w])
113      while(w<nwords&&!match(words[w+1],"^[\\.,]"))
114	add(OFS words[++w])
115      add("''")
116      if(!nospace&&match(words[w+1],"^[\\.,]"))
117	nospace=1
118    } else if(match(words[w],"^Sq|Ql$")) {
119      skip=1
120      add("`" words[++w] "'")
121      if(!nospace&&match(words[w+1],"^[\\.,]"))
122	nospace=1
123    } else if(match(words[w],"^Oo$")) {
124      skip=1
125      extopt=1
126      if(!nospace)
127	nospace=1
128      add("[")
129    } else if(match(words[w],"^Oc$")) {
130      skip=1
131      extopt=0
132      add("]")
133    }
134    if(!skip) {
135      if(!nospace&&length(line)&&!(match(line," $")||prenl))
136	add(OFS)
137      if(nospace==1)
138	nospace=0
139    }
140    if(match(words[w],"^Dd$")) {
141      if(match(words[w+1],"^\\$Mdocdate:")) {
142        w++;
143        if(match(words[w+4],"^\\$$")) {
144          words[w+4] = ""
145        }
146      }
147      date=wtail()
148      next
149    } else if(match(words[w],"^Dt$")) {
150      id=wtail()
151      next
152    } else if(match(words[w],"^Ux$")) {
153      add("UNIX")
154      skip=1
155    } else if(match(words[w],"^Ox$")) {
156      add("OpenBSD")
157      skip=1
158    } else if(match(words[w],"^Os$")) {
159      add(".TH " id " \"" date "\" \"" wtail() "\"")
160    } else if(match(words[w],"^Sh$")) {
161      add(".SH")
162      synopsis=match(words[w+1],"SYNOPSIS")
163    } else if(match(words[w],"^Xr$")) {
164      add("\\fB" words[++w] "\\fP(" words[++w] ")" words[++w])
165    } else if(match(words[w],"^Rs$")) {
166      split("",refauthors)
167      nrefauthors=0
168      reftitle=""
169      refissue=""
170      refdate=""
171      refopt=""
172      refreport=""
173      reference=1
174      next
175    } else if(match(words[w],"^Re$")) {
176      prenl++
177      for(i=nrefauthors-1;i>0;i--) {
178	add(refauthors[i])
179	if(i>1)
180	  add(", ")
181      }
182      if(nrefauthors>1)
183	add(" and ")
184      if(nrefauthors>0)
185        add(refauthors[0] ", ")
186      add("\\fI" reftitle "\\fP")
187      if(length(refissue))
188	add(", " refissue)
189      if(length(refreport)) {
190	add(", " refreport)
191      }
192      if(length(refdate))
193	add(", " refdate)
194      if(length(refopt))
195	add(", " refopt)
196      add(".")
197      reference=0
198    } else if(reference) {
199      if(match(words[w],"^%A$")) { refauthors[nrefauthors++]=wtail() }
200      if(match(words[w],"^%T$")) {
201	reftitle=wtail()
202	sub("^\"","",reftitle)
203	sub("\"$","",reftitle)
204      }
205      if(match(words[w],"^%N$")) { refissue=wtail() }
206      if(match(words[w],"^%D$")) { refdate=wtail() }
207      if(match(words[w],"^%O$")) { refopt=wtail() }
208      if(match(words[w],"^%R$")) { refreport=wtail() }
209    } else if(match(words[w],"^Nm$")) {
210      if(synopsis) {
211	add(".br")
212	prenl++
213      }
214      n=words[++w]
215      if(!length(name))
216	name=n
217      if(!length(n))
218	n=name
219      add("\\fB" n "\\fP")
220      if(!nospace&&match(words[w+1],"^[\\.,]"))
221	nospace=1
222    } else if(match(words[w],"^Nd$")) {
223      add("\\- " wtail())
224    } else if(match(words[w],"^Fl$")) {
225      add("\\fB\\-" words[++w] "\\fP")
226      if(!nospace&&match(words[w+1],"^[\\.,]"))
227	nospace=1
228    } else if(match(words[w],"^Ar$")) {
229      add("\\fI")
230      if(w==nwords)
231	add("file ...\\fP")
232      else {
233	add(words[++w] "\\fP")
234	while(match(words[w+1],"^\\|$"))
235	  add(OFS words[++w] " \\fI" words[++w] "\\fP")
236      }
237      if(!nospace&&match(words[w+1],"^[\\.,]"))
238	nospace=1
239    } else if(match(words[w],"^Cm$")) {
240      add("\\fB" words[++w] "\\fP")
241      while(w<nwords&&match(words[w+1],"^[\\.,:;)]"))
242	add(words[++w])
243    } else if(match(words[w],"^Op$")) {
244      option=1
245      if(!nospace)
246	nospace=1
247      add("[")
248    } else if(match(words[w],"^Pp$")) {
249      prenl++
250    } else if(match(words[w],"^An$")) {
251      prenl++
252    } else if(match(words[w],"^Ss$")) {
253      add(".SS")
254    } else if(match(words[w],"^Pa$")&&!option) {
255      add("\\fI")
256      w++
257      if(match(words[w],"^\\."))
258	add("\\&")
259      add(words[w] "\\fP")
260      while(w<nwords&&match(words[w+1],"^[\\.,:;)]"))
261	add(words[++w])
262    } else if(match(words[w],"^Dv$")) {
263      add(".BR")
264    } else if(match(words[w],"^Em|Ev$")) {
265      add(".IR")
266    } else if(match(words[w],"^Pq$")) {
267      add("(")
268      nospace=1
269      parens=1
270    } else if(match(words[w],"^Aq$")) {
271      add("<")
272      nospace=1
273      angles=1
274    } else if(match(words[w],"^S[xy]$")) {
275      add(".B " wtail())
276    } else if(match(words[w],"^Ic$")) {
277      plain=1
278      add("\\fB")
279      while(w<nwords) {
280	w++
281	if(match(words[w],"^Op$")) {
282	  w++
283	  add("[")
284	  words[nwords]=words[nwords] "]"
285	}
286	if(match(words[w],"^Ar$")) {
287	  add("\\fI" words[++w] "\\fP")
288	} else if(match(words[w],"^[\\.,]")) {
289	  sub(" $","",line)
290	  if(plain) {
291	    add("\\fP")
292	    plain=0
293	  }
294	  add(words[w])
295	} else {
296	  if(!plain) {
297	    add("\\fB")
298	    plain=1
299	  }
300	  add(words[w])
301	}
302	if(!nospace)
303	  add(OFS)
304      }
305      sub(" $","",line)
306      if(plain)
307	add("\\fP")
308    } else if(match(words[w],"^Bl$")) {
309      oldoptlist=optlist
310      if(match(words[w+1],"-bullet"))
311	optlist=1
312      else if(match(words[w+1],"-enum")) {
313	optlist=2
314	enum=0
315      } else if(match(words[w+1],"-tag"))
316	optlist=3
317      else if(match(words[w+1],"-item"))
318	optlist=4
319      else if(match(words[w+1],"-bullet"))
320	optlist=1
321      w=nwords
322    } else if(match(words[w],"^El$")) {
323      optlist=oldoptlist
324      if(!optlist)
325        add(".PP")
326    } else if(match(words[w],"^Bk$")) {
327      if(match(words[w+1],"-words")) {
328	w++
329	breakw=1
330      }
331    } else if(match(words[w],"^Ek$")) {
332      breakw=0
333    } else if(match(words[w],"^It$")&&optlist) {
334      if(optlist==1)
335	add(".IP \\(bu")
336      else if(optlist==2)
337	add(".IP " ++enum ".")
338      else if(optlist==3) {
339	add(".TP")
340	prenl++
341	if(match(words[w+1],"^Pa$|^Ev$")) {
342	  add(".B")
343	  w++
344	}
345      } else if(optlist==4)
346	add(".IP")
347    } else if(match(words[w],"^Sm$")) {
348      if(match(words[w+1],"off"))
349	nospace=2
350      else if(match(words[w+1],"on"))
351	nospace=0
352      w++
353    } else if(!skip) {
354      add(words[w])
355    }
356  }
357  if(match(line,"^\\.[^a-zA-Z]"))
358    sub("^\\.","",line)
359  if(parens)
360    add(")")
361  if(angles)
362    add(">")
363  if(option)
364    add("]")
365  if(ext&&!extopt&&!match(line," $"))
366    add(OFS)
367  if(!ext&&!extopt&&length(line)) {
368    print line
369    prenl=0
370    line=""
371  }
372}
373