1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-06-26 16:45:52 +00:00
Radicale/documentation-generator/run.py

188 lines
7.1 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
Documentation generator
Generates the documentation for every Git branch and commits it.
Gracefully handles conflicting commits.
"""
import contextlib
import glob
2021-12-01 22:48:51 +01:00
import json
import os
import re
import shutil
import subprocess
import sys
2021-12-01 22:48:51 +01:00
import urllib.parse
from tempfile import NamedTemporaryFile, TemporaryDirectory
REMOTE = "origin"
GIT_CONFIG = {"protocol.version": "2",
"user.email": "<>",
"user.name": "Github Actions"}
COMMIT_MESSAGE = "Generate documentation"
DOCUMENTATION_SRC = "DOCUMENTATION.md"
2021-12-01 22:48:51 +01:00
REDIRECT_CONFIG_PATH = "redirect.json"
2021-12-14 00:23:13 +01:00
TOC_DEPTH = 4
TOOLS_PATH = os.path.dirname(__file__)
2021-12-01 22:48:51 +01:00
REDIRECT_TEMPLATE_PATH = os.path.join(TOOLS_PATH, "template-redirect.html")
TEMPLATE_PATH = os.path.join(TOOLS_PATH, "template.html")
FILTER_EXE = os.path.join(TOOLS_PATH, "filter.py")
POSTPROCESSOR_EXE = os.path.join(TOOLS_PATH, "postprocessor.py")
PANDOC_EXE = "pandoc"
2020-02-27 14:18:57 +01:00
BRANCH_ORDERING = [ # Format: (REGEX, ORDER, DEFAULT)
2021-12-05 02:59:43 +01:00
(r"v?\d+(?:\.\d+)*(?:\.x)*", 0, True),
(r".*", 1, False)]
PROG = "documentation-generator"
2025-03-30 08:50:54 +02:00
VENV_EXECUTABLE = "venv/bin/python3"
def convert_doc(src_path, to_path, branch, branches):
with NamedTemporaryFile(mode="w", prefix="%s-" % PROG,
suffix=".json") as metadata_file:
json.dump({
"document-css": False,
"branch": branch,
2021-12-05 14:06:59 +01:00
"branches": [{"name": b,
"href": urllib.parse.quote_plus("%s.html" % b),
2021-12-05 14:06:59 +01:00
"default": b == branch}
for b in reversed(branches)]}, metadata_file)
metadata_file.flush()
raw_html = subprocess.run([
PANDOC_EXE,
"--sandbox",
"--from=gfm",
"--to=html5",
os.path.abspath(src_path),
"--toc",
"--template=%s" % os.path.basename(TEMPLATE_PATH),
"--metadata-file=%s" % os.path.abspath(metadata_file.name),
"--section-divs",
2021-12-14 00:23:13 +01:00
"--toc-depth=%d" % TOC_DEPTH,
"--filter=%s" % os.path.abspath(FILTER_EXE)],
cwd=os.path.dirname(TEMPLATE_PATH),
stdout=subprocess.PIPE, check=True).stdout
2025-03-30 08:50:54 +02:00
raw_html = subprocess.run([VENV_EXECUTABLE, POSTPROCESSOR_EXE], input=raw_html,
stdout=subprocess.PIPE, check=True).stdout
2021-03-23 00:12:42 +01:00
with open(to_path, "wb") as f:
f.write(raw_html)
def install_dependencies():
2025-03-30 08:50:54 +02:00
subprocess.run(["sudo", "apt", "install", "--assume-yes", "python3-pip"], check=True)
subprocess.run(["sudo", "apt", "install", "--assume-yes", "python3-venv"], check=True)
subprocess.run([sys.executable, "-m", "venv", "venv"], check=True),
subprocess.run([VENV_EXECUTABLE, "-m", "pip", "install", "beautifulsoup4"], check=True)
2025-03-30 08:44:06 +02:00
subprocess.run(["sudo", "apt", "install", "--assume-yes", "pandoc"], check=True)
def natural_sort_key(s):
# https://stackoverflow.com/a/16090640
return [int(part) if part.isdigit() else part.lower()
for part in re.split(r"(\d+)", s)]
2020-02-27 14:18:57 +01:00
def sort_branches(branches):
branches = list(branches)
order_least = min(order for _, order, _ in BRANCH_ORDERING) - 1
for i, branch in enumerate(branches):
for regex, order, default in BRANCH_ORDERING:
if re.fullmatch(regex, branch):
branches[i] = (order, natural_sort_key(branch), default,
branch)
break
else:
branches[i] = (order_least, natural_sort_key(branch), False,
branch)
branches.sort()
default_branch = [
None, *(branch for _, _, _, branch in branches),
*(branch for _, _, default, branch in branches if default)][-1]
return [branch for _, _, _, branch in branches], default_branch
def run_git(*args):
config_args = []
for key, value in GIT_CONFIG.items():
config_args.extend(["-c", "%s=%s" % (key, value)])
output = subprocess.run(["git", *config_args, *args],
stdout=subprocess.PIPE, check=True,
universal_newlines=True).stdout
return tuple(filter(None, output.split("\n")))
def checkout(branch):
run_git("checkout", "--progress", "--force", "-B", branch,
"refs/remotes/%s/%s" % (REMOTE, branch))
def run_git_fetch_and_restart_if_changed(remote_commits, target_branch):
run_git("fetch", "--no-tags", "--prune", "--progress",
"--no-recurse-submodules", "--depth=1", REMOTE,
"+refs/heads/*:refs/remotes/%s/*" % REMOTE)
if remote_commits != run_git("rev-parse", "--remotes=%s" % REMOTE):
checkout(target_branch)
print("Remote changed, restarting", file=sys.stderr)
os.execv(__file__, sys.argv)
def main():
if os.environ.get("GITHUB_ACTIONS", "") == "true":
install_dependencies()
target_branch, = run_git("rev-parse", "--abbrev-ref", "HEAD")
remote_commits = run_git("rev-parse", "--remotes=%s" % REMOTE)
run_git_fetch_and_restart_if_changed(remote_commits, target_branch)
branches = [ref[len("refs/remotes/%s/" % REMOTE):] for ref in run_git(
"rev-parse", "--symbolic-full-name", "--remotes=%s" % REMOTE)]
2025-04-13 00:03:55 +02:00
branches = list(set(branches))
with TemporaryDirectory(prefix="%s-" % PROG) as temp:
branch_docs = {}
for branch in branches[:]:
checkout(branch)
if os.path.exists(DOCUMENTATION_SRC):
branch_docs[branch] = os.path.join(temp, "%s.md" % branch)
shutil.copy(DOCUMENTATION_SRC, branch_docs[branch])
else:
branches.remove(branch)
checkout(target_branch)
2020-03-02 03:04:37 +01:00
for path in glob.iglob("*.html"):
2020-03-01 01:30:05 +01:00
run_git("rm", "--", path)
2020-02-27 14:18:57 +01:00
branches, default_branch = sort_branches(branches)
for branch, src_path in branch_docs.items():
2021-12-02 00:48:14 +01:00
to_path = "%s.html" % branch
convert_doc(src_path, to_path, branch, branches)
run_git("add", "--", to_path)
2021-12-01 22:48:51 +01:00
try:
with open(REDIRECT_CONFIG_PATH) as f:
redirect_config = json.load(f)
except FileNotFoundError:
redirect_config = {}
with open(REDIRECT_TEMPLATE_PATH) as f:
redirect_template = f.read()
for source, target in redirect_config.items():
if target == ":DEFAULT_BRANCH:":
if default_branch is None:
raise RuntimeError("no default branch")
2021-12-02 00:48:14 +01:00
target = default_branch
2021-12-01 22:48:51 +01:00
source_path = "%s.html" % str(source)
2021-12-05 02:59:43 +01:00
target_url = urllib.parse.quote_plus("%s.html" % str(target))
2021-12-01 22:48:51 +01:00
with open(source_path, "w") as f:
f.write(redirect_template.format(target=target_url))
run_git("add", "--", source_path)
with contextlib.suppress(subprocess.CalledProcessError):
run_git("diff", "--cached", "--quiet")
print("No changes", file=sys.stderr)
return
run_git("commit", "-m", COMMIT_MESSAGE)
try:
run_git("push", REMOTE, "HEAD:%s" % target_branch)
except subprocess.CalledProcessError:
run_git_fetch_and_restart_if_changed(remote_commits, target_branch)
raise
if __name__ == "__main__":
main()