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

1"""Defines the extension for processing a markdown block to get the github release information. 

2 

3Uses a Markdown [block processor](https://python-markdown.github.io/extensions/api/#blockprocessors) 

4that looks for on '::github-release-changelog <org_or_user>/<repo>'. 

5 

6The specifics can be configured with YAML configuration in the block, and include !ENV flags: 

7 

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 

12 

13 # Set the base indent to work from - optional, can use the heading of the block instead. 

14 base_indent: 2 

15 

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}}" 

18 

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 

21 

22``` 

23""" 

24 

25from __future__ import annotations 

26 

27import re 

28from typing import Any, MutableSequence, TYPE_CHECKING 

29from xml.etree.ElementTree import Element # nosec: B405 

30 

31from markdown.blockprocessors import BlockProcessor 

32from markdown.extensions import Extension 

33from mkdocs.utils.yaml import get_yaml_loader, yaml_load 

34 

35from mkdocs_github_changelog.get_releases import get_releases_as_markdown 

36 

37if TYPE_CHECKING: 

38 from markdown import Markdown 

39 from markdown.blockparser import BlockParser 

40 

41 

42class GithubReleaseChangelogProcessor(BlockProcessor): 

43 """Changelog Markdown block processor.""" 

44 

45 regex = re.compile(r"^(?P<heading>#{1,6} *|)::github-release-changelog ?(?P<org>[a-zA-Z0-9-]+?)\/(?P<repo>.+?) *$", flags=re.MULTILINE) 

46 

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 

55 

56 def test(self, parent: Element, block: str) -> bool: # noqa: U100 

57 """Match the extension instructions.""" 

58 return bool(self.regex.search(block)) 

59 

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) 

64 

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() :] 

70 

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) 

77 

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 

85 

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 

116 

117 

118class GithubReleaseChangelogExtension(Extension): 

119 """The Markdown extension.""" 

120 

121 def __init__(self, config: dict, **kwargs: Any) -> None: 

122 """Initialize the object.""" 

123 super().__init__(**kwargs) 

124 self._config = config 

125 

126 def extendMarkdown(self, md: Markdown) -> None: 

127 """Register the extension. 

128 

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 )