aboutsummaryrefslogtreecommitdiff
#!/usr/bin/env python3
#
# InspIRCd -- Internet Relay Chat Daemon
#
#   Copyright (C) 2026 Sadie Powell <sadie@witchery.services>
#
# This file is part of InspIRCd.  InspIRCd is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import os
import pathlib
import re
import shutil
import subprocess
import sys
import textwrap

import columnize
import yaml

ROOT = pathlib.Path(__file__).resolve().parents[1]

sys.path.append(str(ROOT))
from src.python import console

GIT = os.getenv("GIT", "git")
if not shutil.which(GIT):
    console.error(f"Git ({GIT}) must be installed to build the author list!")

commit_log = subprocess.check_output(
    [GIT, "log", "--pretty=%an <%ae>%n%(trailers:key=Co-Authored-By,valueonly)", "HEAD"]
).splitlines()

committers = {}
for committer in [c for c in commit_log if c]:
    committers[committer] = committers.get(committer, 0) + 1

authors = {}
for committer, commits in committers.items():
    author = (
        subprocess.check_output([GIT, "check-mailmap", committer])
        .strip()
        .decode("utf-8")
    )

    # Remove email addresses that aren"t real.
    author = re.sub(
        r"^(.+) <(?:unknown@email.invalid|\S+@users.noreply.github.com)>$",
        r"\1",
        author,
    )

    # Skip GitHub bots like dependabot.
    if author.endswith("[bot]"):
        continue

    # Skip commits made by our own scripts.
    if author == "InspIRCd Robot <noreply@inspircd.org>":
        continue

    authors[author] = authors.get(author, 0) + commits

authors_txt = ROOT / "docs" / "AUTHORS.txt"
with open(authors_txt, "w") as fh:
    print(
        textwrap.dedent(
            f"""
            Since the first commit in January 2003 {len(authors)} people have submitted patches,
            commits, and other useful contributions to InspIRCd. These people, ordered by
            the number of contributions they have made, are:
            """
        ).lstrip(),
        file=fh,
    )

    # We show the names in the authors file ordered by number of commits and
    # then name case insensitively.
    sorted_authors = list(authors.items())
    sorted_authors.sort(key=lambda a: a[0].lower())
    sorted_authors.sort(key=lambda a: a[1], reverse=True)
    for author, commits in sorted_authors:
        print(f"  * {author}", file=fh)

info_lines = []
with open(ROOT / "docs" / "BANNER.txt") as fh:
    for line in fh:
        escaped_line = line.rstrip().replace("\\", r"\\").replace('"', r"\"")
        info_lines.append(escaped_line)

with open(ROOT / "modules" / "core" / "core_info" / "info.yml", "r") as fh:
    seen = []
    for team, members in yaml.safe_load(fh).items():
        info_lines.append(" ")
        info_lines.append(f"\\002{team}\\002:")

        names = []
        if isinstance(members, list):
            for member in members:
                if "name" in member:
                    names.append(f"{member['name']} ({member['nick']})")
                    seen.append(member["name"])
                else:
                    names.append(member["nick"])
        else:
            for author in authors:
                name = re.sub(r"^(.+) <.+>$", r"\1", author)
                if name not in seen:
                    names.append(name)
        names.sort(key=lambda n: n.lower())

        columnized_names = columnize.columnize(
            names,
            arrange_vertical=False,
            colsep=" " * 4,
            displaywidth=78,
        ).splitlines()
        info_lines.extend([f"  {line.strip()}" for line in columnized_names])

info_h = ROOT / "modules" / "core" / "core_info" / "info.h"
with open(info_h, "w") as fh:
    print(
        textwrap.dedent(
            """
            // This file was generated by mkauthors. Any changes will be overwritten.
            #pragma once
            static constexpr const char* const lines[] = {
            """
        ).strip("\n"),
        file=fh,
    )

    for info_line in info_lines:
        print(f'\t"{info_line}",', file=fh)

    print(
        textwrap.dedent(
            """
            \t" ",
            \t"For more information visit https://www.inspircd.org/ or our IRC channel at",
            \t"ircs://irc.teranova.net/inspircd.",
            \tnullptr,
            };
            """
        ).strip("\n"),
        file=fh,
    )

if int(os.getenv("MKAUTHORS_COMMIT", "1")) > 0:
    subprocess.check_call(
        [
            GIT,
            "commit",
            "--author", "InspIRCd Robot <noreply@inspircd.org>",
            "--message", "Update the author list.",
            "--",
            authors_txt,
            info_h,
        ]
    )