857b911f3b
Change-Id: I30068d6492bd6ec4ce42cc50d4a481960a0197fe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/133985 Reviewed-by: Alexander Aprelev <aam@google.com>
121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
# Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
|
# for details. All rights reserved. Use of this source code is governed by a
|
|
# BSD-style license that can be found in the LICENSE file.
|
|
#
|
|
"""Support for @{ref|text} reference links in Markdown.
|
|
|
|
This Markdown extension converts @{ref|text} into a link with the given
|
|
[text] pointing to a particular source code location. [ref] can be one of
|
|
the following:
|
|
|
|
* package:-scheme URI - it will be resolved using .packages file in the
|
|
root directory
|
|
* file path
|
|
* C++ symbol - will be resolved through xref.json file (see README.md)
|
|
|
|
Usage: markdown.markdown(extensions=[XrefExtension()])
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
|
|
from markdown.extensions import Extension
|
|
from markdown.inlinepatterns import InlineProcessor
|
|
from markdown.util import etree
|
|
from typing import Optional
|
|
from urllib.parse import urlparse
|
|
|
|
_current_commit_hash = subprocess.run(['git', 'rev-parse', 'HEAD'],
|
|
capture_output=True,
|
|
encoding='utf8').stdout
|
|
|
|
# Load .packages file into a dictionary.
|
|
with open('.packages') as packages_file:
|
|
_packages = dict([
|
|
package_mapping.split(':', 1)
|
|
for package_mapping in packages_file
|
|
if not package_mapping.startswith('#')
|
|
])
|
|
|
|
# Load xref.json and verify that it was generated for the current commit to
|
|
# avoid discrepancies in the generated links.
|
|
with open('xref.json') as json_file:
|
|
_xrefs = json.load(json_file)
|
|
|
|
if _current_commit_hash != _xrefs['commit']:
|
|
logging.error(
|
|
'xref.json is generated for commit %s while current commit is %s',
|
|
_xrefs['commit'], _current_commit_hash)
|
|
|
|
|
|
def _make_github_uri(file: str, lineno: str = None) -> str:
|
|
"""Generates source link pointing to GitHub"""
|
|
fragment = '#L%s' % (lineno) if lineno is not None else ''
|
|
return 'https://github.com/dart-lang/sdk/blob/%s/%s%s' % (
|
|
_current_commit_hash, file, fragment)
|
|
|
|
|
|
def _file_ref_to_github_uri(file_ref: str) -> str:
|
|
"""Generates source link pointing to GitHub from an xref.json reference."""
|
|
(file_idx, line_idx) = file_ref.split(':', 1)
|
|
return _make_github_uri(_xrefs['files'][int(file_idx)], line_idx)
|
|
|
|
|
|
def _resolve_ref_via_xref(ref: str) -> Optional[str]:
|
|
"""Resolve the target of the given reference via xref.json"""
|
|
if ref in _xrefs['functions']:
|
|
return _xrefs['functions'][ref]
|
|
if ref in _xrefs['classes']:
|
|
return _xrefs['classes'][ref][0]
|
|
if '::' in ref:
|
|
(class_name, function_name) = ref.rsplit('::', 1)
|
|
if class_name in _xrefs['classes'] and len(
|
|
_xrefs['classes'][class_name]) == 2:
|
|
return _xrefs['classes'][class_name][1][function_name]
|
|
logging.error('Failed to resolve xref %s' % ref)
|
|
return None
|
|
|
|
|
|
def _resolve_ref(ref: str) -> Optional[str]:
|
|
if ref.startswith('package:'):
|
|
# Resolve as package uri via .packages.
|
|
uri = urlparse(ref)
|
|
(package_name, *path_to_file) = uri.path.split('/', 1)
|
|
package_path = _packages[package_name]
|
|
if len(path_to_file) == 0:
|
|
return _make_github_uri(package_path)
|
|
else:
|
|
return _make_github_uri(os.path.join(package_path, path_to_file[0]))
|
|
elif os.path.exists(ref):
|
|
# Resolve as a file link.
|
|
return _make_github_uri(_current_commit_hash, ref)
|
|
else:
|
|
# Resolve as a C++ symbol via xref.json
|
|
file_ref = _resolve_ref_via_xref(ref)
|
|
if file_ref is not None:
|
|
return _file_ref_to_github_uri(file_ref)
|
|
|
|
|
|
class _XrefPattern(InlineProcessor):
|
|
"""InlineProcessor responsible for handling @{ref|text} syntax."""
|
|
|
|
def handleMatch(self, m, data):
|
|
ref = m.group(1)
|
|
text = m.group(2)
|
|
uri = _resolve_ref(ref)
|
|
el = etree.Element('a')
|
|
el.attrib['href'] = uri
|
|
el.attrib['target'] = 'blank'
|
|
el.text = text[1:] if text is not None else ref
|
|
return el, m.start(0), m.end(0)
|
|
|
|
|
|
class XrefExtension(Extension):
|
|
"""Markdown extension responsible for expanding @{ref|text} into links."""
|
|
|
|
def extendMarkdown(self, md):
|
|
md.inlinePatterns.register(
|
|
_XrefPattern(r'@{([^}|]*)(\|[^}]+)?}'), 'xref', 175)
|