--- a/src/main.cpp Tue Jan 21 21:01:54 2025 +0100 +++ b/src/main.cpp Fri Jan 31 22:11:04 2025 +0100 @@ -25,12 +25,17 @@ #include "settings.h" #include "repositories.h" #include "process.h" +#include "heatmap.h" +#include "html.h" +#include <chrono> #include <cstdlib> #include <cstdio> #include <cstring> #include <cerrno> +namespace chrono = std::chrono; + static void print_help() { fputs( "Usage: fallusmeter [OPTION]... [PATH]...\n\n" @@ -123,14 +128,6 @@ return 0; } -static void print_html_header() { - puts("<html>\n\t<body>"); -} - -static void print_html_footer() { - puts("\t</body>\n</html>"); -} - int main(int argc, char *argv[]) { // parse settings fm::settings settings; @@ -176,11 +173,107 @@ } } } - // TODO: calculate the heat maps + + // determine our reporting range + int year; + if (settings.year == fm::settings_current_year) { + year = static_cast<int>(chrono::year_month_day{chrono::floor<chrono::days>(chrono::system_clock::now())}.year()); + } else { + year = settings.year; + } + chrono::year_month_day report_begin{chrono::year{year}, chrono::month{1}, chrono::day{1}}; + chrono::year_month_day report_end{chrono::year{year}, chrono::month{12}, chrono::day{31}}; + + // read the commit logs + fm::heatmap heatmap; + for (auto &&repo : repos.list()) { + if (settings.separate) { + heatmap.set_repo(repo.path); + } + proc.chdir(repo.path); + if (repo.type == fm::HG) { + proc.setbin(settings.hg); + if (proc.exec_log({"log", + "--date", std::format("{0}-01-01 to {0}-12-31", year), + "--template", "{author}#{date|shortdate}\n"})) { + fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str()); + return EXIT_FAILURE; + } + heatmap.add(proc.output()); + } else { + proc.setbin(settings.git); + if (proc.exec({"log", + "--since", std::format("{0}-01-01", year), + "--until", std::format("{0}-12-31", year), + "--format=tformat:%an <%ae>#%cs"})) { + fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str()); + return EXIT_FAILURE; + } + heatmap.add(proc.output()); + } + } + + html::open(); + for (const auto &[repo, authors] : heatmap.data()) { + html::h1(repo); + for (const auto &[author, entries] : authors) { + html::h2(author); + html::table_begin(); + + // initialize counters + unsigned column = 0, row = 0; - print_html_header(); - // TODO: output the heat maps here - print_html_footer(); + // initialize first day (which must be a Monday, possibly the year before) + chrono::sys_days day_to_check{report_begin}; + day_to_check -= chrono::days{chrono::weekday{day_to_check}.iso_encoding() - 1}; + + // remember the starting point + auto start = day_to_check; + + // now add all entries for Monday, Tuesdays, etc. always starting back in january + while (true) { + html::row_begin(row); + + // check if we need to add blank cells + while (day_to_check < report_begin) { + html::cell_out_of_range(); + day_to_check += chrono::days{7}; + column++; + } + + while (day_to_check <= report_end) { + // get the entry from the heatmap + auto find_result = entries.find(day_to_check); + if (find_result == entries.end()) { + html::cell(0); + } else { + html::cell(find_result->second); + } + // advance seven days and one column + day_to_check += chrono::days{7}; + column++; + } + // fill remaining columns with blank cells + for (unsigned i = column ; i < html::columns ; i++) { + html::cell_out_of_range(); + } + + // terminate the row + html::row_end(); + + // if we have seen all seven weekdays, that's it + if (++row == 7) break; + + // otherwise, advance the starting point by one day, reset, and begin a new row + start += chrono::days{1}; + day_to_check = start; + column =0; + } + + html::table_end(); + } + } + html::close(); return EXIT_SUCCESS; }