mdoc2man.awk revision 180744
1#!/usr/bin/awk
2#
3# $Id: mdoc2man.awk,v 1.8 2007/06/05 10:01:16 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],"^Ox$")) {
153      add("OpenBSD")
154      skip=1
155    } else if(match(words[w],"^Os$")) {
156      add(".TH " id " \"" date "\" \"" wtail() "\"")
157    } else if(match(words[w],"^Sh$")) {
158      add(".SH")
159      synopsis=match(words[w+1],"SYNOPSIS")
160    } else if(match(words[w],"^Xr$")) {
161      add("\\fB" words[++w] "\\fP(" words[++w] ")" words[++w])
162    } else if(match(words[w],"^Rs$")) {
163      split("",refauthors)
164      nrefauthors=0
165      reftitle=""
166      refissue=""
167      refdate=""
168      refopt=""
169      refreport=""
170      reference=1
171      next
172    } else if(match(words[w],"^Re$")) {
173      prenl++
174      for(i=nrefauthors-1;i>0;i--) {
175	add(refauthors[i])
176	if(i>1)
177	  add(", ")
178      }
179      if(nrefauthors>1)
180	add(" and ")
181      if(nrefauthors>0)
182        add(refauthors[0] ", ")
183      add("\\fI" reftitle "\\fP")
184      if(length(refissue))
185	add(", " refissue)
186      if(length(refreport)) {
187	add(", " refreport)
188      }
189      if(length(refdate))
190	add(", " refdate)
191      if(length(refopt))
192	add(", " refopt)
193      add(".")
194      reference=0
195    } else if(reference) {
196      if(match(words[w],"^%A$")) { refauthors[nrefauthors++]=wtail() }
197      if(match(words[w],"^%T$")) {
198	reftitle=wtail()
199	sub("^\"","",reftitle)
200	sub("\"$","",reftitle)
201      }
202      if(match(words[w],"^%N$")) { refissue=wtail() }
203      if(match(words[w],"^%D$")) { refdate=wtail() }
204      if(match(words[w],"^%O$")) { refopt=wtail() }
205      if(match(words[w],"^%R$")) { refreport=wtail() }
206    } else if(match(words[w],"^Nm$")) {
207      if(synopsis) {
208	add(".br")
209	prenl++
210      }
211      n=words[++w]
212      if(!length(name))
213	name=n
214      if(!length(n))
215	n=name
216      add("\\fB" n "\\fP")
217      if(!nospace&&match(words[w+1],"^[\\.,]"))
218	nospace=1
219    } else if(match(words[w],"^Nd$")) {
220      add("\\- " wtail())
221    } else if(match(words[w],"^Fl$")) {
222      add("\\fB\\-" words[++w] "\\fP")
223      if(!nospace&&match(words[w+1],"^[\\.,]"))
224	nospace=1
225    } else if(match(words[w],"^Ar$")) {
226      add("\\fI")
227      if(w==nwords)
228	add("file ...\\fP")
229      else {
230	add(words[++w] "\\fP")
231	while(match(words[w+1],"^\\|$"))
232	  add(OFS words[++w] " \\fI" words[++w] "\\fP")
233      }
234      if(!nospace&&match(words[w+1],"^[\\.,]"))
235	nospace=1
236    } else if(match(words[w],"^Cm$")) {
237      add("\\fB" words[++w] "\\fP")
238      while(w<nwords&&match(words[w+1],"^[\\.,:;)]"))
239	add(words[++w])
240    } else if(match(words[w],"^Op$")) {
241      option=1
242      if(!nospace)
243	nospace=1
244      add("[")
245    } else if(match(words[w],"^Pp$")) {
246      prenl++
247    } else if(match(words[w],"^An$")) {
248      prenl++
249    } else if(match(words[w],"^Ss$")) {
250      add(".SS")
251    } else if(match(words[w],"^Pa$")&&!option) {
252      add("\\fI")
253      w++
254      if(match(words[w],"^\\."))
255	add("\\&")
256      add(words[w] "\\fP")
257      while(w<nwords&&match(words[w+1],"^[\\.,:;)]"))
258	add(words[++w])
259    } else if(match(words[w],"^Dv$")) {
260      add(".BR")
261    } else if(match(words[w],"^Em|Ev$")) {
262      add(".IR")
263    } else if(match(words[w],"^Pq$")) {
264      add("(")
265      nospace=1
266      parens=1
267    } else if(match(words[w],"^Aq$")) {
268      add("<")
269      nospace=1
270      angles=1
271    } else if(match(words[w],"^S[xy]$")) {
272      add(".B " wtail())
273    } else if(match(words[w],"^Ic$")) {
274      plain=1
275      add("\\fB")
276      while(w<nwords) {
277	w++
278	if(match(words[w],"^Op$")) {
279	  w++
280	  add("[")
281	  words[nwords]=words[nwords] "]"
282	}
283	if(match(words[w],"^Ar$")) {
284	  add("\\fI" words[++w] "\\fP")
285	} else if(match(words[w],"^[\\.,]")) {
286	  sub(" $","",line)
287	  if(plain) {
288	    add("\\fP")
289	    plain=0
290	  }
291	  add(words[w])
292	} else {
293	  if(!plain) {
294	    add("\\fB")
295	    plain=1
296	  }
297	  add(words[w])
298	}
299	if(!nospace)
300	  add(OFS)
301      }
302      sub(" $","",line)
303      if(plain)
304	add("\\fP")
305    } else if(match(words[w],"^Bl$")) {
306      oldoptlist=optlist
307      if(match(words[w+1],"-bullet"))
308	optlist=1
309      else if(match(words[w+1],"-enum")) {
310	optlist=2
311	enum=0
312      } else if(match(words[w+1],"-tag"))
313	optlist=3
314      else if(match(words[w+1],"-item"))
315	optlist=4
316      else if(match(words[w+1],"-bullet"))
317	optlist=1
318      w=nwords
319    } else if(match(words[w],"^El$")) {
320      optlist=oldoptlist
321    } else if(match(words[w],"^Bk$")) {
322      if(match(words[w+1],"-words")) {
323	w++
324	breakw=1
325      }
326    } else if(match(words[w],"^Ek$")) {
327      breakw=0
328    } else if(match(words[w],"^It$")&&optlist) {
329      if(optlist==1)
330	add(".IP \\(bu")
331      else if(optlist==2)
332	add(".IP " ++enum ".")
333      else if(optlist==3) {
334	add(".TP")
335	prenl++
336	if(match(words[w+1],"^Pa$|^Ev$")) {
337	  add(".B")
338	  w++
339	}
340      } else if(optlist==4)
341	add(".IP")
342    } else if(match(words[w],"^Sm$")) {
343      if(match(words[w+1],"off"))
344	nospace=2
345      else if(match(words[w+1],"on"))
346	nospace=0
347      w++
348    } else if(!skip) {
349      add(words[w])
350    }
351  }
352  if(match(line,"^\\.[^a-zA-Z]"))
353    sub("^\\.","",line)
354  if(parens)
355    add(")")
356  if(angles)
357    add(">")
358  if(option)
359    add("]")
360  if(ext&&!extopt&&!match(line," $"))
361    add(OFS)
362  if(!ext&&!extopt&&length(line)) {
363    print line
364    prenl=0
365    line=""
366  }
367}
368