1 /** Pretty Printing to AsciiDoc, HTML, LaTeX, JIRA Wikitext, etc. 2 * 3 * Copyright: Per Nordlöw 2018-. 4 * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 * Authors: $(WEB Per Nordlöw) 6 * 7 * TODO Convert `Viz` param to delegate like for 8 * `void toString(scope void delegate(scope const(char)[]) @safe sink)` 9 * for independency on arsd. 10 * 11 * Rename `pp1` to `ppL` and `pp` to `pp1` where relevant to reduce number of 12 * template instantiations. And replace multi-calls to viz.pp() to viz.pp1() 13 * 14 * TODO Add tester that prints types from `nxt.geometry` 15 * 16 * TODO Remove all restrictions on pp.*Raw.* and call them using ranges such as repeat 17 * 18 * TODO Use "alias this" on wrapper structures and test! 19 * 20 * TODO How should std.typecons.Tuple be pretty printed? 21 * 22 * TODO Add visited member to keeps track of what objects that have been visited 23 * 24 * TODO Add asGCCMessage pretty prints 25 * seq $PATH, ':', $ROW, ':', $COL, ':', message, '[', $TYPE, ']' 26 */ 27 module nxt.pretty; 28 29 // version = useTerm; 30 31 import core.time : Duration; 32 33 import std.range.primitives : isInputRange; 34 import std.traits : isInstanceOf, isSomeString, Unqual, isArray, isIterable; 35 import std.stdio : stdout; 36 37 /* TODO Move logic (toHTML) to these deps and remove these imports */ 38 import nxt.color : Color; 39 import nxt.mathml; 40 import nxt.lingua; 41 import nxt.attributes; 42 import nxt.rational : Rational; 43 44 import arsd.terminal : Terminal, ConsoleOutputType, Bright; 45 46 /// See_Also: http://forum.dlang.org/thread/fihymjelvnwfevegwryt@forum.dlang.org#post-fihymjelvnwfevegwryt:40forum.dlang.org 47 template Concise(Tuple) 48 { 49 static if(isTuple!Tuple) 50 { 51 struct Concise(Tuple) 52 { 53 Tuple tup; 54 alias tup this; 55 // should use a better overload 56 string toString() const 57 { 58 auto app = appender!string(); // TODO use array_ex.Array instead 59 app.put(`(`); 60 app.put(to!string(concise(tuple[0]))); 61 foreach (t; tuple.expand[1 .. $]) 62 { 63 app.put(`, `); 64 app.put(to!string(concise(t))); 65 } 66 app.put(`)`); 67 return app.data; 68 } 69 } 70 } 71 else 72 { 73 alias Concise = Tuple; 74 } 75 } 76 77 auto concise(T)(T t) { return Concise!T(t); } 78 79 /** Returns: Duration `dur` in a Level-Of-Detail (LOD) string 80 representation. 81 */ 82 string shortDurationString(in Duration dur) @safe pure nothrow 83 { 84 import std.conv: to; 85 immutable weeks = dur.total!`weeks`; 86 if (weeks) 87 { 88 if (weeks < 52) 89 { 90 return to!string(weeks) ~ ` week` ~ (weeks >= 2 ? `s` : ``); 91 } 92 else 93 { 94 immutable years = weeks / 52; 95 immutable weeks_rest = weeks % 52; 96 return to!string(years) ~ ` year` ~ (years >= 2 ? `s` : ``) ~ 97 ` and ` ~ 98 to!string(weeks_rest) ~ ` week` ~ (weeks_rest >= 2 ? `s` : ``); 99 } 100 } 101 immutable days = dur.total!`days`; if (days) return to!string(days) ~ ` day` ~ (days >= 2 ? `s` : ``); 102 immutable hours = dur.total!`hours`; if (hours) return to!string(hours) ~ ` hour` ~ (hours >= 2 ? `s` : ``); 103 immutable minutes = dur.total!`minutes`; if (minutes) return to!string(minutes) ~ ` minute` ~ (minutes >= 2 ? `s` : ``); 104 immutable seconds = dur.total!`seconds`; if (seconds) return to!string(seconds) ~ ` second` ~ (seconds >= 2 ? `s` : ``); 105 immutable msecs = dur.total!`msecs`; if (msecs) return to!string(msecs) ~ ` millisecond` ~ (msecs >= 2 ? `s` : ``); 106 immutable usecs = dur.total!`usecs`; if (usecs) return to!string(usecs) ~ ` microsecond` ~ (msecs >= 2 ? `s` : ``); 107 immutable nsecs = dur.total!`nsecs`; return to!string(nsecs) ~ ` nanosecond` ~ (msecs >= 2 ? `s` : ``); 108 } 109 110 /** Visual Form(at). */ 111 enum VizForm 112 { 113 textAsciiDoc, // TODO. 114 textAsciiDocUTF8, // TODO. 115 HTML, 116 D3js, // TODO. See_Also: http://d3js.org/ 117 LaTeX, // TODO. 118 jiraWikiMarkup, // TODO. See_Also: https://jira.atlassiana.com/secure/WikiRendererHelpAction.jspa?section=all 119 Markdown, // TODO. 120 } 121 122 /** Solarized light color theme. 123 * 124 * Each enumerator is an RGB hex string. 125 * 126 * TODO use RGB-type as enumerator instead 127 * 128 * See_Also: http://ethanschoonover.com/solarized 129 */ 130 enum SolarizedLightColorTheme_hexstring 131 { 132 base00 = `657b83`, 133 base01 = `586e75`, 134 base02 = `073642`, 135 base03 = `002b36`, 136 137 base0 = `839496`, 138 base1 = `93a1a1`, 139 base2 = `eee8d5`, 140 base3 = `fdf6e3`, 141 142 yellow = `b58900`, 143 orange = `cb4b16`, 144 red = `dc322f`, 145 magenta = `d33682`, 146 viole = `6c71c4`, 147 blue = `268bd2`, 148 cya = `2aa198`, 149 gree = `859900` 150 } 151 152 /** HTML tags with no side-effect when its arguments is empty. 153 See_Also: http://www.w3schools.com/html/html_formatting.asp 154 */ 155 static immutable nonStateHTMLTags = [`b`, `i`, `strong`, `em`, `sub`, `sup`, `small`, `ins`, `del`, `mark`, 156 `code`, `kbd`, `samp`, `samp`, `var`, `pre`]; 157 158 static immutable htmlHeader = `<!DOCTYPE html> 159 <html> 160 <head> 161 <meta charset="UTF-8"/> 162 <style> 163 164 body { font: 10px Verdana, sans-serif; } 165 hit0 { background-color:#F2B701; border: solid 0px grey; } 166 hit1 { background-color:#F18204; border: solid 0px grey; } 167 hit2 { background-color:#F50035; border: solid 0px grey; } 168 hit3 { background-color:#F5007A; border: solid 0px grey; } 169 hit4 { background-color:#A449B6; border: solid 0px grey; } 170 hit5 { background-color:#3A70BB; border: solid 0px grey; } 171 hit6 { background-color:#0DE7A6; border: solid 0px grey; } 172 hit7 { background-color:#70AD48; border: solid 0px grey; } 173 174 hit_context { background-color:#c0c0c0; border: solid 0px grey; } 175 176 code { background-color:#FFFFE0; } 177 178 dlang-byte { background-color:#FFFFE0; } 179 dlang-short { background-color:#FFFFE0; } 180 dlang-int { background-color:#FFFFE0; } 181 dlang-long { background-color:#FFFFE0; } 182 183 dlang-ubyte { background-color:#FFFFE0; } 184 dlang-ushort { background-color:#FFFFE0; } 185 dlang-uint { background-color:#FFFFE0; } 186 dlang-ulong { background-color:#FFFFE0; } 187 188 dlang-bool { background-color:#FFFFE0; } 189 190 dlang-float { background-color:#FFFFE0; } 191 dlang-double { background-color:#FFFFE0; } 192 dlang-real { background-color:#FFFFE0; } 193 194 dlang-char { background-color:#FFFFE0; } 195 dlang-wchar { background-color:#FFFFE0; } 196 dlang-dchar { background-color:#FFFFE0; } 197 198 dlang-string { background-color:#FFFFE0; } 199 dlang-wstring { background-color:#FFFFE0; } 200 dlang-dstring { background-color:#FFFFE0; } 201 202 td, th { border: 1px solid black; } 203 table { border-collapse: collapse; } 204 205 tr:nth-child(even) { background-color: #EBEBEB; } 206 tr:nth-child(2n+0) { background: #` ~ SolarizedLightColorTheme_hexstring.base2 ~ `; } 207 tr:nth-child(2n+1) { background: #` ~ SolarizedLightColorTheme_hexstring.base3 ~ `; } 208 209 </style> 210 </head> 211 <body> 212 `; 213 214 /** Visual Backend. 215 */ 216 class Viz 217 { 218 import std.stdio : ioFile = File; 219 220 ioFile outFile; 221 Terminal* term; 222 223 bool treeFlag; 224 VizForm form; 225 226 bool colorFlag; 227 bool flushNewlines = true; 228 229 /* If any (HTML) tags should be ended with a newline. 230 This increases the readability of generated HTML code. 231 */ 232 bool newlinedTags = true; 233 234 this(ioFile outFile, 235 Terminal* term, 236 VizForm form = VizForm.textAsciiDocUTF8, 237 bool treeFlag = true, 238 bool colorFlag = true, 239 bool flushNewlines = true, 240 bool newlinedTags = true, 241 ) 242 { 243 this.outFile = outFile; 244 this.term = term; 245 this.treeFlag = treeFlag; 246 this.form = form; 247 this.colorFlag = colorFlag; 248 this.flushNewlines = flushNewlines; 249 this.newlinedTags = newlinedTags; 250 if (form == VizForm.HTML) 251 { 252 ppRaw(this, htmlHeader); 253 } 254 } 255 256 ~this() 257 { 258 if (form == VizForm.HTML) 259 { 260 ppRaw("</body>\n</html>"); 261 } 262 } 263 264 /** Put `arg` to `this` without any conversion nor coloring. */ 265 void ppRaw(T...)(T args) 266 { 267 foreach (const arg; args) 268 { 269 if (outFile == stdout) 270 { 271 term.write(arg); // trick 272 } 273 else 274 { 275 outFile.write(arg); 276 } 277 } 278 } 279 280 /** Put `arg` to `this` without any conversion nor coloring. */ 281 void pplnRaw(T...)(T args) 282 { 283 foreach (arg; args) 284 { 285 if (outFile == stdout) 286 { 287 if (flushNewlines) 288 { 289 term.writeln(arg); 290 } 291 else 292 { 293 term.write(arg, '\n'); 294 } 295 } 296 else 297 { 298 if (flushNewlines) 299 { 300 outFile.writeln(arg); 301 } 302 else 303 { 304 outFile.write(arg, '\n'); 305 } 306 } 307 } 308 } 309 310 /// Print opening of tag `tag`. 311 void ppTagOpen(T, P...)(T tag, P params) 312 { 313 if (form == VizForm.HTML) 314 { 315 ppRaw(`<` ~ tag); 316 foreach (param; params) 317 { 318 ppRaw(' ', param); 319 } 320 ppRaw(`>`); 321 } 322 } 323 324 /// Print closing of tag `tag`. 325 void ppTagClose(T)(T tag) 326 { 327 immutable arg = (form == VizForm.HTML) ? `</` ~ tag ~ `>` : tag; 328 ppRaw(arg); 329 } 330 331 /// Print opening of tag `tag` on a separate line. 332 void pplnTagOpen(T)(T tag) 333 { 334 immutable arg = (form == VizForm.HTML) ? `<` ~ tag ~ `>` : tag; 335 if (newlinedTags) 336 { 337 pplnRaw(arg); 338 } 339 else 340 { 341 ppRaw(arg); 342 } 343 } 344 345 /// Print closing of tag `tag` on a separate line. 346 void pplnTagClose(T)(T tag) 347 { 348 immutable arg = (form == VizForm.HTML) ? `</` ~ tag ~ `>` : tag; 349 if (newlinedTags) 350 { 351 pplnRaw(arg); 352 } 353 else 354 { 355 ppRaw(arg); 356 } 357 } 358 359 /** Put `arg` to `viz` possibly with conversion. */ 360 void ppPut(T)(T arg, 361 bool nbsp = true) 362 { 363 if (outFile == stdout) 364 { 365 term.write(arg); 366 } 367 else 368 { 369 if (form == VizForm.HTML) 370 { 371 import nxt.w3c : encodeHTML; 372 outFile.write(arg.encodeHTML(nbsp)); 373 } 374 else 375 { 376 outFile.write(arg); 377 } 378 } 379 } 380 381 /** Put `arg` to `viz` possibly with conversion. */ 382 void ppPut(T)(Face face, 383 T arg, 384 bool nbsp = true) 385 { 386 term.setFace(face, colorFlag); 387 ppPut(arg, nbsp); 388 } 389 390 /** Print `args` tagged as `tag`. */ 391 void ppTaggedN(Tag, Args...)(in Tag tag, Args args) 392 if (isSomeString!Tag) 393 { 394 import std.algorithm.searching : find; 395 static if (args.length == 1 && 396 isSomeString!(typeof(args[0]))) 397 { 398 import std..string : empty; 399 if (form == VizForm.HTML && 400 args[0].empty && 401 !nonStateHTMLTags.find(tag).empty) 402 { 403 return; // skip HTML tags with no content 404 } 405 } 406 if (form == VizForm.HTML) { ppRaw(`<` ~ tag ~ `>`); } 407 ppN(args); 408 if (form == VizForm.HTML) { ppRaw(`</` ~ tag ~ `>`); } 409 } 410 411 /** Print `args` tagged as `tag` on a separate line. */ 412 void pplnTaggedN(Tag, Args...)(in Tag tag, Args args) 413 if (isSomeString!Tag) 414 { 415 ppTaggedN(tag, args); 416 if (newlinedTags) 417 pplnRaw(``); 418 } 419 420 // TODO Check for MathML support on backend 421 @property void ppMathML(SomeIntegral)(Rational!SomeIntegral arg) 422 { 423 ppTagOpen(`math`); 424 ppTagOpen(`mfrac`); 425 ppTaggedN(`mi`, arg.numerator); 426 ppTaggedN(`mi`, arg.denominator); 427 ppTagClose(`mfrac`); 428 ppTagClose(`math`); 429 } 430 431 /** Pretty-Print Single Argument `arg` to Terminal `term`. */ 432 void pp1(Arg)(Arg arg) 433 { 434 pp1(0, arg); 435 } 436 437 /** Pretty-Print Single Argument `arg` to Terminal `term`. */ 438 void pp1(Arg)(int depth, 439 Arg arg) 440 { 441 static if (is(typeof(ppMathML(arg)))) 442 { 443 if (form == VizForm.HTML) 444 { 445 return ppMathML(arg); 446 } 447 } 448 static if (is(typeof(arg.toMathML))) 449 { 450 if (form == VizForm.HTML) 451 { 452 // TODO Check for MathML support on backend 453 return ppRaw(arg.toMathML); 454 } 455 } 456 static if (is(typeof(arg.toHTML))) 457 { 458 if (form == VizForm.HTML) 459 { 460 return ppRaw(arg.toHTML); 461 } 462 } 463 static if (is(typeof(arg.toLaTeX))) 464 { 465 if (form == VizForm.LaTeX) 466 { 467 return ppRaw(arg.toLaTeX); 468 } 469 } 470 471 /* TODO Check if any member has mmber toMathML if so call it otherwise call 472 * toString. */ 473 474 static if (isInstanceOf!(AsWords, Arg)) 475 { 476 foreach (ix, subArg; arg.args) 477 { 478 static if (ix >= 1) 479 ppRaw(` `); // separator 480 pp1(depth + 1, subArg); 481 } 482 } 483 else static if (isInstanceOf!(AsCSL, Arg)) 484 { 485 foreach (ix, subArg; arg.args) 486 { 487 static if (ix >= 1) 488 { 489 pp1(depth + 1, `,`); // separator 490 } 491 static if (isInputRange!(typeof(subArg))) 492 { 493 foreach (subsubArg; subArg) 494 { 495 ppN(subsubArg, `,`); 496 } 497 } 498 } 499 } 500 else static if (isInstanceOf!(AsBold, Arg)) 501 { 502 if (form == VizForm.HTML) 503 { 504 ppTaggedN(`b`, arg.args); 505 } 506 else if (form == VizForm.Markdown) 507 { 508 ppRaw(`**`); 509 ppN(arg.args); 510 ppRaw(`**`); 511 } 512 } 513 else static if (isInstanceOf!(AsItalic, Arg)) 514 { 515 if (form == VizForm.HTML) 516 { 517 ppTaggedN(`i`, arg.args); 518 } 519 else if (form == VizForm.Markdown) 520 { 521 ppRaw(`*`); 522 ppN(arg.args); 523 ppRaw(`*`); 524 } 525 } 526 else static if (isInstanceOf!(AsMonospaced, Arg)) 527 { 528 if (form == VizForm.HTML) 529 { 530 ppTaggedN(`tt`, arg.args); 531 } 532 else if (form == VizForm.jiraWikiMarkup) 533 { 534 ppRaw(`{{`); 535 ppN(arg.args); 536 ppRaw(`}}`); 537 } 538 else if (form == VizForm.Markdown) 539 { 540 ppRaw('`'); 541 ppN(arg.args); 542 ppRaw('`'); 543 } 544 } 545 else static if (isInstanceOf!(AsCode, Arg)) 546 { 547 if (form == VizForm.HTML) 548 { 549 /* TODO Use arg.language member to highlight using fs tokenizers 550 * which must be moved out of fs. */ 551 ppTaggedN(`code`, arg.args); 552 } 553 else if (form == VizForm.jiraWikiMarkup) 554 { 555 ppRaw(arg.language ? `{code:` ~ arg.language ~ `}` : `{code}`); 556 ppN(arg.args); 557 ppRaw(`{code}`); 558 } 559 } 560 else static if (isInstanceOf!(AsEmphasized, Arg)) 561 { 562 if (form == VizForm.HTML) 563 { 564 ppTaggedN(`em`, arg.args); 565 } 566 else if (form == VizForm.jiraWikiMarkup) 567 { 568 ppRaw(`_`); 569 ppN(arg.args); 570 ppRaw(`_`); 571 } 572 else if (form == VizForm.Markdown) 573 { 574 ppRaw(`_`); 575 ppN(arg.args); 576 ppRaw(`_`); 577 } 578 } 579 else static if (isInstanceOf!(AsStronglyEmphasized, Arg)) 580 { 581 if (form == VizForm.Markdown) 582 { 583 ppRaw(`__`); 584 ppN(arg.args); 585 ppRaw(`__`); 586 } 587 } 588 else static if (isInstanceOf!(AsStrong, Arg)) 589 { 590 if (form == VizForm.HTML) 591 { 592 ppTaggedN(`strong`, arg.args); 593 } 594 else if (form == VizForm.jiraWikiMarkup) 595 { 596 ppRaw(`*`); 597 ppN(arg.args); 598 ppRaw(`*`); 599 } 600 } 601 else static if (isInstanceOf!(AsCitation, Arg)) 602 { 603 if (form == VizForm.HTML) 604 { 605 ppTaggedN(`cite`, arg.args); 606 } 607 else if (form == VizForm.jiraWikiMarkup) 608 { 609 ppRaw(`??`); 610 ppN(arg.args); 611 ppRaw(`??`); 612 } 613 } 614 else static if (isInstanceOf!(AsDeleted, Arg)) 615 { 616 if (form == VizForm.HTML) 617 { 618 ppTaggedN(`deleted`, arg.args); 619 } 620 else if (form == VizForm.jiraWikiMarkup) 621 { 622 ppRaw(`-`); 623 ppN(arg.args); 624 ppRaw(`-`); 625 } 626 } 627 else static if (isInstanceOf!(AsInserted, Arg)) 628 { 629 if (form == VizForm.HTML) 630 { 631 ppTaggedN(`inserted`, arg.args); 632 } 633 else if (form == VizForm.jiraWikiMarkup) 634 { 635 ppRaw(`+`); 636 ppN(arg.args); 637 ppRaw(`+`); 638 } 639 } 640 else static if (isInstanceOf!(AsSuperscript, Arg)) 641 { 642 if (form == VizForm.HTML) 643 { 644 ppTaggedN(`sup`, arg.args); 645 } 646 else if (form == VizForm.jiraWikiMarkup) 647 { 648 ppRaw(`^`); 649 ppN(arg.args); 650 ppRaw(`^`); 651 } 652 } 653 else static if (isInstanceOf!(AsSubscript, Arg)) 654 { 655 if (form == VizForm.HTML) 656 { 657 ppTaggedN(`sub`, arg.args); 658 } 659 else if (form == VizForm.jiraWikiMarkup) 660 { 661 ppRaw(`~`); 662 ppN(arg.args); 663 ppRaw(`~`); 664 } 665 } 666 else static if (isInstanceOf!(AsPreformatted, Arg)) 667 { 668 if (form == VizForm.HTML) 669 { 670 pplnTagOpen(`pre`); 671 ppN(arg.args); 672 pplnTagClose(`pre`); 673 } 674 else if (form == VizForm.jiraWikiMarkup) 675 { 676 pplnRaw(`{noformat}`); 677 ppN(arg.args); 678 pplnRaw(`{noformat}`); 679 } 680 } 681 else static if (isInstanceOf!(AsHeader, Arg)) 682 { 683 if (form == VizForm.HTML) 684 { 685 import std.conv: to; 686 pplnTaggedN(`h` ~ to!string(arg.level), 687 arg.args); 688 } 689 else if (form == VizForm.jiraWikiMarkup) 690 { 691 import std.conv: to; 692 ppRaw(`h` ~ to!string(arg.level) ~ `. `); 693 ppN(arg.args); 694 pplnRaw(``); 695 } 696 else if (form == VizForm.Markdown) 697 { 698 foreach (_; 0 .. arg.level) { pp1(0, `#`); } 699 ppN(` `, arg.args); 700 pplnRaw(``); 701 } 702 else if (form == VizForm.textAsciiDoc || 703 form == VizForm.textAsciiDocUTF8) 704 { 705 ppRaw('\n'); 706 foreach (_; 0 .. arg.level) { pp1(0, `=`); } 707 ppN(' ', arg.args, ' '); 708 foreach (_; 0 .. arg.level) { pp1(0, `=`); } 709 ppRaw('\n'); 710 } 711 } 712 else static if (isInstanceOf!(AsParagraph, Arg)) 713 { 714 if (form == VizForm.HTML) 715 { 716 pplnTaggedN(`p`, arg.args); 717 } 718 else if (form == VizForm.LaTeX) 719 { 720 ppRaw(`\par `); 721 pplnTaggedN(arg.args); 722 } 723 else if (form == VizForm.textAsciiDoc || 724 form == VizForm.textAsciiDocUTF8) 725 { 726 ppRaw('\n'); 727 foreach (_; 0 .. arg.level) { pp1(0, `=`); } 728 ppN(` `, arg.args, ` `, tag, '\n'); 729 } 730 } 731 else static if (isInstanceOf!(AsBlockquote, Arg)) 732 { 733 if (form == VizForm.HTML) 734 { 735 pplnTaggedN(`blockquote`, arg.args); 736 } 737 else if (form == VizForm.jiraWikiMarkup) 738 { 739 pplnRaw(`{quote}`); 740 pplnRaw(arg.args); 741 pplnRaw(`{quote}`); 742 } 743 else if (form == VizForm.Markdown) 744 { 745 foreach (subArg; arg.args) 746 { 747 pplnRaw(`> `, subArg); // TODO Iterate for each line in subArg 748 } 749 } 750 } 751 else static if (isInstanceOf!(AsBlockquoteSP, Arg)) 752 { 753 if (form == VizForm.jiraWikiMarkup) 754 { 755 ppRaw(`bq. `); 756 ppN(arg.args); 757 pplnRaw(``); 758 } 759 } 760 else static if (is(HorizontalRuler == Arg)) 761 { 762 if (form == VizForm.HTML) 763 { 764 pplnTagOpen(`hr`); 765 } 766 if (form == VizForm.jiraWikiMarkup) 767 { 768 pplnRaw(`----`); 769 } 770 } 771 else static if (isInstanceOf!(MDash, Arg)) 772 { 773 if (form == VizForm.HTML) 774 { 775 ppRaw(`—`); 776 } 777 if (form == VizForm.jiraWikiMarkup || 778 form == VizForm.Markdown || 779 form == VizForm.LaTeX) 780 { 781 pplnRaw(`---`); 782 } 783 } 784 else static if (isInstanceOf!(AsUList, Arg)) 785 { 786 if (form == VizForm.HTML) { pplnTagOpen(`ul`); } 787 else if (form == VizForm.LaTeX) { pplnRaw(`\begin{enumerate}`); } 788 ppN(arg.args); 789 if (form == VizForm.HTML) { pplnTagClose(`ul`); } 790 else if (form == VizForm.LaTeX) { pplnRaw(`\end{enumerate}`); } 791 } 792 else static if (isInstanceOf!(AsOList, Arg)) 793 { 794 if (form == VizForm.HTML) { pplnTagOpen(`ol`); } 795 else if (form == VizForm.LaTeX) { pplnRaw(`\begin{itemize}`); } 796 ppN(arg.args); 797 if (form == VizForm.HTML) { pplnTagClose(`ol`); } 798 else if (form == VizForm.LaTeX) { pplnRaw(`\end{itemize}`); } 799 } 800 else static if (isInstanceOf!(AsDescription, Arg)) // if args .length == 1 && an InputRange of 2-tuples pairs 801 { 802 if (form == VizForm.HTML) { pplnTagOpen(`dl`); } // TODO TERM <dt>, DEFINITION <dd> 803 else if (form == VizForm.LaTeX) { pplnRaw(`\begin{description}`); } // TODO \item[TERM] DEFINITION 804 ppN(arg.args); 805 if (form == VizForm.HTML) { pplnTagClose(`dl`); } 806 else if (form == VizForm.LaTeX) { pplnRaw(`\end{description}`); } 807 } 808 else static if (isInstanceOf!(AsTable, Arg)) 809 { 810 if (form == VizForm.HTML) 811 { 812 const border = (arg.border ? ` border=` ~ arg.border : ``); 813 pplnTagOpen(`table` ~ border); 814 } 815 else if (form == VizForm.LaTeX) 816 { 817 pplnRaw(`\begin{tabular}`); 818 } 819 820 static if (arg.args.length == 1 && 821 isIterable!(typeof(arg.args[0]))) 822 { 823 auto rows = arg.args[0].asRows(); 824 rows.recurseFlag = arg.recurseFlag; // propagate 825 rows.rowNr = arg.rowNr; 826 ppNFlushed(rows); 827 } 828 else 829 { 830 ppN(arg.args); 831 } 832 833 if (form == VizForm.HTML) 834 { 835 pplnTagClose(`table`); 836 } 837 else if (form == VizForm.LaTeX) 838 { 839 pplnRaw(`\end{tabular}`); 840 } 841 } 842 else static if (isInstanceOf!(AsRows, Arg) && 843 arg.args.length == 1 && 844 isIterable!(typeof(arg.args[0]))) 845 { 846 bool capitalizeHeadings = true; 847 848 /* See_Also: http://forum.dlang.org/thread/wjksldfpkpenoskvhsqa@forum.dlang.org#post-jwfildowqrbwtamywsmy:40forum.dlang.org */ 849 850 // use aggregate members as header 851 import std.range.primitives : ElementType; 852 alias Front = ElementType!(typeof(arg.args[0])); 853 static if (is(Front == struct) || 854 is(Front == union) || 855 is(Front == class) || 856 is(Front == interface)) // isAggregateType 857 { 858 /* TODO When __traits(documentation,x) 859 here https://github.com/D-Programming-Language/dmd/pull/3531 860 get merged use it! */ 861 // pplnTaggedN(`tr`, subArg.asCols); // TODO asItalic 862 // Use __traits(allMembers, T) instead 863 // Can we lookup file and line of user defined types aswell? 864 865 // member names header. 866 if (form == VizForm.HTML) { pplnTagOpen(`tr`); } // TODO Functionize 867 868 // index column 869 if (arg.rowNr == RowNr.offsetZero) pplnTaggedN(`td`, `0-Offset`); 870 else if (arg.rowNr == RowNr.offsetOne) pplnTaggedN(`td`, `1-Offset`); 871 foreach (const ix, Member; typeof(Front.tupleof)) 872 { 873 import std.ascii : isUpper; // TODO support ASCII in fast path and Unicode in slow path 874 import std..string : capitalize; 875 import std.algorithm.iteration : joiner; 876 877 static if (is(Memb == struct)) immutable qual = `struct `; 878 else static if (is(Memb == class)) immutable qual = `class `; 879 else static if (is(Memb == enum)) immutable qual = `enum `; 880 else static if (is(Memb == interface)) immutable qual = `interface `; 881 else immutable qual = ``; // TODO Are there more qualifiers 882 883 import std.algorithm.iteration : map; 884 import nxt.slicing : preSlicer; 885 immutable idName = __traits(identifier, Front.tupleof[ix]).preSlicer!isUpper.map!capitalize.joiner(` `); // TODO reuse `nxt.casing.camelCasedToLowerSpaced` 886 immutable typeName = Unqual!(Member).stringof; // constness of no interest here 887 888 pplnTaggedN(`td`, 889 idName.asItalic.asBold, 890 `<br>`, 891 qual.asKeyword, 892 typeName.asType); 893 } 894 if (form == VizForm.HTML) { pplnTagClose(`tr`); } 895 } 896 897 size_t ix = 0; 898 foreach (subArg; arg.args[0]) // for each table row 899 { 900 auto cols = subArg.asCols(); 901 cols.recurseFlag = arg.recurseFlag; // propagate 902 cols.rowNr = arg.rowNr; 903 cols.rowIx = ix; 904 pplnTaggedN(`tr`, cols); // print columns 905 ix++; 906 } 907 } 908 else static if (isInstanceOf!(AsCols, Arg)) 909 { 910 alias T_ = typeof(arg.args[0]); 911 if (arg.args.length == 1 && 912 (is(T_ == struct) || 913 is(T_ == class) || 914 is(T_ == union) || 915 is(T_ == interface))) // isAggregateType 916 { 917 auto args0 = arg.args[0]; 918 if (form == VizForm.jiraWikiMarkup) 919 { 920 /* if (args0.length >= 1) { ppRaw(`|`); } */ 921 } 922 if (arg.rowNr == RowNr.offsetZero) 923 { 924 pplnTaggedN(`td`, arg.rowIx + 0); 925 } 926 else if (arg.rowNr == RowNr.offsetOne) 927 { 928 pplnTaggedN(`td`, arg.rowIx + 1); 929 } 930 foreach (subArg; args0.tupleof) // for each table column 931 { 932 if (form == VizForm.HTML) 933 { 934 pplnTaggedN(`td`, subArg); // each element in aggregate as a column 935 } 936 else if (form == VizForm.jiraWikiMarkup) 937 { 938 /* pp1(subArg); ppRaw(`|`); */ 939 } 940 } 941 } 942 else 943 { 944 pplnTaggedN(`tr`, arg.args); 945 } 946 } 947 else static if (isInstanceOf!(AsRow, Arg)) 948 { 949 string spanArg; 950 static if (arg.args.length == 1 && 951 isInstanceOf!(Span, typeof(arg.args[0]))) 952 { 953 spanArg ~= ` rowspan="` ~ to!string(arg._span) ~ `"`; 954 } 955 if (form == VizForm.HTML) { pplnTagOpen(`tr` ~ spanArg); } 956 ppN(arg.args); 957 if (form == VizForm.HTML) { pplnTagClose(`tr`); } 958 } 959 else static if (isInstanceOf!(AsCell, Arg)) 960 { 961 string spanArg; 962 static if (arg.args.length != 0 && 963 isInstanceOf!(Span, typeof(arg.args[0]))) 964 { 965 spanArg ~= ` colspan="` ~ to!string(arg._span) ~ `"`; 966 } 967 if (form == VizForm.HTML) { ppTagOpen(`td` ~ spanArg); } 968 ppN(arg.args); 969 if (form == VizForm.HTML) { pplnTagClose(`td`); } 970 } 971 else static if (isInstanceOf!(AsTHeading, Arg)) 972 { 973 if (form == VizForm.HTML) 974 { 975 pplnTagOpen(`th`); 976 ppN(arg.args); 977 pplnTagClose(`th`); 978 } 979 else if (form == VizForm.jiraWikiMarkup) 980 { 981 if (args.length != 0) 982 { 983 ppRaw(`||`); 984 } 985 foreach (subArg; args) 986 { 987 pp1(subArg); 988 ppRaw(`||`); 989 } 990 } 991 } 992 else static if (isInstanceOf!(AsItem, Arg)) 993 { 994 if (form == VizForm.HTML) { ppTagOpen(`li`); } 995 else if (form == VizForm.textAsciiDoc) { ppRaw(` - `); } // if inside ordered list use . instead of - 996 else if (form == VizForm.LaTeX) { ppRaw(`\item `); } 997 else if (form == VizForm.textAsciiDocUTF8) { ppRaw(` • `); } 998 else if (form == VizForm.Markdown) { ppRaw(`* `); } // TODO Alternatively +,-,*, or 1. TODO Need counter for ordered lists 999 ppN(arg.args); 1000 if (form == VizForm.HTML) { pplnTagClose(`li`); } 1001 else if (form == VizForm.LaTeX) { pplnRaw(``); } 1002 else if (form == VizForm.textAsciiDoc || 1003 form == VizForm.textAsciiDocUTF8 || 1004 form == VizForm.Markdown) { pplnRaw(``); } 1005 } 1006 else static if (isInstanceOf!(AsPath, Arg) || 1007 isInstanceOf!(AsURL, Arg)) 1008 { 1009 auto vizArg = this; 1010 vizArg.treeFlag = false; 1011 1012 enum isString = isSomeString!(typeof(arg.arg)); // only create hyperlink if arg is a string 1013 1014 static if (isString) 1015 { 1016 if (form == VizForm.HTML) 1017 { 1018 static if (isInstanceOf!(AsPath, Arg)) 1019 { 1020 ppTagOpen(`a href="file://` ~ arg.arg ~ `"`); 1021 } 1022 else static if (isInstanceOf!(AsURL, Arg)) 1023 { 1024 ppTagOpen(`a href="` ~ arg.arg ~ `"`); 1025 } 1026 } 1027 } 1028 1029 vizArg.pp1(depth + 1, arg.arg); 1030 1031 static if (isString) 1032 { 1033 if (form == VizForm.HTML) 1034 { 1035 ppTagClose(`a`); 1036 } 1037 } 1038 } 1039 else static if (isInstanceOf!(AsName, Arg)) 1040 { 1041 auto vizArg = viz; 1042 vizArg.treeFlag = true; 1043 pp1(term, vizArg, depth + 1, arg.arg); 1044 } 1045 // else static if (isInstanceOf!(AsHit, Arg)) 1046 // { 1047 // const ixs = to!string(arg.ix); 1048 // if (form == VizForm.HTML) { ppTagOpen(`hit` ~ ixs); } 1049 // pp1(depth + 1, arg.args); 1050 // if (form == VizForm.HTML) { ppTagClose(`hit` ~ ixs); } 1051 // } 1052 // else static if (isInstanceOf!(AsCtx, Arg)) 1053 // { 1054 // if (form == VizForm.HTML) { ppTagOpen(`hit_context`); } 1055 // pp1(depth + 1, arg.args); 1056 // if (form == VizForm.HTML) { ppTagClose(`hit_context`); } 1057 // } 1058 else static if (isArray!Arg && 1059 !isSomeString!Arg) 1060 { 1061 ppRaw(`[`); 1062 foreach (ix, subArg; arg) 1063 { 1064 if (ix >= 1) 1065 ppRaw(`,`); // separator 1066 pp1(depth + 1, subArg); 1067 } 1068 ppRaw(`]`); 1069 } 1070 else static if (isInputRange!Arg) 1071 { 1072 foreach (subArg; arg) 1073 { 1074 pp1(depth + 1, subArg); 1075 } 1076 } 1077 else static if (__traits(hasMember, arg, `parent`)) // TODO Use isFile = File or NonNull!File 1078 { 1079 import std.path: dirSeparator; 1080 if (form == VizForm.HTML) 1081 { 1082 ppRaw(`<a href="file://`); 1083 ppPut(arg.path); 1084 ppRaw(`">`); 1085 } 1086 1087 if (!treeFlag) 1088 { 1089 // write parent path 1090 foreach (parent; arg.parents) 1091 { 1092 ppPut(dirSeparator); 1093 if (form == VizForm.HTML) { ppTagOpen(`b`); } 1094 ppPut(dirFace, parent.name); 1095 if (form == VizForm.HTML) { ppTagClose(`b`); } 1096 } 1097 ppPut(dirSeparator); 1098 } 1099 1100 // write name 1101 static if (__traits(hasMember, arg, `isRoot`)) // TODO Use isDir = Dir or NonNull!Dir 1102 { 1103 immutable name = arg.isRoot ? dirSeparator : arg.name ~ dirSeparator; 1104 } 1105 else 1106 { 1107 immutable name = arg.name; 1108 } 1109 1110 if (form == VizForm.HTML) 1111 { 1112 // static if (isSymlink!Arg) { ppTagOpen(`i`); } 1113 // static if (isDir!Arg) { ppTagOpen(`b`); } 1114 } 1115 1116 ppPut(arg.getFace(), name); 1117 1118 if (form == VizForm.HTML) 1119 { 1120 // static if (isSymlink!Arg) { ppTagClose(`i`); } 1121 // static if (isDir!Arg) { ppTagClose(`b`); } 1122 } 1123 1124 if (form == VizForm.HTML) { ppTagClose(`a`); } 1125 } 1126 else 1127 { 1128 static if (__traits(hasMember, arg, `path`)) 1129 { 1130 const arg_string = arg.path; 1131 } 1132 else 1133 { 1134 import std.conv: to; 1135 const arg_string = to!string(arg); 1136 } 1137 1138 static if (__traits(hasMember, arg, `face`) && 1139 __traits(hasMember, arg.face, `tagsHTML`)) 1140 { 1141 if (form == VizForm.HTML) 1142 { 1143 foreach (tag; arg.face.tagsHTML) 1144 { 1145 outFile.write(`<`, tag, `>`); 1146 } 1147 } 1148 } 1149 1150 // write 1151 term.setFace(arg.getFace(), colorFlag); 1152 if (outFile == stdout) 1153 { 1154 term.write(arg_string); 1155 } 1156 else 1157 { 1158 ppPut(arg.getFace(), arg_string); 1159 } 1160 1161 static if (__traits(hasMember, arg, `face`) && 1162 __traits(hasMember, arg.face, `tagsHTML`)) 1163 { 1164 if (form == VizForm.HTML) 1165 { 1166 foreach (tag; arg.face.tagsHTML) 1167 { 1168 outFile.write(`</`, tag, `>`); 1169 } 1170 } 1171 } 1172 } 1173 } 1174 1175 /** Pretty-Print Multiple Arguments `args` to Terminal `term`. */ 1176 void ppN(Args...)(Args args) 1177 { 1178 foreach (arg; args) 1179 { 1180 pp1(0, arg); 1181 } 1182 } 1183 1184 /** Pretty-Print Arguments `args` to Terminal `term` without Line Termination. */ 1185 void ppNFlushed(Args...)(Args args) 1186 { 1187 ppN(args); 1188 if (outFile == stdout) 1189 { 1190 term.flush(); 1191 } 1192 } 1193 1194 /** Pretty-Print Arguments `args` including final line termination. */ 1195 void ppln(Args...)(Args args) 1196 { 1197 ppN(args); 1198 if (outFile == stdout) 1199 { 1200 term.writeln(lbr(form == VizForm.HTML)); 1201 term.flush(); 1202 } 1203 else 1204 { 1205 outFile.writeln(lbr(form == VizForm.HTML)); 1206 } 1207 } 1208 1209 /** Pretty-Print Arguments `args` each including a final line termination. */ 1210 void pplns(Args...)(Args args) 1211 { 1212 foreach (arg; args) 1213 { 1214 ppln(args); 1215 } 1216 } 1217 1218 /** Print End of Line to Terminal `term`. */ 1219 void ppendl() 1220 { 1221 ppln(``); 1222 } 1223 1224 } 1225 1226 /// Face with color of type `SomeColor`. 1227 struct Face 1228 { 1229 this(Color foregroundColor, 1230 Color backgroundColor, 1231 bool bright = false, 1232 bool italic = false, 1233 string[] tagsHTML = []) 1234 { 1235 this.foregroundColor = foregroundColor; 1236 this.backgroundColor = backgroundColor; 1237 this.bright = bright; 1238 this.tagsHTML = tagsHTML; 1239 } 1240 string[] tagsHTML; 1241 Color foregroundColor; 1242 Color backgroundColor; 1243 bool bright; 1244 bool italic; 1245 } 1246 1247 // Faces (Font/Color) 1248 enum stdFace = Face(Color.white, Color.black); 1249 enum pathFace = Face(Color.green, Color.black, true); 1250 1251 enum dirFace = Face(Color.blue, Color.black, true); 1252 enum fileFace = Face(Color.magenta, Color.black, true); 1253 enum baseNameFace = fileFace; 1254 enum specialFileFace = Face(Color.red, Color.black, true); 1255 enum regFileFace = Face(Color.white, Color.black, true, false, [`b`]); 1256 enum symlinkFace = Face(Color.cyan, Color.black, true, true, [`i`]); 1257 enum symlinkBrokenFace = Face(Color.red, Color.black, true, true, [`i`]); 1258 enum missingSymlinkTargetFace = Face(Color.red, Color.black, false, true, [`i`]); 1259 1260 enum contextFace = Face(Color.green, Color.black); 1261 1262 enum timeFace = Face(Color.magenta, Color.black); 1263 enum digestFace = Face(Color.yellow, Color.black); 1264 1265 enum infoFace = Face(Color.white, Color.black, true); 1266 enum warnFace = Face(Color.yellow, Color.black); 1267 enum kindFace = warnFace; 1268 enum errorFace = Face(Color.red, Color.black); 1269 1270 enum titleFace = Face(Color.white, Color.black, false, false, [`title`]); 1271 enum h1Face = Face(Color.white, Color.black, false, false, [`h1`]); 1272 1273 // Support these as immutable 1274 1275 /** Key (Hit) Face Palette. */ 1276 enum ctxFaces = [Face(Color.red, Color.black), 1277 Face(Color.green, Color.black), 1278 Face(Color.blue, Color.black), 1279 Face(Color.cyan, Color.black), 1280 Face(Color.magenta, Color.black), 1281 Face(Color.yellow, Color.black), 1282 ]; 1283 1284 import std.algorithm.iteration : map; 1285 1286 /** Key (Hit) Faces. */ 1287 enum keyFaces = ctxFaces.map!(a => Face(a.foregroundColor, a.backgroundColor, true)); // TODO avoid map 1288 1289 void setFace(Term, Face)(ref Term term, 1290 Face face, 1291 bool colorFlag) 1292 { 1293 if (colorFlag) 1294 { 1295 version(useTerm) 1296 { 1297 term.color(face.foregroundColor | (face.bright ? Bright : 0) , 1298 face.backgroundColor); 1299 } 1300 } 1301 } 1302 1303 /** Fazed (Rich) Text. */ 1304 struct Fazed(T) 1305 { 1306 T text; 1307 const Face face; 1308 string toString() const @property pure nothrow 1309 { 1310 import std.conv : to; 1311 return to!string(text); 1312 } 1313 } 1314 auto faze(T)(T text, in Face face = stdFace) @safe pure nothrow 1315 { 1316 return Fazed!T(text, face); 1317 } 1318 1319 auto getFace(Arg)(in Arg arg) @safe pure nothrow 1320 { 1321 // pick face 1322 static if (__traits(hasMember, arg, `face`)) 1323 { 1324 return arg.face; 1325 } 1326 // else static if (isInstanceOf!(Digest, Arg)) // instead of is(Unqual!(Arg) == SHA1Digest) 1327 // { 1328 // return digestFace; 1329 // } 1330 // else static if (isInstanceOf!(AsHit, Arg)) 1331 // { 1332 // return keyFaces.cycle[arg.ix]; 1333 // } 1334 // else static if (isInstanceOf!(AsCtx, Arg)) 1335 // { 1336 // return ctxFaces.cycle[arg.ix]; 1337 // } 1338 else 1339 { 1340 return stdFace; 1341 } 1342 } 1343 1344 /** Show `viz`. 1345 */ 1346 void show(Viz viz) 1347 { 1348 viz.outFile.flush(); 1349 import std.process : spawnProcess, wait; 1350 auto pid = spawnProcess([`xdg-open`, viz.outFile.name]); 1351 assert(wait(pid) == 0); 1352 } 1353 1354 version(unittest) 1355 { 1356 // TODO hide these stuff in constructor for Viz 1357 import std.uuid : randomUUID; 1358 import std.stdio : File; 1359 import nxt.rational : rational; 1360 } 1361 1362 unittest 1363 { 1364 import std.algorithm.iteration : map; 1365 1366 string outPath = `/tmp/fs-` ~ randomUUID.toString() ~ `.` ~ `html`; // reuse `nxt.tempfs` 1367 File outFile = File(outPath, `w`); 1368 1369 auto term = Terminal(ConsoleOutputType.linear); 1370 1371 auto viz = new Viz(outFile, &term, VizForm.HTML); 1372 1373 viz.pp1(`Pretty Printing`.asH!1); 1374 viz.pp1(horizontalRuler); 1375 1376 viz.pp1(`First Heading`.asH!2); 1377 viz.ppln(`Something first.`); 1378 1379 viz.pp1(`Second Heading`.asH!2); 1380 viz.ppln(`Something else.`); 1381 1382 struct S 1383 { 1384 string theUnit; 1385 int theSuperValue; 1386 } 1387 1388 S[] s = [S("meter", 42), 1389 S("second", 43)]; 1390 1391 viz.pp1("Struct Array".asH!2); 1392 viz.pp1(s.asTable); 1393 1394 viz.pp1("Map Struct Array".asH!2); 1395 viz.pp1(s.map!(_ => S(_.theUnit, 1396 _.theSuperValue^^2)).asTable); 1397 1398 viz.pp1("Rational Number Array".asH!2); 1399 viz.pp1([rational(11, 13), 1400 rational(14, 15), 1401 rational(17, 32)]); 1402 1403 struct NamedRational 1404 { 1405 string name; 1406 Rational!long value; 1407 } 1408 1409 viz.pp1("Named Rational Number Array as Table".asH!2); 1410 viz.pp1([NamedRational("x", Rational!long(11, 13)), 1411 NamedRational("y", Rational!long(111, 133)), 1412 NamedRational("z", Rational!long(1111, 1333))].asTable); 1413 1414 viz.show(); 1415 }