Sat, 01 Feb 2025 14:04:19 +0100
simplify day and month literals
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 | ||
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
29 | namespace chrono = std::chrono; |
11
127fb6a8f706
simplify day and month literals
Mike Becker <universe@uap-core.de>
parents:
7
diff
changeset
|
30 | using chrono::operator ""d; |
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
31 | |
5 | 32 | namespace html { |
7 | 33 | static constexpr const char* weekdays[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; |
34 | ||
5 | 35 | static unsigned indentation; |
36 | static const char *tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; | |
37 | static void indent(int change = 0) { | |
38 | indentation += change; | |
39 | fwrite(tabs, 1, indentation, stdout); | |
40 | } | |
41 | ||
42 | static std::string encode(const std::string &data) { | |
43 | std::string buffer; | |
44 | buffer.reserve(data.size()+16); | |
45 | for (const char &pos: data) { | |
46 | switch (pos) { | |
47 | case '&': | |
48 | buffer.append("&"); | |
49 | break; | |
50 | case '\"': | |
51 | buffer.append("""); | |
52 | break; | |
53 | case '\'': | |
54 | buffer.append("'"); | |
55 | break; | |
56 | case '<': | |
57 | buffer.append("<"); | |
58 | break; | |
59 | case '>': | |
60 | buffer.append(">"); | |
61 | break; | |
62 | default: | |
63 | buffer.append(&pos, 1); | |
64 | break; | |
65 | } | |
66 | } | |
67 | return buffer; | |
68 | } | |
69 | } | |
70 | ||
71 | void html::open() { | |
72 | puts( | |
73 | R"(<html> | |
74 | <head> | |
75 | <style> | |
76 | table.heatmap { | |
77 | table-layout: fixed; | |
78 | border-collapse: collapse; | |
79 | font-family: sans-serif; | |
80 | } | |
81 | table.heatmap td, table.heatmap th { | |
82 | text-align: center; | |
83 | border: solid 1px lightgray; | |
84 | height: 1.5em; | |
85 | } | |
86 | table.heatmap td { | |
87 | border: solid 1px lightgray; | |
88 | width: 1.5em; | |
89 | height: 1.5em; | |
90 | } | |
91 | table.heatmap td.out-of-range { | |
92 | background-color: gray; | |
93 | } | |
7 | 94 | |
95 | table.heatmap td.zero-commits { | |
96 | background-color: white; | |
97 | } | |
98 | ||
99 | table.heatmap td.one-commit { | |
100 | background-color: #80E7A0; | |
101 | } | |
102 | ||
103 | table.heatmap td.up-to-5-commits { | |
104 | background-color: #30D350; | |
105 | } | |
106 | ||
107 | table.heatmap td.up-to-10-commits { | |
108 | background-color: #00BF00; | |
109 | } | |
110 | ||
111 | table.heatmap td.up-to-20-commits { | |
112 | background-color: #00A300; | |
113 | } | |
114 | ||
115 | table.heatmap td.commit-spam { | |
116 | background-color: #008000; | |
117 | } | |
5 | 118 | </style> |
119 | </head> | |
120 | <body>)"); | |
121 | indentation = 2; | |
122 | } | |
123 | ||
124 | void html::close() { | |
125 | puts("\t</body>\n</html>"); | |
126 | } | |
127 | ||
128 | void html::h1(const std::string& heading) { | |
129 | indent(); | |
130 | printf("<h1>%s</h1>\n", encode(heading).c_str()); | |
131 | } | |
132 | ||
133 | void html::h2(const std::string& heading) { | |
134 | indent(); | |
135 | printf("<h2>%s</h2>\n", encode(heading).c_str()); | |
136 | } | |
137 | ||
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
138 | void html::table_begin(int year) { |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
139 | static constexpr const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
140 | // compute the column spans, first |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
141 | unsigned colspans[12] = {}; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
142 | { |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
143 | unsigned total_cols = 0; |
11
127fb6a8f706
simplify day and month literals
Mike Becker <universe@uap-core.de>
parents:
7
diff
changeset
|
144 | chrono::sys_days day{chrono::year_month_day{chrono::year{year}, chrono::January, 1d}}; |
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
145 | if (chrono::weekday{day}.iso_encoding() != 1) { |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
146 | colspans[0] = 1; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
147 | total_cols = 1; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
148 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
149 | for (unsigned col = 0; col < 12; ++col) { |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
150 | while (total_cols < html::columns && chrono::year_month_day{day}.month() <= chrono::month{col + 1}) { |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
151 | ++total_cols; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
152 | ++colspans[col]; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
153 | day += chrono::days{7}; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
154 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
155 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
156 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
157 | |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
158 | // now render the table heading |
5 | 159 | indent(); |
160 | puts("<table class=\"heatmap\">"); | |
161 | indent(1); | |
162 | puts("<tr>"); | |
163 | indent(1); | |
164 | puts("<th></th>"); | |
165 | for (unsigned i = 0 ; i < 12 ; i++) { | |
166 | indent(); | |
167 | printf("<th scope=\"col\" colspan=\"%d\">%s</th>\n", colspans[i], months[i]); | |
168 | } | |
169 | indent(-1); | |
170 | puts("</tr>"); | |
171 | } | |
172 | ||
173 | void html::table_end() { | |
174 | indentation--; | |
175 | indent(); | |
176 | puts("</table>"); | |
177 | } | |
178 | ||
179 | void html::row_begin(unsigned int row) { | |
180 | indent(); | |
181 | puts("<tr>"); | |
182 | indent(1); | |
183 | printf("<th scope=\"row\">%s</th>\n", weekdays[row]); | |
184 | } | |
185 | ||
186 | void html::row_end() { | |
187 | indent(-1); | |
188 | puts("</tr>"); | |
189 | } | |
190 | ||
191 | void html::cell_out_of_range() { | |
192 | indent(); | |
193 | puts("<td class=\"out-of-range\"></td>"); | |
194 | } | |
195 | ||
7 | 196 | void html::cell(std::chrono::year_month_day ymd, unsigned commits) { |
197 | const char *color_class; | |
198 | if (commits == 0) { | |
199 | color_class = "zero-commits"; | |
200 | } else if (commits == 1) { | |
201 | color_class = "one-commit"; | |
202 | } else if (commits <= 5) { | |
203 | color_class = "up-to-5-commits"; | |
204 | } else if (commits <= 10) { | |
205 | color_class = "up-to-10-commits"; | |
206 | } else if (commits <= 20) { | |
207 | color_class = "up-to-20-commits"; | |
208 | } else { | |
209 | color_class = "commit-spam"; | |
210 | } | |
5 | 211 | indent(); |
7 | 212 | printf("<td class=\"%s\" title=\"%s, %d-%u-%u: %u commits\"></td>\n", |
213 | color_class, | |
214 | weekdays[chrono::weekday(ymd).iso_encoding() - 1], | |
215 | static_cast<int>(ymd.year()), | |
216 | static_cast<unsigned>(ymd.month()), | |
217 | static_cast<unsigned>(ymd.day()), | |
218 | commits); | |
5 | 219 | } |