|
1 | 1 | import os |
2 | 2 | import subprocess |
| 3 | +from collections.abc import Iterable |
3 | 4 | from pathlib import Path |
4 | 5 | from typing import Optional |
5 | 6 |
|
|
8 | 9 | from mkdocs.plugins import BasePlugin |
9 | 10 |
|
10 | 11 |
|
11 | | -def _latest_mtime(root: Path) -> float: |
12 | | - """Return the latest modification time for all files under a directory.""" |
13 | | - mtimes = [] |
14 | | - for path in root.rglob("*"): |
15 | | - if path.is_file(): |
16 | | - mtimes.append(path.stat().st_mtime) |
17 | | - return max(mtimes) if mtimes else 0.0 |
| 12 | +def _is_relative_to(path: Path, base: Path) -> bool: |
| 13 | + try: |
| 14 | + path.relative_to(base) |
| 15 | + return True |
| 16 | + except ValueError: |
| 17 | + return False |
| 18 | + |
| 19 | + |
| 20 | +def _latest_mtime(root: Path, ignored: Optional[Iterable[Path]] = None) -> float: |
| 21 | + """Return the newest mtime under ``root`` ignoring any ``ignored`` directories.""" |
| 22 | + root = root.resolve() |
| 23 | + ignore_roots = tuple(p.resolve() for p in ignored or ()) |
| 24 | + latest = 0.0 |
| 25 | + for dirpath, dirnames, filenames in os.walk(root): |
| 26 | + current_dir = Path(dirpath) |
| 27 | + if any(_is_relative_to(current_dir, skipped) for skipped in ignore_roots): |
| 28 | + dirnames[:] = [] |
| 29 | + continue |
| 30 | + for name in filenames: |
| 31 | + file_path = current_dir / name |
| 32 | + if any(_is_relative_to(file_path, skipped) for skipped in ignore_roots): |
| 33 | + continue |
| 34 | + try: |
| 35 | + latest = max(latest, file_path.stat().st_mtime) |
| 36 | + except FileNotFoundError: |
| 37 | + continue |
| 38 | + return latest |
18 | 39 |
|
19 | 40 |
|
20 | 41 | class ExamplesGalleryPlugin(BasePlugin): |
@@ -68,7 +89,16 @@ def on_pre_build(self, config: MkDocsConfig) -> None: |
68 | 89 | if not src.exists(): |
69 | 90 | return |
70 | 91 |
|
71 | | - src_mtime = _latest_mtime(src.parent) |
| 92 | + # Accept either a specific entry file or a folder and consistently inspect |
| 93 | + # the directory that actually contains the source files. |
| 94 | + src_root = src if src.is_dir() else src.parent |
| 95 | + ignore_roots: list[Path] = [] |
| 96 | + if _is_relative_to(dist_dir, src_root): |
| 97 | + # Publishing into the source tree dirties files for every build, so |
| 98 | + # ignore the generated dist folder when computing the latest mtime. |
| 99 | + ignore_roots.append(dist_dir) |
| 100 | + |
| 101 | + src_mtime = _latest_mtime(src_root, ignore_roots) |
72 | 102 | dist_mtime = dist_index.stat().st_mtime if dist_index.exists() else 0.0 |
73 | 103 | if dist_mtime >= src_mtime: |
74 | 104 | return |
|
0 commit comments