1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
"""
# Relative Path Markdown Extension
During the MkDocs build we rewrite URLs that link to local
Markdown or media files. Using the following pages configuration
we can look at how the output is changed.
pages:
- ['index.md']
- ['tutorial/install.md']
- ['tutorial/intro.md']
## Markdown URLs
When linking from `install.md` to `intro.md` the link would
simply be `[intro](intro.md)`. However, when we build
`install.md` we place it in a directory to create nicer URLs.
This means that the path to `intro.md` becomes `../intro/`
## Media URLs
To make it easier to work with media files and store them all
under one directory we re-write those to all be based on the
root. So, with the following markdown to add an image.

The output would depend on the location of the Markdown file it
was added too.
Source file | Generated Path | Image Path |
------------------- | ----------------- | ---------------------------- |
index.md | / | ./img/initial-layout.png |
tutorial/install.md | tutorial/install/ | ../img/initial-layout.png |
tutorial/intro.md | tutorial/intro/ | ../../img/initial-layout.png |
"""
from __future__ import unicode_literals
import logging
import os
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor
from markdown.util import AMP_SUBSTITUTE
from mkdocs import utils
from mkdocs.exceptions import MarkdownNotFound
log = logging.getLogger(__name__)
def _iter(node):
# TODO: Remove when dropping Python 2.6. Replace this
# function call with note.iter()
return [node] + node.findall('.//*')
def path_to_url(url, nav, strict):
scheme, netloc, path, params, query, fragment = (
utils.urlparse(url))
if scheme or netloc or not path or AMP_SUBSTITUTE in url:
# Ignore URLs unless they are a relative link to a markdown file.
# AMP_SUBSTITUTE is used internally by Markdown only for email,which is
# not a relative link. As urlparse errors on them, skip explicitly
return url
if nav and not utils.is_markdown_file(path):
path = utils.create_relative_media_url(nav, path)
elif nav:
# If the site navigation has been provided, then validate
# the internal hyperlink, making sure the target actually exists.
target_file = nav.file_context.make_absolute(path)
if target_file.startswith(os.path.sep):
target_file = target_file[1:]
if target_file not in nav.source_files:
source_file = nav.file_context.current_file
msg = (
'The page "%s" contained a hyperlink to "%s" which '
'is not listed in the "pages" configuration.'
) % (source_file, target_file)
# In strict mode raise an error at this point.
if strict:
raise MarkdownNotFound(msg)
# Otherwise, when strict mode isn't enabled, log a warning
# to the user and leave the URL as it is.
log.warning(msg)
return url
path = utils.get_url_path(target_file, nav.use_directory_urls)
path = nav.url_context.make_relative(path)
else:
path = utils.get_url_path(path).lstrip('/')
# Convert the .md hyperlink to a relative hyperlink to the HTML page.
fragments = (scheme, netloc, path, params, query, fragment)
url = utils.urlunparse(fragments)
return url
class RelativePathTreeprocessor(Treeprocessor):
def __init__(self, site_navigation, strict):
self.site_navigation = site_navigation
self.strict = strict
def run(self, root):
"""Update urls on anchors and images to make them relative
Iterates through the full document tree looking for specific
tags and then makes them relative based on the site navigation
"""
for element in _iter(root):
if element.tag == 'a':
key = 'href'
elif element.tag == 'img':
key = 'src'
else:
continue
url = element.get(key)
new_url = path_to_url(url, self.site_navigation, self.strict)
element.set(key, new_url)
return root
class RelativePathExtension(Extension):
"""
The Extension class is what we pass to markdown, it then
registers the Treeprocessor.
"""
def __init__(self, site_navigation, strict):
self.site_navigation = site_navigation
self.strict = strict
def extendMarkdown(self, md, md_globals):
relpath = RelativePathTreeprocessor(self.site_navigation, self.strict)
md.treeprocessors.add("relpath", relpath, "_end")
|