blob: be82226bd57108c45d075500714cdb1c3fc488ad [file] [log] [blame]
# Copyright (c) 2019 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
# based on http://protips.readthedocs.io/link-roles.html
from __future__ import print_function
from __future__ import unicode_literals
import re
import subprocess
from docutils import nodes
from pathlib import Path
from sphinx.util import logging
from typing import Final
ZEPHYR_BASE: Final[str] = Path(__file__).parents[3]
try:
import west.manifest
try:
west_manifest = west.manifest.Manifest.from_file()
except west.util.WestNotFound:
west_manifest = None
except ImportError:
west_manifest = None
logger = logging.getLogger(__name__)
def get_github_rev():
try:
output = subprocess.check_output(
"git describe --exact-match", shell=True, stderr=subprocess.DEVNULL
)
except subprocess.CalledProcessError:
return "main"
return output.strip().decode("utf-8")
def setup(app):
app.add_role("zephyr_file", modulelink("zephyr"))
app.add_role("zephyr_raw", modulelink("zephyr", format="raw"))
app.add_role("module_file", modulelink())
app.add_config_value("link_roles_manifest_baseurl", None, "env")
app.add_config_value("link_roles_manifest_project", None, "env")
app.add_config_value("link_roles_manifest_project_broken_links_ignore_globs", [], "env")
# The role just creates new nodes based on information in the
# arguments; its behavior doesn't depend on any other documents.
return {
"parallel_read_safe": True,
"parallel_write_safe": True,
}
def modulelink(default_module=None, format="blob"):
def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
# Set default values
module = default_module
rev = get_github_rev()
config = inliner.document.settings.env.app.config
baseurl = config.link_roles_manifest_baseurl
source, line = inliner.reporter.get_source_and_line(lineno)
trace = f"at '{source}:{line}'"
m = re.search(r"(.*)\s*<(.*)>", text)
if m:
link_text = m.group(1)
link = m.group(2)
else:
link_text = text
link = text
module_match = re.search(r"(.+?):\s*(.+)", link)
if module_match:
module = module_match.group(1).strip()
link = module_match.group(2).strip()
# Try to get a module repository's GitHub URL from the manifest.
#
# This allows e.g. building the docs in downstream Zephyr-based
# software with forks of the zephyr repository, and getting
# :zephyr_file: / :zephyr_raw: output that links to the fork,
# instead of mainline zephyr.
projects = [p.name for p in west_manifest.projects] if west_manifest else []
if module in projects:
project = west_manifest.get_projects([module])[0]
baseurl = project.url
rev = project.revision
# No module provided
elif module is None:
raise ValueError(
f"Role 'module_file' must take a module as an argument\n\t{trace}"
)
# Invalid module provided
elif module != config.link_roles_manifest_project:
logger.debug(f"Module {module} not found in the west manifest")
# Baseurl for manifest project not set
elif baseurl is None:
raise ValueError(
f"Configuration value `link_roles_manifest_baseurl` not set\n\t{trace}"
)
if module == config.link_roles_manifest_project:
p = Path(source).relative_to(inliner.document.settings.env.srcdir)
if not any(
p.match(glob)
for glob in config.link_roles_manifest_project_broken_links_ignore_globs
):
if not Path(ZEPHYR_BASE, link).exists():
logger.warning(
f"{link} not found in {config.link_roles_manifest_project} {trace}"
)
url = f"{baseurl}/{format}/{rev}/{link}"
node = nodes.reference(rawtext, link_text, refuri=url, **options)
return [node], []
return role