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: 
50diff
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: 
5diff
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: 
50diff
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: 
50diff
changeset | 39 | assert(indentation >= 0); | 
| 
e9edc3bd0301
add custom fragment indentation
 Mike Becker <universe@uap-core.de> parents: 
50diff
changeset | 40 | assert(indentation <= max_indentation); | 
| 22 
a9230f197e61
fix inconsistent use of tabs and spaces in indentation
 Mike Becker <universe@uap-core.de> parents: 
20diff
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: 
50diff
changeset | 73 | void html::open(bool fragment, unsigned char fragment_indent) { | 
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 74 | if (fragment) { | 
| 52 
e9edc3bd0301
add custom fragment indentation
 Mike Becker <universe@uap-core.de> parents: 
50diff
changeset | 75 | indent(fragment_indent); | 
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 76 | puts("<div class=\"heatmap-content\">"); | 
| 52 
e9edc3bd0301
add custom fragment indentation
 Mike Becker <universe@uap-core.de> parents: 
50diff
changeset | 77 | indentation++; | 
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
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: 
15diff
changeset | 127 | <body> | 
| 41 
19cc90878968
fix wrong escape in raw string
 Mike Becker <universe@uap-core.de> parents: 
35diff
changeset | 128 | <div class="heatmap-content">)"); | 
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 129 | indentation = 3; | 
| 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 130 | } | 
| 5 | 131 | } | 
| 132 | ||
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 133 | void html::close(bool fragment) { | 
| 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 134 | if (fragment) { | 
| 52 
e9edc3bd0301
add custom fragment indentation
 Mike Becker <universe@uap-core.de> parents: 
50diff
changeset | 135 | indent(-1); | 
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 136 | puts("</div>"); | 
| 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 137 | } else { | 
| 22 
a9230f197e61
fix inconsistent use of tabs and spaces in indentation
 Mike Becker <universe@uap-core.de> parents: 
20diff
changeset | 138 | puts(" </div>\n </body>\n</html>"); | 
| 20 
8639ccd855ba
implement --fragment option
 Mike Becker <universe@uap-core.de> parents: 
15diff
changeset | 139 | } | 
| 5 | 140 | } | 
| 141 | ||
| 46 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
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: 
44diff
changeset | 143 | indent(); | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
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: 
44diff
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: 
44diff
changeset | 146 | indentation++; | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 147 | } | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 148 | |
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 149 | void html::chart_end() { | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 150 | indent(-1); | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 151 | puts("</div>"); | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 152 | } | 
| 
7e099403e5b0
make charts identifiable with a query - fixes #608
 Mike Becker <universe@uap-core.de> parents: 
44diff
changeset | 153 | |
| 44 
de22ded6d50a
add total commits counters
 Mike Becker <universe@uap-core.de> parents: 
42diff
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: 
46diff
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: 
42diff
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: 
42diff
changeset | 161 | printf("<h2 title=\"Total commits: %u\">%s</h2>\n", | 
| 
de22ded6d50a
add total commits counters
 Mike Becker <universe@uap-core.de> parents: 
42diff
changeset | 162 | total_commits, | 
| 
de22ded6d50a
add total commits counters
 Mike Becker <universe@uap-core.de> parents: 
42diff
changeset | 163 | encode(author).c_str()); | 
| 5 | 164 | } | 
| 165 | ||
| 44 
de22ded6d50a
add total commits counters
 Mike Becker <universe@uap-core.de> parents: 
42diff
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: 
5diff
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: 
5diff
changeset | 168 | // compute the column spans, first | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 169 | unsigned colspans[12] = {}; | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 170 | { | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
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: 
5diff
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: 
5diff
changeset | 175 | ++total_cols; | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 176 | ++colspans[col]; | 
| 13 | 177 | day += days{7}; | 
| 6 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 178 | } | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 179 | } | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 180 | } | 
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
changeset | 181 | |
| 
1040ba37d4c9
improve alignment of month headers
 Mike Becker <universe@uap-core.de> parents: 
5diff
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: 
42diff
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: 
42diff
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: 
44diff
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: 
26diff
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: 
26diff
changeset | 242 | commits, | 
| 
d75805c1e3b9
fix "1 commits" fixes #601
 Mike Becker <universe@uap-core.de> parents: 
26diff
changeset | 243 | commits == 1 ? "commit" : "commits"); | 
| 5 | 244 | } |