|
import logging |
|
import os |
|
|
|
from recommonmark import transform |
|
""" |
|
Allow linking of Markdown documentation from the source code tree into the Sphinx |
|
documentation tree. |
|
|
|
The Markdown documents will have links relative to the source code root, rather |
|
than the place they are now linked too - this code fixes these paths up. |
|
|
|
We also want links from two Markdown documents found in the Sphinx docs to |
|
work, so that is also fixed up. |
|
""" |
|
|
|
|
|
def path_contains(parent_path, child_path): |
|
"""Check a path contains another path. |
|
|
|
>>> path_contains("a/b", "a/b") |
|
True |
|
>>> path_contains("a/b", "a/b/") |
|
True |
|
>>> path_contains("a/b", "a/b/c") |
|
True |
|
>>> path_contains("a/b", "c") |
|
False |
|
>>> path_contains("a/b", "c/b") |
|
False |
|
>>> path_contains("a/b", "c/../a/b/d") |
|
True |
|
>>> path_contains("../a/b", "../a/b/d") |
|
True |
|
>>> path_contains("../a/b", "../a/c") |
|
False |
|
>>> path_contains("a", "abc") |
|
False |
|
>>> path_contains("aa", "abc") |
|
False |
|
""" |
|
# Append a separator to the end of both paths to work around the fact that |
|
# os.path.commonprefix does character by character comparisons rather than |
|
# path segment by path segment. |
|
parent_path = os.path.join(os.path.normpath(parent_path), '') |
|
child_path = os.path.join(os.path.normpath(child_path), '') |
|
common_path = os.path.commonprefix((parent_path, child_path)) |
|
return common_path == parent_path |
|
|
|
|
|
def relative(parent_dir, child_path): |
|
"""Get the relative between a path that contains another path.""" |
|
child_dir = os.path.dirname(child_path) |
|
assert path_contains(parent_dir, child_dir), "{} not inside {}".format( |
|
child_path, parent_dir) |
|
return os.path.relpath(child_path, start=parent_dir) |
|
|
|
|
|
class MarkdownCodeSymlinks(transform.AutoStructify, object): |
|
docs_root_dir = os.path.realpath(os.path.dirname(__file__)) |
|
code_root_dir = os.path.realpath(os.path.join(docs_root_dir, "..", "..")) |
|
|
|
mapping = { |
|
'docs2code': {}, |
|
'code2docs': {}, |
|
} |
|
|
|
@classmethod |
|
def relative_code(cls, url): |
|
"""Get a value relative to the code directory.""" |
|
return relative(cls.code_root_dir, url) |
|
|
|
@classmethod |
|
def relative_docs(cls, url): |
|
"""Get a value relative to the docs directory.""" |
|
return relative(cls.docs_root_dir, url) |
|
|
|
@classmethod |
|
def add_mapping(cls, docs_rel, code_rel): |
|
assert docs_rel not in cls.mapping['docs2code'], """\ |
|
Assertion error! Document already in mapping! |
|
New Value: {} |
|
Current Value: {} |
|
""".format(docs_rel, cls.mapping['docs2code'][docs_rel]) |
|
assert code_rel not in cls.mapping['code2docs'], """\ |
|
Assertion error! Document already in mapping! |
|
New Value: {} |
|
Current Value: {} |
|
""".format(docs_rel, cls.mapping['code2docs'][code_rel]) |
|
|
|
cls.mapping['docs2code'][docs_rel] = code_rel |
|
cls.mapping['code2docs'][code_rel] = docs_rel |
|
|
|
@classmethod |
|
def find_links(cls): |
|
"""Walk the docs dir and find links to docs in the code dir.""" |
|
for root, dirs, files in os.walk(cls.docs_root_dir): |
|
for fname in files: |
|
fpath = os.path.abspath(os.path.join(root, fname)) |
|
|
|
if not os.path.islink(fpath): |
|
continue |
|
|
|
link_path = os.path.join(root, os.readlink(fpath)) |
|
# Is link outside the code directory? |
|
if not path_contains(cls.code_root_dir, link_path): |
|
continue |
|
|
|
# Is link internal to the docs directory? |
|
if path_contains(cls.docs_root_dir, link_path): |
|
continue |
|
|
|
docs_rel = cls.relative_docs(fpath) |
|
code_rel = cls.relative_code(link_path) |
|
|
|
cls.add_mapping(docs_rel, code_rel) |
|
import pprint |
|
pprint.pprint(cls.mapping) |
|
|
|
@property |
|
def url_resolver(self): |
|
return self._url_resolver |
|
|
|
@url_resolver.setter |
|
def url_resolver(self, value): |
|
print(self, value) |
|
|
|
# Resolve a link from one markdown to another document. |
|
def _url_resolver(self, ourl): |
|
"""Resolve a URL found in a markdown file.""" |
|
assert self.docs_root_dir == os.path.realpath(self.root_dir), """\ |
|
Configuration error! Document Root != Current Root |
|
Document Root: {} |
|
Current Root: {} |
|
""".format(self.docs_root_dir, self.root_dir) |
|
|
|
src_path = os.path.abspath(self.document['source']) |
|
src_dir = os.path.dirname(src_path) |
|
dst_path = os.path.abspath(os.path.join(self.docs_root_dir, ourl)) |
|
dst_rsrc = os.path.relpath(dst_path, start=src_dir) |
|
|
|
src_rdoc = self.relative_docs(src_path) |
|
|
|
print |
|
print("url_resolver") |
|
print(src_path) |
|
print(dst_path) |
|
print(dst_rsrc) |
|
print(src_rdoc) |
|
|
|
# Is the source document a linked one? |
|
if src_rdoc not in self.mapping['docs2code']: |
|
# Don't do any rewriting on non-linked markdown. |
|
url = ourl |
|
|
|
# Is the destination also inside docs? |
|
elif dst_rsrc not in self.mapping['code2docs']: |
|
# Return a path to the GitHub repo. |
|
url = "{}/blob/master/{}".format( |
|
self.config['github_code_repo'], dst_rsrc) |
|
else: |
|
url = os.path.relpath( |
|
os.path.join( |
|
self.docs_root_dir, self.mapping['code2docs'][dst_rsrc]), |
|
start=src_dir) |
|
base_url, ext = os.path.splitext(url) |
|
assert ext in (".md", |
|
".markdown"), ("Unknown extension {}".format(ext)) |
|
url = "{}.html".format(base_url) |
|
|
|
print("---") |
|
print(ourl) |
|
print(url) |
|
print |
|
return url |
|
|
|
|
|
if __name__ == "__main__": |
|
import doctest |
|
doctest.testmod() |