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