1implement Man2html; 2 3include "sys.m"; 4 stderr: ref Sys->FD; 5 sys: Sys; 6 print, fprint, sprint: import sys; 7 8 9include "bufio.m"; 10 11include "draw.m"; 12 13include "daytime.m"; 14 dt: Daytime; 15 16include "string.m"; 17 str: String; 18 19include "arg.m"; 20 21Man2html: module 22{ 23 init: fn(ctxt: ref Draw->Context, args: list of string); 24}; 25 26Runeself: con 16r80; 27false, true: con iota; 28 29Troffspec: adt { 30 name: string; 31 value: string; 32}; 33 34tspec := array [] of { Troffspec 35 ("ff", "ff"), 36 ("fi", "fi"), 37 ("fl", "fl"), 38 ("Fi", "ffi"), 39 ("ru", "_"), 40 ("em", "—"), 41 ("14", "¼"), 42 ("12", "½"), 43 ("co", "©"), 44 ("de", "°"), 45 ("dg", "¡"), 46 ("fm", "´"), 47 ("rg", "®"), 48# ("bu", "*"), 49 ("bu", "•"), 50 ("sq", "¤"), 51 ("hy", "-"), 52 ("pl", "+"), 53 ("mi", "-"), 54 ("mu", "×"), 55 ("di", "÷"), 56 ("eq", "="), 57 ("==", "=="), 58 (">=", ">="), 59 ("<=", "<="), 60 ("!=", "!="), 61 ("+-", "±"), 62 ("no", "¬"), 63 ("sl", "/"), 64 ("ap", "&"), 65 ("~=", "~="), 66 ("pt", "oc"), 67 ("gr", "GRAD"), 68 ("->", "->"), 69 ("<-", "<-"), 70 ("ua", "^"), 71 ("da", "v"), 72 ("is", "Integral"), 73 ("pd", "DIV"), 74 ("if", "oo"), 75 ("sr", "-/"), 76 ("sb", "(~"), 77 ("sp", "~)"), 78 ("cu", "U"), 79 ("ca", "(^)"), 80 ("ib", "(="), 81 ("ip", "=)"), 82 ("mo", "C"), 83 ("es", "Ø"), 84 ("aa", "´"), 85 ("ga", "`"), 86 ("ci", "O"), 87 ("L1", "Lucent"), 88 ("sc", "§"), 89 ("dd", "++"), 90 ("lh", "<="), 91 ("rh", "=>"), 92 ("lt", "("), 93 ("rt", ")"), 94 ("lc", "|"), 95 ("rc", "|"), 96 ("lb", "("), 97 ("rb", ")"), 98 ("lf", "|"), 99 ("rf", "|"), 100 ("lk", "|"), 101 ("rk", "|"), 102 ("bv", "|"), 103 ("ts", "s"), 104 ("br", "|"), 105 ("or", "|"), 106 ("ul", "_"), 107 ("rn", " "), 108 ("*p", "PI"), 109 ("**", "*"), 110}; 111 112 Entity: adt { 113 name: string; 114 value: int; 115 }; 116 Entities: array of Entity; 117 118Entities = array[] of { 119 Entity( "¡", '¡' ), 120 Entity( "¢", '¢' ), 121 Entity( "£", '£' ), 122 Entity( "¤", '¤' ), 123 Entity( "¥", '¥' ), 124 Entity( "¦", '¦' ), 125 Entity( "§", '§' ), 126 Entity( "¨", '¨' ), 127 Entity( "©", '©' ), 128 Entity( "ª", 'ª' ), 129 Entity( "«", '«' ), 130 Entity( "¬", '¬' ), 131 Entity( "­", '' ), 132 Entity( "®", '®' ), 133 Entity( "¯", '¯' ), 134 Entity( "°", '°' ), 135 Entity( "±", '±' ), 136 Entity( "²", '²' ), 137 Entity( "³", '³' ), 138 Entity( "´", '´' ), 139 Entity( "µ", 'µ' ), 140 Entity( "¶", '¶' ), 141 Entity( "·", '·' ), 142 Entity( "¸", '¸' ), 143 Entity( "¹", '¹' ), 144 Entity( "º", 'º' ), 145 Entity( "»", '»' ), 146 Entity( "¼", '¼' ), 147 Entity( "½", '½' ), 148 Entity( "¾", '¾' ), 149 Entity( "¿", '¿' ), 150 Entity( "À", 'À' ), 151 Entity( "Á", 'Á' ), 152 Entity( "Â", 'Â' ), 153 Entity( "Ã", 'Ã' ), 154 Entity( "Ä", 'Ä' ), 155 Entity( "Å", 'Å' ), 156 Entity( "Æ", 'Æ' ), 157 Entity( "Ç", 'Ç' ), 158 Entity( "È", 'È' ), 159 Entity( "É", 'É' ), 160 Entity( "Ê", 'Ê' ), 161 Entity( "Ë", 'Ë' ), 162 Entity( "Ì", 'Ì' ), 163 Entity( "Í", 'Í' ), 164 Entity( "Î", 'Î' ), 165 Entity( "Ï", 'Ï' ), 166 Entity( "Ð", 'Ð' ), 167 Entity( "Ñ", 'Ñ' ), 168 Entity( "Ò", 'Ò' ), 169 Entity( "Ó", 'Ó' ), 170 Entity( "Ô", 'Ô' ), 171 Entity( "Õ", 'Õ' ), 172 Entity( "Ö", 'Ö' ), 173 Entity( "&215;", '×' ), 174 Entity( "Ø", 'Ø' ), 175 Entity( "Ù", 'Ù' ), 176 Entity( "Ú", 'Ú' ), 177 Entity( "Û", 'Û' ), 178 Entity( "Ü", 'Ü' ), 179 Entity( "Ý", 'Ý' ), 180 Entity( "Þ", 'Þ' ), 181 Entity( "ß", 'ß' ), 182 Entity( "à", 'à' ), 183 Entity( "á", 'á' ), 184 Entity( "â", 'â' ), 185 Entity( "ã", 'ã' ), 186 Entity( "ä", 'ä' ), 187 Entity( "å", 'å' ), 188 Entity( "æ", 'æ' ), 189 Entity( "ç", 'ç' ), 190 Entity( "è", 'è' ), 191 Entity( "é", 'é' ), 192 Entity( "ê", 'ê' ), 193 Entity( "ë", 'ë' ), 194 Entity( "ì", 'ì' ), 195 Entity( "í", 'í' ), 196 Entity( "î", 'î' ), 197 Entity( "ï", 'ï' ), 198 Entity( "ð", 'ð' ), 199 Entity( "ñ", 'ñ' ), 200 Entity( "ò", 'ò' ), 201 Entity( "ó", 'ó' ), 202 Entity( "ô", 'ô' ), 203 Entity( "õ", 'õ' ), 204 Entity( "ö", 'ö' ), 205 Entity( "&247;", '÷' ), 206 Entity( "ø", 'ø' ), 207 Entity( "ù", 'ù' ), 208 Entity( "ú", 'ú' ), 209 Entity( "û", 'û' ), 210 Entity( "ü", 'ü' ), 211 Entity( "ý", 'ý' ), 212 Entity( "þ", 'þ' ), 213 Entity( "ÿ", 'ÿ' ), # ÿ 214 215 Entity( "&#SPACE;", ' ' ), 216 Entity( "&#RS;", '\n' ), 217 Entity( "&#RE;", '\r' ), 218 Entity( """, '"' ), 219 Entity( "&", '&' ), 220 Entity( "<", '<' ), 221 Entity( ">", '>' ), 222 223 Entity( "CAP-DELTA", 'Δ' ), 224 Entity( "ALPHA", 'α' ), 225 Entity( "BETA", 'β' ), 226 Entity( "DELTA", 'δ' ), 227 Entity( "EPSILON", 'ε' ), 228 Entity( "THETA", 'θ' ), 229 Entity( "MU", 'μ' ), 230 Entity( "PI", 'π' ), 231 Entity( "TAU", 'τ' ), 232 Entity( "CHI", 'χ' ), 233 234 Entity( "<-", '←' ), 235 Entity( "^", '↑' ), 236 Entity( "->", '→' ), 237 Entity( "v", '↓' ), 238 Entity( "!=", '≠' ), 239 Entity( "<=", '≤' ), 240 Entity( nil, 0 ), 241}; 242 243 244Hit: adt { 245 glob: string; 246 chap: string; 247 mtype: string; 248 page: string; 249}; 250 251Lnone, Lordered, Lunordered, Ldef, Lother: con iota; # list types 252 253Chaps: adt { 254 name: string; 255 primary: int; 256}; 257 258Types: adt { 259 name: string; 260 desc: string; 261}; 262 263 264# having two separate flags here allows for inclusion of old-style formatted pages 265# under a new-style three-level tree 266Oldstyle: adt { 267 names: int; # two-level directory tree? 268 fmt: int; # old internal formats: e.g., "B" font means "L"; name in .TH in all caps 269}; 270 271Href: adt { 272 title: string; 273 chap: string; 274 mtype: string; 275 man: string; 276}; 277 278# per-thread global data 279Global: adt { 280 bufio: Bufio; 281 bin: ref Bufio->Iobuf; 282 bout: ref Bufio->Iobuf; 283 topname: string; # name of the top level categories in the manual 284 chaps: array of Chaps; # names of top-level partitions of this manual 285 types: array of Types; # names of second-level partitions 286 oldstyle: Oldstyle; 287 mantitle: string; 288 mandir: string; 289 thisone: Hit; # man page we're displaying 290 mtime: int; # last modification time of thisone 291 href: Href; # hrefs of components of this man page 292 hits: array of Hit; 293 nhits: int; 294 list_type: int; 295 pm: string; # proprietary marking 296 def_goobie: string; # deferred goobie 297 sop: int; # output at start of paragraph? 298 sol: int; # input at start of line? 299 broken: int; # output at a break? 300 fill: int; # in fill mode? 301 pre: int; # in PRE block? 302 example: int; # an example active? 303 ipd: int; # emit inter-paragraph distance? 304 indents: int; 305 hangingdt: int; 306 curfont: string; # current font 307 prevfont: string; # previous font 308 lastc: int; # previous char from input scanner 309 def_sm: int; # amount of deferred "make smaller" request 310 311 mk_href_chap: fn(g: self ref Global, chap: string); 312 mk_href_man: fn(g: self ref Global, man: string, oldstyle: int); 313 mk_href_mtype: fn(g: self ref Global, chap, mtype: string); 314 dobreak: fn(g: self ref Global); 315 print: fn(g: self ref Global, s: string); 316 softbr: fn(g: self ref Global): string; 317 softp: fn(g: self ref Global): string; 318}; 319 320header := "<HTML><HEAD>"; 321initial := ""; 322trailer := "</BODY></HTML>"; 323 324usage() 325{ 326 sys->fprint(stderr, "Usage: man2html [-h header] [-i initialtext] [-t trailer] file [section]\n"); 327 raise "fail:usage"; 328} 329 330 331init(nil: ref Draw->Context, args: list of string) 332{ 333 sys = load Sys Sys->PATH; 334 stderr = sys->fildes(2); 335 str = load String String->PATH; 336 dt = load Daytime Daytime->PATH; 337 arg := load Arg Arg->PATH; 338 arg->init(args); 339 arg->setusage("man2html [-h header] [-t trailer] file [section]"); 340 while((o := arg->opt()) != 0) 341 case o { 342 'h' => header = arg->earg(); 343 't' => trailer = arg->earg(); 344 * => arg->usage(); 345 } 346 args = arg->argv(); 347 if(args == nil) 348 arg->usage(); 349 arg = nil; 350 g := Global_init(); 351 page := hd args; 352 args = tl args; 353 section := "1"; 354 if(args != nil) 355 section = hd args; 356 hit := Hit ("", "man", section, page); 357 domanpage(g, hit); 358 g.print(trailer+"\n"); 359 g.bufio->g.bout.flush(); 360} 361 362# remove markup from a string 363# doesn't handle nested/quoted delimiters 364demark(s: string): string 365{ 366 t: string; 367 clean := true; 368 for (i := 0; i < len s; i++) { 369 case s[i] { 370 '<' => 371 clean = false; 372 '>' => 373 clean = true; 374 * => 375 if (clean) 376 t[len t] = s[i]; 377 } 378 } 379 return t; 380} 381 382 383# 384# Convert an individual man page to HTML and output. 385# 386domanpage(g: ref Global, man: Hit) 387{ 388 file := man.page; 389 g.bin = g.bufio->open(file, Bufio->OREAD); 390 g.bout = g.bufio->fopen(sys->fildes(1), Bufio->OWRITE); 391 if (g.bin == nil) { 392 fprint(stderr, "Cannot open %s: %r\n", file); 393 return; 394 } 395 (err, info) := sys->fstat(g.bin.fd); 396 if (! err) { 397 g.mtime = info.mtime; 398 } 399 g.thisone = man; 400 while ((p := getnext(g)) != nil) { 401 c := p[0]; 402 if (c == '.' && g.sol) { 403 if (g.pre) { 404 g.print("</PRE>"); 405 g.pre = false; 406 } 407 dogoobie(g, false); 408 dohangingdt(g); 409 } else if (g.def_goobie != nil || g.def_sm != 0) { 410 g.bufio->g.bin.ungetc(); 411 dogoobie(g, true); 412 } else if (c == '\n') { 413 g.print(p); 414 dohangingdt(g); 415 } else 416 g.print(p); 417 } 418 if (g.pm != nil) { 419 g.print("<BR><BR><BR><FONT SIZE=-2><CENTER>\n"); 420 g.print(g.pm); 421 g.print("<BR></CENTER></FONT>\n"); 422 } 423 closeall(g, 0); 424 rev(g, g.bin); 425} 426 427dogoobie(g: ref Global, deferred: int) 428{ 429 # read line, translate special chars 430 line := getline(g); 431 if (line == nil || line == "\n") 432 return; 433 434 # parse into arguments 435 token: string; 436 argl, rargl: list of string; # create reversed version, then invert 437 while ((line = str->drop(line, " \t\n")) != nil) 438 if (line[0] == '"') { 439 (token, line) = split(line[1:], '"'); 440 rargl = token :: rargl; 441 } else { 442 (token, line) = str->splitl(line, " \t"); 443 rargl = token :: rargl; 444 } 445 446 if (rargl == nil && !deferred) 447 return; 448 for ( ; rargl != nil; rargl = tl rargl) 449 argl = hd rargl :: argl; 450 451 def_sm := g.def_sm; 452 if (deferred && def_sm > 0) { 453 g.print(sprint("<FONT SIZE=-%d>", def_sm)); 454 if (g.def_goobie == nil) 455 argl = "dS" :: argl; # dS is our own local creation 456 } 457 458 subgoobie(g, argl); 459 460 if (deferred && def_sm > 0) { 461 g.def_sm = 0; 462 g.print("</FONT>"); 463 } 464} 465 466subgoobie(g: ref Global, argl: list of string) 467{ 468 if (g.def_goobie != nil) { 469 argl = g.def_goobie :: argl; 470 g.def_goobie = nil; 471 if (tl argl == nil) 472 return; 473 } 474 475 # the command part is at most two characters, but may be concatenated with the first arg 476 cmd := hd argl; 477 argl = tl argl; 478 if (len cmd > 2) { 479 cmd = cmd[0:2]; 480 argl = cmd[2:] :: argl; 481 } 482 483 case cmd { 484 485 "B" or "I" or "L" or "R" => 486 font(g, cmd, argl); # "R" macro implicitly generated by deferred R* macros 487 488 "BI" or "BL" or "BR" or 489 "IB" or "IL" or 490 "LB" or "LI" or 491 "RB" or "RI" or "RL" => 492 altfont(g, cmd[0:1], cmd[1:2], argl, true); 493 494 "IR" or "LR" => 495 anchor(g, cmd[0:1], cmd[1:2], argl); # includes man page refs ("IR" is old style, "LR" is new) 496 497 "dS" => 498 printargs(g, argl); 499 g.print("\n"); 500 501 "1C" or "2C" or "DT" or "TF" => # ignore these 502 return; 503 504 "ig" => 505 while ((line := getline(g)) != nil){ 506 if(len line > 1 && line[0:2] == "..") 507 break; 508 } 509 return; 510 511 "P" or "PP" or "LP" => 512 g_PP(g); 513 514 "EE" => g_EE(g); 515 "EX" => g_EX(g); 516 "HP" => g_HP_TP(g, 1); 517 "IP" => g_IP(g, argl); 518 "PD" => g_PD(g, argl); 519 "PM" => g_PM(g, argl); 520 "RE" => g_RE(g); 521 "RS" => g_RS(g); 522 "SH" => g_SH(g, argl); 523 "SM" => g_SM(g, argl); 524 "SS" => g_SS(g, argl); 525 "TH" => g_TH(g, argl); 526 "TP" => g_HP_TP(g, 3); 527 528 "br" => g_br(g); 529 "sp" => g_sp(g, argl); 530 "ti" => g_br(g); 531 "nf" => g_nf(g); 532 "fi" => g_fi(g); 533 "ft" => g_ft(g, argl); 534 535 * => return; # ignore unrecognized commands 536 } 537 538} 539 540g_br(g: ref Global) 541{ 542 if (g.hangingdt != 0) { 543 g.print("<DD>"); 544 g.hangingdt = 0; 545 } else if (g.fill && ! g.broken) 546 g.print("<BR>\n"); 547 g.broken = true; 548} 549 550g_EE(g: ref Global) 551{ 552 g.print("</PRE>\n"); 553 g.fill = true; 554 g.broken = true; 555 g.example = false; 556} 557 558g_EX(g: ref Global) 559{ 560 g.print("<PRE>"); 561 if (! g.broken) 562 g.print("\n"); 563 g.sop = true; 564 g.fill = false; 565 g.broken = true; 566 g.example = true; 567} 568 569g_fi(g: ref Global) 570{ 571 if (g.fill) 572 return; 573 g.fill = true; 574 g.print("<P style=\"display: inline; white-space: normal\">\n"); 575 g.broken = true; 576 g.sop = true; 577} 578 579g_ft(g: ref Global, argl: list of string) 580{ 581 font: string; 582 arg: string; 583 584 if (argl == nil) 585 arg = "P"; 586 else 587 arg = hd argl; 588 589 if (g.curfont != nil) 590 g.print(sprint("</%s>", g.curfont)); 591 592 case arg { 593 "2" or "I" => 594 font = "I"; 595 "3" or "B" => 596 font = "B"; 597 "5" or "L" => 598 font = "TT"; 599 "P" => 600 font = g.prevfont; 601 * => 602 font = nil; 603 } 604 g.prevfont = g.curfont; 605 g.curfont = font; 606 if (g.curfont != nil) 607 if (g.fill) 608 g.print(sprint("<%s>", g.curfont)); 609 else 610 g.print(sprint("<%s style=\"white-space: pre\">", g.curfont)); 611} 612 613# level == 1 is a .HP; level == 3 is a .TP 614g_HP_TP(g: ref Global, level: int) 615{ 616 case g.list_type { 617 Ldef => 618 if (g.hangingdt != 0) 619 g.print("<DD>"); 620 g.print(g.softbr() + "<DT>"); 621 * => 622 closel(g); 623 g.list_type = Ldef; 624 g.print("<DL compact>\n" + g.softbr() + "<DT>"); 625 } 626 g.hangingdt = level; 627 g.broken = true; 628} 629 630g_IP(g: ref Global, argl: list of string) 631{ 632 case g.list_type { 633 634 Lordered or Lunordered or Lother => 635 ; # continue with an existing list 636 637 * => 638 # figure out the type of a new list and start it 639 closel(g); 640 arg := ""; 641 if (argl != nil) 642 arg = hd argl; 643 case arg { 644 "1" or "i" or "I" or "a" or "A" => 645 g.list_type = Lordered; 646 g.print(sprint("<OL type=%s>\n", arg)); 647 "*" or "•" or "•" => 648 g.list_type = Lunordered; 649 g.print("<UL type=disc>\n"); 650 "○" or "○"=> 651 g.list_type = Lunordered; 652 g.print("<UL type=circle>\n"); 653 "□" or "□" => 654 g.list_type = Lunordered; 655 g.print("<UL type=square>\n"); 656 * => 657 g.list_type = Lother; 658 g.print("<DL compact>\n"); 659 } 660 } 661 662 # actually do this list item 663 case g.list_type { 664 Lother => 665 g.print(g.softp()); # make sure there's space before each list item 666 if (argl != nil) { 667 g.print("<DT>"); 668 printargs(g, argl); 669 } 670 g.print("\n<DD>"); 671 672 Lordered or Lunordered => 673 g.print(g.softp() + "<LI>"); 674 } 675 g.broken = true; 676} 677 678g_nf(g: ref Global) 679{ 680 if (! g.fill) 681 return; 682 g.fill = false; 683 g.print("<PRE>\n"); 684 g.broken = true; 685 g.sop = true; 686 g.pre = true; 687} 688 689g_PD(g: ref Global, argl: list of string) 690{ 691 if (len argl == 1 && hd argl == "0") 692 g.ipd = false; 693 else 694 g.ipd = true; 695} 696 697g_PM(g: ref Global, argl: list of string) 698{ 699 code := "P"; 700 if (argl != nil) 701 code = hd argl; 702 case code { 703 * => # includes "1" and "P" 704 g.pm = "<B>Lucent Technologies - Proprietary</B>\n" + 705 "<BR>Use pursuant to Company Instructions.\n"; 706 "2" or "RS" => 707 g.pm = "<B>Lucent Technologies - Proprietary (Restricted)</B>\n" + 708 "<BR>Solely for authorized persons having a need to know\n" + 709 "<BR>pursuant to Company Instructions.\n"; 710 "3" or "RG" => 711 g.pm = "<B>Lucent Technologies - Proprietary (Registered)</B>\n" + 712 "<BR>Solely for authorized persons having a need to know\n" + 713 "<BR>and subject to cover sheet instructions.\n"; 714 "4" or "CP" => 715 g.pm = "SEE PROPRIETARY NOTICE ON COVER PAGE\n"; 716 "5" or "CR" => 717 g.pm = "Copyright xxxx Lucent Technologies\n" + # should fill in the year from the date register 718 "<BR>All Rights Reserved.\n"; 719 "6" or "UW" => 720 g.pm = "THIS DOCUMENT CONTAINS PROPRIETARY INFORMATION OF\n" + 721 "<BR>LUCENT TECHNOLOGIES INC. AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN\n" + 722 "<BR>ACCORDANCE WITH APPLICABLE AGREEMENTS.\n" + 723 "<BR>Unpublished & Not for Publication\n"; 724 } 725} 726 727g_PP(g: ref Global) 728{ 729 closel(g); 730 reset_font(g); 731 p := g.softp(); 732 if (p != nil) 733 g.print(p); 734 g.sop = true; 735 g.broken = true; 736} 737 738g_RE(g: ref Global) 739{ 740 g.print("</DL>\n"); 741 g.indents--; 742 g.broken = true; 743} 744 745g_RS(g: ref Global) 746{ 747 g.print("<DL>\n<DT><DD>"); 748 g.indents++; 749 g.broken = true; 750} 751 752g_SH(g: ref Global, argl: list of string) 753{ 754 closeall(g, 1); # .SH is top-level list item 755 if (g.example) 756 g_EE(g); 757 g_fi(g); 758 if (g.fill && ! g.sop) 759 g.print("<P>"); 760 g.print("<DT><H4>"); 761 printargs(g, argl); 762 g.print("</H4>\n"); 763 g.print("<DD>\n"); 764 g.sop = true; 765 g.broken = true; 766} 767 768g_SM(g: ref Global, argl: list of string) 769{ 770 g.def_sm++; # can't use def_goobie, lest we collide with a deferred font macro 771 if (argl == nil) 772 return; 773 g.print(sprint("<FONT SIZE=-%d>", g.def_sm)); 774 printargs(g, argl); 775 g.print("</FONT>\n"); 776 g.def_sm = 0; 777} 778 779g_sp(g: ref Global, argl: list of string) 780{ 781 if (g.sop && g.fill) 782 return; 783 count := 1; 784 if (argl != nil) { 785 rcount := real hd argl; 786 count = int rcount; # may be 0 (e.g., ".sp .5") 787 if (count == 0 && rcount > 0.0) 788 count = 1; # force whitespace for fractional lines 789 } 790 g.dobreak(); 791 for (i := 0; i < count; i++) 792 g.print(" <BR>\n"); 793 g.broken = true; 794 g.sop = count > 0; 795} 796 797g_SS(g: ref Global, argl: list of string) 798{ 799 closeall(g, 1); 800 g.indents++; 801 g.print(g.softp() + "<DL><DT><FONT SIZE=3><B>"); 802 printargs(g, argl); 803 g.print("</B></FONT>\n"); 804 g.print("<DD>\n"); 805 g.sop = true; 806 g.broken = true; 807} 808 809g_TH(g: ref Global, argl: list of string) 810{ 811 if (g.oldstyle.names && len argl > 2) 812 argl = hd argl :: hd tl argl :: nil; # ignore extra .TH args on pages in oldstyle trees 813 case len argl { 814 0 => 815 g.oldstyle.fmt = true; 816 title(g, sprint("%s", g.href.title), false); 817 1 => 818 g.oldstyle.fmt = true; 819 title(g, sprint("%s", hd argl), false); # any pages use this form? 820 2 => 821 g.oldstyle.fmt = true; 822 g.thisone.page = hd argl; 823 g.thisone.mtype = hd tl argl; 824 g.mk_href_man(hd argl, true); 825 g.mk_href_mtype(nil, hd tl argl); 826 title(g, sprint("%s(%s)", g.href.man, g.href.mtype), false); 827 * => 828 g.oldstyle.fmt = false; 829 chap := hd tl tl argl; 830 g.mk_href_chap(chap); 831 g.mk_href_man(hd argl, false); 832 g.mk_href_mtype(chap, hd tl argl); 833 title(g, sprint("%s/%s/%s(%s)", g.href.title, g.href.chap, g.href.man, g.href.mtype), false); 834 } 835 g.print("[<a href=\"../index.html\">manual index</a>]"); 836 g.print("[<a href=\"INDEX.html\">section index</a>]<p>"); 837 g.print("<DL>\n"); # whole man page is just one big list 838 g.indents = 1; 839 g.sop = true; 840 g.broken = true; 841} 842 843dohangingdt(g: ref Global) 844{ 845 case g.hangingdt { 846 3 => 847 g.hangingdt--; 848 2 => 849 g.print("<DD>"); 850 g.hangingdt = 0; 851 g.broken = true; 852 } 853} 854 855# close a list, if there's one active 856closel(g: ref Global) 857{ 858 case g.list_type { 859 Lordered => 860 g.print("</OL>\n"); 861 g.broken = true; 862 Lunordered => 863 g.print("</UL>\n"); 864 g.broken = true; 865 Lother or Ldef => 866 g.print("</DL>\n"); 867 g.broken = true; 868 } 869 g.list_type = Lnone; 870} 871 872closeall(g: ref Global, level: int) 873{ 874 closel(g); 875 reset_font(g); 876 while (g.indents > level) { 877 g.indents--; 878 g.print("</DL>\n"); 879 g.broken = true; 880 } 881} 882 883# 884# Show last revision date for a file. 885# 886rev(g: ref Global, filebuf: ref Bufio->Iobuf) 887{ 888 if (g.mtime == 0) { 889 (err, info) := sys->fstat(filebuf.fd); 890 if (! err) 891 g.mtime = info.mtime; 892 } 893 if (g.mtime != 0) { 894 g.print("<P><TABLE width=\"100%\" border=0 cellpadding=10 cellspacing=0 bgcolor=\"#E0E0E0\">\n"); 895 g.print("<TR>"); 896 g.print(sprint("<TD align=left><FONT SIZE=-1>")); 897 g.print(sprint("%s(%s)", g.thisone.page, g.thisone.mtype)); 898 g.print("</FONT></TD>\n"); 899 g.print(sprint("<TD align=right><FONT SIZE=-1><I>Rev: %s</I></FONT></TD></TR></TABLE>\n", 900 dt->text(dt->gmt(g.mtime)))); 901 } 902} 903 904# 905# Some font alternation macros are references to other man pages; 906# detect them (second arg contains balanced parens) and make them into hot links. 907# 908anchor(g: ref Global, f1, f2: string, argl: list of string) 909{ 910 final := ""; 911 link := false; 912 if (len argl == 2) { 913 (s, e) := str->splitl(hd tl argl, ")"); 914 if (str->prefix("(", s) && e != nil) { 915 # emit href containing search for target first 916 # if numeric, do old style 917 link = true; 918 file := hd argl; 919 (chap, man) := split(httpunesc(file), '/'); 920 if (man == nil) { 921 # given no explicit chapter prefix, use current chapter 922 man = chap; 923 chap = g.thisone.chap; 924 } 925 mtype := s[1:]; 926 if (mtype == nil) 927 mtype = "-"; 928 (n, toks) := sys->tokenize(mtype, "."); # Fix section 10 929 if (n > 1) mtype = hd toks; 930 g.print(sprint("<A href=\"../%s/%s.html\">", mtype, fixlink(man))); 931 932 # 933 # now generate the name the user sees, with terminal punctuation 934 # moved after the closing </A>. 935 # 936 if (len e > 1) 937 final = e[1:]; 938 argl = hd argl :: s + ")" :: nil; 939 } 940 } 941 altfont(g, f1, f2, argl, false); 942 if (link) { 943 g.print("</A>"); 944 font(g, f2, final :: nil); 945 } else 946 g.print("\n"); 947} 948 949 950# 951# Fix up a link 952# 953 954fixlink(l: string): string 955{ 956 ll := str->tolower(l); 957 if (ll == "copyright") ll = "1" + ll; 958 (a, b) := str->splitstrl(ll, "intro"); 959 if (len b == 5) ll = a + "0" + b; 960 return ll; 961} 962 963 964# 965# output argl in font f 966# 967font(g: ref Global, f: string, argl: list of string) 968{ 969 if (argl == nil) { 970 g.def_goobie = f; 971 return; 972 } 973 case f { 974 "L" => f = "TT"; 975 "R" => f = nil; 976 } 977 if (f != nil) # nil == default (typically Roman) 978 g.print(sprint("<%s>", f)); 979 printargs(g, argl); 980 if (f != nil) 981 g.print(sprint("</%s>", f)); 982 g.print("\n"); 983 g.prevfont = f; 984} 985 986# 987# output concatenated elements of argl, alternating between fonts f1 and f2 988# 989altfont(g: ref Global, f1, f2: string, argl: list of string, newline: int) 990{ 991 reset_font(g); 992 if (argl == nil) { 993 g.def_goobie = f1; 994 return; 995 } 996 case f1 { 997 "L" => f1 = "TT"; 998 "R" => f1 = nil; 999 } 1000 case f2 { 1001 "L" => f2 = "TT"; 1002 "R" => f2 = nil; 1003 } 1004 f := f1; 1005 for (; argl != nil; argl = tl argl) { 1006 if (f != nil) 1007 g.print(sprint("<%s>%s</%s>", f, hd argl, f)); 1008 else 1009 g.print(hd argl); 1010 if (f == f1) 1011 f = f2; 1012 else 1013 f = f1; 1014 } 1015 if (newline) 1016 g.print("\n"); 1017 g.prevfont = f; 1018} 1019 1020# not yet implemented 1021map_font(nil: ref Global, nil: string) 1022{ 1023} 1024 1025reset_font(g: ref Global) 1026{ 1027 if (g.curfont != nil) { 1028 g.print(sprint("</%s>", g.curfont)); 1029 g.prevfont = g.curfont; 1030 g.curfont = nil; 1031 } 1032} 1033 1034printargs(g: ref Global, argl: list of string) 1035{ 1036 for (; argl != nil; argl = tl argl) 1037 if (tl argl != nil) 1038 g.print(hd argl + " "); 1039 else 1040 g.print(hd argl); 1041} 1042 1043# any parameter can be nil 1044addhit(g: ref Global, chap, mtype, page: string) 1045{ 1046 # g.print(sprint("Adding %s / %s (%s) . . .", chap, page, mtype)); # debug 1047 # always keep a spare slot at the end 1048 if (g.nhits >= len g.hits - 1) 1049 g.hits = (array[len g.hits + 32] of Hit)[0:] = g.hits; 1050 g.hits[g.nhits].glob = chap + " " + mtype + " " + page; 1051 g.hits[g.nhits].chap = chap; 1052 g.hits[g.nhits].mtype = mtype; 1053 g.hits[g.nhits++].page = page; 1054} 1055 1056Global.dobreak(g: self ref Global) 1057{ 1058 if (! g.broken) { 1059 g.broken = true; 1060 g.print("<BR>\n"); 1061 } 1062} 1063 1064Global.print(g: self ref Global, s: string) 1065{ 1066 g.bufio->g.bout.puts(s); 1067 if (g.sop || g.broken) { 1068 # first non-white space, non-HTML we print takes us past the start of the paragraph & line 1069 # (or even white space, if we're in no-fill mode) 1070 for (i := 0; i < len s; i++) { 1071 case s[i] { 1072 '<' => 1073 while (++i < len s && s[i] != '>') 1074 ; 1075 continue; 1076 ' ' or '\t' or '\n' => 1077 if (g.fill) 1078 continue; 1079 } 1080 g.sop = false; 1081 g.broken = false; 1082 break; 1083 } 1084 } 1085} 1086 1087Global.softbr(g: self ref Global): string 1088{ 1089 if (g.broken) 1090 return nil; 1091 g.broken = true; 1092 return "<BR>"; 1093} 1094 1095# provide a paragraph marker, unless we're already at the start of a section 1096Global.softp(g: self ref Global): string 1097{ 1098 if (g.sop) 1099 return nil; 1100 else if (! g.ipd) 1101 return "<BR>"; 1102 if (g.fill) 1103 return "<P>"; 1104 else 1105 return "<P style=\"white-space: pre\">"; 1106} 1107 1108# 1109# get (remainder of) a line 1110# 1111getline(g: ref Global): string 1112{ 1113 line := ""; 1114 while ((token := getnext(g)) != "\n") { 1115 if (token == nil) 1116 return line; 1117 line += token; 1118 } 1119 return line+"\n"; 1120} 1121 1122# 1123# Get next logical character. Expand it with escapes. 1124# 1125getnext(g: ref Global): string 1126{ 1127 iob := g.bufio; 1128 Iobuf: import iob; 1129 1130 font: string; 1131 token: string; 1132 bin := g.bin; 1133 1134 g.sol = (g.lastc == '\n'); 1135 1136 c := bin.getc(); 1137 if (c < 0) 1138 return nil; 1139 g.lastc = c; 1140 if (c >= Runeself) { 1141 for (i := 0; i < len Entities; i++) 1142 if (Entities[i].value == c) 1143 return Entities[i].name; 1144 return sprint("&#%d;", c); 1145 } 1146 case c { 1147 '<' => 1148 return "<"; 1149 '>' => 1150 return ">"; 1151 '\\' => 1152 c = bin.getc(); 1153 if (c < 0) 1154 return nil; 1155 g.lastc = c; 1156 case c { 1157 1158 ' ' => 1159 return " "; 1160 1161 # chars to ignore 1162 '|' or '&' or '^' => 1163 return getnext(g); 1164 1165 # ignore arg 1166 'k' => 1167 nil = bin.getc(); 1168 return getnext(g); 1169 1170 # defined strings 1171 '*' => 1172 case bin.getc() { 1173 'R' => 1174 return "®"; 1175 } 1176 return getnext(g); 1177 1178 # special chars 1179 '(' => 1180 token[0] = bin.getc(); 1181 token[1] = bin.getc(); 1182 for (i := 0; i < len tspec; i++) 1183 if (token == tspec[i].name) 1184 return tspec[i].value; 1185 return "¿"; 1186 'c' => 1187 c = bin.getc(); 1188 if (c < 0) 1189 return nil; 1190 else if (c == '\n') { 1191 g.lastc = c; 1192 g.sol = true; 1193 token[0] = bin.getc(); 1194 return token; 1195 } 1196 # DEBUG: should there be a "return xxx" here? 1197 'e' => 1198 return "\\"; 1199 'f' => 1200 g.lastc = c = bin.getc(); 1201 if (c < 0) 1202 return nil; 1203 case c { 1204 '2' or 'I' => 1205 font = "I"; 1206 '3' or 'B' => 1207 font = "B"; 1208 '5' or 'L' => 1209 font = "TT"; 1210 'P' => 1211 font = g.prevfont; 1212 * => # includes '1' and 'R' 1213 font = nil; 1214 } 1215# There are serious problems with this. We don't know the fonts properly at this stage. 1216# g.prevfont = g.curfont; 1217# g.curfont = font; 1218# if (g.prevfont != nil) 1219# token = sprint("</%s>", g.prevfont); 1220# if (g.curfont != nil) 1221# token += sprint("<%s>", g.curfont); 1222 if (token == nil) 1223 return "<i></i>"; # looks odd but it avoids inserting a space in <pre> text 1224 return token; 1225 's' => 1226 sign := '+'; 1227 size := 0; 1228 relative := false; 1229 getsize: 1230 for (;;) { 1231 c = bin.getc(); 1232 if (c < 0) 1233 return nil; 1234 case c { 1235 '+' => 1236 relative = true; 1237 '-' => 1238 sign = '-'; 1239 relative = true; 1240 '0' to '9' => 1241 size = size * 10 + (c - '0'); 1242 * => 1243 bin.ungetc(); 1244 break getsize; 1245 } 1246 g.lastc = c; 1247 } 1248 if (size == 0) 1249 token = "</FONT>"; 1250 else if (relative) 1251 token = sprint("<FONT SIZE=%c%d>", sign, size); 1252 else 1253 token = sprint("<FONT SIZE=%d>", size); 1254 return token; 1255 } 1256 } 1257 token[0] = c; 1258 return token; 1259} 1260 1261# 1262# Return strings before and after the left-most instance of separator; 1263# (s, nil) if no match or separator is last char in s. 1264# 1265split(s: string, sep: int): (string, string) 1266{ 1267 for (i := 0; i < len s; i++) 1268 if (s[i] == sep) 1269 return (s[:i], s[i+1:]); # s[len s:] is a valid slice, with value == nil 1270 return (s, nil); 1271} 1272 1273Global_init(): ref Global 1274{ 1275 g := ref Global; 1276 g.bufio = load Bufio Bufio->PATH; 1277 g.chaps = array[20] of Chaps; 1278 g.types = array[20] of Types; 1279 g.mantitle = ""; 1280 g.href.title = g.mantitle; # ?? 1281 g.mtime = 0; 1282 g.nhits = 0; 1283 g.oldstyle.names = false; 1284 g.oldstyle.fmt = false; 1285 g.topname = "System"; 1286 g.list_type = Lnone; 1287 g.def_sm = 0; 1288 g.hangingdt = 0; 1289 g.indents = 0; 1290 g.sop = true; 1291 g.broken = true; 1292 g.ipd = true; 1293 g.fill = true; 1294 g.example = false; 1295 g.pre = false; 1296 g.lastc = '\n'; 1297 return g; 1298} 1299 1300Global.mk_href_chap(g: self ref Global, chap: string) 1301{ 1302 if (chap != nil) 1303 g.href.chap = sprint("<A href=\"%s/%s?man=*\"><B>%s</B></A>", g.mandir, chap, chap); 1304} 1305 1306Global.mk_href_man(g: self ref Global, man: string, oldstyle: int) 1307{ 1308 rman := man; 1309 if (oldstyle) 1310 rman = str->tolower(man); # compensate for tradition of putting titles in all CAPS 1311 g.href.man = sprint("<A href=\"%s?man=%s\"><B>%s</B></A>", g.mandir, rman, man); 1312} 1313 1314Global.mk_href_mtype(g: self ref Global, chap, mtype: string) 1315{ 1316 g.href.mtype = sprint("<A href=\"%s/%s/%s\"><B>%s</B></A>", g.mandir, chap, mtype, mtype); 1317} 1318 1319# We assume that anything >= Runeself is already in UTF. 1320# 1321httpunesc(s: string): string 1322{ 1323 t := ""; 1324 for (i := 0; i < len s; i++) { 1325 c := s[i]; 1326 if (c == '&' && i + 1 < len s) { 1327 (char, rem) := str->splitl(s[i+1:], ";"); 1328 if (rem == nil) 1329 break; # require the terminating ';' 1330 if (char == nil) 1331 continue; 1332 if (char[0] == '#' && len char > 1) { 1333 c = int char[1:]; 1334 i += len char; 1335 if (c < 256 && c >= 161) { 1336 t[len t] = Entities[c-161].value; 1337 continue; 1338 } 1339 } else { 1340 for (j := 0; j < len Entities; j++) 1341 if (Entities[j].name == char) 1342 break; 1343 if (j < len Entities) { 1344 i += len char; 1345 t[len t] = Entities[j].value; 1346 continue; 1347 } 1348 } 1349 } 1350 t[len t] = c; 1351 } 1352 return t; 1353} 1354 1355 1356 1357title(g: ref Global, t: string, search: int) 1358{ 1359 if(search) 1360 ; # not yet used 1361 g.print(header+"\n"); 1362 g.print(sprint("<TITLE>Inferno's %s</TITLE>\n", demark(t))); 1363 g.print("</HEAD>\n"); 1364 g.print("<BODY>"+initial+"\n"); 1365 1366} 1367