evennia/docs/pylib/auto_link_remapper.py
2020-07-27 21:09:13 +02:00

250 lines
8.2 KiB
Python

"""
Build a TOC-tree; Sphinx requires it and this makes it easy to just
add/build/link new files without needing to explicitly add it to a toctree
directive somewhere.
"""
import re
from collections import defaultdict
from sphinx.errors import DocumentError
from pathlib import Path
from os.path import abspath, dirname, join as pathjoin, sep, relpath
_IGNORE_FILES = []
_SOURCEDIR_NAME = "source"
_SOURCE_DIR = pathjoin(dirname(dirname(abspath(__file__))), _SOURCEDIR_NAME)
_TOC_FILE = pathjoin(_SOURCE_DIR, "toc.md")
_NO_REMAP_STARTSWITH = [
"http://",
"https://",
"github:",
"api:",
"feature-request",
"report-bug",
"issue",
"bug-report",
]
TXT_REMAPS = {
"Developer Central": "Evennia Components overview",
"Getting Started": "Setup Quickstart",
}
URL_REMAPS = {
"Developer-Central": "Components/Components-Overview",
"Tutorials": "Howto/Howto-Overview",
"../Howto/Starting/Directory-Overview": "Gamedir-Overview",
"Howto/Starting/Directory-Overview": "Gamedir-Overview",
"Starting/Directory-Overview": "Gamedir-Overview",
"Directory-Overview": "Gamedir-Overview",
"../Setup/Getting-Started": "Setup-Quickstart",
"Setup/Getting-Started": "Setup-Quickstart",
"Getting-Started": "Setup-Quickstart",
"First-Steps-Coding": "Starting-Part1",
"../Howto/Starting/Adding-Command-Tutorial": "Adding-Commands",
"Howto/Starting/Adding-Command-Tutorial": "Adding-Commands",
"Starting/Adding-Command-Tutorial": "Adding-Commands",
"Adding-Command-Tutorial": "Adding-Commands",
"CmdSet": "Command-Sets",
"Spawner": "Spawner-and-Prototypes",
"issue": "github:issue",
"issues": "github:issue",
"bug": "github:issue",
"bug-report": "github:issue",
"./Default-Command-Help": "api:evennia.commands.default#modules",
"../Components/Default-Command-Help": "api:evennia.commands.default#modules",
"../../../Components/Default-Command-Help": "api:evennia.commands.default#modules",
}
_USED_REFS = {}
_CURRFILE = None
def auto_link_remapper():
"""
- Auto-Remaps links to fit with the actual document file structure. Requires
all doc files to have a unique name.
- Creates source/toc.md file
"""
global _CURRFILE
print(" -- Auto-Remapper starting.")
def _get_rel_source_ref(path):
"""Get the path relative the source/ dir"""
pathparts = path.split("/")
# we allow a max of 4 levels of nesting in the source dir
ind = pathparts[-5:].index(_SOURCEDIR_NAME)
# get the part after source/
pathparts = pathparts[-5 + 1 + ind :]
url = "/".join(pathparts)
# get the reference, without .md
url = url.rsplit(".", 1)[0]
return url
toc_map = {}
docref_map = defaultdict(dict)
for path in Path(_SOURCE_DIR).rglob("*.md"):
# find the source/ part of the path and strip it out
if path.name in _IGNORE_FILES:
# this is the name including .md
continue
sourcepath = path.as_posix()
# get name and url relative to source/
fname = path.name.rsplit(".", 1)[0]
src_url = _get_rel_source_ref(sourcepath)
# check for duplicate files
if fname in toc_map:
duplicate_src_url = toc_map[fname]
raise DocumentError(
f" Tried to add {src_url}.md, but a file {duplicate_src_url}.md already exists.\n"
" Evennia's auto-link-corrector does not accept doc-files with the same \n"
" name, even in different folders. Rename one.\n"
)
toc_map[fname] = src_url
# find relative links to all other files
for targetpath in Path(_SOURCE_DIR).rglob("*.md"):
targetname = targetpath.name.rsplit(".", 1)[0]
targetpath = targetpath.as_posix()
url = relpath(targetpath, dirname(sourcepath))
if not "/" in url:
# need to be explicit or there will be link ref collisions between
# e.g. TickerHandler page and TickerHandle api node
url = "./" + url
docref_map[sourcepath][targetname] = url.rsplit(".", 1)[0]
# normal reference-links [txt](urls)
ref_regex = re.compile(
r"\[(?P<txt>[\w -\[\]\`]+?)\]\((?P<url>.+?)\)", re.I + re.S + re.U + re.M
)
# in document references
ref_doc_regex = re.compile(
r"\[(?P<txt>[\w -\`]+?)\]:\s+?(?P<url>.+?)(?=$|\n)", re.I + re.S + re.U + re.M
)
def _sub(match):
# inline reference links
global _USED_REFS
grpdict = match.groupdict()
txt, url = grpdict["txt"], grpdict["url"]
txt = TXT_REMAPS.get(txt, txt)
url = URL_REMAPS.get(url, url)
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
return f"[{txt}]({url})"
if "http" in url and "://" in url:
urlout = url
else:
fname, *part = url.rsplit("/", 1)
fname = part[0] if part else fname
fname = fname.rsplit(".", 1)[0]
fname, *anchor = fname.rsplit("#", 1)
if not _CURRFILE.endswith("toc.md"):
_USED_REFS[fname] = url
if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]:
cfilename = _CURRFILE.rsplit("/", 1)[-1]
urlout = docref_map[_CURRFILE][fname] + ("#" + anchor[0] if anchor else "")
if urlout != url:
print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})")
else:
urlout = url
return f"[{txt}]({urlout})"
def _sub_doc(match):
# reference links set at the bottom of the page
global _USED_REFS
grpdict = match.groupdict()
txt, url = grpdict["txt"], grpdict["url"]
txt = TXT_REMAPS.get(txt, txt)
url = URL_REMAPS.get(url, url)
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
return f"[{txt}]: {url}"
if "http" in url and "://" in url:
urlout = url
else:
fname, *part = url.rsplit("/", 1)
fname = part[0] if part else fname
fname = fname.rsplit(".", 1)[0]
fname, *anchor = fname.rsplit("#", 1)
if not _CURRFILE.endswith("toc.md"):
_USED_REFS[fname] = url
if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]:
cfilename = _CURRFILE.rsplit("/", 1)[-1]
urlout = docref_map[_CURRFILE][fname] + ("#" + anchor[0] if anchor else "")
if urlout != url:
print(f" {cfilename}: [{txt}]: {url} -> [{txt}]: {urlout}")
else:
urlout = url
return f"[{txt}]: {urlout}"
# replace / correct links in all files
count = 0
for path in sorted(Path(_SOURCE_DIR).rglob("*.md"), key=lambda p: p.name):
# from pudb import debugger;debugger.Debugger().set_trace()
_CURRFILE = path.as_posix()
with open(path, "r") as fil:
intxt = fil.read()
outtxt = ref_regex.sub(_sub, intxt)
outtxt = ref_doc_regex.sub(_sub_doc, outtxt)
if intxt != outtxt:
with open(path, "w") as fil:
fil.write(outtxt)
count += 1
print(f" -- Auto-relinked links in {path.name}")
if count > 0:
print(f" -- Auto-corrected links in {count} documents.")
for (fname, src_url) in sorted(toc_map.items(), key=lambda tup: tup[0]):
if fname not in _USED_REFS:
print(f" ORPHANED DOC: no refs found to {src_url}.md")
# write tocfile
with open(_TOC_FILE, "w") as fil:
fil.write("# Toc\n")
fil.write("- [API root](api/evennia-api.rst)")
for ref in sorted(toc_map.values()):
if ref == "toc":
continue
if "Part1/" in ref:
continue
if not "/" in ref:
ref = "./" + ref
linkname = ref.replace("-", " ")
fil.write(f"\n- [{linkname}]({ref})")
# we add a self-reference so the toc itself is also a part of a toctree
fil.write("\n\n```toctree::\n :hidden:\n\n toc\n```")
print(" -- Auto-Remapper finished.")
if __name__ == "__main__":
auto_link_remapper()