src/html.cpp

Tue, 18 Feb 2025 19:09:02 +0100

author
Mike Becker <universe@uap-core.de>
date
Tue, 18 Feb 2025 19:09:02 +0100
changeset 33
52c1464e27f8
parent 26
0b3a92df96db
child 35
d75805c1e3b9
permissions
-rw-r--r--

Added tag v1.0.0 for changeset d7c22d329045

/* Copyright 2025 Mike Becker. All rights reserved.
*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "html.h"

#include <cstdio>

using namespace std::chrono;

namespace html {
    static constexpr const char* weekdays[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};

    static unsigned indentation;
    static const char *tabs = "                                ";
    static void indent(int change = 0) {
        indentation += change;
        fwrite(tabs, 4, indentation, stdout);
    }

    static std::string encode(const std::string &data) {
        std::string buffer;
        buffer.reserve(data.size()+16);
        for (const char &pos: data) {
            switch (pos) {
                case '&':
                    buffer.append("&amp;");
                    break;
                case '\"':
                    buffer.append("&quot;");
                    break;
                case '\'':
                    buffer.append("&apos;");
                    break;
                case '<':
                    buffer.append("&lt;");
                    break;
                case '>':
                    buffer.append("&gt;");
                    break;
                default:
                    buffer.append(&pos, 1);
                    break;
            }
        }
        return buffer;
    }
}

void html::open(bool fragment) {
    if (fragment) {
        puts("<div class=\"heatmap-content\">");
        indentation = 1;
    } else {
        puts(R"(<html>
    <head>
        <style>
            table.heatmap {
                table-layout: fixed;
                border-collapse: collapse;
                font-family: sans-serif;
            }
            table.heatmap td, table.heatmap th {
                text-align: center;
                border: solid 1px lightgray;
                height: 1.5em;
            }
            table.heatmap td {
                border: solid 1px lightgray;
                width: 1.5em;
                height: 1.5em;
            }
            table.heatmap td.out-of-range {
                background-color: gray;
            }

            table.heatmap td.zero-commits {
                background-color: white;
            }

            table.heatmap td.one-commit {
                background-color: #80E7A0;
            }

            table.heatmap td.up-to-5-commits {
                background-color: #30D350;
            }

            table.heatmap td.up-to-10-commits {
                background-color: #00BF00;
            }

            table.heatmap td.up-to-20-commits {
                background-color: #00A300;
            }

            table.heatmap td.commit-spam {
                background-color: #008000;
            }
        </style>
    </head>
    <body>
        <div class=\"heatmap-content\">)");
        indentation = 3;
    }
}

void html::close(bool fragment) {
    if (fragment) {
        puts("</div>");
    } else {
        puts("        </div>\n    </body>\n</html>");
    }
}

void html::h1(const std::string& heading) {
    indent();
    printf("<h1>%s</h1>\n", encode(heading).c_str());
}

void html::h2(const std::string& heading) {
    indent();
    printf("<h2>%s</h2>\n", encode(heading).c_str());
}

void html::table_begin(year y) {
    static constexpr const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    // compute the column spans, first
    unsigned colspans[12] = {};
    {
        unsigned total_cols = 0;
        sys_days day{year_month_day{y, January, 1d}};
        for (unsigned col = 0; col < 12; ++col) {
            while (total_cols < html::columns && year_month_day{day}.month() <= month{col + 1}) {
                ++total_cols;
                ++colspans[col];
                day += days{7};
            }
        }
    }

    // now render the table heading
    indent();
    puts("<table class=\"heatmap\">");
    indent(1);
    puts("<tr>");
    indent(1);
    puts("<th></th>");
    for (unsigned i = 0 ; i < 12 ; i++) {
        indent();
        printf("<th scope=\"col\" colspan=\"%d\">%s</th>\n", colspans[i], months[i]);
    }
    indent(-1);
    puts("</tr>");
}

void html::table_end() {
    indentation--;
    indent();
    puts("</table>");
}

void html::row_begin(unsigned int row) {
    indent();
    puts("<tr>");
    indent(1);
    printf("<th scope=\"row\">%s</th>\n", weekdays[row]);
}

void html::row_end() {
    indent(-1);
    puts("</tr>");
}

void html::cell_out_of_range() {
    indent();
    puts("<td class=\"out-of-range\"></td>");
}

void html::cell(year_month_day ymd, unsigned commits) {
    const char *color_class;
    if (commits == 0) {
        color_class = "zero-commits";
    } else if (commits == 1) {
        color_class = "one-commit";
    } else if (commits <= 5) {
        color_class = "up-to-5-commits";
    } else if (commits <= 10) {
        color_class = "up-to-10-commits";
    } else if (commits <= 20) {
        color_class = "up-to-20-commits";
    } else {
        color_class = "commit-spam";
    }
    indent();
    printf("<td class=\"%s\" title=\"%s, %d-%02u-%02u: %u commits\"></td>\n",
        color_class,
        weekdays[weekday(ymd).iso_encoding() - 1],
        static_cast<int>(ymd.year()),
        static_cast<unsigned>(ymd.month()),
        static_cast<unsigned>(ymd.day()),
        commits);
}

mercurial