|
| 1 | +""" |
| 2 | +Sphinx Markdown link fixer |
| 3 | +""" |
| 4 | + |
| 5 | +import sphinx |
| 6 | + |
| 7 | +from docutils import nodes |
| 8 | +from os.path import isdir, isfile, join, basename, dirname |
| 9 | +from os import makedirs |
| 10 | +from shutil import copyfile |
| 11 | + |
| 12 | +############################################################################## |
| 13 | +# |
| 14 | +# This section determines the behavior of links to local items in .md files. |
| 15 | +# |
| 16 | +# if useGitHubURL == True: |
| 17 | +# |
| 18 | +# links to local files and directories will be turned into github URLs |
| 19 | +# using either the baseBranch defined here or using the commit SHA. |
| 20 | +# |
| 21 | +# if useGitHubURL == False: |
| 22 | +# |
| 23 | +# local files will be moved to the website directory structure when built |
| 24 | +# local directories will still be links to github URLs |
| 25 | +# |
| 26 | +# if built with GitHub workflows: |
| 27 | +# |
| 28 | +# the GitHub URLs will use the commit SHA (GITHUB_SHA environment variable |
| 29 | +# is defined by GitHub workflows) to link to the specific commit. |
| 30 | +# |
| 31 | +############################################################################## |
| 32 | + |
| 33 | +# End GitHub URL section |
| 34 | + |
| 35 | +############################################################################## |
| 36 | +# |
| 37 | +# This section defines callbacks that make markdown specific tweaks to |
| 38 | +# either: |
| 39 | +# |
| 40 | +# 1. Fix something that recommonmark does wrong. |
| 41 | +# 2. Provide support for .md files that are written as READMEs in a GitHub |
| 42 | +# repo. |
| 43 | +# |
| 44 | +# Only use these changes if using the extension ``recommonmark``. |
| 45 | +# |
| 46 | +############################################################################## |
| 47 | + |
| 48 | +# Callback registerd with 'missing-reference'. |
| 49 | +def fixRSTLinkInMD(app, env, node, contnode): |
| 50 | + refTarget = node.get('reftarget') |
| 51 | + filePath = refTarget.lstrip("/") |
| 52 | + if '.rst' in refTarget and "://" not in refTarget: |
| 53 | + # This occurs when a .rst file is referenced from a .md file |
| 54 | + # Currently unable to check if file exists as no file |
| 55 | + # context is provided and links are relative. |
| 56 | + # |
| 57 | + # Example: [Application examples](examples/readme.rst) |
| 58 | + # |
| 59 | + contnode['refuri'] = contnode['refuri'].replace('.rst','.html') |
| 60 | + contnode['internal'] = "True" |
| 61 | + return contnode |
| 62 | + else: |
| 63 | + # This occurs when a file is referenced for download from an .md file. |
| 64 | + # Construct a list of them and short-circuit the warning. The files |
| 65 | + # are moved later (need file location context). To avoid warnings, |
| 66 | + # write .md files, make the links absolute. This only marks them fixed |
| 67 | + # if it can verify that they exist. |
| 68 | + # |
| 69 | + # Example: [Makefile](/Makefile) |
| 70 | + # |
| 71 | + if isfile(filePath) or isdir(filePath): |
| 72 | + return contnode |
| 73 | + |
| 74 | + |
| 75 | +def normalizePath(docPath,uriPath): |
| 76 | + if uriPath == "": |
| 77 | + return uriPath |
| 78 | + if "#" in uriPath: |
| 79 | + # Strip out anchors |
| 80 | + uriPath = uriPath.split("#")[0] |
| 81 | + if uriPath.startswith("/"): |
| 82 | + # It's an absolute path |
| 83 | + return uriPath.lstrip("/") #path to file from project directory |
| 84 | + else: |
| 85 | + # It's a relative path |
| 86 | + docDir = dirname(docPath) |
| 87 | + return join(docDir,uriPath) #path to file from referencing file |
| 88 | + |
| 89 | + |
| 90 | +# Callback registerd with 'doctree-resolved'. |
| 91 | +def fixLocalMDAnchors(app, doctree, docname): |
| 92 | + useGitHubURL = app.config.sphinx_md_useGitHubURL |
| 93 | + githubFileURL = app.config.sphinx_md_githubFileURL |
| 94 | + githubDirURL = app.config.sphinx_md_githubDirURL |
| 95 | + for node in doctree.traverse(nodes.reference): |
| 96 | + uri = node.get('refuri') |
| 97 | + filePath = normalizePath(docname,uri) |
| 98 | + if isfile(filePath): |
| 99 | + # Only do this if the file exists. |
| 100 | + # |
| 101 | + # TODO: Pop a warning if the file doesn't exist. |
| 102 | + # |
| 103 | + if '.md' in uri and '://' not in uri: |
| 104 | + # Make sure .md file links that weren't caught are converted. |
| 105 | + # These occur when creating an explicit link to an .md file |
| 106 | + # from an .rst file. By default these are not validated by Sphinx |
| 107 | + # or recommonmark. Only toctree references are validated. recommonmark |
| 108 | + # also fails to convert links to local Markdown files that include |
| 109 | + # anchors. This fixes that as well. |
| 110 | + # |
| 111 | + # Only include this code if .md files are being converted to html |
| 112 | + # |
| 113 | + # Example: `Google Cloud Engine <gce.md>`__ |
| 114 | + # [configuration options](autotest.md#configuration-options) |
| 115 | + # |
| 116 | + node['refuri'] = node['refuri'].replace('.md','.html') |
| 117 | + else: |
| 118 | + # Handle the case where markdown is referencing local files in the repo |
| 119 | + # |
| 120 | + # Example: [Makefile](/Makefile) |
| 121 | + # |
| 122 | + if useGitHubURL: |
| 123 | + # Replace references to local files with links to the GitHub repo |
| 124 | + # |
| 125 | + newURI = githubFileURL + filePath |
| 126 | + print("new url: ", newURI) |
| 127 | + node['refuri']=newURI |
| 128 | + else: |
| 129 | + # If there are links to local files other than .md (.rst files are caught |
| 130 | + # when warnings are fired), move the files into the Sphinx project, so |
| 131 | + # they can be accessed. |
| 132 | + newFileDir = join(app.outdir,dirname(filePath)) # where to move the file in Sphinx output. |
| 133 | + newFilePath = join(app.outdir,filePath) |
| 134 | + newURI = uri # if the path is relative no need to change it. |
| 135 | + if uri.startswith("/"): |
| 136 | + # It's an absolute path. Need to make it relative. |
| 137 | + uri = uri.lstrip("/") |
| 138 | + docDirDepth = len(docname.split("/")) - 1 |
| 139 | + newURI = "../"*docDirDepth + uri |
| 140 | + if not isdir(newFileDir): |
| 141 | + makedirs(newFileDir) |
| 142 | + copyfile(filePath,newFilePath) |
| 143 | + node['refuri'] = newURI |
| 144 | + elif "#" not in uri: # ignore anchors |
| 145 | + # turn links to directories into links to the repo |
| 146 | + if isdir(filePath): |
| 147 | + newURI = githubDirURL + filePath |
| 148 | + node['refuri']=newURI |
| 149 | + |
| 150 | +def setup(app): |
| 151 | + app.add_config_value('sphinx_md_useGitHubURL',False,'') |
| 152 | + app.add_config_value('sphinx_md_githubFileURL','','') |
| 153 | + app.add_config_value('sphinx_md_githubDirURL','','') |
| 154 | + app.connect('doctree-resolved',fixLocalMDAnchors) |
| 155 | + app.connect('missing-reference',fixRSTLinkInMD) |
| 156 | + |
| 157 | + |
| 158 | + |
0 commit comments