Mon, 19 May 2025 16:05:58 +0200
add custom fragment indentation
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> | |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
28 | #include <cassert> |
5 | 29 | |
13 | 30 | using namespace std::chrono; |
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; |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
36 | static const char *tabs = " "; |
5 | 37 | static void indent(int change = 0) { |
38 | indentation += change; | |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
39 | assert(indentation >= 0); |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
40 | assert(indentation <= max_indentation); |
22
a9230f197e61
fix inconsistent use of tabs and spaces in indentation
Mike Becker <universe@uap-core.de>
parents:
20
diff
changeset
|
41 | fwrite(tabs, 4, indentation, stdout); |
5 | 42 | } |
43 | ||
44 | static std::string encode(const std::string &data) { | |
45 | std::string buffer; | |
46 | buffer.reserve(data.size()+16); | |
47 | for (const char &pos: data) { | |
48 | switch (pos) { | |
49 | case '&': | |
50 | buffer.append("&"); | |
51 | break; | |
52 | case '\"': | |
53 | buffer.append("""); | |
54 | break; | |
55 | case '\'': | |
56 | buffer.append("'"); | |
57 | break; | |
58 | case '<': | |
59 | buffer.append("<"); | |
60 | break; | |
61 | case '>': | |
62 | buffer.append(">"); | |
63 | break; | |
64 | default: | |
65 | buffer.append(&pos, 1); | |
66 | break; | |
67 | } | |
68 | } | |
69 | return buffer; | |
70 | } | |
71 | } | |
72 | ||
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
73 | void html::open(bool fragment, unsigned char fragment_indent) { |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
74 | if (fragment) { |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
75 | indent(fragment_indent); |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
76 | puts("<div class=\"heatmap-content\">"); |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
77 | indentation++; |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
78 | } else { |
42 | 79 | puts(R"(<!DOCTYPE html> |
80 | <html> | |
5 | 81 | <head> |
82 | <style> | |
83 | table.heatmap { | |
84 | table-layout: fixed; | |
85 | border-collapse: collapse; | |
86 | font-family: sans-serif; | |
87 | } | |
88 | table.heatmap td, table.heatmap th { | |
89 | text-align: center; | |
90 | border: solid 1px lightgray; | |
91 | height: 1.5em; | |
92 | } | |
93 | table.heatmap td { | |
94 | border: solid 1px lightgray; | |
95 | width: 1.5em; | |
96 | height: 1.5em; | |
97 | } | |
98 | table.heatmap td.out-of-range { | |
99 | background-color: gray; | |
100 | } | |
7 | 101 | |
102 | table.heatmap td.zero-commits { | |
103 | background-color: white; | |
104 | } | |
105 | ||
106 | table.heatmap td.one-commit { | |
107 | background-color: #80E7A0; | |
108 | } | |
109 | ||
110 | table.heatmap td.up-to-5-commits { | |
111 | background-color: #30D350; | |
112 | } | |
113 | ||
114 | table.heatmap td.up-to-10-commits { | |
115 | background-color: #00BF00; | |
116 | } | |
117 | ||
118 | table.heatmap td.up-to-20-commits { | |
119 | background-color: #00A300; | |
120 | } | |
121 | ||
122 | table.heatmap td.commit-spam { | |
123 | background-color: #008000; | |
124 | } | |
5 | 125 | </style> |
126 | </head> | |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
127 | <body> |
41
19cc90878968
fix wrong escape in raw string
Mike Becker <universe@uap-core.de>
parents:
35
diff
changeset
|
128 | <div class="heatmap-content">)"); |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
129 | indentation = 3; |
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
130 | } |
5 | 131 | } |
132 | ||
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
133 | void html::close(bool fragment) { |
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
134 | if (fragment) { |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
50
diff
changeset
|
135 | indent(-1); |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
136 | puts("</div>"); |
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
137 | } else { |
22
a9230f197e61
fix inconsistent use of tabs and spaces in indentation
Mike Becker <universe@uap-core.de>
parents:
20
diff
changeset
|
138 | puts(" </div>\n </body>\n</html>"); |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
15
diff
changeset
|
139 | } |
5 | 140 | } |
141 | ||
46
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
142 | void html::chart_begin(const std::string& repo, const std::string& author) { |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
143 | indent(); |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
144 | printf("<div class=\"chart\" data-repo=\"%s\" data-author=\"%s\">\n", |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
145 | encode(repo).c_str(), encode(author).c_str()); |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
146 | indentation++; |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
147 | } |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
148 | |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
149 | void html::chart_end() { |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
150 | indent(-1); |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
151 | puts("</div>"); |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
152 | } |
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
153 | |
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
154 | void html::heading_repo(const std::string& repo) { |
5 | 155 | indent(); |
50
1ebab6df60c2
fix mix of positional and non-positional printf specifiers
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
156 | printf("<h1 data-repo=\"%1$s\">%1$s</h1>\n", encode(repo).c_str()); |
5 | 157 | } |
158 | ||
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
159 | void html::heading_author(const std::string& author, unsigned int total_commits) { |
5 | 160 | indent(); |
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
161 | printf("<h2 title=\"Total commits: %u\">%s</h2>\n", |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
162 | total_commits, |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
163 | encode(author).c_str()); |
5 | 164 | } |
165 | ||
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
166 | void html::table_begin(year y, const std::array<unsigned int, 12> &commits_per_month) { |
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
167 | 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
|
168 | // compute the column spans, first |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
169 | unsigned colspans[12] = {}; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
170 | { |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
171 | unsigned total_cols = 0; |
13 | 172 | sys_days day{year_month_day{y, January, 1d}}; |
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
173 | for (unsigned col = 0; col < 12; ++col) { |
13 | 174 | while (total_cols < html::columns && year_month_day{day}.month() <= month{col + 1}) { |
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
175 | ++total_cols; |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
176 | ++colspans[col]; |
13 | 177 | day += days{7}; |
6
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
178 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
179 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
180 | } |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
181 | |
1040ba37d4c9
improve alignment of month headers
Mike Becker <universe@uap-core.de>
parents:
5
diff
changeset
|
182 | // now render the table heading |
5 | 183 | indent(); |
184 | puts("<table class=\"heatmap\">"); | |
185 | indent(1); | |
186 | puts("<tr>"); | |
187 | indent(1); | |
188 | puts("<th></th>"); | |
189 | for (unsigned i = 0 ; i < 12 ; i++) { | |
190 | indent(); | |
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
191 | printf("<th scope=\"col\" title=\"Total commits: %u\" colspan=\"%d\">%s</th>\n", |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
42
diff
changeset
|
192 | commits_per_month[i], colspans[i], months[i]); |
5 | 193 | } |
194 | indent(-1); | |
195 | puts("</tr>"); | |
196 | } | |
197 | ||
198 | void html::table_end() { | |
46
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
199 | indent(-1); |
5 | 200 | puts("</table>"); |
201 | } | |
202 | ||
203 | void html::row_begin(unsigned int row) { | |
204 | indent(); | |
205 | puts("<tr>"); | |
206 | indent(1); | |
207 | printf("<th scope=\"row\">%s</th>\n", weekdays[row]); | |
208 | } | |
209 | ||
210 | void html::row_end() { | |
211 | indent(-1); | |
212 | puts("</tr>"); | |
213 | } | |
214 | ||
215 | void html::cell_out_of_range() { | |
216 | indent(); | |
217 | puts("<td class=\"out-of-range\"></td>"); | |
218 | } | |
219 | ||
13 | 220 | void html::cell(year_month_day ymd, unsigned commits) { |
7 | 221 | const char *color_class; |
222 | if (commits == 0) { | |
223 | color_class = "zero-commits"; | |
224 | } else if (commits == 1) { | |
225 | color_class = "one-commit"; | |
226 | } else if (commits <= 5) { | |
227 | color_class = "up-to-5-commits"; | |
228 | } else if (commits <= 10) { | |
229 | color_class = "up-to-10-commits"; | |
230 | } else if (commits <= 20) { | |
231 | color_class = "up-to-20-commits"; | |
232 | } else { | |
233 | color_class = "commit-spam"; | |
234 | } | |
5 | 235 | indent(); |
35
d75805c1e3b9
fix "1 commits" fixes #601
Mike Becker <universe@uap-core.de>
parents:
26
diff
changeset
|
236 | printf("<td class=\"%s\" title=\"%s, %d-%02u-%02u: %u %s\"></td>\n", |
7 | 237 | color_class, |
13 | 238 | weekdays[weekday(ymd).iso_encoding() - 1], |
7 | 239 | static_cast<int>(ymd.year()), |
240 | static_cast<unsigned>(ymd.month()), | |
241 | static_cast<unsigned>(ymd.day()), | |
35
d75805c1e3b9
fix "1 commits" fixes #601
Mike Becker <universe@uap-core.de>
parents:
26
diff
changeset
|
242 | commits, |
d75805c1e3b9
fix "1 commits" fixes #601
Mike Becker <universe@uap-core.de>
parents:
26
diff
changeset
|
243 | commits == 1 ? "commit" : "commits"); |
5 | 244 | } |