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
« 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
4from datetime import datetime
5import json
6import os
7import re
8import sys
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
15if sys.version_info.major >= 3 and sys.version_info.minor < 11:
16 from dateutil.parser import parse
19from ghapi.all import GhApi, paged
20from jinja2 import Environment
22RELEASE_TEMPLATE = "# [{{release.name}}]({{release.html_url}})\n*Released at {{release.published_at.isoformat()}}*\n\n{{release.body}}"
25class _EnvironmentFactory():
26 """Jinja2 Environment Factory to allow for extension/customisation.
28 Adapted from https://djpugh.github.io/nskit.
29 """
31 def __init__(self):
32 """Initialise the factory."""
33 self._environment = None
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
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)
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()()
62 @staticmethod
63 def default_environment():
64 """Get the default environment object."""
65 return Environment() # nosec B701
68JINJA_ENVIRONMENT_FACTORY = _EnvironmentFactory()
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]+'
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})'
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})'
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
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]