Coverage for .nox/test/lib/python3.12/site-packages/mkdocs_github_changelog/extension.py: 91%
53 statements
« prev ^ index » next coverage.py v7.3.4, created at 2023-12-26 13:15 +0000
« prev ^ index » next coverage.py v7.3.4, created at 2023-12-26 13:15 +0000
1"""Defines the extension for processing a markdown block to get the github release information.
3Uses a Markdown [block processor](https://python-markdown.github.io/extensions/api/#blockprocessors)
4that looks for on '::github-release-changelog <org_or_user>/<repo>'.
6The specifics can be configured with YAML configuration in the block, and include !ENV flags:
8```yaml
9::github-changelog <org_or_user>/<repo>
10 # Set the Github token - Needed for private repos, can be set globally as well.
11 token: !ENV GITHUB_TOKEN
13 # Set the base indent to work from - optional, can use the heading of the block instead.
14 base_indent: 2
16 # Set the release template to process the release into as an Jinja2 template (optional) (the github api response is passed in as release)
17 release_template: "{{release.title}}"
19 # Set the github API url for e.g. self-hosted enterprise - optional (and not tested on those)
20 github_api_url: https://api.github.com
22```
23"""
25from __future__ import annotations
27import re
28from typing import Any, MutableSequence, TYPE_CHECKING
29from xml.etree.ElementTree import Element # nosec: B405
31from markdown.blockprocessors import BlockProcessor
32from markdown.extensions import Extension
33from mkdocs.utils.yaml import get_yaml_loader, yaml_load
35from mkdocs_github_changelog.get_releases import get_releases_as_markdown
37if TYPE_CHECKING:
38 from markdown import Markdown
39 from markdown.blockparser import BlockParser
42class GithubReleaseChangelogProcessor(BlockProcessor):
43 """Changelog Markdown block processor."""
45 regex = re.compile(r"^(?P<heading>#{1,6} *|)::github-release-changelog ?(?P<org>[a-zA-Z0-9-]+?)\/(?P<repo>.+?) *$", flags=re.MULTILINE)
47 def __init__(
48 self,
49 parser: BlockParser,
50 config: dict,
51 ) -> None:
52 """Initialize the processor."""
53 super().__init__(parser=parser)
54 self._config = config
56 def test(self, parent: Element, block: str) -> bool: # noqa: U100
57 """Match the extension instructions."""
58 return bool(self.regex.search(block))
60 def run(self, parent: Element, blocks: MutableSequence[str]) -> None:
61 """Run code on the matched blocks to get the markdown."""
62 block = blocks.pop(0)
63 match = self.regex.search(block)
65 if match:
66 if match.start() > 0:
67 self.parser.parseBlocks(parent, [block[: match.start()]])
68 # removes the first line
69 block = block[match.end() :]
71 block, the_rest = self.detab(block)
72 if the_rest:
73 # This block contained unindented line(s) after the first indented
74 # line. Insert these lines as the first block of the master blocks
75 # list for future processing.
76 blocks.insert(0, the_rest)
78 if match:
79 heading_level = match["heading"].count("#")
80 # We are going to process the markdown from the releases and then
81 # insert it back into the blocks to be processed as markdown
82 block = self._process_block(match.groupdict()['org'], match.groupdict()['repo'], block, heading_level)
83 blocks.insert(0, block)
84 return False
86 def _process_block(
87 self,
88 org: str,
89 repo: str,
90 yaml_block: str,
91 heading_level: int = 0,
92 ) -> str:
93 """Process a block."""
94 config = yaml_load(yaml_block, loader=get_yaml_loader()) or {}
95 if heading_level is None:
96 heading_level = 0
97 base_indent = config.get('base_indent', heading_level)
98 token = config.get('token', self._config.get('token', None))
99 github_api_url = config.get('github_api_url', self._config.get('github_api_url', None))
100 release_template = config.get('release_template', self._config.get('release_template', None))
101 match = config.get('match', self._config.get('match', None))
102 autoprocess = config.get('autoprocess', self._config.get('autoprocess', True))
103 block = '\n\n'.join(get_releases_as_markdown(
104 organisation_or_user=org,
105 repository=repo,
106 token=token,
107 release_template=release_template,
108 github_api_url=github_api_url,
109 match=match,
110 autoprocess=autoprocess
111 ))
112 # We need to decrease/increase the base indent level
113 if base_indent > 0:
114 block = block.replace('# ', ('#'*base_indent)+'# ')
115 return block
118class GithubReleaseChangelogExtension(Extension):
119 """The Markdown extension."""
121 def __init__(self, config: dict, **kwargs: Any) -> None:
122 """Initialize the object."""
123 super().__init__(**kwargs)
124 self._config = config
126 def extendMarkdown(self, md: Markdown) -> None:
127 """Register the extension.
129 Add an instance of [`GithubReleaseChangelogProcessor`][mkdocs_github_changelog.extension.GithubReleaseChangelogProcessor]
130 to the Markdown parser.
131 """
132 md.parser.blockprocessors.register(
133 GithubReleaseChangelogProcessor(md.parser, self._config),
134 "github_release_changelog",
135 priority=75, # Right before markdown.blockprocessors.HashHeaderProcessor
136 )