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