Skip to content

Commit b9ed061

Browse files
SilverRainZMiMoCode
andcommitted
fix: Resolve paths relative to srcdir or current document
Co-authored-by: MiMoCode <mimo@xiaomi.com>
1 parent 8668f1e commit b9ed061

4 files changed

Lines changed: 94 additions & 6 deletions

File tree

docs/index.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@ Introduction
3030
Get the Sphinx document update information from Git repository.
3131

3232
This extension provides a :rst:dir:`recentupdate` directive that displays
33-
recent document update information read from a Git_ repository. It also
33+
recent document update information read from a Git repository. It also
3434
integrates with :external+render:doc:`sphinxnotes-render <index>` by
3535
providing an extra context for use in render templates.
3636

37-
.. _Git: https://git-scm.com/
38-
3937
.. INTRODUCTION END
4038
4139
Getting Started

docs/usage.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ The ``recentupdate`` Directive
2828
.. rst:directive:option:: paths
2929
:type: lines of str
3030
31-
Git pathspecs (:manpage:`gitglossary(7)`) to filter file changes,
32-
one per line. Defaults to ``.``.
31+
Paths to filter file changes, one per line.
32+
Paths starting with ``/`` are relative to the source directory;
33+
other paths are relative to the current document's directory.
3334
3435
See also :example:`Recent Updates of Custom Path`.
3536
@@ -137,7 +138,7 @@ Examples
137138
Recent changes of the :doc:`changelog`:
138139

139140
.. recentupdate::
140-
:paths: docs/changelog.rst
141+
:paths: /changelog.rst
141142

142143
{% for r in revisions %}
143144
``{{ r.date.strftime('%Y-%m-%d') }}`` — {{ r.message[0] }}

src/sphinxnotes/recentupdate/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,30 @@ def path2doc(repo: Repo, env: BuildEnvironment, blob_path: str) -> str | None:
201201
return '/' + docname if (docname and not path.isabs(docname)) else None
202202

203203

204+
def _resolve_paths(
205+
repo: Repo,
206+
env: BuildEnvironment,
207+
docname: str,
208+
paths: list[str],
209+
) -> list[str]:
210+
"""Resolve user-supplied paths to repo-relative paths.
211+
212+
- Paths starting with '/' are relative to srcdir.
213+
- Paths starting with './' or without prefix are relative to the current
214+
document's directory.
215+
"""
216+
resolved = []
217+
for p in paths:
218+
if p.startswith('/'):
219+
abspath = path.join(env.srcdir, p[1:])
220+
else:
221+
docdir = path.dirname(env.doc2path(docname))
222+
abspath = path.join(docdir, p)
223+
rel = path.relpath(abspath, repo.working_dir)
224+
resolved.append(rel)
225+
return resolved
226+
227+
204228
def collect_revisions(
205229
repo: Repo,
206230
env: BuildEnvironment,
@@ -255,6 +279,7 @@ def current_context(self) -> dict:
255279
paths = [path.relpath(docpath, repo.working_dir)]
256280
elif 'paths' in self.options:
257281
paths = [p.strip() for p in self.options['paths'].splitlines() if p.strip()]
282+
paths = _resolve_paths(repo, self.env, self.env.docname, paths)
258283
else:
259284
paths = []
260285

@@ -288,6 +313,8 @@ def generate(
288313
if self_only:
289314
docpath = req.env.doc2path(req.env.docname)
290315
paths = [path.relpath(docpath, repo.working_dir)]
316+
else:
317+
paths = _resolve_paths(repo, req.env, req.env.docname, paths)
291318

292319
return collect_revisions(repo, req.env, count, paths, group_by)
293320

tests/test_resolve_paths.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import unittest
2+
from unittest.mock import MagicMock
3+
4+
from sphinxnotes.recentupdate import _resolve_paths
5+
6+
7+
def _make_repo(working_dir='/srv/repo'):
8+
repo = MagicMock()
9+
repo.working_dir = working_dir
10+
return repo
11+
12+
13+
def _make_env(srcdir='/srv/repo/docs'):
14+
env = MagicMock()
15+
env.srcdir = srcdir
16+
17+
def doc2path(docname):
18+
return f'{srcdir}/{docname}.rst'
19+
20+
env.doc2path = doc2path
21+
return env
22+
23+
24+
class TestResolvePaths(unittest.TestCase):
25+
def test_absolute_path_resolves_relative_to_srcdir(self):
26+
"""Paths starting with '/' are relative to srcdir."""
27+
repo = _make_repo('/srv/repo')
28+
env = _make_env('/srv/repo/docs')
29+
result = _resolve_paths(repo, env, 'index', ['/subdir/page'])
30+
self.assertEqual(result, ['docs/subdir/page'])
31+
32+
def test_relative_path_resolves_relative_to_doc_dir(self):
33+
"""Paths without prefix are relative to current document's directory."""
34+
repo = _make_repo('/srv/repo')
35+
env = _make_env('/srv/repo/docs')
36+
result = _resolve_paths(repo, env, 'subdir/index', ['other'])
37+
self.assertEqual(result, ['docs/subdir/other'])
38+
39+
def test_dot_slash_path_resolves_relative_to_doc_dir(self):
40+
"""Paths starting with './' are relative to current document's directory."""
41+
repo = _make_repo('/srv/repo')
42+
env = _make_env('/srv/repo/docs')
43+
result = _resolve_paths(repo, env, 'subdir/index', ['./other'])
44+
self.assertEqual(result, ['docs/subdir/other'])
45+
46+
def test_root_doc_relative_path(self):
47+
"""Relative path from root document resolves correctly."""
48+
repo = _make_repo('/srv/repo')
49+
env = _make_env('/srv/repo/docs')
50+
result = _resolve_paths(repo, env, 'index', ['page'])
51+
self.assertEqual(result, ['docs/page'])
52+
53+
def test_empty_paths(self):
54+
"""Empty paths list returns empty list."""
55+
repo = _make_repo('/srv/repo')
56+
env = _make_env('/srv/repo/docs')
57+
result = _resolve_paths(repo, env, 'index', [])
58+
self.assertEqual(result, [])
59+
60+
61+
if __name__ == '__main__':
62+
unittest.main()

0 commit comments

Comments
 (0)