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