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  listdepth = 0
24  trailer = ""
25  out = ""
26  sep = ""
27  nextsep = " "
28  spaces = "                    "
29}
30
31# Add a word with appropriate preceding whitespace
32# Maintain a short queue of the expected upcoming word separators.
33function add(str) {
34  out=out sep str
35  sep = nextsep
36  nextsep = " "
37}
38
39# Add a word with no following whitespace
40# Use for opening punctuation such as '('
41function addopen(str) {
42  add(str)
43  sep = ""
44}
45
46# Add a word with no preceding whitespace
47# Use for closing punctuation such as ')' or '.'
48function addclose(str) {
49  sep = ""
50  add(str)
51}
52
53# Add a word with no space before or after
54# Use for separating punctuation such as '='
55function addpunct(str) {
56  sep = ""
57  add(str)
58  sep = ""
59}
60
61# Emit the current line so far
62function endline() {
63  addclose(trailer)
64  trailer = ""
65  if(length(out) > 0) {
66    print out
67    out=""
68  }
69  if(displaylines > 0) {
70    displaylines = displaylines - 1
71    if (displaylines == 0)
72      dispend()
73  }
74  # First word on next line has no preceding whitespace
75  sep = ""
76}
77
78function linecmd(cmd) {
79  endline()
80  add(cmd)
81  endline()
82}
83
84function breakline() {
85  linecmd("<br>")
86}
87
88# Start an indented display
89function dispstart() {
90  linecmd("{{{")
91}
92
93# End an indented display
94function dispend() {
95  linecmd("}}}")
96}
97
98# Collect rest of input line
99function wtail() {
100  retval=""
101  while(w<nwords) {
102    if(length(retval))
103      retval=retval " "
104    retval=retval words[++w]
105  }
106  return retval
107}
108
109function splitwords(l, dest, n, o, w) {
110  n = 1
111  delete dest
112  while (length(l) > 0) {
113    sub("^[ \t]*", "", l)
114    if (match(l, "^\"")) {
115      l = substr(l, 2)
116      o = index(l, "\"")
117      if (o > 0) {
118	w = substr(l, 1, o-1)
119	l = substr(l, o+1)
120	dest[n++] = w
121      } else {
122	dest[n++] = l
123	l = ""
124      }
125    } else {
126      o = match(l, "[ \t]")
127      if (o > 0) {
128	w = substr(l, 1, o-1)
129	l = substr(l, o+1)
130	dest[n++] = w
131      } else {
132	dest[n++] = l
133	l = ""
134      }
135    }
136  }
137  return n-1
138}
139
140! /^\./ {
141  out = $0
142  endline()
143  next
144}
145
146/^\.\\"/ { next }
147
148{
149  sub("^\\.","")
150  nwords=splitwords($0, words)
151  # TODO: Instead of iterating 'w' over the array, have a separate
152  # function that returns 'next word' and use that.  This will allow
153  # proper handling of double-quoted arguments as well.
154  for(w=1;w<=nwords;w++) {
155    if(match(words[w],"^Li$")) { # Literal; rest of line is unformatted
156      dispstart()
157      displaylines = 1
158    } else if(match(words[w],"^Dl$")) { # Display literal
159      dispstart()
160      displaylines = 1
161    } else if(match(words[w],"^Bd$")) { # Begin display
162      if(match(words[w+1],"-literal")) {
163        dispstart()
164	displaylines=10000
165	w=nwords
166      }
167    } else if(match(words[w],"^Ed$")) { # End display
168      displaylines = 0
169      dispend()
170    } else if(match(words[w],"^Ns$")) { # Suppress space before next word
171      sep=""
172    } else if(match(words[w],"^No$")) { # Normal text
173      add(words[++w])
174    } else if(match(words[w],"^Dq$")) { # Quote
175      addopen("\"")
176      add(words[++w])
177      while(w<nwords&&!match(words[w+1],"^[\\.,]"))
178	add(words[++w])
179      addclose("\"")
180    } else if(match(words[w],"^Do$")) {
181      addopen("\"")
182    } else if(match(words[w],"^Dc$")) {
183      addclose("\"")
184    } else if(match(words[w],"^Oo$")) {
185      addopen("`[`")
186    } else if(match(words[w],"^Oc$")) {
187      addclose("`]`")
188    } else if(match(words[w],"^Ao$")) {
189      addopen("`<`")
190    } else if(match(words[w],"^Ac$")) {
191      addclose("`>`")
192    } else if(match(words[w],"^Dd$")) {
193      date=wtail()
194      next
195    } else if(match(words[w],"^Dt$")) {
196      id=wtail()
197      next
198    } else if(match(words[w],"^Ox$")) {
199      add("OpenBSD")
200    } else if(match(words[w],"^Fx$")) {
201      add("FreeBSD")
202    } else if(match(words[w],"^Bx$")) {
203      add("BSD")
204    } else if(match(words[w],"^Nx$")) {
205      add("NetBSD")
206    } else if(match(words[w],"^St$")) {
207      if (match(words[w+1], "^-p1003.1$")) {
208         w++
209         add("IEEE Std 1003.1 (``POSIX.1'')")
210      } else if(match(words[w+1], "^-p1003.1-96$")) {
211         w++
212         add("ISO/IEC 9945-1:1996 (``POSIX.1'')")
213      } else if(match(words[w+1], "^-p1003.1-88$")) {
214         w++
215         add("IEEE Std 1003.1-1988 (``POSIX.1'')")
216      } else if(match(words[w+1], "^-p1003.1-2001$")) {
217         w++
218         add("IEEE Std 1003.1-2001 (``POSIX.1'')")
219      } else if(match(words[w+1], "^-susv2$")) {
220         w++
221         add("Version 2 of the Single UNIX Specification (``SUSv2'')")
222      }
223    } else if(match(words[w],"^Ex$")) {
224      if (match(words[w+1], "^-std$")) {
225         w++
226         add("The *" name "* utility exits 0 on success, and >0 if an error occurs.")
227      }
228    } else if(match(words[w],"^Os$")) {
229      add("#summary " id " manual page")
230    } else if(match(words[w],"^Sh$")) {
231      section=wtail()
232      linecmd("== " section " ==")
233    } else if(match(words[w],"^Xr$")) {
234      add("*" words[++w] "*(" words[++w] ")" words[++w])
235    } else if(match(words[w],"^Nm$")) {
236      if(match(section,"SYNOPSIS"))
237        breakline()
238      if(w >= nwords)
239	n=name
240      else if (match(words[w+1], "^[A-Z][a-z]$"))
241	n=name
242      else if (match(words[w+1], "^[.,;:]$"))
243	n=name
244      else {
245        n=words[++w]
246        if(!length(name))
247          name=n
248      }
249      if(!length(n))
250        n=name
251      if (displaylines == 0)
252	add("*" n "*")
253      else
254	add(n)
255    } else if(match(words[w],"^Nd$")) {
256      add("- " wtail())
257    } else if(match(words[w],"^Fl$")) {
258      if (displaylines == 0)
259	add("*-" words[++w] "*")
260      else
261	add("-" words[++w])
262    } else if(match(words[w],"^Ar$")) {
263      if(w==nwords)
264	add("_file ..._")
265      else {
266	++w
267	gsub("<", "`<`", words[w])
268	add("_" words[w] "_")
269      }
270    } else if(match(words[w],"^Cm$")) {
271      ++w
272      if (displaylines == 0) {
273	gsub("^_", "`_`", words[w])
274	gsub("\\*$", "`*`", words[w])
275	add("*" words[w] "*")
276      } else
277	add(words[w])
278    } else if(match(words[w],"^Op$")) {
279      addopen("`[`")
280      option=1
281      trailer="`]`" trailer
282    } else if(match(words[w],"^Pp$")) {
283      ++w
284      endline()
285      print ""
286    } else if(match(words[w],"^An$")) {
287      if (match(words[w+1],"-nosplit"))
288	++w
289      endline()
290    } else if(match(words[w],"^Ss$")) {
291      add("===")
292      trailer="==="
293    } else if(match(words[w],"^Ft$")) {
294      if (match(section, "SYNOPSIS")) {
295	breakline()
296      }
297      l = wtail()
298      gsub("\\*", "`*`", l)
299
300      add("*" l "*")
301      if (match(section, "SYNOPSIS")) {
302	breakline()
303      }
304    } else if(match(words[w],"^Fn$")) {
305      ++w
306      F = "*" words[w] "*("
307      Fsep = ""
308      while(w<nwords) {
309	++w
310	if (match(words[w], "^[.,:]$")) {
311	  --w
312	  break
313	}
314	gsub("\\*", "`*`", words[w])
315	F = F Fsep "_"  words[w] "_"
316	Fsep = ", "
317      }
318      add(F ")")
319      if (match(section, "SYNOPSIS")) {
320	addclose(";")
321      }
322    } else if(match(words[w],"^Fo$")) {
323      w++
324      F = "*" words[w] "*("
325      Fsep = ""
326    } else if(match(words[w],"^Fa$")) {
327      w++
328      gsub("\\*", "`*`", words[w])
329      F = F Fsep "_"  words[w] "_"
330      Fsep = ", "
331    } else if(match(words[w],"^Fc$")) {
332      add(F ")")
333      if (match(section, "SYNOPSIS")) {
334	addclose(";")
335      }
336    } else if(match(words[w],"^Va$")) {
337      w++
338      add("_" words[w] "_")
339    } else if(match(words[w],"^In$")) {
340      w++
341      add("*#include <" words[w] ">*")
342    } else if(match(words[w],"^Pa$")) {
343      w++
344#      if(match(words[w],"^\\."))
345#	add("\\&")
346      if (displaylines == 0)
347	add("_" words[w] "_")
348      else
349	add(words[w])
350    } else if(match(words[w],"^Dv$")) {
351      linecmd()
352    } else if(match(words[w],"^Em|Ev$")) {
353      add(".IR")
354    } else if(match(words[w],"^Pq$")) {
355      addopen("(")
356      trailer=")" trailer
357    } else if(match(words[w],"^Aq$")) {
358      addopen(" <")
359      trailer=">" trailer
360    } else if(match(words[w],"^Brq$")) {
361      addopen("{")
362      trailer="}" trailer
363    } else if(match(words[w],"^S[xy]$")) {
364      add(".B " wtail())
365    } else if(match(words[w],"^Tn$")) {
366      n=wtail()
367      gsub("\\*$", "`*`", n)
368      add("*" n "*")
369    } else if(match(words[w],"^Ic$")) {
370      add("\\fB")
371      trailer="\\fP" trailer
372    } else if(match(words[w],"^Bl$")) {
373      ++listdepth
374      listnext[listdepth]=""
375      if(match(words[w+1],"-bullet")) {
376	optlist[listdepth]=1
377	addopen("<ul>")
378	listclose[listdepth]="</ul>"
379      } else if(match(words[w+1],"-enum")) {
380	optlist[listdepth]=2
381	enum=0
382	addopen("<ol>")
383	listclose[listdepth]="</ol>"
384      } else if(match(words[w+1],"-tag")) {
385	optlist[listdepth]=3
386	addopen("<dl>")
387	listclose[listdepth]="</dl>"
388      } else if(match(words[w+1],"-item")) {
389	optlist[listdepth]=4
390	addopen("<ul>")
391	listclose[listdepth]="</ul>"
392      }
393      w=nwords
394    } else if(match(words[w],"^El$")) {
395      addclose(listnext[listdepth])
396      addclose(listclose[listdepth])
397      listclose[listdepth]=""
398      listdepth--
399    } else if(match(words[w],"^It$")) {
400      addclose(listnext[listdepth])
401      if(optlist[listdepth]==1) {
402	addpunct("<li>")
403	listnext[listdepth] = "</li>"
404      } else if(optlist[listdepth]==2) {
405	addpunct("<li>")
406	listnext[listdepth] = "</li>"
407      } else if(optlist[listdepth]==3) {
408	addpunct("<dt>")
409	listnext[listdepth] = "</dt>"
410	if(match(words[w+1],"^Xo$")) {
411	  # Suppress trailer
412	  w++
413	} else if(match(words[w+1],"^Pa$|^Ev$")) {
414	  addopen("*")
415	  w++
416	  add(words[++w] "*")
417	} else {
418	  trailer = listnext[listdepth] "<dd>" trailer
419	  listnext[listdepth] = "</dd>"
420	}
421      } else if(optlist[listdepth]==4) {
422	addpunct("<li>")
423	listnext[listdepth] = "</li>"
424      }
425    } else if(match(words[w],"^Xo$")) {
426      # TODO: Figure out how to handle this
427    } else if(match(words[w],"^Xc$")) {
428      # TODO: Figure out how to handle this
429      if (optlist[listdepth] == 3) {
430	addclose(listnext[listdepth])
431	addopen("<dd>")
432	listnext[listdepth] = "</dd>"
433      }
434    } else if(match(words[w],"^[=]$")) {
435      addpunct(words[w])
436    } else if(match(words[w],"^[\[{(]$")) {
437      addopen(words[w])
438    } else if(match(words[w],"^[\\\])}.,;:]$")) {
439      addclose(words[w])
440    } else {
441      sub("\\\\&", "", words[w])
442      add(words[w])
443    }
444  }
445  if(match(out,"^\\.[^a-zA-Z]"))
446    sub("^\\.","",out)
447  endline()
448}
449