""" 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 os.path import abspath, dirname from os.path import join as pathjoin from os.path import relpath from pathlib import Path from sphinx.errors import DocumentError _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:", "feature-request", "report-bug", "issue", "bug-report", ] # remove these prefixes from the url _STRIP_PREFIX = [ "../../api/", "../api/", "./api/", "api/", "api:", ] TXT_REMAPS = { "Developer Central": "Evennia Components overview", "Getting Started": "Setup Quickstart", } URL_REMAPS = { "Developer-Central": "Components/Components-Overview", "Tutorials": "Howtos/Howtos-Overview", "../Howtos/Beginner-Tutorial/Directory-Overview": "Gamedir-Overview", "Howtos/Beginner-Tutorial/Directory-Overview": "Gamedir-Overview", "Beginner-Tutorial/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": "Beginner-Tutorial-Part1", "../Howtos/Beginner-Tutorial/Adding-Command-Tutorial": "Adding-Commands", "Howtos/Beginner-Tutorial/Adding-Command-Tutorial": "Adding-Commands", "Beginner-Tutorial/Adding-Command-Tutorial": "Adding-Commands", "Adding-Command-Tutorial": "Adding-Commands", "CmdSet": "Command-Sets", "Spawner": "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", "./Locks.md#permissions": "Permissions", "modules": "Default-Commands.md", } DJANGO_LAST_TESTED_VERSION = "4.1" # update as django requirements update DJANGO_URL_REGEX = re.compile( r"(?Phttps://docs\.djangoproject\.com/en/)(?Pdev|[1-9.]+)(?P/.*?$)", re.MULTILINE + re.DOTALL, ) _USED_REFS = {} _CURRFILE = None def auto_link_remapper(no_autodoc=False): """ - 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[\n\w -\[\]\`]+?)\]\((?P.+?)\)", re.I + re.S + re.U + re.M ) # in document references ref_doc_regex = re.compile( r"\[(?P[\n\w -\`]+?)\]:\s+?(?P.+?)(?=$|\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) for strip_prefix in _STRIP_PREFIX: if url.startswith(strip_prefix): url = url[len(strip_prefix) :] # remap to specified django version of docs django_url = django_match = DJANGO_URL_REGEX.sub( rf"\g{DJANGO_LAST_TESTED_VERSION}\g", url ) if url != django_url: print(f" Django doc url remap: {url} -> {django_url}") url = django_url if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH): # skip regular http/s urls etc return f"[{txt}]({url})" if url.startswith("evennia."): # api link - we want to remove legacy #reference and remove .md if "#" in url: _, url = url.rsplit("#", 1) if url.endswith(".md"): url, _ = url.rsplit(".", 1) return f"[{txt}]({url})" fname, *part = url.rsplit("/", 1) fname = part[0] if part else fname fname, *anchor = fname.rsplit("#", 1) if ".md" in fname: fname = fname.rsplit(".", 1)[0] 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] + ".md" + ("#" + anchor[0].lower() 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) for strip_prefix in _STRIP_PREFIX: if url.startswith(strip_prefix): url = url[len(strip_prefix) :] if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH): return f"[{txt}]: {url}" if "http" in url and "://" in url: urlout = url elif url.startswith("evennia."): # api link - we want to remove legacy #reference if "#" in url: _, urlout = url.rsplit("#", 1) 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 and not src_url.startswith("api/"): print(f" ORPHANED DOC: no refs found to {src_url}.md") # write tocfile # with open(_TOC_FILE, "w") as fil: # fil.write("```{toctree}\n") # if not no_autodoc: # fil.write("- [API root](api/evennia-api.rst)") # for ref in sorted(toc_map.values()): # if ref == "toc": # continue # # if not "/" in ref: # # ref = "./" + ref # # linkname = ref.replace("-", " ") # fil.write(f"\n{ref}") # - [{linkname}]({ref})") # # we add a self-reference so the toc itself is also a part of a toctree # fil.write("\n```\n\n```{toctree}\n :hidden:\n\ntoc\n```") # print(" -- File toc.md updated.") print(" -- Auto-Remapper finished.") if __name__ == "__main__": auto_link_remapper()