Skip to content

Commit

Permalink
Optimize getting relative page URLs (#2407)
Browse files Browse the repository at this point in the history
This is a custom implementation that's significantly faster but gives the exact same results as the current one. The use of `posixpath.relpath` pulled in several path-specific transformations that are never needed here.

Efficiency is important because calls to `normalize_url` (e.g.) on a site with ~300 pages currently take up ~10% of the total run time due to the sheer number of them. The number of calls is at least the number of pages squared.
  • Loading branch information
oprypin committed Jul 7, 2021
1 parent a012d2e commit f6d8830
Showing 1 changed file with 25 additions and 15 deletions.
40 changes: 25 additions & 15 deletions mkdocs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ def is_error_template(path):
return bool(_ERROR_TEMPLATE_RE.match(path))


@functools.lru_cache(maxsize=None)
def _norm_parts(path):
if not path.startswith('/'):
path = '/' + path
path = posixpath.normpath(path)[1:]
return path.split('/') if path else []


def get_relative_url(url, other):
"""
Return given url relative to other.
Expand All @@ -251,11 +259,21 @@ def get_relative_url(url, other):
Paths are normalized ('..' works as parent directory), but going higher than the
root has no effect ('foo/../../bar' ends up just as 'bar').
"""
if other != '.':
# Remove filename from other url if it has one.
parts = posixpath.split(other)
other = parts[0] if '.' in parts[1] else other
relurl = posixpath.relpath('/' + url, '/' + other)
# Remove filename from other url if it has one.
dirname, _, basename = other.rpartition('/')
if '.' in basename:
other = dirname

other_parts = _norm_parts(other)
dest_parts = _norm_parts(url)
common = 0
for a, b in zip(other_parts, dest_parts):
if a != b:
break
common += 1

rel_parts = ['..'] * (len(other_parts) - common) + dest_parts[common:]
relurl = '/'.join(rel_parts) or '.'
return relurl + '/' if url.endswith('/') else relurl


Expand All @@ -265,8 +283,8 @@ def normalize_url(path, page=None, base=''):
if is_abs:
return path
if page is not None:
base = page.url
return _get_rel_path(path, base, page is not None)
return get_relative_url(path, page.url)
return posixpath.join(base, path)


@functools.lru_cache(maxsize=None)
Expand All @@ -279,14 +297,6 @@ def _get_norm_url(path):
return path, False


@functools.lru_cache()
def _get_rel_path(path, base, base_is_url):
if base_is_url:
return get_relative_url(path, base)
else:
return posixpath.join(base, path)


def create_media_urls(path_list, page=None, base=''):
"""
Return a list of URLs relative to the given page or using the base.
Expand Down

0 comments on commit f6d8830

Please sign in to comment.