pdoc.render_helpers
1from __future__ import annotations 2 3import inspect 4import os 5import re 6import warnings 7from collections.abc import Collection, Iterable, Mapping 8from contextlib import contextmanager 9from unittest.mock import patch 10 11import pygments.formatters.html 12import pygments.lexers.python 13from jinja2 import ext, nodes 14 15try: 16 # Jinja2 >= 3.0 17 from jinja2 import pass_context # type: ignore 18except ImportError: # pragma: no cover 19 from jinja2 import contextfilter as pass_context # type: ignore 20 21from jinja2.runtime import Context 22from markupsafe import Markup 23 24import pdoc.markdown2 25 26from . import docstrings 27from ._compat import cache, removesuffix 28 29lexer = pygments.lexers.python.PythonLexer() 30""" 31The pygments lexer used for pdoc.render_helpers.highlight. 32Overwrite this to configure pygments lexing. 33""" 34 35formatter = pygments.formatters.html.HtmlFormatter( 36 cssclass="pdoc-code codehilite", 37 linenos="inline", 38 anchorlinenos=True, 39) 40""" 41The pygments formatter used for pdoc.render_helpers.highlight. 42Overwrite this to configure pygments highlighting of code blocks. 43 44The usage of the `.codehilite` CSS selector in custom templates is deprecated since pdoc 10, use `.pdoc-code` instead. 45""" 46 47signature_formatter = pygments.formatters.html.HtmlFormatter(nowrap=True) 48""" 49The pygments formatter used for pdoc.render_helpers.format_signature. 50Overwrite this to configure pygments highlighting of signatures. 51""" 52 53markdown_extensions = { 54 "code-friendly": None, 55 "cuddled-lists": None, 56 "fenced-code-blocks": {"cssclass": formatter.cssclass}, 57 "footnotes": None, 58 "header-ids": None, 59 "markdown-in-html": None, 60 "pyshell": None, 61 "strike": None, 62 "tables": None, 63 "task_list": None, 64 "toc": {"depth": 2}, 65} 66""" 67The default extensions loaded for `markdown2`. 68Overwrite this to configure Markdown rendering. 69""" 70 71 72@cache 73def highlight(doc: pdoc.doc.Doc) -> str: 74 """Highlight the source code of a documentation object using pygments.""" 75 if isinstance(doc, str): # pragma: no cover 76 warnings.warn( 77 "Passing a string to the `highlight` render helper is deprecated, pass a pdoc.doc.Doc object instead.", 78 DeprecationWarning, 79 ) 80 return Markup(pygments.highlight(doc, lexer, formatter)) 81 82 # set up correct line numbers and anchors 83 formatter.linespans = doc.qualname or "L" 84 formatter.linenostart = doc.source_lines[0] + 1 if doc.source_lines else 1 85 return Markup(pygments.highlight(doc.source, lexer, formatter)) 86 87 88def format_signature(sig: inspect.Signature, colon: bool) -> str: 89 """Format and highlight a function signature using pygments. Returns HTML.""" 90 # First get a list with all params as strings. 91 result = pdoc.doc._PrettySignature._params(sig) # type: ignore 92 return_annot = pdoc.doc._PrettySignature._return_annotation_str(sig) # type: ignore 93 94 multiline = ( 95 sum(len(x) + 2 for x in result) + len(return_annot) 96 > pdoc.doc._PrettySignature.MULTILINE_CUTOFF 97 ) 98 99 # Next, individually highlight each parameter using pygments and wrap it in a span.param. 100 # This later allows us to properly control line breaks. 101 pretty_result = [] 102 for i, param in enumerate(result): 103 pretty = pygments.highlight(param, lexer, signature_formatter).strip() 104 if multiline: 105 pretty = f"""<span class="param">\t{pretty},</span>""" 106 else: 107 pretty = f"""<span class="param">{pretty}, </span>""" 108 pretty_result.append(pretty) 109 110 # remove last comma. 111 if pretty_result: 112 pretty_result[-1] = pretty_result[-1].rpartition(",")[0] + "</span>" 113 114 # Add return annotation. 115 rendered = "(%s)" % "".join(pretty_result) 116 if return_annot: 117 anno = pygments.highlight(return_annot, lexer, signature_formatter).strip() 118 rendered = ( 119 rendered[:-1] 120 + f'<span class="return-annotation">) -> {anno}{":" if colon else ""}</span>' 121 ) 122 123 if multiline: 124 rendered = f'<span class="signature pdoc-code multiline">{rendered}</span>' 125 else: 126 rendered = f'<span class="signature pdoc-code condensed">{rendered}</span>' 127 128 return Markup(rendered) 129 130 131@cache 132def to_html(docstring: str) -> str: 133 """ 134 Convert `docstring` from Markdown to HTML. 135 """ 136 # careful: markdown2 returns a subclass of str with an extra 137 # .toc_html attribute. don't further process the result, 138 # otherwise this attribute will be lost. 139 return pdoc.markdown2.markdown(docstring, extras=markdown_extensions) # type: ignore 140 141 142@pass_context 143def to_markdown_with_context(context: Context, docstring: str) -> str: 144 """ 145 Converts `docstring` from a custom docformat to Markdown (if necessary), and then from Markdown to HTML. 146 """ 147 module: pdoc.doc.Module = context["module"] 148 docformat: str = context["docformat"] 149 return to_markdown(docstring, module, docformat) 150 151 152def to_markdown(docstring: str, module: pdoc.doc.Module, default_docformat: str) -> str: 153 docformat = getattr(module.obj, "__docformat__", default_docformat) or "" 154 return docstrings.convert(docstring, docformat, module.source_file) 155 156 157def possible_sources( 158 all_modules: Collection[str], identifier: str 159) -> Iterable[tuple[str, str]]: 160 """ 161 For a given identifier, return all possible sources where it could originate from. 162 For example, assume `examplepkg._internal.Foo` with all_modules=["examplepkg"]. 163 This could be a Foo class in _internal.py, or a nested `class _internal: class Foo` in examplepkg. 164 We return both candidates as we don't know if _internal.py exists. 165 It may not be in all_modules because it's been excluded by `__all__`. 166 However, if `examplepkg._internal` is in all_modules we know that it can only be that option. 167 """ 168 if identifier in all_modules: 169 yield identifier, "" 170 return 171 172 modulename = identifier 173 qualname = None 174 while modulename: 175 modulename, _, add = modulename.rpartition(".") 176 qualname = f"{add}.{qualname}" if qualname else add 177 yield modulename, qualname 178 if modulename in all_modules: 179 return 180 raise ValueError(f"Invalid identifier: {identifier}") 181 182 183def split_identifier(all_modules: Collection[str], fullname: str) -> tuple[str, str]: 184 """ 185 Split an identifier into a `(modulename, qualname)` tuple. For example, `pdoc.render_helpers.split_identifier` 186 would be split into `("pdoc.render_helpers","split_identifier")`. This is necessary to generate links to the 187 correct module. 188 """ 189 warnings.warn( 190 "pdoc.render_helpers.split_identifier is deprecated and will be removed in a future release. " 191 "Use pdoc.render_helpers.possible_sources instead.", 192 DeprecationWarning, 193 ) 194 *_, last = possible_sources(all_modules, fullname) 195 return last 196 197 198def _relative_link(current: list[str], target: list[str]) -> str: 199 if target == current: 200 return f"../{target[-1]}.html" 201 elif target[: len(current)] == current: 202 return "/".join(target[len(current) :]) + ".html" 203 else: 204 return "../" + _relative_link(current[:-1], target) 205 206 207@cache 208def relative_link(current_module: str, target_module: str) -> str: 209 """Compute the relative link to another module's HTML file.""" 210 if current_module == target_module: 211 return "" 212 return _relative_link( 213 current_module.split(".")[:-1], 214 target_module.split("."), 215 ) 216 217 218def qualname_candidates(identifier: str, context_qualname: str) -> list[str]: 219 """ 220 Given an identifier in a current namespace, return all possible qualnames in the current module. 221 For example, if we are in Foo's subclass Bar and `baz()` is the identifier, 222 return `Foo.Bar.baz()`, `Foo.baz()`, and `baz()`. 223 """ 224 end = len(context_qualname) 225 ret = [] 226 while end > 0: 227 ret.append(f"{context_qualname[:end]}.{identifier}") 228 end = context_qualname.rfind(".", 0, end) 229 ret.append(identifier) 230 return ret 231 232 233@pass_context 234def linkify(context: Context, code: str, namespace: str = "") -> str: 235 """ 236 Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that 237 are not rendered at the moment will be ignored. 238 A piece of text is considered to be an identifier if it either contains a `.` or is surrounded by `<code>` tags. 239 """ 240 241 def linkify_repl(m: re.Match): 242 text = m.group(0) 243 identifier = removesuffix(text, "()") 244 245 # Check if this is a local reference within this module? 246 mod: pdoc.doc.Module = context["module"] 247 for qualname in qualname_candidates(identifier, namespace): 248 doc = mod.get(qualname) 249 if doc and context["is_public"](doc).strip(): 250 return f'<a href="#{qualname}">{text}</a>' 251 252 module = "" 253 qualname = "" 254 try: 255 # Check if the object we are interested in is imported and re-exposed in the current namespace. 256 for module, qualname in possible_sources( 257 context["all_modules"], identifier 258 ): 259 doc = mod.get(qualname) 260 if ( 261 doc 262 and doc.taken_from == (module, qualname) 263 and context["is_public"](doc).strip() 264 ): 265 if text.endswith("()"): 266 text = f"{doc.fullname}()" 267 else: 268 text = doc.fullname 269 return f'<a href="#{qualname}">{text}</a>' 270 except ValueError: 271 # possible_sources did not find a parent module. 272 return text 273 else: 274 # It's not, but we now know the parent module. Does the target exist? 275 doc = context["all_modules"][module] 276 if qualname: 277 assert isinstance(doc, pdoc.doc.Module) 278 doc = doc.get(qualname) 279 target_exists_and_public = ( 280 doc is not None and context["is_public"](doc).strip() 281 ) 282 if target_exists_and_public: 283 if qualname: 284 qualname = f"#{qualname}" 285 return f'<a href="{relative_link(context["module"].modulename, module)}{qualname}">{text}</a>' 286 else: 287 return text 288 289 return Markup( 290 re.sub( 291 r""" 292 # Part 1: foo.bar or foo.bar() (without backticks) 293 (?<![/=?#&]) # heuristic: not part of a URL 294 \b 295 (?!\d)[a-zA-Z0-9_]+ 296 (?:\.(?!\d)[a-zA-Z0-9_]+)+ 297 (?:\(\)|\b(?!\(\))) # we either end on () or on a word boundary. 298 (?!</a>) # not an existing link 299 (?![/#]) # heuristic: not part of a URL 300 301 | # Part 2: `foo` or `foo()`. `foo.bar` is already covered with part 1. 302 (?<=<code>) 303 (?!\d)[a-zA-Z0-9_]+ 304 (?:\(\))? 305 (?=</code>(?!</a>)) 306 """, 307 linkify_repl, 308 code, 309 flags=re.VERBOSE, 310 ) 311 ) 312 313 314@pass_context 315def link(context: Context, spec: tuple[str, str], text: str | None = None) -> str: 316 """Create a link for a specific `(modulename, qualname)` tuple.""" 317 mod: pdoc.doc.Module = context["module"] 318 modulename, qualname = spec 319 320 # Check if the object we are interested is also imported and re-exposed in the current namespace. 321 doc = mod.get(qualname) 322 if doc and doc.taken_from == spec and context["is_public"](doc).strip(): 323 if text: 324 text = text.replace(f"{modulename}.", f"{mod.modulename}.") 325 modulename = mod.modulename 326 327 if mod.modulename == modulename: 328 fullname = qualname 329 else: 330 fullname = removesuffix(f"{modulename}.{qualname}", ".") 331 332 if qualname: 333 qualname = f"#{qualname}" 334 if modulename in context["all_modules"]: 335 return Markup( 336 f'<a href="{relative_link(context["module"].modulename, modulename)}{qualname}">{text or fullname}</a>' 337 ) 338 return text or fullname 339 340 341def edit_url( 342 modulename: str, is_package: bool, mapping: Mapping[str, str] 343) -> str | None: 344 """Create a link to edit a particular file in the used version control system.""" 345 for m, prefix in mapping.items(): 346 if m == modulename or modulename.startswith(f"{m}."): 347 filename = modulename[len(m) + 1 :].replace(".", "/") 348 if is_package: 349 filename = f"{filename}/__init__.py".lstrip("/") 350 else: 351 filename += ".py" 352 return f"{prefix}{filename}" 353 return None 354 355 356def root_module_name(all_modules: Mapping[str, pdoc.doc.Module]) -> str | None: 357 """ 358 Return the name of the (unique) top-level module, or `None` 359 if no such module exists. 360 361 For example, assuming `foo`, `foo.bar`, and `foo.baz` are documented, 362 this function will return `foo`. If `foo` and `bar` are documented, 363 this function will return `None` as there is no unique top-level module. 364 """ 365 shortest_name = min(all_modules, key=len, default=None) 366 prefix = f"{shortest_name}." 367 all_others_are_submodules = all( 368 x.startswith(prefix) or x == shortest_name for x in all_modules 369 ) 370 if all_others_are_submodules: 371 return shortest_name 372 else: 373 return None 374 375 376def minify_css(css: str) -> str: 377 """Do some very basic CSS minification.""" 378 css = re.sub(r"[ ]{4}|\n|(?<=[:{}]) | (?=[{}])", "", css) 379 css = re.sub( 380 r"/\*.+?\*/", lambda m: m.group(0) if m.group(0).startswith("/*!") else "", css 381 ) 382 return Markup(css.replace("<style", "\n<style")) 383 384 385@contextmanager 386def defuse_unsafe_reprs(): 387 """This decorator is applied by pdoc before calling an object's repr(). 388 It applies some heuristics to patch our sensitive information. 389 For example, `os.environ`'s default `__repr__` implementation exposes all 390 local secrets. 391 """ 392 with (patch.object(os._Environ, "__repr__", lambda self: "os.environ")): 393 yield 394 395 396class DefaultMacroExtension(ext.Extension): 397 """ 398 This extension provides a new `{% defaultmacro %}` statement, which defines a macro only if it does not exist. 399 400 For example, 401 402 ```html+jinja 403 {% defaultmacro example() %} 404 test 123 405 {% enddefaultmacro %} 406 ``` 407 408 is equivalent to 409 410 ```html+jinja 411 {% macro default_example() %} 412 test 123 413 {% endmacro %} 414 {% if not example %} 415 {% macro example() %} 416 test 123 417 {% endmacro %} 418 {% endif %} 419 ``` 420 421 Additionally, the default implementation is also available as `default_$macroname`, which makes it possible 422 to reference it in the override. 423 """ 424 425 tags = {"defaultmacro"} 426 427 def parse(self, parser): 428 m = nodes.Macro(lineno=next(parser.stream).lineno) 429 name = parser.parse_assign_target(name_only=True).name 430 m.name = f"default_{name}" 431 parser.parse_signature(m) 432 m.body = parser.parse_statements(("name:enddefaultmacro",), drop_needle=True) 433 434 if_stmt = nodes.If( 435 nodes.Not(nodes.Name(name, "load")), 436 [nodes.Macro(name, m.args, m.defaults, m.body)], 437 [], 438 [], 439 ) 440 return [m, if_stmt]
The pygments lexer used for pdoc.render_helpers.highlight. Overwrite this to configure pygments lexing.
The pygments formatter used for pdoc.render_helpers.highlight. Overwrite this to configure pygments highlighting of code blocks.
The usage of the .codehilite
CSS selector in custom templates is deprecated since pdoc 10, use .pdoc-code
instead.
The pygments formatter used for pdoc.render_helpers.format_signature. Overwrite this to configure pygments highlighting of signatures.
The default extensions loaded for markdown2
.
Overwrite this to configure Markdown rendering.
73@cache 74def highlight(doc: pdoc.doc.Doc) -> str: 75 """Highlight the source code of a documentation object using pygments.""" 76 if isinstance(doc, str): # pragma: no cover 77 warnings.warn( 78 "Passing a string to the `highlight` render helper is deprecated, pass a pdoc.doc.Doc object instead.", 79 DeprecationWarning, 80 ) 81 return Markup(pygments.highlight(doc, lexer, formatter)) 82 83 # set up correct line numbers and anchors 84 formatter.linespans = doc.qualname or "L" 85 formatter.linenostart = doc.source_lines[0] + 1 if doc.source_lines else 1 86 return Markup(pygments.highlight(doc.source, lexer, formatter))
Highlight the source code of a documentation object using pygments.
89def format_signature(sig: inspect.Signature, colon: bool) -> str: 90 """Format and highlight a function signature using pygments. Returns HTML.""" 91 # First get a list with all params as strings. 92 result = pdoc.doc._PrettySignature._params(sig) # type: ignore 93 return_annot = pdoc.doc._PrettySignature._return_annotation_str(sig) # type: ignore 94 95 multiline = ( 96 sum(len(x) + 2 for x in result) + len(return_annot) 97 > pdoc.doc._PrettySignature.MULTILINE_CUTOFF 98 ) 99 100 # Next, individually highlight each parameter using pygments and wrap it in a span.param. 101 # This later allows us to properly control line breaks. 102 pretty_result = [] 103 for i, param in enumerate(result): 104 pretty = pygments.highlight(param, lexer, signature_formatter).strip() 105 if multiline: 106 pretty = f"""<span class="param">\t{pretty},</span>""" 107 else: 108 pretty = f"""<span class="param">{pretty}, </span>""" 109 pretty_result.append(pretty) 110 111 # remove last comma. 112 if pretty_result: 113 pretty_result[-1] = pretty_result[-1].rpartition(",")[0] + "</span>" 114 115 # Add return annotation. 116 rendered = "(%s)" % "".join(pretty_result) 117 if return_annot: 118 anno = pygments.highlight(return_annot, lexer, signature_formatter).strip() 119 rendered = ( 120 rendered[:-1] 121 + f'<span class="return-annotation">) -> {anno}{":" if colon else ""}</span>' 122 ) 123 124 if multiline: 125 rendered = f'<span class="signature pdoc-code multiline">{rendered}</span>' 126 else: 127 rendered = f'<span class="signature pdoc-code condensed">{rendered}</span>' 128 129 return Markup(rendered)
Format and highlight a function signature using pygments. Returns HTML.
132@cache 133def to_html(docstring: str) -> str: 134 """ 135 Convert `docstring` from Markdown to HTML. 136 """ 137 # careful: markdown2 returns a subclass of str with an extra 138 # .toc_html attribute. don't further process the result, 139 # otherwise this attribute will be lost. 140 return pdoc.markdown2.markdown(docstring, extras=markdown_extensions) # type: ignore
Convert docstring
from Markdown to HTML.
143@pass_context 144def to_markdown_with_context(context: Context, docstring: str) -> str: 145 """ 146 Converts `docstring` from a custom docformat to Markdown (if necessary), and then from Markdown to HTML. 147 """ 148 module: pdoc.doc.Module = context["module"] 149 docformat: str = context["docformat"] 150 return to_markdown(docstring, module, docformat)
Converts docstring
from a custom docformat to Markdown (if necessary), and then from Markdown to HTML.
158def possible_sources( 159 all_modules: Collection[str], identifier: str 160) -> Iterable[tuple[str, str]]: 161 """ 162 For a given identifier, return all possible sources where it could originate from. 163 For example, assume `examplepkg._internal.Foo` with all_modules=["examplepkg"]. 164 This could be a Foo class in _internal.py, or a nested `class _internal: class Foo` in examplepkg. 165 We return both candidates as we don't know if _internal.py exists. 166 It may not be in all_modules because it's been excluded by `__all__`. 167 However, if `examplepkg._internal` is in all_modules we know that it can only be that option. 168 """ 169 if identifier in all_modules: 170 yield identifier, "" 171 return 172 173 modulename = identifier 174 qualname = None 175 while modulename: 176 modulename, _, add = modulename.rpartition(".") 177 qualname = f"{add}.{qualname}" if qualname else add 178 yield modulename, qualname 179 if modulename in all_modules: 180 return 181 raise ValueError(f"Invalid identifier: {identifier}")
For a given identifier, return all possible sources where it could originate from.
For example, assume examplepkg._internal.Foo
with all_modules=["examplepkg"].
This could be a Foo class in _internal.py, or a nested class _internal: class Foo
in examplepkg.
We return both candidates as we don't know if _internal.py exists.
It may not be in all_modules because it's been excluded by __all__
.
However, if examplepkg._internal
is in all_modules we know that it can only be that option.
184def split_identifier(all_modules: Collection[str], fullname: str) -> tuple[str, str]: 185 """ 186 Split an identifier into a `(modulename, qualname)` tuple. For example, `pdoc.render_helpers.split_identifier` 187 would be split into `("pdoc.render_helpers","split_identifier")`. This is necessary to generate links to the 188 correct module. 189 """ 190 warnings.warn( 191 "pdoc.render_helpers.split_identifier is deprecated and will be removed in a future release. " 192 "Use pdoc.render_helpers.possible_sources instead.", 193 DeprecationWarning, 194 ) 195 *_, last = possible_sources(all_modules, fullname) 196 return last
Split an identifier into a (modulename, qualname)
tuple. For example, pdoc.render_helpers.split_identifier
would be split into ("pdoc.render_helpers","split_identifier")
. This is necessary to generate links to the
correct module.
208@cache 209def relative_link(current_module: str, target_module: str) -> str: 210 """Compute the relative link to another module's HTML file.""" 211 if current_module == target_module: 212 return "" 213 return _relative_link( 214 current_module.split(".")[:-1], 215 target_module.split("."), 216 )
Compute the relative link to another module's HTML file.
219def qualname_candidates(identifier: str, context_qualname: str) -> list[str]: 220 """ 221 Given an identifier in a current namespace, return all possible qualnames in the current module. 222 For example, if we are in Foo's subclass Bar and `baz()` is the identifier, 223 return `Foo.Bar.baz()`, `Foo.baz()`, and `baz()`. 224 """ 225 end = len(context_qualname) 226 ret = [] 227 while end > 0: 228 ret.append(f"{context_qualname[:end]}.{identifier}") 229 end = context_qualname.rfind(".", 0, end) 230 ret.append(identifier) 231 return ret
Given an identifier in a current namespace, return all possible qualnames in the current module.
For example, if we are in Foo's subclass Bar and baz()
is the identifier,
return Foo.Bar.baz()
, Foo.baz()
, and baz()
.
234@pass_context 235def linkify(context: Context, code: str, namespace: str = "") -> str: 236 """ 237 Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that 238 are not rendered at the moment will be ignored. 239 A piece of text is considered to be an identifier if it either contains a `.` or is surrounded by `<code>` tags. 240 """ 241 242 def linkify_repl(m: re.Match): 243 text = m.group(0) 244 identifier = removesuffix(text, "()") 245 246 # Check if this is a local reference within this module? 247 mod: pdoc.doc.Module = context["module"] 248 for qualname in qualname_candidates(identifier, namespace): 249 doc = mod.get(qualname) 250 if doc and context["is_public"](doc).strip(): 251 return f'<a href="#{qualname}">{text}</a>' 252 253 module = "" 254 qualname = "" 255 try: 256 # Check if the object we are interested in is imported and re-exposed in the current namespace. 257 for module, qualname in possible_sources( 258 context["all_modules"], identifier 259 ): 260 doc = mod.get(qualname) 261 if ( 262 doc 263 and doc.taken_from == (module, qualname) 264 and context["is_public"](doc).strip() 265 ): 266 if text.endswith("()"): 267 text = f"{doc.fullname}()" 268 else: 269 text = doc.fullname 270 return f'<a href="#{qualname}">{text}</a>' 271 except ValueError: 272 # possible_sources did not find a parent module. 273 return text 274 else: 275 # It's not, but we now know the parent module. Does the target exist? 276 doc = context["all_modules"][module] 277 if qualname: 278 assert isinstance(doc, pdoc.doc.Module) 279 doc = doc.get(qualname) 280 target_exists_and_public = ( 281 doc is not None and context["is_public"](doc).strip() 282 ) 283 if target_exists_and_public: 284 if qualname: 285 qualname = f"#{qualname}" 286 return f'<a href="{relative_link(context["module"].modulename, module)}{qualname}">{text}</a>' 287 else: 288 return text 289 290 return Markup( 291 re.sub( 292 r""" 293 # Part 1: foo.bar or foo.bar() (without backticks) 294 (?<![/=?#&]) # heuristic: not part of a URL 295 \b 296 (?!\d)[a-zA-Z0-9_]+ 297 (?:\.(?!\d)[a-zA-Z0-9_]+)+ 298 (?:\(\)|\b(?!\(\))) # we either end on () or on a word boundary. 299 (?!</a>) # not an existing link 300 (?![/#]) # heuristic: not part of a URL 301 302 | # Part 2: `foo` or `foo()`. `foo.bar` is already covered with part 1. 303 (?<=<code>) 304 (?!\d)[a-zA-Z0-9_]+ 305 (?:\(\))? 306 (?=</code>(?!</a>)) 307 """, 308 linkify_repl, 309 code, 310 flags=re.VERBOSE, 311 ) 312 )
Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that
are not rendered at the moment will be ignored.
A piece of text is considered to be an identifier if it either contains a .
or is surrounded by <code>
tags.
315@pass_context 316def link(context: Context, spec: tuple[str, str], text: str | None = None) -> str: 317 """Create a link for a specific `(modulename, qualname)` tuple.""" 318 mod: pdoc.doc.Module = context["module"] 319 modulename, qualname = spec 320 321 # Check if the object we are interested is also imported and re-exposed in the current namespace. 322 doc = mod.get(qualname) 323 if doc and doc.taken_from == spec and context["is_public"](doc).strip(): 324 if text: 325 text = text.replace(f"{modulename}.", f"{mod.modulename}.") 326 modulename = mod.modulename 327 328 if mod.modulename == modulename: 329 fullname = qualname 330 else: 331 fullname = removesuffix(f"{modulename}.{qualname}", ".") 332 333 if qualname: 334 qualname = f"#{qualname}" 335 if modulename in context["all_modules"]: 336 return Markup( 337 f'<a href="{relative_link(context["module"].modulename, modulename)}{qualname}">{text or fullname}</a>' 338 ) 339 return text or fullname
Create a link for a specific (modulename, qualname)
tuple.
342def edit_url( 343 modulename: str, is_package: bool, mapping: Mapping[str, str] 344) -> str | None: 345 """Create a link to edit a particular file in the used version control system.""" 346 for m, prefix in mapping.items(): 347 if m == modulename or modulename.startswith(f"{m}."): 348 filename = modulename[len(m) + 1 :].replace(".", "/") 349 if is_package: 350 filename = f"{filename}/__init__.py".lstrip("/") 351 else: 352 filename += ".py" 353 return f"{prefix}{filename}" 354 return None
Create a link to edit a particular file in the used version control system.
357def root_module_name(all_modules: Mapping[str, pdoc.doc.Module]) -> str | None: 358 """ 359 Return the name of the (unique) top-level module, or `None` 360 if no such module exists. 361 362 For example, assuming `foo`, `foo.bar`, and `foo.baz` are documented, 363 this function will return `foo`. If `foo` and `bar` are documented, 364 this function will return `None` as there is no unique top-level module. 365 """ 366 shortest_name = min(all_modules, key=len, default=None) 367 prefix = f"{shortest_name}." 368 all_others_are_submodules = all( 369 x.startswith(prefix) or x == shortest_name for x in all_modules 370 ) 371 if all_others_are_submodules: 372 return shortest_name 373 else: 374 return None
Return the name of the (unique) top-level module, or None
if no such module exists.
For example, assuming foo
, foo.bar
, and foo.baz
are documented,
this function will return foo
. If foo
and bar
are documented,
this function will return None
as there is no unique top-level module.
377def minify_css(css: str) -> str: 378 """Do some very basic CSS minification.""" 379 css = re.sub(r"[ ]{4}|\n|(?<=[:{}]) | (?=[{}])", "", css) 380 css = re.sub( 381 r"/\*.+?\*/", lambda m: m.group(0) if m.group(0).startswith("/*!") else "", css 382 ) 383 return Markup(css.replace("<style", "\n<style"))
Do some very basic CSS minification.
386@contextmanager 387def defuse_unsafe_reprs(): 388 """This decorator is applied by pdoc before calling an object's repr(). 389 It applies some heuristics to patch our sensitive information. 390 For example, `os.environ`'s default `__repr__` implementation exposes all 391 local secrets. 392 """ 393 with (patch.object(os._Environ, "__repr__", lambda self: "os.environ")): 394 yield
This decorator is applied by pdoc before calling an object's repr().
It applies some heuristics to patch our sensitive information.
For example, os.environ
's default __repr__
implementation exposes all
local secrets.
397class DefaultMacroExtension(ext.Extension): 398 """ 399 This extension provides a new `{% defaultmacro %}` statement, which defines a macro only if it does not exist. 400 401 For example, 402 403 ```html+jinja 404 {% defaultmacro example() %} 405 test 123 406 {% enddefaultmacro %} 407 ``` 408 409 is equivalent to 410 411 ```html+jinja 412 {% macro default_example() %} 413 test 123 414 {% endmacro %} 415 {% if not example %} 416 {% macro example() %} 417 test 123 418 {% endmacro %} 419 {% endif %} 420 ``` 421 422 Additionally, the default implementation is also available as `default_$macroname`, which makes it possible 423 to reference it in the override. 424 """ 425 426 tags = {"defaultmacro"} 427 428 def parse(self, parser): 429 m = nodes.Macro(lineno=next(parser.stream).lineno) 430 name = parser.parse_assign_target(name_only=True).name 431 m.name = f"default_{name}" 432 parser.parse_signature(m) 433 m.body = parser.parse_statements(("name:enddefaultmacro",), drop_needle=True) 434 435 if_stmt = nodes.If( 436 nodes.Not(nodes.Name(name, "load")), 437 [nodes.Macro(name, m.args, m.defaults, m.body)], 438 [], 439 [], 440 ) 441 return [m, if_stmt]
This extension provides a new {% defaultmacro %}
statement, which defines a macro only if it does not exist.
For example,
{% defaultmacro example() %}
test 123
{% enddefaultmacro %}
is equivalent to
{% macro default_example() %}
test 123
{% endmacro %}
{% if not example %}
{% macro example() %}
test 123
{% endmacro %}
{% endif %}
Additionally, the default implementation is also available as default_$macroname
, which makes it possible
to reference it in the override.
428 def parse(self, parser): 429 m = nodes.Macro(lineno=next(parser.stream).lineno) 430 name = parser.parse_assign_target(name_only=True).name 431 m.name = f"default_{name}" 432 parser.parse_signature(m) 433 m.body = parser.parse_statements(("name:enddefaultmacro",), drop_needle=True) 434 435 if_stmt = nodes.If( 436 nodes.Not(nodes.Name(name, "load")), 437 [nodes.Macro(name, m.args, m.defaults, m.body)], 438 [], 439 [], 440 ) 441 return [m, if_stmt]
If any of the tags
matched this method is called with the
parser as first argument. The token the parser stream is pointing at
is the name token that matched. This method has to return one or a
list of multiple nodes.
Inherited Members
- jinja2.ext.Extension
- Extension
- priority
- bind
- preprocess
- filter_stream
- attr
- call_method