1#!/usr/bin/awk
2#
3# Copyright (c) 2003 Peter Stuge <stuge-mdoc2man@cdy.org>
4#
5# Permission to use, copy, modify, and distribute this software for any
6# purpose with or without fee is hereby granted, provided that the above
7# copyright notice and this permission notice appear in all copies.
8#
9# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17# Dramatically overhauled by Tim Kientzle.  This version almost
18# handles library-style pages with Fn, Ft, etc commands.  Still
19# a lot of problems...
20
21BEGIN {
22  displaylines = 0
23  trailer = ""
24  out = ""
25  sep = ""
26  nextsep = " "
27}
28
29# Add a word with appropriate preceding whitespace
30# Maintain a short queue of the expected upcoming word separators.
31function add(str) {
32  out=out sep str
33  sep = nextsep
34  nextsep = " "
35}
36
37# Add a word with no following whitespace
38# Use for opening punctuation such as '('
39function addopen(str) {
40  add(str)
41  sep = ""
42}
43
44# Add a word with no preceding whitespace
45# Use for closing punctuation such as ')' or '.'
46function addclose(str) {
47  sep = ""
48  add(str)
49}
50
51# Add a word with no space before or after
52# Use for separating punctuation such as '='
53function addpunct(str) {
54  sep = ""
55  add(str)
56  sep = ""
57}
58
59# Emit the current line so far
60function endline() {
61  addclose(trailer)
62  trailer = ""
63  if(length(out) > 0) {
64    print out
65    out=""
66  }
67  if(displaylines > 0) {
68    displaylines = displaylines - 1
69    if (displaylines == 0)
70      dispend()
71  }
72  # First word on next line has no preceding whitespace
73  sep = ""
74}
75
76function linecmd(cmd) {
77  endline()
78  add(cmd)
79  endline()
80}
81
82function breakline() {
83  linecmd(".br")
84}
85
86# Start an indented display
87function dispstart() {
88  linecmd(".RS 4")
89}
90
91# End an indented display
92function dispend() {
93  linecmd(".RE")
94}
95
96# Collect rest of input line
97function wtail() {
98  retval=""
99  while(w<nwords) {
100    if(length(retval))
101      retval=retval " "
102    retval=retval words[++w]
103  }
104  return retval
105}
106
107function splitwords(l, dest, n, o, w) {
108  n = 1
109  delete dest
110  while (length(l) > 0) {
111    sub("^[ \t]*", "", l)
112    if (match(l, "^\"")) {
113      l = substr(l, 2)
114      o = index(l, "\"")
115      if (o > 0) {
116	w = substr(l, 1, o-1)
117	l = substr(l, o+1)
118	dest[n++] = w
119      } else {
120	dest[n++] = l
121	l = ""
122      }
123    } else {
124      o = match(l, "[ \t]")
125      if (o > 0) {
126	w = substr(l, 1, o-1)
127	l = substr(l, o+1)
128	dest[n++] = w
129      } else {
130	dest[n++] = l
131	l = ""
132      }
133    }
134  }
135  return n-1
136}
137
138! /^\./ {
139  out = $0
140  endline()
141  next
142}
143
144/^\.\\"/ { next }
145
146{
147  sub("^\\.","")
148  nwords=splitwords($0, words)
149  # TODO: Instead of iterating 'w' over the array, have a separate
150  # function that returns 'next word' and use that.  This will allow
151  # proper handling of double-quoted arguments as well.
152  for(w=1;w<=nwords;w++) {
153    if(match(words[w],"^Li$")) { # Literal; rest of line is unformatted
154      dispstart()
155      displaylines = 1
156    } else if(match(words[w],"^Dl$")) { # Display literal
157      dispstart()
158      displaylines = 1
159    } else if(match(words[w],"^Bd$")) { # Begin display
160      if(match(words[w+1],"-literal")) {
161        dispstart()
162	linecmd(".nf")
163	displaylines=10000
164	w=nwords
165      }
166    } else if(match(words[w],"^Ed$")) { # End display
167      displaylines = 0
168      dispend()
169    } else if(match(words[w],"^Ns$")) { # Suppress space after next word
170      nextsep = ""
171    } else if(match(words[w],"^No$")) { # Normal text
172      add(words[++w])
173    } else if(match(words[w],"^Dq$")) { # Quote
174      addopen("``")
175      add(words[++w])
176      while(w<nwords&&!match(words[w+1],"^[\\.,]"))
177	add(words[++w])
178      addclose("''")
179    } else if(match(words[w],"^Do$")) {
180      addopen("``")
181    } else if(match(words[w],"^Dc$")) {
182      addclose("''")
183    } else if(match(words[w],"^Oo$")) {
184      addopen("[")
185    } else if(match(words[w],"^Oc$")) {
186      addclose("]")
187    } else if(match(words[w],"^Ao$")) {
188      addopen("<")
189    } else if(match(words[w],"^Ac$")) {
190      addclose(">")
191    } else if(match(words[w],"^Dd$")) {
192      date=wtail()
193      next
194    } else if(match(words[w],"^Dt$")) {
195      id=wtail()
196      next
197    } else if(match(words[w],"^Ox$")) {
198      add("OpenBSD")
199    } else if(match(words[w],"^Fx$")) {
200      add("FreeBSD")
201    } else if(match(words[w],"^Nx$")) {
202      add("NetBSD")
203    } else if(match(words[w],"^St$")) {
204      if (match(words[w+1], "^-p1003.1$")) {
205         w++
206         add("IEEE Std 1003.1 (``POSIX.1'')")
207      } else if(match(words[w+1], "^-p1003.1-96$")) {
208         w++
209         add("ISO/IEC 9945-1:1996 (``POSIX.1'')")
210      } else if(match(words[w+1], "^-p1003.1-88$")) {
211         w++
212         add("IEEE Std 1003.1-1988 (``POSIX.1'')")
213      } else if(match(words[w+1], "^-p1003.1-2001$")) {
214         w++
215         add("IEEE Std 1003.1-2001 (``POSIX.1'')")
216      } else if(match(words[w+1], "^-susv2$")) {
217         w++
218         add("Version 2 of the Single UNIX Specification (``SUSv2'')")
219      }
220    } else if(match(words[w],"^Ex$")) {
221      if (match(words[w+1], "^-std$")) {
222         w++
223         add("The \\fB" name "\\fP utility exits 0 on success, and >0 if an error occurs.")
224      }
225    } else if(match(words[w],"^Os$")) {
226      add(".TH " id " \"" date "\" \"" wtail() "\"")
227    } else if(match(words[w],"^Sh$")) {
228      section=wtail()
229      add(".SH " section)
230      linecmd(".ad l")
231    } else if(match(words[w],"^Xr$")) {
232      add("\\fB" words[++w] "\\fP(" words[++w] ")" words[++w])
233    } else if(match(words[w],"^Nm$")) {
234      if(match(section,"SYNOPSIS"))
235        breakline()
236      if(w >= nwords)
237	n=name
238      else if (match(words[w+1], "^[A-Z][a-z]$"))
239	n=name
240      else if (match(words[w+1], "^[.,;:]$"))
241	n=name
242      else {
243        n=words[++w]
244        if(!length(name))
245          name=n
246      }
247      if(!length(n))
248        n=name
249      add("\\fB\\%" n "\\fP")
250    } else if(match(words[w],"^Nd$")) {
251      add("\\- " wtail())
252    } else if(match(words[w],"^Fl$")) {
253      add("\\fB\\-" words[++w] "\\fP")
254    } else if(match(words[w],"^Ar$")) {
255      addopen("\\fI")
256      if(w==nwords)
257	add("file ...\\fP")
258      else
259	add(words[++w] "\\fP")
260    } else if(match(words[w],"^Cm$")) {
261      add("\\fB" words[++w] "\\fP")
262    } else if(match(words[w],"^Op$")) {
263      addopen("[")
264      option=1
265      trailer="]" trailer
266    } else if(match(words[w],"^Pp$")) {
267      linecmd(".PP")
268    } else if(match(words[w],"^An$")) {
269      endline()
270    } else if(match(words[w],"^Ss$")) {
271      add(".SS")
272    } else if(match(words[w],"^Ft$")) {
273      if (match(section, "SYNOPSIS")) {
274	breakline()
275      }
276      add("\\fI" wtail() "\\fP")
277      if (match(section, "SYNOPSIS")) {
278	breakline()
279      }
280    } else if(match(words[w],"^Fn$")) {
281      ++w
282      F = "\\fB\\%" words[w] "\\fP("
283      Fsep = ""
284      while(w<nwords) {
285	++w
286	if (match(words[w], "^[.,:]$")) {
287	  --w
288	  break
289	}
290	gsub(" ", "\\ ", words[w])
291	F = F Fsep "\\fI\\%"  words[w] "\\fP"
292	Fsep = ", "
293      }
294      add(F ")")
295      if (match(section, "SYNOPSIS")) {
296	addclose(";")
297      }
298    } else if(match(words[w],"^Fo$")) {
299      w++
300      F = "\\fB\\%" words[w] "\\fP("
301      Fsep = ""
302    } else if(match(words[w],"^Fa$")) {
303      w++
304      gsub(" ", "\\ ", words[w])
305      F = F Fsep "\\fI\\%"  words[w] "\\fP"
306      Fsep = ", "
307    } else if(match(words[w],"^Fc$")) {
308      add(F ")")
309      if (match(section, "SYNOPSIS")) {
310	addclose(";")
311      }
312    } else if(match(words[w],"^Va$")) {
313      w++
314      add("\\fI" words[w] "\\fP")
315    } else if(match(words[w],"^In$")) {
316      w++
317      add("\\fB#include <" words[w] ">\\fP")
318    } else if(match(words[w],"^Pa$")) {
319      addopen("\\fI")
320      w++
321      if(match(words[w],"^\\."))
322	add("\\&")
323      add(words[w] "\\fP")
324    } else if(match(words[w],"^Dv$")) {
325      add(".BR")
326    } else if(match(words[w],"^Em|Ev$")) {
327      add(".IR")
328    } else if(match(words[w],"^Pq$")) {
329      addopen("(")
330      trailer=")" trailer
331    } else if(match(words[w],"^Aq$")) {
332      addopen("\\%<")
333      trailer=">" trailer
334    } else if(match(words[w],"^Brq$")) {
335      addopen("{")
336      trailer="}" trailer
337    } else if(match(words[w],"^S[xy]$")) {
338      add(".B " wtail())
339    } else if(match(words[w],"^Ic$")) {
340      add("\\fB")
341      trailer="\\fP" trailer
342    } else if(match(words[w],"^Bl$")) {
343      oldoptlist=optlist
344      linecmd(".RS 5")
345      if(match(words[w+1],"-bullet"))
346	optlist=1
347      else if(match(words[w+1],"-enum")) {
348	optlist=2
349	enum=0
350      } else if(match(words[w+1],"-tag"))
351	optlist=3
352      else if(match(words[w+1],"-item"))
353	optlist=4
354      else if(match(words[w+1],"-bullet"))
355	optlist=1
356      w=nwords
357    } else if(match(words[w],"^El$")) {
358      linecmd(".RE")
359      optlist=oldoptlist
360    } else if(match(words[w],"^It$")&&optlist) {
361      if(optlist==1)
362	add(".IP \\(bu")
363      else if(optlist==2)
364	add(".IP " ++enum ".")
365      else if(optlist==3) {
366	add(".TP")
367        endline()
368	if(match(words[w+1],"^Pa$|^Ev$")) {
369	  add(".B")
370	  w++
371	}
372      } else if(optlist==4)
373	add(".IP")
374    } else if(match(words[w],"^Xo$")) {
375      # TODO: Figure out how to handle this
376    } else if(match(words[w],"^Xc$")) {
377      # TODO: Figure out how to handle this
378    } else if(match(words[w],"^[=]$")) {
379      addpunct(words[w])
380    } else if(match(words[w],"^[\[{(]$")) {
381      addopen(words[w])
382    } else if(match(words[w],"^[\\\])}.,;:]$")) {
383      addclose(words[w])
384    } else {
385      add(words[w])
386    }
387  }
388  if(match(out,"^\\.[^a-zA-Z]"))
389    sub("^\\.","",out)
390  endline()
391}
392