Coverage for .nox/test/lib/python3.12/site-packages/mkdocs_github_changelog/get_releases.py: 92%

78 statements  

« prev     ^ index     » next       coverage.py v7.3.4, created at 2023-12-26 13:54 +0000

1"""Get releases from Github and convert to markdown.""" 

2from __future__ import annotations 

3 

4from datetime import datetime 

5import json 

6import os 

7import re 

8import sys 

9 

10if sys.version_info.major >= 3 and sys.version_info.minor >= 10: 

11 from importlib.metadata import entry_points 

12else: 

13 from backports.entry_points_selectable import entry_points 

14 

15if sys.version_info.major >= 3 and sys.version_info.minor < 11: 

16 from dateutil.parser import parse 

17 

18 

19from ghapi.all import GhApi, paged 

20from jinja2 import Environment 

21 

22RELEASE_TEMPLATE = "# [{{release.name}}]({{release.html_url}})\n*Released at {{release.published_at.isoformat()}}*\n\n{{release.body}}" 

23 

24 

25class _EnvironmentFactory(): 

26 """Jinja2 Environment Factory to allow for extension/customisation. 

27 

28 Adapted from https://djpugh.github.io/nskit. 

29 """ 

30 

31 def __init__(self): 

32 """Initialise the factory.""" 

33 self._environment = None 

34 

35 @property 

36 def environment(self) -> Environment: 

37 """Handle caching the environment object so it is lazily initialised.""" 

38 if self._environment is None: 

39 self._environment = self.get_environment() 

40 self.add_extensions(self._environment) 

41 return self._environment 

42 

43 def add_extensions(self, environment: Environment): 

44 """Add Extensions to the environment object.""" 

45 # Assuming no risk of extension clash 

46 extensions = [] 

47 # Load from JSON 

48 for ext in json.loads(os.environ.get('MKDOCS_GITHUB_CHANGELOG_JINJA_EXTENSIONS', '[]')): 

49 extensions.append(ext) 

50 for extension in list(set(extensions)): 

51 environment.add_extension(extension) 

52 

53 def get_environment(self) -> Environment: 

54 """Get the environment object based on the env var.""" 

55 selected_method = os.environ.get('MKDOCS_GITHUB_CHANGELOG_JINJA_ENVIRONMENT_FACTORY', None) 

56 if selected_method is None or selected_method.lower() == 'default': 

57 # This is our simple implementation 

58 selected_method = 'default' 

59 for ep in entry_points().select(group='mkdocs_github_changelog.jinja_environment_factory', name=selected_method): 

60 return ep.load()() 

61 

62 @staticmethod 

63 def default_environment(): 

64 """Get the default environment object.""" 

65 return Environment() # nosec B701 

66 

67 

68JINJA_ENVIRONMENT_FACTORY = _EnvironmentFactory() 

69 

70 

71def autoprocess_github_links(release): 

72 """We process the release to convert #xy and @abc links.""" 

73 if not getattr(release, 'processed', False): 

74 base_url = release.html_url.split('releases')[0] 

75 # We also want to parse this to get the 

76 root_url = '/'.join(base_url.split('/')[:-3]) 

77 user_re = '@[a-zA-Z0-9-]+' 

78 issue_re = '#[0-9]+' 

79 

80 def github_user_link(match_obj): 

81 user_name = match_obj.string[match_obj.start(): match_obj.end()] 

82 user_link = user_name.replace('@', root_url+'/') 

83 return f'[{user_name}]({user_link})' 

84 

85 def github_issue_link(match_obj): 

86 issue_key = match_obj.string[match_obj.start(): match_obj.end()] 

87 issue_link = issue_key.replace('#', base_url+'issues/') 

88 return f'[{issue_key}]({issue_link})' 

89 

90 release.body = re.sub(user_re, github_user_link, release.body) 

91 release.body = re.sub(issue_re, github_issue_link, release.body) 

92 release.processed = True 

93 return release 

94 

95 

96def get_releases_as_markdown(organisation_or_user: str, repository: str, token: str | None = None, release_template: str | None = RELEASE_TEMPLATE, github_api_url: str | None = None, match: str | None = None, autoprocess: bool | None = True): 

97 """Get the releases from github as a list of rendered markdown strings.""" 

98 if github_api_url is not None: 

99 github_api_url = github_api_url.rstrip('/') 

100 api = GhApi(token=token, gh_host=github_api_url) 

101 releases = [] 

102 for page in paged(api.repos.list_releases, organisation_or_user, repository, per_page=100): 

103 releases += page 

104 jinja_environment = JINJA_ENVIRONMENT_FACTORY.environment 

105 selected_releases = [] 

106 for release in releases: 

107 # Convert the published_at to datetime object 

108 if not isinstance(release.published_at, datetime): 

109 if sys.version_info.major >= 3 and sys.version_info.minor < 11: 

110 release.published_at = parse(release.published_at) 

111 else: 

112 release.published_at = datetime.fromisoformat(release.published_at) 

113 if autoprocess is None or autoprocess: 

114 autoprocess_github_links(release) 

115 if (match and re.match(match, release.name) is not None) or not match: 

116 selected_releases.append(release) 

117 if release_template is None: 

118 release_template = RELEASE_TEMPLATE 

119 return [jinja_environment.from_string(release_template).render(release=release) for release in selected_releases]