Skip to content

Commit 6ee46bd

Browse files
SilverRainZMiMoCode
andcommitted
feat: Add recentupdate directive using BaseContextDirective
Co-authored-by: MiMoCode <mimo@xiaomi.com>
1 parent ee1c5b3 commit 6ee46bd

1 file changed

Lines changed: 102 additions & 22 deletions

File tree

src/sphinxnotes/recentupdate/__init__.py

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@
1515
from collections import OrderedDict
1616
from os import path
1717
from itertools import islice
18+
from textwrap import dedent
1819

1920
from git import Repo
2021

22+
from docutils.parsers.rst import directives
23+
2124
from sphinx.util import logging
2225
from sphinx.config import ENUM
2326

2427
from sphinxnotes.render import (
2528
extra_context,
2629
ExtraContext,
2730
ExtraContextRequest,
31+
BaseContextDirective,
32+
Phase,
33+
Template,
2834
)
2935

3036
from . import meta
@@ -156,7 +162,7 @@ def get_git_revisions(
156162
# :T: change in the type of the file
157163
# :U: file is unmerged (you must complete the merge before it can be committed)
158164
# :X: "unknown" change type (most probably a bug, please report it)
159-
status_maps = {'M': m, 'A': a, 'D': d }
165+
status_maps = {'M': m, 'A': a, 'D': d}
160166

161167
# Use git diff --name-status with pathspecs for native pathspec matching
162168
name_status = repo.git.diff(
@@ -190,11 +196,98 @@ def get_git_revisions(
190196

191197

192198
def path2doc(repo: Repo, env: BuildEnvironment, blob_path: str) -> str | None:
193-
"""Convert a git repo-relative blob path to a Sphinx document name. """
199+
"""Convert a git repo-relative blob path to a Sphinx document name."""
194200
docname = env.path2doc(path.join(repo.working_dir, blob_path))
195201
return docname if (docname and not path.isabs(docname)) else None
196202

197203

204+
def collect_revisions(
205+
repo: Repo,
206+
env: BuildEnvironment,
207+
count: int,
208+
paths: list[str],
209+
group_by: str = '',
210+
) -> list[Revision]:
211+
"""Collect recent revisions from git, optionally grouped by time period."""
212+
count = count or env.config.recentupdate_count
213+
group_by = group_by or env.config.recentupdate_group_by
214+
215+
git_revs = get_git_revisions(repo, env, paths)
216+
217+
if group_by:
218+
groups = OrderedDict()
219+
for rev in git_revs:
220+
group_revisions(groups, rev, group_by)
221+
if len(groups) >= count:
222+
break
223+
revs = compact_groups(groups)
224+
else:
225+
revs = list(islice(git_revs, count))
226+
logger.info(
227+
f'[recentupdate] Expect {count} revisions, finally get {len(revs)}, group by {group_by}'
228+
)
229+
230+
return revs
231+
232+
233+
DEFAULT_TEMPLATE = dedent("""\
234+
{% for r in revisions %}
235+
{{ r.date.strftime('%Y-%m-%d') }}
236+
:Author: {{ r.author }}
237+
:Message: {{ r.message | join(', ') }}
238+
239+
{% if r.changed_docs -%}
240+
- Modified {{ r.changed_docs | roles("doc") | join(", ") }}
241+
{% endif %}
242+
{% if r.added_docs -%}
243+
- Added {{ r.added_docs | roles("doc") | join(", ") }}
244+
{% endif %}
245+
{% if r.removed_docs -%}
246+
- Deleted {{ r.removed_docs | join(", ") }}
247+
{% endif %}
248+
{% endfor %}
249+
""")
250+
251+
252+
class RecentUpdateDirective(BaseContextDirective):
253+
"""Directive for displaying recent document updates."""
254+
255+
has_content = True
256+
required_arguments = 0
257+
optional_arguments = 1
258+
option_spec = {
259+
'self': directives.flag,
260+
'paths': directives.unchanged,
261+
}
262+
263+
def current_context(self) -> dict:
264+
repo = RecentUpdateExtraContext.repo
265+
266+
count = (
267+
int(self.arguments[0])
268+
if self.arguments
269+
else self.env.config.recentupdate_count
270+
)
271+
272+
current_doc = 'self' in self.options
273+
if current_doc:
274+
docpath = self.env.doc2path(self.env.docname)
275+
paths = [path.relpath(docpath, repo.working_dir)]
276+
elif 'paths' in self.options:
277+
paths = [p.strip() for p in self.options['paths'].splitlines() if p.strip()]
278+
else:
279+
paths = ['.']
280+
281+
revs = collect_revisions(repo, self.env, count, paths)
282+
return {'revisions': revs}
283+
284+
def current_template(self) -> Template:
285+
text = '\n'.join(self.content) if self.has_content and self.content else ''
286+
if not text.strip():
287+
text = self.env.config.recentupdate_template
288+
return Template(text, phase=Phase.Parsing)
289+
290+
198291
@extra_context('recentupdate')
199292
class RecentUpdateExtraContext(ExtraContext):
200293
"""Extra context providing recent document revisions from Git."""
@@ -206,34 +299,18 @@ def generate(
206299
self,
207300
req: ExtraContextRequest,
208301
count: int = 0,
209-
paths: list[str] = ['.', ],
302+
paths: list[str] = [
303+
'.',
304+
],
210305
current_doc: bool = False,
211306
group_by: str = '',
212307
) -> Any:
213-
count = count or req.env.config.recentupdate_count
214-
group_by = group_by or req.env.config.recentupdate_group_by
215-
216308
if current_doc:
217309
docpath = req.env.doc2path(req.env.docname)
218310
repo_path = path.relpath(docpath, self.repo.working_dir)
219311
paths = [repo_path]
220312

221-
git_revs = get_git_revisions(self.repo, req.env, paths)
222-
223-
if group_by:
224-
groups = OrderedDict()
225-
for rev in git_revs:
226-
group_revisions(groups, rev, group_by)
227-
if len(groups) >= count:
228-
break
229-
revs = compact_groups(groups)
230-
else:
231-
revs = list(islice(git_revs, count))
232-
logger.info(
233-
f'[recentupdate] Expect {count} revisions, finally get {len(revs)}, group by {group_by}'
234-
)
235-
236-
return revs
313+
return collect_revisions(self.repo, req.env, count, paths, group_by)
237314

238315

239316
def setup(app: Sphinx):
@@ -243,10 +320,13 @@ def setup(app: Sphinx):
243320

244321
app.setup_extension('sphinxnotes.render')
245322

323+
app.add_directive('recentupdate', RecentUpdateDirective)
324+
246325
app.add_config_value(
247326
'recentupdate_exclude_commit', ['skip-recentupdate'], 'env', types=list[str]
248327
)
249328
app.add_config_value('recentupdate_count', 10, 'env', types=int)
329+
app.add_config_value('recentupdate_template', DEFAULT_TEMPLATE, 'env', types=str)
250330
app.add_config_value(
251331
'recentupdate_group_by', None, 'env', types=ENUM(None, 'day', 'month', 'year')
252332
)

0 commit comments

Comments
 (0)