Fri, 31 Jan 2025 22:11:04 +0100
finish MVP
5 | 1 | /* Copyright 2025 Mike Becker. All rights reserved. |
2 | * | |
3 | * Redistribution and use in source and binary forms, with or without | |
4 | * modification, are permitted provided that the following conditions are met: | |
5 | * | |
6 | * 1. Redistributions of source code must retain the above copyright | |
7 | * notice, this list of conditions and the following disclaimer. | |
8 | * | |
9 | * 2. Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * | |
13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
20 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
21 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
22 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | */ | |
24 | ||
25 | #include "html.h" | |
26 | ||
27 | #include <cstdio> | |
28 | ||
29 | namespace html { | |
30 | static unsigned indentation; | |
31 | static const char *tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; | |
32 | static void indent(int change = 0) { | |
33 | indentation += change; | |
34 | fwrite(tabs, 1, indentation, stdout); | |
35 | } | |
36 | ||
37 | static std::string encode(const std::string &data) { | |
38 | std::string buffer; | |
39 | buffer.reserve(data.size()+16); | |
40 | for (const char &pos: data) { | |
41 | switch (pos) { | |
42 | case '&': | |
43 | buffer.append("&"); | |
44 | break; | |
45 | case '\"': | |
46 | buffer.append("""); | |
47 | break; | |
48 | case '\'': | |
49 | buffer.append("'"); | |
50 | break; | |
51 | case '<': | |
52 | buffer.append("<"); | |
53 | break; | |
54 | case '>': | |
55 | buffer.append(">"); | |
56 | break; | |
57 | default: | |
58 | buffer.append(&pos, 1); | |
59 | break; | |
60 | } | |
61 | } | |
62 | return buffer; | |
63 | } | |
64 | } | |
65 | ||
66 | void html::open() { | |
67 | puts( | |
68 | R"(<html> | |
69 | <head> | |
70 | <style> | |
71 | table.heatmap { | |
72 | table-layout: fixed; | |
73 | border-collapse: collapse; | |
74 | font-family: sans-serif; | |
75 | } | |
76 | table.heatmap td, table.heatmap th { | |
77 | text-align: center; | |
78 | border: solid 1px lightgray; | |
79 | height: 1.5em; | |
80 | } | |
81 | table.heatmap td { | |
82 | border: solid 1px lightgray; | |
83 | width: 1.5em; | |
84 | height: 1.5em; | |
85 | } | |
86 | table.heatmap td.out-of-range { | |
87 | background-color: gray; | |
88 | } | |
89 | </style> | |
90 | </head> | |
91 | <body>)"); | |
92 | indentation = 2; | |
93 | } | |
94 | ||
95 | void html::close() { | |
96 | puts("\t</body>\n</html>"); | |
97 | } | |
98 | ||
99 | void html::h1(const std::string& heading) { | |
100 | indent(); | |
101 | printf("<h1>%s</h1>\n", encode(heading).c_str()); | |
102 | } | |
103 | ||
104 | void html::h2(const std::string& heading) { | |
105 | indent(); | |
106 | printf("<h2>%s</h2>\n", encode(heading).c_str()); | |
107 | } | |
108 | ||
109 | void html::table_begin() { | |
110 | indent(); | |
111 | puts("<table class=\"heatmap\">"); | |
112 | indent(1); | |
113 | puts("<tr>"); | |
114 | indent(1); | |
115 | puts("<th></th>"); | |
116 | static constexpr const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; | |
117 | static constexpr int colspans[] = {5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 5}; | |
118 | for (unsigned i = 0 ; i < 12 ; i++) { | |
119 | indent(); | |
120 | printf("<th scope=\"col\" colspan=\"%d\">%s</th>\n", colspans[i], months[i]); | |
121 | } | |
122 | indent(-1); | |
123 | puts("</tr>"); | |
124 | } | |
125 | ||
126 | void html::table_end() { | |
127 | indentation--; | |
128 | indent(); | |
129 | puts("</table>"); | |
130 | } | |
131 | ||
132 | void html::row_begin(unsigned int row) { | |
133 | static constexpr const char* weekdays[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; | |
134 | indent(); | |
135 | puts("<tr>"); | |
136 | indent(1); | |
137 | printf("<th scope=\"row\">%s</th>\n", weekdays[row]); | |
138 | } | |
139 | ||
140 | void html::row_end() { | |
141 | indent(-1); | |
142 | puts("</tr>"); | |
143 | } | |
144 | ||
145 | void html::cell_out_of_range() { | |
146 | indent(); | |
147 | puts("<td class=\"out-of-range\"></td>"); | |
148 | } | |
149 | ||
150 | void html::cell(unsigned commits) { | |
151 | indent(); | |
152 | // TODO: use nice coloring and tooltips instead of printing the commits | |
153 | printf("<td>%u</td>\n", commits); | |
154 | } |