1 module pegged.tohtml;
2 
3 import std.stdio;
4 import std.conv, std..string;
5 import std.algorithm.searching;
6 import pegged.peg;
7 
8 enum Expand
9 {
10     no, ifMatch, ifNotMatch
11 }
12 
13 /**
14  * Writes a parse tree to a html file.
15  *
16  * Params:
17  *      e = Defines if the tree is expanded.
18  *      Details = Defines the details, as a list of strings, that are expanded
19  *          when e is equal to `Expand.ifMatch`, and not expanded when e is equal to
20  *          `Expand.ifNotMatch`. When no details are passed, each node is expanded.
21  *      p = The ParseTree to represent.
22  *      file = The file where to write the tree.
23  */
24 void toHTML(Expand e = Expand.no, Details...)(const ref ParseTree p,
25     File file)
26 {
27     file.write(`
28 <!DOCTYPE html>
29 <html>
30 <head>
31 <meta charset="UTF-8">
32 <title>Pegged produced parse tree</title>
33 <style>
34 code {
35     position: relative;
36     font-family: monospace;
37     white-space: pre;
38 }
39 code.failure {
40     color: red;
41 }
42 /* hide tooltip */
43 code span {
44     display: none;
45 }
46 /* show and style tooltip */
47 code:hover span {
48     /* show tooltip */
49     display: block;
50     /* position relative to container div.tooltip */
51     position: absolute;
52     top: -1.6em;
53     left: -0.6em;
54     background: #ffffff;
55     padding: 0.5em;
56     color: #000000;
57     border: 0.1em solid #b7ddf2;
58     border-radius: 0.5em;
59     white-space: nowrap;
60     z-index: 1;
61 }
62 details {
63     margin-left:25px;
64     white-space: nowrap;
65 }
66 details.leaf summary::-webkit-details-marker {
67     opacity: 0;
68 }
69 </style>
70 </head>
71 <body>
72     `);
73 
74     void treeToHTML(const ref ParseTree p)
75     {
76         file.write("<details");
77         static if (e != Expand.no)
78         {
79             static if (!Details.length)
80             {
81                 file.write(" open");
82             }
83             else
84             {
85                 static if (e == Expand.ifMatch)
86                 {
87                     foreach(detail; Details)
88                         if (indexOf(p.name, detail) > -1L)
89                     {
90                         file.write(" open");
91                         break;
92                     }
93                 }
94                 else
95                 {
96                     bool w;
97                     foreach(detail; Details)
98                         if (indexOf(p.name, detail) > -1L)
99                     {
100                         w = true;
101                         break;
102                     }
103                     if (!w)
104                         file.write(" open");
105                 }
106             }
107         }
108 
109         if (p.children.length == 0)
110             file.write(` class="leaf"`);
111         file.write("><summary>", p.name, " ", to!string([p.begin, p.end]));
112 
113         if (p.children.length == 0 && !p.successful)
114         {   // Failure leaf.
115             Position pos = position(p);
116             auto left = pos.index < 10 ? p.input[0 .. pos.index] : p.input[pos.index-10 .. pos.index];
117             auto right = pos.index + 10 < p.input.length ? p.input[pos.index .. pos.index + 10] : p.input[pos.index .. $];
118             file.write(" failure at line ", to!string(pos.line), ", col ", to!string(pos.col), ", ");
119             if (left.length > 0)
120                 file.write("after <code>", left.stringified, "</code> ");
121             file.write("expected <code>");
122             if (p.matches.length > 0)
123                 file.write(p.matches[$-1].stringified);
124             else
125                 file.write("NO MATCH");
126             file.write(`</code>, but got <code class="failure">`, right.stringified, "</code>\n");
127         }
128         else
129         {
130             file.write(" <code");
131             if (!p.successful)
132                 file.write(` class="failure"`);
133             auto firstNewLine = p.input[p.begin .. p.end].countUntil('\n');
134             auto firstLine = p.input[p.begin .. firstNewLine >= 0 ? p.begin + firstNewLine : p.end];
135             if (firstLine.length > 0)
136                 file.write(">", firstLine, "<span><pre>", p.input[p.begin .. p.end], "</pre></span></code>");
137             else // Insert the return-symbol so the mouse has something to hover over.
138                 file.write(">&#x23ce;<span><pre>&#x23ce;", p.input[p.begin .. p.end], "</pre></span></code>");
139         }
140 
141         file.write("</summary>\n");
142         foreach (child; p.children)
143             treeToHTML(child);
144         file.write("</details>\n");
145     }
146 
147     treeToHTML(p);
148 
149     file.write(`
150 </body>
151 </html>
152     `);
153 }
154 
155 /**
156  * Writes a parse tree to a html file.
157  *
158  * Params:
159  *      e = Defines if the tree is expanded.
160  *      Details = Defines the details, as a list of strings, that are expanded
161  *          when e is equal to `Expand.yes`, and not exapnded when e is equal to
162  *          `Expand.invert`. When no details are passed, each node is expanded.
163  *      p = The ParseTree to represent.
164  *      filename = The name of file where tree is written.
165  */
166 void toHTML(Expand e = Expand.no, Details...)(const ref ParseTree p,
167     string filename)
168 {
169     if (filename.endsWith(".html", ".htm") == 0)
170         filename ~= ".html";
171     toHTML!(e, Details)(p, File(filename, "w"));
172 }