| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import re |
| import os |
| import os.path as path |
| from util import debug |
| |
| cleanup_on_finish_files = [] |
| |
| |
| def setup(app): |
| app.add_event("create_index_softlink") |
| app.connect("create_index_softlink", index_softlink) |
| app.add_event("toc_from_markdown") |
| app.connect("toc_from_markdown", auto_generate_toc) |
| app.connect("build-finished", after_build_cleanup) |
| return {"version": "1.0", "parallel_read_safe": True} |
| |
| |
| def after_build_cleanup(app, exception): |
| for f in cleanup_on_finish_files: |
| os.remove(path.join(app.srcdir, f)) |
| debug(f"[TOC] Deleted {f}.") |
| |
| |
| def extract_markdown_links(file): |
| """Extracts list of local markdown links from markdown file""" |
| # two formats for local markdown file links |
| linknameexp1 = r"\[[\/\.\w]*\]" |
| linktargetexp1 = r"\(\.[\/\.\w]*{fileext}\)" |
| linkexp1 = linknameexp1 + linktargetexp1.format(fileext=r"\.md") |
| |
| # case 2 [tag] link |
| linknameexp2 = r"\[[0-9]*\]\:\s*" |
| linktargetexp2 = r"\.[\/\.\w]*{fileext}\s*\n" |
| linkexp2 = linknameexp2 + linktargetexp2.format(fileext=r"\.md") |
| |
| links = [] |
| try: |
| with open(file, "r") as f: |
| block = f.read() |
| for m in re.finditer(linkexp1, block): |
| link = m.group(0).partition("(")[2].rpartition(")")[0].strip() |
| links.append(link) |
| for m in re.finditer(linkexp2, block): |
| link = m.group(0).rpartition(":")[2].strip() |
| links.append(link) |
| except Exception: |
| debug(f"[TOC] Warning: Failed to process {file}.") |
| |
| return links |
| |
| |
| def auto_generate_toc(app, master, tocname, cleanup=False, hidden=True, maxdepth=3): |
| """ |
| Automaticaly generate rst file with hidden toc' |
| """ |
| global cleanup_on_finish_files |
| |
| if cleanup: |
| cleanup_on_finish_files.append(tocname) |
| |
| master = path.join(app.srcdir, master) |
| tocname = path.join(app.srcdir, path.basename(tocname)) |
| links = [master] |
| |
| while maxdepth: |
| newlinks = [] |
| for file in links: |
| fromfile = extract_markdown_links(file) |
| newlinks += [ |
| path.normpath(path.join(os.path.dirname(file), file_link)) |
| for file_link in fromfile |
| ] |
| newlinks = [newlink for newlink in set(newlinks) if newlink not in links] |
| if not len(newlinks): |
| break |
| links += newlinks |
| maxdepth -= 1 |
| |
| links = [os.path.relpath(abslink, os.path.dirname(tocname)) for abslink in links] |
| links.sort() |
| |
| with open(tocname, "w") as f: |
| indent = " " |
| f.write(":orphan:\n\n") |
| f.write(".. toctree::\n") |
| if hidden: |
| f.write(indent + ":hidden:\n") |
| f.write("\n") |
| for link in links: |
| f.write(indent + link + "\n") |
| |
| |
| def index_softlink(app, master, cleanup=False): |
| global cleanup_on_finish_files |
| |
| softlink = "index." + master.rpartition(".")[2] |
| if cleanup: |
| cleanup_on_finish_files.append(softlink) |
| |
| master = path.join(app.srcdir, master) |
| softlink = path.join(app.srcdir, softlink) |
| |
| try: |
| os.symlink(master, softlink) |
| except FileExistsError: |
| os.remove(softlink) |
| os.symlink(master, softlink) |