manpp: add support for mdoc .Ql directive
[rofl0r-hardcore-utils.git] / manpp
blob461a48fdeb99f817106f964fc493d41bdc34229d
1 #!/usr/bin/awk -f
2 function indent() {
3 if(close_indent) {
4 print ".RE"
5 close_indent = 0
6 } else {
7 print ".RS"
8 close_indent = 1
11 function make_date(d) {
12 gsub("[ \t,]+", "-", d)
13 return d;
16 function trigger_header(hdate, hsect, htitle) {
17 if(hdate) date = make_date(hdate)
18 if(hsect) sect = hsect
19 if(htitle) title = htitle
20 if(sect && date && title)
21 print ".TH", title, sect, date
23 # turns something like aa "b c" d "e f g"
24 # into a 0-indexed array with the members(aa, b c, d, e f g)
25 # returns length of array
26 function unquotewords(wordsstr, ao, l,a,out,inpara,start,i) {
27 l = split(wordsstr, a, "")
28 start = 1
29 out = 0
30 inpara = 0
31 for(i = 1; i<=l; i++) {
32 if(a[i] == " ") {
33 if(start == i) print "ERR"
34 if(!inpara) {
35 ao[out++] = substr(wordsstr, start, i-start);
36 start = i + 1
38 } else if(a[i] == "\"") {
39 if(inpara) {
40 ao[out++] = substr(wordsstr, start, i-start);
41 start = i + 2
42 i++
43 } else {
44 if(start != i) print "err"
45 start++;
47 inpara = !inpara;
50 if(i > start) ao[out++] = substr(wordsstr, start, i-start);
51 return out;
54 function arr2str(arr, start, len, i,str) {
55 for(i=start;i<=len;i++) if(i in arr) str=str arr[i] " ";
56 return str
59 function cfunc(str, i,l,a,out,restore) {
60 l = unquotewords(str, a)
61 # first array index, 0, contains macro name still so we skip it
62 #print arr2str(a, 0, l)
63 if(a[l-1] == ",") {
64 restore = a[l-1];
65 l--;
67 for(i = 0; i < l; i++) {
68 out = out a[i]
69 if(i == 0) out = out " "
70 else if(i == 1) out = out "("
71 if (i == l - 1) out = out ")"
72 else if(i > 1) out = out ", "
74 if(restore) out = out restore
75 return out;
77 function brify(str,appendnl, temp) {
78 str = ".BR \"" substr(str, index(str, " ")+1) "\""
79 if(appendnl) str = str "\n"
80 return str
82 function italify(str,appendnl, temp) {
83 str = ".I \"" substr(str, index(str, " ")+1) "\""
84 if(appendnl) str = str "\n"
85 return str
87 function fields(start, last, buf,i,sp) {
88 buf = ""
89 for(i=start;i<=last;i++) {
90 sp = i+1<=last ? " " : ""
91 buf = buf $i sp
93 return buf
95 function update_var(var_name, complete_line) {
96 if(!current_var) {
97 current_var = var_name
98 groff_vars[current_var] = substr(complete_line, 4+length(var_name)+2)
99 } else {
100 groff_vars[current_var] = groff_vars[current_var] complete_line
102 if(groff_vars[current_var] ~ /\\$/) {
103 sub(/\\$/, "\n", groff_vars[current_var])
104 } else {
105 current_var = ""
109 function replace_vars(line, var, p, c, start, len) {
110 # match(s,r) test whether s contains a substring matched by r
111 # return index or 0, sets RSTART and RLENGTH
112 while((p = match(line, /\\\*([\(\[]{0,1}[A-Za-z0-9_]+)/))) {
113 c = substr(line, RSTART+2, 1)
114 if(c == "[" || c == "(") {
115 start = 1
116 if(c == "(") len = 2
117 else len = RLENGTH-3
118 } else {
119 start = 0
120 len = 1
122 var = substr(line, RSTART+2+start, len)
123 len += start
124 if(c == "[") {
125 if(substr(line, RSTART+2+len, 1) != "]")
126 print "ERROR: expected ']'"
127 len++
129 if(!var in groff_vars) print "ERROR: $var not in groff_vars"
130 if(groff_vars[var] ~ /\\\*/) {
131 print "ERROR: groff variable recursion"
132 exit 1
134 line = substr(line, 0, RSTART-1) groff_vars[var] substr(line, RSTART+2+len)
136 return line
138 function groff_escape(line, nr, repl, p) {
139 # replace \N'34' with "
140 while((p = match(line, /\\N\'([0-9]+)\'/))) {
141 nr = 0+substr(line, RSTART+3, RLENGTH-4)
142 if(nr < 32 || nr > 127) {
143 print("ERROR: escapes < 32 || > 127 not implemented");
144 repl = " "
145 } else if(nr == 34) {
146 repl = "\\\""
147 } else
148 repl = sprintf("%c", nr)
149 line = substr(line, 0, RSTART-1) repl substr(line, RSTART+RLENGTH)
151 return line
153 function shift(n, k) {
154 while (n > 0) {
155 k += length($n) + length(FS)
158 $0 = substr($0, k + 1)
160 function print_error(lineno, text) {
161 print "ERROR: @" lineno ": " text
163 BEGIN {
164 current_var = ""
165 line_no = 0
169 line_no = line_no + 1
170 if($1 ~ /^\./ && close_indent) {
171 #indent()
174 if(!current_var) {
175 if($1 != ".ds") {
176 $0 = groff_escape(replace_vars($0))
177 sub(/^\.[ ]+/, ".")
179 if(!in_macro) {
180 if($1 == ".ie" && substr($0, length($0) - 2) != "\\{\\") {
181 if($2 == "n" || $2 == "!t" || $2 == "e" || $2 == "!o") {
182 ie_taken = 1
183 $0 = substr($0, length($1 FS $2 FS)+1)
184 } else {
185 ie_taken = 0
186 $0 = ""
188 } else if($1 == ".el" && substr($0, length($0) - 2) != "\\{\\") {
189 if(ie_taken) $0 = "";
190 else $0 = substr($0, length($1 FS)+1)
191 ie_taken = 0
192 } else if($1 == ".if" && substr($0, length($0) - 2) != "\\{\\") {
193 if($2 == "n" || $2 == "!t" || $2 == "e" || $2 == "!o")
194 $0 = substr($0, length($1 FS $2 FS)+1);
195 else
196 $0 = ""
201 if(0) ;
202 else if(current_var) {
203 #process multiline groff string vars.
204 update_var(current_var, $0)
205 $0 = ""
206 } else if(ignore_until != "") {
207 if($0 == ignore_until)
208 ignore_until = ""
209 $0 = ""
210 # troff macro
211 # https://www.lemoda.net/unix/troff-dictionary/index.html
212 } else if(in_macro && $1 != "..") {
213 $0 = ""
214 } else if(in_macro && $1 == "..") {
215 $0 = ""
216 in_macro = 0
217 } else if(!in_macro && $1 == "..") {
218 print_error(line_no, ".. without .de")
219 } else if(!in_macro && $1 == ".de") {
220 in_macro = 1
221 $0 = ""
222 # troff conditional
223 } else if(multi_line_if) {
224 if(substr($0, length($0) - 1) == "\\}")
225 multi_line_if = multi_line_if - 1
226 $0 = ""
227 } else if($1 == ".if" || $1 == ".ie" || $1 == ".el\\{\\" ||
228 $1 == ".el" || $1 == "\\{\\") {
229 if(substr($0, length($0) - 2) == "\\{\\")
230 multi_line_if = multi_line_if + 1
231 $0 = ""
232 # mdoc commands
233 # http://web.archive.org/web/20140327172811/http://mdocml.bsd.lv/mdoc.7.html
234 } else if($1 == ".Bl") {
235 $0 = ""
236 $0 = ".sp\n.RS\n.nf\n"
237 $0 = ".sp\n.RS\n"
238 blockmode = 1
239 } else if ($1 == ".El") {
240 $0 = ""
241 $0 = ".fi\n.RE"
242 $0 = ".RE"
243 blockmode = 0
244 #indent()
245 } else if($1 == ".Bd") {
246 # code block - dump directly until we hit .Ed
247 $0 = ".sp\n.RS\n.nf\n\\fB"
248 } else if($1 == ".Ed") {
249 $0 = "\\fP\n.fi\n.RE"
250 } else if($1 == ".Ex") {
251 locname = NF==3?$3:name
252 $0 = "The \n.BR " locname "\nutility exits 0 on success, and >0 if an error occurs.\n"
253 } else if($1 == ".Dd") {
254 $0 = substr($0, 5)
255 trigger_header($0, "", "")
256 $0 = ""
257 } else if($1 == ".Dt") {
258 trigger_header("", $3, $2)
259 $0 = ""
260 } else if($1 == ".Dq" || $1 == ".Ql") {
261 if ($NF == "." || $NF == ",") {
262 safe = $NF;
263 $0 = substr($0, 0, length($0) - 2)
264 } else safe = ""
265 $0 = "“" substr($0, 5) "”" safe
266 } else if($1 == ".Dv") {
267 sub(/^\.Dv /, "")
268 } else if($1 == ".Xr") {
269 $0 = $2 "(" $3 ")"
270 } else if($1 == ".An") {
271 if($2 ~ "^-") $0 = ""
272 else $0 = substr($0, 5)
273 } else if($1 == ".In") {
274 $0 = "\n.BR \"#include <" $2 ">\"\n"
275 } else if($1 == ".Ft") {
276 $1 = ".I"
277 } else if($1 == ".Fn") {
278 $0 = brify(cfunc($0), $NF != ",")
279 } else if($1 == ".Fo") {
280 fnblock = $0
281 $0 = ""
282 } else if($1 == ".Fa") {
283 if(fnblock != "") {
284 fnblock = fnblock " " substr($0, 5)
285 $0 = ""
286 } else {
287 l = unquotewords(substr($0, 5), foo)
288 $0 = ".XX "
289 for(i=0;i<l;i++) {
290 sp = i+1<l?", ":"";
291 $0 = $0 foo[i] sp
293 $0 = italify($0, 0)
295 } else if($1 == ".Fc") {
296 $0 = brify(cfunc(fnblock), 1)
297 fnblock = ""
298 } else if($1 == ".Cm") {
299 out = ".Cm "
300 for(i = 2; i <= NF; i+=2)
301 out = out $i " "
302 $0 = brify(out, 0)
303 } else if($1 == ".Ic") {
304 $0 = brify($0, 0)
305 } else if($1 == ".Tn" || $1 == ".Va" || $1 == ".Fx") {
306 $0 = substr($0, 5)
307 } else if($1 == ".Po") {
308 $1 = "("
309 } else if($1 == ".Pc") {
310 $1 = ")"
311 } else if($1 == ".Pq") {
312 out = ""
313 for(i = 2; i <= NF; i++)
314 if(!(length($i) == 2 && $i ~ /[A-Z][a-z]/))
315 out = out $i " "
316 if(substr(out, length(out)) == " ")
317 out = substr(out, 0, length(out) -1)
318 $0 = "(“" out "”)"
319 } else if($1 == ".Pf") {
320 $0 = $2 $4
321 } else if($1 == ".Pp") {
322 $0 = ".sp\n"
323 } else if($1 == ".Sm") {
324 spacing = $2=="on"?1:0 #unused
325 $0 = ""
326 } else if($1 == ".Oo" || $1 == ".Oc") {
327 # denotes .Op block start/end - ignore
328 $0 = ""
329 } else if($1 == ".It" || $1 == ".Op") {
330 if(blockmode && $1 == ".It" && $2 != "Pa") print "\n"
331 #if(blockmode) indent_after=1
332 out = $1 == ".Op" ? "[" : "";
333 for(i = 2; i <= NF; i++) {
334 if(i == 2 && $i == "Fn" && $1 == ".It") {
335 print ".sp"
336 out = ".Fn " substr($0, 8)
337 out = brify(cfunc(out),1)
338 indent_after = 1
339 break;
340 } else if (i == 2 && $i == "Pa" && $1 == ".It") {
341 sub(/^\.It Pa/, ".I")
342 out = $0 "\n.br"
343 break
344 } else if (i == 2 && $i == "Ev" && $1 == ".It") {
345 # ignore
346 } else if (i == 2 && $i == "Xo") {
347 out = ""
348 break
349 } else if($i == "Fl" || $i == "Ar" || $i == "Cm") {
350 if($i == "Fl") str = "BR -"
351 else if($i == "Ar") str = "I "
352 else str = "BR "
353 i++;
354 if(out == "") { nl = "" } else { nl = "\n" }
355 out = out nl "." str $i;
356 if($i == "Fl") while($(i+1) == "|") {
357 out = out " | -" $(i+2)
358 i += 2;
360 } else {
361 sp = i+1<=NF?" ":""
362 out = out $i sp;
365 if($1 == ".Op") out = out "\n]"
366 if(blockmode && $1 == ".It") out = out "\n"
367 $0 = out;
368 } else if($1 == ".Ar") {
369 $1 = ".I"
370 $(NF+1) = "\n.br"
371 } else if($1 == ".Fl") {
372 #if(blockmode) indent()
373 $1 = ".BR"
374 for(i = 2; i <= NF; i++) {
375 $i = "-" $i
377 } else if($1 == ".Nd") {
378 $1 = "-"
379 } else if($1 == ".Nm") {
380 if(name == "" && NF == 2) {
381 name = $2
382 $0 = ".Nm"
384 sep = NF > 1 ? "\n" : ""
385 $0 = ".BR \"" name "\"" sep fields(2, NF)
386 } else if($1 == ".At") {
387 $0 = "AT&T UNIX"
388 } else if($1 == ".Bx") {
389 $0 = "BSD"
390 } else if($1 == ".Nx") {
391 $0 = "NetBSD"
392 } else if($1 == ".Ux") {
393 $0 = "UNIX"
394 } else if($1 == ".St") {
395 if(0) ;
396 else if($2 == "-p1003.1-88")
397 $0 = "IEEE Std 1003.1-1988 (“POSIX.1”)"
398 else if($2 == "-p1003.1-90")
399 $0 = "IEEE Std 1003.1-1990 (“POSIX.1”)"
400 else if($2 == "-p1003.1-96")
401 $0 = "ISO/IEC 9945-1:1996 (“POSIX.1”)"
402 else if($2 == "-p1003.1-2001")
403 $0 = "IEEE Std 1003.1-2001 (“POSIX.1”)"
404 else if($2 == "-p1003.1-2004")
405 $0 = "IEEE Std 1003.1-2004 (“POSIX.1”)"
406 else if($2 == "-p1003.1-2008")
407 $0 = "IEEE Std 1003.1-2008 (“POSIX.1”)"
408 else if($2 == "-p1003.1")
409 $0 = "IEEE Std 1003.1 (“POSIX.1”)"
410 else if($2 == "-p1003.1b")
411 $0 = "IEEE Std 1003.1b (“POSIX.1”)"
412 else if($2 == "-p1003.1b-93")
413 $0 = "IEEE Std 1003.1b-1993 (“POSIX.1”)"
414 else if($2 == "-p1003.1c-95")
415 $0 = "IEEE Std 1003.1c-1995 (“POSIX.1”)"
416 else if($2 == "-p1003.1g-2000")
417 $0 = "IEEE Std 1003.1g-2000 (“POSIX.1”)"
418 else if($2 == "-p1003.1i-95")
419 $0 = "IEEE Std 1003.1i-1995 (“POSIX.1”)"
420 else if($2 == "-p1003.2-92")
421 $0 = "IEEE Std 1003.2-1992 (“POSIX.2”)"
422 else if($2 == "-p1003.2a-92")
423 $0 = "IEEE Std 1003.2a-1992 (“POSIX.2”)"
424 else if($2 == "-p1387.2-95")
425 $0 = "IEEE Std 1387.2-1995 (“POSIX.7.2”)"
426 else if($2 == "-p1003.2")
427 $0 = "IEEE Std 1003.2 (“POSIX.2”)"
428 else if($2 == "-p1387.2")
429 $0 = "IEEE Std 1387.2 (“POSIX.7.2”)"
430 else if($2 == "-isoC")
431 $0 = "ISO/IEC 9899:1990 (“ISO C90”)"
432 else if($2 == "-isoC-90")
433 $0 = "ISO/IEC 9899:1990 (“ISO C90”)"
434 else if($2 == "-isoC-amd1")
435 $0 = "ISO/IEC 9899/AMD1:1995 (“ISO C90, Amendment 1”)"
436 else if($2 == "-isoC-tcor1")
437 $0 = "ISO/IEC 9899/TCOR1:1994 (“ISO C90, Technical Corrigendum 1”)"
438 else if($2 == "-isoC-tcor2")
439 $0 = "ISO/IEC 9899/TCOR2:1995 (“ISO C90, Technical Corrigendum 2”)"
440 else if($2 == "-isoC-99")
441 $0 = "ISO/IEC 9899:1999 (“ISO C99”)"
442 else if($2 == "-isoC-2011")
443 $0 = "ISO/IEC 9899:2011 (“ISO C11”)"
444 else if($2 == "-iso9945-1-90")
445 $0 = "ISO/IEC 9945-1:1990 (“POSIX.1”)"
446 else if($2 == "-iso9945-1-96")
447 $0 = "ISO/IEC 9945-1:1996 (“POSIX.1”)"
448 else if($2 == "-iso9945-2-93")
449 $0 = "ISO/IEC 9945-2:1993 (“POSIX.2”)"
450 else if($2 == "-ansiC")
451 $0 = "ANSI X3.159-1989 (“ANSI C89”)"
452 else if($2 == "-ansiC-89")
453 $0 = "ANSI X3.159-1989 (“ANSI C89”)"
454 else if($2 == "-ansiC-99")
455 $0 = "ANSI/ISO/IEC 9899-1999 (“ANSI C99”)"
456 else if($2 == "-ieee754")
457 $0 = "IEEE Std 754-1985"
458 else if($2 == "-iso8802-3")
459 $0 = "ISO 8802-3: 1989"
460 else if($2 == "-iso8601")
461 $0 = "ISO 8601"
462 else if($2 == "-ieee1275-94")
463 $0 = "IEEE Std 1275-1994 (“Open Firmware”)"
464 else if($2 == "-xpg3")
465 $0 = "X/Open Portability Guide Issue 3 (“XPG3”)"
466 else if($2 == "-xpg4")
467 $0 = "X/Open Portability Guide Issue 4 (“XPG4”)"
468 else if($2 == "-xpg4.2")
469 $0 = "X/Open Portability Guide Issue 4, Version 2 (“XPG4.2”)"
470 else if($2 == "-xpg4.3")
471 $0 = "X/Open Portability Guide Issue 4, Version 3 (“XPG4.3”)"
472 else if($2 == "-xbd5")
473 $0 = "X/Open Base Definitions Issue 5 (“XBD5”)"
474 else if($2 == "-xcu5")
475 $0 = "X/Open Commands and Utilities Issue 5 (“XCU5”)"
476 else if($2 == "-xsh5")
477 $0 = "X/Open System Interfaces and Headers Issue 5 (“XSH5”)"
478 else if($2 == "-xns5")
479 $0 = "X/Open Networking Services Issue 5 (“XNS5”)"
480 else if($2 == "-xns5.2")
481 $0 = "X/Open Networking Services Issue 5.2 (“XNS5.2”)"
482 else if($2 == "-xns5.2d2.0")
483 $0 = "X/Open Networking Services Issue 5.2 Draft 2.0 (“XNS5.2D2.0”)"
484 else if($2 == "-xcurses4.2")
485 $0 = "X/Open Curses Issue 4, Version 2 (“XCURSES4.2”)"
486 else if($2 == "-susv2")
487 $0 = "Version 2 of the Single UNIX Specification"
488 else if($2 == "-susv3")
489 $0 = "Version 3 of the Single UNIX Specification"
490 else if($2 == "-svid4")
491 $0 = "System V Interface Definition, Fourth Edition (“SVID4”)"
492 } else if($1 == ".Pa" || $1 == ".Em") {
493 $0 = italify($0, 0)
494 } else if($1 == ".Bk" || $1 == ".Ek" || $1 == ".Xc" || $1 == ".Os") {
495 # comments go here
496 $0 = "\n"
497 } else if($1 == ".D1" || $1 == ".Dl") {
498 $1 = "\t"
499 } else if($1 == ".Ev") {
500 $0 = brify($0, 0)
501 } else if($1 == ".Aq") {
502 cut=4
503 if(NF > 2 && length($2)==2) cut+=3
504 $0 = substr($0, cut)
505 $1 = "<" $1 ">"
506 } else if($1 == ".Qq") {
507 $0 = "\"" $2 "\"" fields(3, NF)
508 } else if($1 == ".Sq") {
509 $0 = "'" $2 "'" fields(3, NF)
510 } else if($1 == ".Sx" || $1 == ".Sy") {
511 $0 = brify($0, 0)
512 } else if($1 == ".Sh") {
513 $1 = ".SH"
514 } else if($1 == ".Ss") {
515 $1 = ".SS"
516 # xman stuff starts here
517 } else if($1 == ".ZN") {
518 $0 = italify($0, 0)
519 # groff stuff starts here
520 # http://web.cecs.pdx.edu/~trent/gnu/groff/groff.html
521 # lowercase stuff supported by man.c:
522 # nh hy nf fi sp br bp ad na ta
523 } else if($1 == ".ig") {
524 if($2 != "")
525 ignore_until = $2
526 else
527 ignore_until = ".."
528 $0 = 0
529 } else if($1 == ".ds") { #set a string variable.
530 update_var($2, $0)
531 $0 = ""
532 } else if($1 == ".UR") {
533 url = $2
534 gsub(":", "", url)
535 sub("//", "://", url)
536 $0 = ""
537 } else if($1 == ".UE") {
538 shift(1)
539 $0 = "<" url ">" $0
540 url = ""
541 } else if($1 == ".ds" ||
542 $1 == ".ce" ||
543 $1 == ".ll" ||
544 $1 == ".in" ||
545 $1 == "." ||
546 $1 == "..." ||
547 $1 == ".\\\\$" ||
548 $1 == ".\\\"" ||
549 $1 == ".ft" ||
550 $1 == ".ps" ||
551 $1 == ".ny0" ||
552 $1 == ".nr" ||
553 $1 == ".ns" ||
554 $1 == ".ne" ||
555 $1 == ".rr" || #remove number register ident
556 0) { #other groff junk
557 $0 = ""
558 } else if($1 == ".KS" ||
559 $1 == ".DE" ||
560 $1 == ".TA" ||
561 $1 == ".TB" ||
562 $1 == ".KE" ||
563 $1 == ".D" ||
564 $1 == ".R" ||
565 $1 == ".Sp" ||
567 0) { #other unknown stuff
568 $0 = ""
569 # perl stuff starts here
570 # http://www.opensource.apple.com/source/perl/perl-24.1/perl/lib/Pod/Man.pm
571 } else if($1 == ".Vb") { #perl verbose section starts
572 $0 = ".sp\n.RS\n.nf\n"
573 } else if($1 == ".Ve") { #perl verbose section ends
574 $0 = ".fi\n.RE\n"
575 } else if($1 == ".tr" ||
576 $1 == ".el\\" ||
577 $1 == ".rm" ||
578 $1 == ".\\}" ||
579 0) { #other perl junk
580 $0 = 0
582 if($0) print;
583 if(indent_after) {
584 #indent()
585 indent_after = 0