189 lines
6.6 KiB
Python
189 lines
6.6 KiB
Python
|
"""
|
||
|
Path Converter.
|
||
|
|
||
|
pymdownx.pathconverter
|
||
|
An extension for Python Markdown.
|
||
|
|
||
|
An extension to covert tag paths to relative or absolute:
|
||
|
|
||
|
Given an absolute base and a target relative path, this extension searches for file
|
||
|
references that are relative and converts them to a path relative
|
||
|
to the base path.
|
||
|
|
||
|
-or-
|
||
|
|
||
|
Given an absolute base path, this extension searches for file
|
||
|
references that are relative and converts them to absolute paths.
|
||
|
|
||
|
MIT license.
|
||
|
|
||
|
Copyright (c) 2014 - 2017 Isaac Muse <isaacmuse@gmail.com>
|
||
|
|
||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||
|
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||
|
|
||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
||
|
of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||
|
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
DEALINGS IN THE SOFTWARE.
|
||
|
"""
|
||
|
from markdown import Extension
|
||
|
from markdown.postprocessors import Postprocessor
|
||
|
from . import util
|
||
|
import os
|
||
|
import re
|
||
|
from urllib.parse import urlunparse
|
||
|
|
||
|
RE_TAG_HTML = r'''(?xus)
|
||
|
(?:
|
||
|
(?P<avoid>
|
||
|
<\s*(?P<script_name>script|style)[^>]*>.*?</\s*(?P=script_name)\s*> |
|
||
|
(?:(\r?\n?\s*)<!--[\s\S]*?-->(\s*)(?=\r?\n)|<!--[\s\S]*?-->)
|
||
|
)|
|
||
|
(?P<open><\s*(?P<tag>(?:%s)))
|
||
|
(?P<attr>(?:\s+[\w\-:]+(?:\s*=\s*(?:"[^"]*"|'[^']*'))?)*)
|
||
|
(?P<close>\s*(?:\/?)>)
|
||
|
)
|
||
|
'''
|
||
|
|
||
|
RE_TAG_LINK_ATTR = re.compile(
|
||
|
r'''(?xus)
|
||
|
(?P<attr>
|
||
|
(?:
|
||
|
(?P<name>\s+(?:href|src)\s*=\s*)
|
||
|
(?P<path>"[^"]*"|'[^']*')
|
||
|
)
|
||
|
)
|
||
|
'''
|
||
|
)
|
||
|
|
||
|
|
||
|
def repl_relative(m, base_path, relative_path):
|
||
|
"""Replace path with relative path."""
|
||
|
|
||
|
link = m.group(0)
|
||
|
try:
|
||
|
scheme, netloc, path, params, query, fragment, is_url, is_absolute = util.parse_url(m.group('path')[1:-1])
|
||
|
|
||
|
if not is_url:
|
||
|
# Get the absolute path of the file or return
|
||
|
# if we can't resolve the path
|
||
|
path = util.url2path(path)
|
||
|
if (not is_absolute):
|
||
|
# Convert current relative path to absolute
|
||
|
path = os.path.relpath(
|
||
|
os.path.normpath(os.path.join(base_path, path)),
|
||
|
os.path.normpath(relative_path)
|
||
|
)
|
||
|
# Convert the path, URL encode it, and format it as a link
|
||
|
path = util.path2url(path)
|
||
|
link = '{}"{}"'.format(
|
||
|
m.group('name'),
|
||
|
urlunparse((scheme, netloc, path, params, query, fragment))
|
||
|
)
|
||
|
except Exception: # pragma: no cover
|
||
|
# Parsing crashed and burned; no need to continue.
|
||
|
pass
|
||
|
|
||
|
return link
|
||
|
|
||
|
|
||
|
def repl_absolute(m, base_path, file_scheme):
|
||
|
"""Replace path with absolute path."""
|
||
|
|
||
|
link = m.group(0)
|
||
|
try:
|
||
|
scheme, netloc, path, params, query, fragment, is_url, is_absolute = util.parse_url(m.group('path')[1:-1])
|
||
|
|
||
|
if (not is_absolute and not is_url):
|
||
|
path = util.url2path(path)
|
||
|
path = os.path.normpath(os.path.join(base_path, path))
|
||
|
path = util.path2url(path)
|
||
|
if file_scheme:
|
||
|
link = '{}"{}"'.format(
|
||
|
m.group('name'),
|
||
|
urlunparse(("file", netloc, path, params, query, fragment))
|
||
|
)
|
||
|
else:
|
||
|
start = '/' if not path.startswith('/') else ''
|
||
|
link = '{}"{}{}"'.format(
|
||
|
m.group('name'),
|
||
|
start,
|
||
|
urlunparse((scheme, netloc, path, params, query, fragment))
|
||
|
)
|
||
|
except Exception: # pragma: no cover
|
||
|
# Parsing crashed and burned; no need to continue.
|
||
|
pass
|
||
|
|
||
|
return link
|
||
|
|
||
|
|
||
|
def repl(m, base_path, rel_path=None, file_scheme=None):
|
||
|
"""Replace."""
|
||
|
|
||
|
if m.group('avoid'):
|
||
|
tag = m.group('avoid')
|
||
|
else:
|
||
|
tag = m.group('open')
|
||
|
if rel_path is None:
|
||
|
tag += RE_TAG_LINK_ATTR.sub(lambda m2: repl_absolute(m2, base_path, file_scheme), m.group('attr'))
|
||
|
else:
|
||
|
tag += RE_TAG_LINK_ATTR.sub(lambda m2: repl_relative(m2, base_path, rel_path), m.group('attr'))
|
||
|
tag += m.group('close')
|
||
|
return tag
|
||
|
|
||
|
|
||
|
class PathConverterPostprocessor(Postprocessor):
|
||
|
"""Post process to find tag lings to convert."""
|
||
|
|
||
|
def run(self, text):
|
||
|
"""Find and convert paths."""
|
||
|
|
||
|
basepath = self.config['base_path']
|
||
|
relativepath = self.config['relative_path']
|
||
|
absolute = bool(self.config['absolute'])
|
||
|
filescheme = bool(self.config['file_scheme'])
|
||
|
tags = re.compile(RE_TAG_HTML % '|'.join(self.config['tags'].split()))
|
||
|
if not absolute and basepath and relativepath:
|
||
|
text = tags.sub(lambda m: repl(m, basepath, rel_path=relativepath), text)
|
||
|
elif absolute and basepath:
|
||
|
text = tags.sub(lambda m: repl(m, basepath, file_scheme=filescheme), text)
|
||
|
return text
|
||
|
|
||
|
|
||
|
class PathConverterExtension(Extension):
|
||
|
"""PathConverter extension."""
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
"""Initialize."""
|
||
|
|
||
|
self.config = {
|
||
|
'base_path': ["", "Base path used to find files - Default: \"\""],
|
||
|
'relative_path': ["", "Path that files will be relative to (not needed if using absolute) - Default: \"\""],
|
||
|
'absolute': [False, "Paths are absolute by default; disable for relative - Default: False"],
|
||
|
'tags': ["img script a link", "tags to convert src and/or href in - Default: 'img scripts a link'"],
|
||
|
'file_scheme': [False, "Use file:// scheme for absolute paths - Default: False"],
|
||
|
}
|
||
|
|
||
|
super().__init__(*args, **kwargs)
|
||
|
|
||
|
def extendMarkdown(self, md):
|
||
|
"""Add post processor to Markdown instance."""
|
||
|
|
||
|
rel_path = PathConverterPostprocessor(md)
|
||
|
rel_path.config = self.getConfigs()
|
||
|
md.postprocessors.register(rel_path, "path-converter", 2)
|
||
|
md.registerExtension(self)
|
||
|
|
||
|
|
||
|
def makeExtension(*args, **kwargs):
|
||
|
"""Return extension."""
|
||
|
|
||
|
return PathConverterExtension(*args, **kwargs)
|