Coverage for .nox/test/lib/python3.12/site-packages/mkdocs_licenseinfo/extension.py: 90%

59 statements  

« prev     ^ index     » next       coverage.py v7.3.4, created at 2023-12-27 09:44 +0000

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

2 

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

4that looks for on '::licenseinfo'. 

5 

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

7 

8```yaml 

9::license_check 

10 

11 # The information on what to use for requirments (see the [licensecheck docs](https://pypi.org/project/licensecheck/#configuration-example)) - default is PEP631 (pyproject.toml) 

12 using: <PEP631:dev;dev-test> 

13 # Packages to remove (see the [licensecheck docs](https://pypi.org/project/licensecheck/#configuration-example)) - default is None, the packages in this are not shown in the section 

14 diff: <PEP631> 

15 # A list of packages to ignore 

16 ignore_packages: <list of packages> 

17 # A list of packages to fail on 

18 fail_packages: <list of packages> 

19 # A list of packages to skip 

20 skip_packages: <list of packages> 

21 # A list of licenses to ignore 

22 ignore_licenses: <list of licenses> 

23 # A list of licenses to skip 

24 fail_licenses: <list of licenses> 

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

26 base_indent: 2 

27 # Set the package template to process the package into as an Jinja2 template (optional) (the github api response is passed in as package) 

28 package_template: "{{package.name}}" 

29 # Path to requirements containing folder relative to docs_dir - if not set the working dir is used 

30 requirements_path: <path string> 

31``` 

32""" 

33 

34from __future__ import annotations 

35 

36from pathlib import Path 

37import re 

38from typing import Any, MutableSequence, TYPE_CHECKING 

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

40 

41from markdown.blockprocessors import BlockProcessor 

42from markdown.extensions import Extension 

43from mkdocs.utils.yaml import get_yaml_loader, yaml_load 

44 

45from mkdocs_licenseinfo.render_markdown import get_licenses_as_markdown 

46 

47if TYPE_CHECKING: 

48 from markdown import Markdown 

49 from markdown.blockparser import BlockParser 

50 

51 

52class LicenseInfoProcessor(BlockProcessor): 

53 """License info Markdown block processor.""" 

54 

55 regex = re.compile(r"^(?P<heading>#{1,6} *|)::licenseinfo*$", flags=re.MULTILINE) 

56 

57 def __init__( 

58 self, 

59 parser: BlockParser, 

60 config: dict, 

61 ) -> None: 

62 """Initialize the processor.""" 

63 super().__init__(parser=parser) 

64 self._config = config 

65 

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

67 """Match the extension instructions.""" 

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

69 

70 def run(self, parent: Element, blocks: MutableSequence[str]) -> None: 

71 """Run code on the matched blocks to get the markdown.""" 

72 block = blocks.pop(0) 

73 match = self.regex.search(block) 

74 

75 if match: 

76 if match.start() > 0: 

77 self.parser.parseBlocks(parent, [block[: match.start()]]) 

78 # removes the first line 

79 block = block[match.end() :] 

80 

81 block, the_rest = self.detab(block) 

82 if the_rest: 

83 # This block contained unindented line(s) after the first indented 

84 # line. Insert these lines as the first block of the master blocks 

85 # list for future processing. 

86 blocks.insert(0, the_rest) 

87 

88 if match: 

89 heading_level = match["heading"].count("#") 

90 # We are going to process the markdown from the releases and then 

91 # insert it back into the blocks to be processed as markdown 

92 block = self._process_block(block, heading_level) 

93 blocks.insert(0, block) 

94 

95 def _process_block( 

96 self, 

97 yaml_block: str, 

98 heading_level: int = 0, 

99 ) -> str: 

100 """Process a block.""" 

101 config = yaml_load(yaml_block, loader=get_yaml_loader()) or {} 

102 if heading_level is None: 

103 heading_level = 0 

104 base_indent = config.get('base_indent', heading_level) 

105 using = config.get('using', None) 

106 diff = config.get('diff', None) 

107 ignore_packages = config.get('ignore_packages', self._config.get('ignore_packages', None)) 

108 fail_packages = config.get('fail_packages', self._config.get('fail_packages', None)) 

109 skip_packages = config.get('skip_packages', self._config.get('skip_packages', None)) 

110 ignore_licenses = config.get('ignore_licenses', self._config.get('ignore_licenses', None)) 

111 fail_licenses = config.get('fail_licenses', self._config.get('fail_licenses', None)) 

112 package_template = config.get('package_template', self._config.get('package_template', None)) 

113 requirements_path = config.get('requirements_path', self._config.get('requirements_path', None)) 

114 if requirements_path: 

115 requirements_path = (Path(self._config.get('docs_dir', '.')) / Path(requirements_path)).resolve() 

116 block = '\n\n'.join(get_licenses_as_markdown( 

117 using=using, 

118 ignore_packages=ignore_packages, 

119 fail_packages=fail_packages, 

120 skip_packages=skip_packages, 

121 ignore_licenses=ignore_licenses, 

122 fail_licenses=fail_licenses, 

123 diff=diff, 

124 package_template=package_template, 

125 path=requirements_path 

126 )) 

127 # We need to decrease/increase the base indent level 

128 if base_indent > 0: 

129 block = block.replace('# ', ('#'*base_indent)+'# ') 

130 return block 

131 

132 

133class LicenseInfoExtension(Extension): 

134 """The Markdown extension.""" 

135 

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

137 """Initialize the object.""" 

138 super().__init__(**kwargs) 

139 self._config = config 

140 

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

142 """Register the extension. 

143 

144 Add an instance of [`LicenseInfoProcessor`][mkdocs_licenseinfo.extension.LicenseInfoProcessor] 

145 to the Markdown parser. 

146 """ 

147 md.parser.blockprocessors.register( 

148 LicenseInfoProcessor(md.parser, self._config), 

149 "license_check", 

150 priority=75, # Right before markdown.blockprocessors.HashHeaderProcessor 

151 )