Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
xrefs.py
Go to the documentation of this file.
1# Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2# for details. All rights reserved. Use of this source code is governed by a
3# BSD-style license that can be found in the LICENSE file.
4#
5"""Markdown extension for xrefs and reference to other Markdown files.
6
7Xref is a reference of form [`symbol`][] or [text][`symbol`], where symbol
8is expected to be one of the following:
9
10 * package:-scheme URI - it will be resolved using .packages file in the
11 root directory
12 * file path
13 * C++ symbol - will be resolved through xref.json file (see README.md)
14
15Xrefs are converted to GitHub links.
16
17Additionally this extension retargets links pointing to markdown files to
18the html files produced from these markdown files.
19
20Usage: markdown.markdown(extensions=[XrefExtension()])
21"""
22
23import json
24import logging
25import os
26import re
27
28import xml.etree.ElementTree as etree
29from typing import Dict, Optional
30from urllib.parse import urlparse
31
32from cpp_indexer import SymbolsIndex, load_index
33from markdown.extensions import Extension
34from markdown.inlinepatterns import InlineProcessor
35from markdown.treeprocessors import Treeprocessor
36
37
38class _XrefPattern(InlineProcessor):
39 """Converts xrefs into GitHub links.
40
41 Recognizes [`symbol`][] and [text][`symbol`] link formats where symbol
42 is expected to be one of the following:
43
44 * Fully qualified reference to a C++ class, method or function;
45 * Package URI pointing to one of the packages included in the SDK
46 checkout.
47 * File reference to one of the file in the SDK.
48 """
49
50 XREF_RE = r'\[`(?P<symbol>[^]]+)`\]?\[\]|\[(?P<text>[^]]*)\]\[`(?P<target>[^]]+)`\]'
51
52 def __init__(self, md, symbols_index: SymbolsIndex,
53 packages: Dict[str, str]):
54 super().__init__(_XrefPattern.XREF_RE)
55 self.symbols_index = symbols_index
56 self.packages = packages
57 self.md = md
58
59 def handleMatch(self, m, data):
60 text = m.group('text')
61 symbol = m.group('symbol')
62 if symbol is None:
63 symbol = m.group('target')
64
65 uri = self._resolve_ref(symbol) or '#broken-link'
66
67 # Remember this xref. build process can later use this information
68 # to produce xref reference section at the end of the markdown file.
69 self.md.xrefs[f"`{symbol}`"] = uri
70
71 # Create <a href='uri'>text</a> element. If text is not defined
72 # simply use a slightly sanitized symbol name.
73 anchor = etree.Element('a')
74 anchor.attrib['href'] = uri
75 anchor.attrib['target'] = '_blank'
76 if text is not None:
77 anchor.text = text
78 else:
79 code = etree.Element('code')
80 code.text = re.sub(r'^dart::', '', symbol)
81 anchor.append(code)
82
83 # Replace the whole pattern match with anchor element.
84 return anchor, m.start(0), m.end(0)
85
86 def _resolve_ref(self, ref: str) -> Optional[str]:
87 if ref.startswith('package:'):
88 # Resolve as package uri via .packages.
89 uri = urlparse(ref)
90 (package_name, *path_to_file) = uri.path.split('/', 1)
91 package_path = self.packages[package_name]
92 if len(path_to_file) == 0:
93 return self._make_github_uri(package_path)
94 else:
95 return self._make_github_uri(
96 os.path.join(package_path, path_to_file[0]))
97 elif os.path.exists(ref):
98 # Resolve as a file link.
99 return self._make_github_uri(ref)
100 else:
101 # Resolve as a symbol.
102 loc = self.symbols_index.try_resolve(ref)
103 if loc is not None:
104 return self._make_github_uri(loc.filename, loc.lineno)
105
106 logging.error('Failed to resolve xref %s', ref)
107 return None
108
109 def _make_github_uri(self, file: str, lineno: Optional[int] = None) -> str:
110 """Generates source link pointing to GitHub"""
111 fragment = f'#L{lineno}' if lineno is not None else ''
112 return f'https://github.com/dart-lang/sdk/blob/{self.symbols_index.commit}/{file}{fragment}'
113
114
115class _MdLinkFixerTreeprocessor(Treeprocessor):
116 """Redirects links pointing to .md files to .html files built from them."""
117
118 def run(self, root):
119 for elem in root.iter('a'):
120 href = elem.get('href')
121 if href is None:
122 continue
123 parsed_href = urlparse(href)
124 if parsed_href.path.endswith('.md'):
125 elem.set(
126 'href',
127 parsed_href._replace(path=parsed_href.path[:-3] +
128 '.html').geturl())
129
130
131class XrefExtension(Extension):
132 """Markdown extension which handles xrefs and links to markdown files."""
133 symbols_index: SymbolsIndex
134 packages: Dict[str, str]
135
136 def __init__(self) -> None:
137 super().__init__()
138 self.symbols_index = load_index('xref.json')
139 self.packages = XrefExtension._load_package_config()
140
141 def extendMarkdown(self, md):
142 md.xrefs = {}
143 md.treeprocessors.register(_MdLinkFixerTreeprocessor(), 'mdlinkfixer',
144 0)
145 md.inlinePatterns.register(
146 _XrefPattern(md, self.symbols_index, self.packages), 'xref', 200)
147
148 @staticmethod
149 def _load_package_config() -> Dict[str, str]:
150 # Load package_config.json file into a dictionary.
151 with open('.dart_tool/package_config.json',
152 encoding='utf-8') as package_config_file:
153 package_config = json.load(package_config_file)
154 return dict([(pkg['name'],
155 os.path.normpath(
156 os.path.join('.dart_tool/', pkg['rootUri'],
157 pkg['packageUri'])))
158 for pkg in package_config['packages']
159 if 'packageUri' in pkg])
def extendMarkdown(self, md)
Definition: xrefs.py:141
None __init__(self)
Definition: xrefs.py:136
def handleMatch(self, m, data)
Definition: xrefs.py:59
def __init__(self, md, SymbolsIndex symbols_index, Dict[str, str] packages)
Definition: xrefs.py:53
str _make_github_uri(self, str file, Optional[int] lineno=None)
Definition: xrefs.py:109
Optional[str] _resolve_ref(self, str ref)
Definition: xrefs.py:86
SymbolsIndex load_index(str filename)
Definition: cpp_indexer.py:272