implement authormap

Sat, 01 Feb 2025 16:01:14 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 01 Feb 2025 16:01:14 +0100
changeset 16
730a5638c4ad
parent 15
ef0f2497843e
child 17
bb4e34e77974

implement authormap

src/heatmap.cpp file | annotate | diff | comparison | revisions
src/heatmap.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/settings.cpp file | annotate | diff | comparison | revisions
src/settings.h file | annotate | diff | comparison | revisions
--- a/src/heatmap.cpp	Sat Feb 01 15:42:48 2025 +0100
+++ b/src/heatmap.cpp	Sat Feb 01 16:01:14 2025 +0100
@@ -30,13 +30,13 @@
 
 namespace chrono = std::chrono;
 
-void fm::heatmap::add(const std::string &log) {
+void fm::heatmap::add(const fm::settings &settings, const std::string &log) {
     using std::string_view_literals::operator ""sv;
 
     for (auto &&line: std::views::split(log, "\n"sv)) {
         if (line.empty()) continue;
         auto parts = std::views::split(line, "#"sv).begin();
-        std::string author{(*parts).begin(), (*parts).end()};
+        std::string author{settings.map_author({(*parts).begin(), (*parts).end()})};
 
         int year = 0;
         unsigned int month = 0, day = 0;
--- a/src/heatmap.h	Sat Feb 01 15:42:48 2025 +0100
+++ b/src/heatmap.h	Sat Feb 01 16:01:14 2025 +0100
@@ -29,6 +29,8 @@
 #include <string>
 #include <chrono>
 
+#include "settings.h"
+
 namespace fm {
 
 class heatmap {
@@ -46,7 +48,7 @@
     void set_repo(const std::string& repo) {
         m_current_repo.assign(repo);
     }
-    void add(const std::string& log);
+    void add(const settings &settings, const std::string& log);
 
     [[nodiscard]] const auto& data() const {
         return m_heatmap;
--- a/src/main.cpp	Sat Feb 01 15:42:48 2025 +0100
+++ b/src/main.cpp	Sat Feb 01 16:01:14 2025 +0100
@@ -70,11 +70,11 @@
         "to specify a file that contains pairs of author strings, like in the following\n"
         "example:\n\n"
         "   Full Name <full.name@example.org> = New Name <new.name@example.org>\n"
-        "   just.mail@example.org = new.name@example.org\n"
-        "   username = new.name\n\n"
+        "   just.mail@example.org = Jus Mail <just.mail@example.org>\n"
+        "   jane = Jane Doe <jane.doe@example.org>\n\n"
         "The different variants of full string, only mail address, and only local-part\n"
-        "can be combined as you like. When you use the \033[1m--author\033[22m option at the same\n"
-        "time, you only need to specify the new author names.\n\n"
+        "should \033[4monly\033[24m be used on the left-hand side. When you use the \033[1m--author\033[22m option at\n"
+        "the same time, you only need to specify the new author names.\n\n"
         "Finally, this tool prints an HTML page to stdout. A separate heap map is\n"
         "generated for each author showing commits across all repositories, unless the\n"
         "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n"
@@ -126,6 +126,16 @@
             settings.update_repos = false;
         } else if (chk_arg(argv[i], "-s", "--separate")) {
             settings.separate = true;
+        } else if (chk_arg(argv[i], "-A", "--authormap")) {
+            if (i + 1 < argc) {
+                if (settings.parse_authormap(argv[++i])) {
+                    fputs("parsing authormap failed\n", stderr);
+                    return -1;
+                }
+            } else {
+                fputs("missing filename for authormap\n", stderr);
+                return -1;
+            }
         } else if (chk_arg(argv[i], "--hg", nullptr)) {
             if (i + 1 < argc) {
                 settings.hg.assign(argv[++i]);
@@ -225,7 +235,7 @@
                 fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str());
                 return EXIT_FAILURE;
             }
-            heatmap.add(proc.output());
+            heatmap.add(settings, proc.output());
         } else {
             proc.setbin(settings.git);
             if (proc.exec_log({"log",
@@ -235,7 +245,7 @@
                 fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str());
                 return EXIT_FAILURE;
             }
-            heatmap.add(proc.output());
+            heatmap.add(settings, proc.output());
         }
     }
 
--- a/src/settings.cpp	Sat Feb 01 15:42:48 2025 +0100
+++ b/src/settings.cpp	Sat Feb 01 16:01:14 2025 +0100
@@ -26,24 +26,75 @@
 
 #include <format>
 #include <algorithm>
+#include <fstream>
 
 using namespace fm;
 
+static bool check_author(std::string_view author, std::string_view allowed) {
+    // check for exact match
+    if (author == allowed) return true;
+
+    // check mail address matching
+    if (author.contains(std::format("<{}>", allowed))) return true;
+
+    // check local-part from mail address matching
+    if (author.contains(std::format("<{}@", allowed))) return true;
+
+    return false;
+}
+
 [[nodiscard]] bool settings::exclude_author(const std::string &author) const {
     // no allow-list means: always allowed
     if (authors.empty()) return false;
 
     // check all allowed authors
-    return std::ranges::all_of(authors, [&](const auto &allowed) {
-        // check for exact match
-        if (author == allowed) return false;
-
-        // check mail address matching
-        if (author.contains(std::format("<{}>", allowed))) return false;
-
-        // check local-part from mail address matching
-        if (author.contains(std::format("<{}@", allowed))) return false;
-
-        return true;
+    return !std::ranges::any_of(authors, [&](const auto &allowed) {
+        return check_author(author, allowed);
     });
 }
+
+std::string_view trim(const std::string& str) {
+    size_t s = str.find_first_not_of(" \t");
+    size_t l = str.find_last_not_of(" \t") + 1 - s;
+    return std::string_view{str}.substr(s, l);
+}
+
+int settings::parse_authormap(const std::string& path) {
+    std::ifstream file(path);
+
+    if (!file.is_open()) {
+        return -1;
+    }
+
+    std::string line;
+    while (std::getline(file, line)) {
+        line = trim(line);
+
+        // skip empty lines and comments
+        if (line.empty() || line[0] == '#') {
+            continue;
+        }
+
+        // find delimiter
+        size_t delimPos = line.find('=');
+        if (delimPos == std::string::npos) {
+            return -1;
+        }
+
+        auto key = std::string{trim(line.substr(0, delimPos))};
+        auto value = std::string{trim(line.substr(delimPos + 1))};
+        authormap[std::move(key)] = std::move(value);
+    }
+
+    return 0;
+}
+
+std::string settings::map_author(std::string_view author) const {
+    if (authormap.empty()) return std::string{author};
+
+    for (const auto &[old_name, new_name] : authormap) {
+        if (check_author(author, old_name)) return new_name;
+    }
+
+    return std::string{author};
+}
--- a/src/settings.h	Sat Feb 01 15:42:48 2025 +0100
+++ b/src/settings.h	Sat Feb 01 16:01:14 2025 +0100
@@ -28,6 +28,7 @@
 
 #include <string>
 #include <vector>
+#include <unordered_map>
 
 namespace fm {
 
@@ -38,12 +39,17 @@
     std::string git{"/usr/bin/git"};
     std::vector<std::string> paths;
     std::vector<std::string> authors;
+    std::unordered_map<std::string, std::string> authormap;
+
     unsigned char depth = 1;
     bool update_repos = true;
     bool separate = false;
     unsigned short year = settings_current_year;
 
+    int parse_authormap(const std::string& path);
     [[nodiscard]] bool exclude_author(const std::string &author) const;
+
+    std::string map_author(std::string_view author) const;
 };
 
 }

mercurial