mkdocs-document-dates 3.7.4__tar.gz → 3.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mkdocs_document_dates-3.7.4/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.8.0}/PKG-INFO +3 -3
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/README.md +2 -2
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/cache_manager.py +3 -3
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/plugin.py +45 -63
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/utils.py +72 -35
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0/mkdocs_document_dates.egg-info}/PKG-INFO +3 -3
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/pyproject.toml +1 -1
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/setup.py +1 -1
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/LICENSE +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/MANIFEST.in +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/__init__.py +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks/pre-commit +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks_installer.py +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/.DS_Store +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/config/user.config.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/config/user.config.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/core.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/core.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/default.config.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/md5.min.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/utils.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/fonts/material-icons.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/fonts/materialicons.woff2 +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_detail.html +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_grid.html +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_group.html +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_list.html +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/light.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/material.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/scale.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/SOURCES.txt +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/requires.txt +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
- {mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/setup.cfg +0 -0
{mkdocs_document_dates-3.7.4/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.8.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-document-dates
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents
|
|
5
5
|
Author-email: Aaron Wang <aaronwqt@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -82,8 +82,8 @@ In addition to the above basic configuration, the plug-in also provides a wealth
|
|
|
82
82
|
- [Structure and Style](https://jaywhj.netlify.app/document-dates-en#Structure-and-Style): You can freely configure the plugin's display structure in mkdocs.yml or Front Matter. You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
|
|
83
83
|
- [Template Variables](https://jaywhj.netlify.app/document-dates-en#Template-Variables): Can be used to optimize `sitemap.xml` for site SEO
|
|
84
84
|
- [Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Recently-Updated-Module): Enable list of recently updated documents (in descending order of update date), this is ideal for sites with a large number of documents, so that readers can quickly see what's new
|
|
85
|
-
- [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
86
|
-
- [
|
|
85
|
+
- [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
86
|
+
- [Developer API](https://jaywhj.netlify.app/document-dates-en#Developer-API): Provides a date data API for developers, making it easy to retrieve exact dates in other plugins or hooks
|
|
87
87
|
|
|
88
88
|
See the documentation for details: https://jaywhj.netlify.app/document-dates-en
|
|
89
89
|
|
|
@@ -64,8 +64,8 @@ In addition to the above basic configuration, the plug-in also provides a wealth
|
|
|
64
64
|
- [Structure and Style](https://jaywhj.netlify.app/document-dates-en#Structure-and-Style): You can freely configure the plugin's display structure in mkdocs.yml or Front Matter. You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
|
|
65
65
|
- [Template Variables](https://jaywhj.netlify.app/document-dates-en#Template-Variables): Can be used to optimize `sitemap.xml` for site SEO
|
|
66
66
|
- [Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Recently-Updated-Module): Enable list of recently updated documents (in descending order of update date), this is ideal for sites with a large number of documents, so that readers can quickly see what's new
|
|
67
|
-
- [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
68
|
-
- [
|
|
67
|
+
- [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
68
|
+
- [Developer API](https://jaywhj.netlify.app/document-dates-en#Developer-API): Provides a date data API for developers, making it easy to retrieve exact dates in other plugins or hooks
|
|
69
69
|
|
|
70
70
|
See the documentation for details: https://jaywhj.netlify.app/document-dates-en
|
|
71
71
|
|
{mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/cache_manager.py
RENAMED
|
@@ -164,7 +164,7 @@ def update_cache():
|
|
|
164
164
|
global_updated = setup_gitattributes(docs_dir)
|
|
165
165
|
|
|
166
166
|
# 获取docs目录下已跟踪(tracked)的markdown文件
|
|
167
|
-
cmd = [
|
|
167
|
+
cmd = ['git', '-c', 'core.quotepath=false', 'ls-files', '*.md']
|
|
168
168
|
result = subprocess.run(cmd, cwd=docs_dir, env=_clean_git_env(), capture_output=True, encoding='utf-8')
|
|
169
169
|
tracked_files = result.stdout.splitlines() if result.stdout else []
|
|
170
170
|
|
|
@@ -185,13 +185,13 @@ def update_cache():
|
|
|
185
185
|
|
|
186
186
|
full_path = docs_dir / rel_path
|
|
187
187
|
if full_path.exists():
|
|
188
|
-
created_time = load_file_creation_date(full_path)
|
|
188
|
+
created_time = load_file_creation_date(full_path)
|
|
189
189
|
if not jsonl_cache_file.exists():
|
|
190
190
|
git_time = load_git_first_commit_date(full_path)
|
|
191
191
|
if git_time:
|
|
192
192
|
created_time = min(created_time, git_time)
|
|
193
193
|
jsonl_dates_cache[rel_path] = {
|
|
194
|
-
"created": created_time.
|
|
194
|
+
"created": int(created_time.timestamp())
|
|
195
195
|
}
|
|
196
196
|
project_updated = True
|
|
197
197
|
except Exception as e:
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import yaml
|
|
3
2
|
import shutil
|
|
4
3
|
import logging
|
|
5
4
|
from jinja2 import ChoiceLoader, FileSystemLoader
|
|
6
|
-
from datetime import datetime
|
|
5
|
+
from datetime import datetime, timezone
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from mkdocs.plugins import BasePlugin, event_priority
|
|
9
8
|
from mkdocs.config import config_options
|
|
10
9
|
from mkdocs.structure.pages import Page
|
|
11
10
|
from mkdocs.utils import get_relative_url
|
|
12
11
|
from urllib.parse import urlparse
|
|
13
|
-
from .utils import
|
|
12
|
+
from .utils import compile_exclude_patterns, is_excluded, get_recently_updated_files, load_dates_and_authors
|
|
14
13
|
|
|
15
14
|
logger = logging.getLogger("mkdocs.plugins.document_dates")
|
|
16
15
|
logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
@@ -45,14 +44,13 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
45
44
|
super().__init__()
|
|
46
45
|
|
|
47
46
|
self.data_cached = {}
|
|
48
|
-
self.last_updated_dates = {}
|
|
49
47
|
self.authors_yml = {}
|
|
50
48
|
self.recent_docs_html = None
|
|
51
49
|
self.recent_enable = False
|
|
52
50
|
self._exclude_patterns = []
|
|
53
51
|
|
|
54
52
|
def on_config(self, config):
|
|
55
|
-
docs_dir_path = Path(config
|
|
53
|
+
docs_dir_path = Path(config.docs_dir)
|
|
56
54
|
|
|
57
55
|
# 加载 author 配置
|
|
58
56
|
authors_file = docs_dir_path / 'authors.yml'
|
|
@@ -65,20 +63,6 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
65
63
|
pass
|
|
66
64
|
self._load_authors_from_yaml(authors_file)
|
|
67
65
|
|
|
68
|
-
# 加载文档 git 元数据(日期 & 作者)
|
|
69
|
-
self.data_cached = load_git_metadata(docs_dir_path)
|
|
70
|
-
# 加载 jsonl 缓存数据
|
|
71
|
-
jsonl_cache_file = docs_dir_path / '.dates_cache.jsonl'
|
|
72
|
-
if jsonl_cache_file.exists():
|
|
73
|
-
jsonl_cache = read_jsonl_cache(jsonl_cache_file)
|
|
74
|
-
for filename, new_info in jsonl_cache.items():
|
|
75
|
-
if filename in self.data_cached:
|
|
76
|
-
self.data_cached[filename].update(new_info)
|
|
77
|
-
|
|
78
|
-
# 加载文档最近更新时间(日期)
|
|
79
|
-
self.last_updated_dates = load_git_last_updated_dates(docs_dir_path)
|
|
80
|
-
|
|
81
|
-
|
|
82
66
|
# 复制配置文件到用户目录(如果不存在)
|
|
83
67
|
dest_dir = docs_dir_path / 'assets' / 'document_dates'
|
|
84
68
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -151,32 +135,41 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
151
135
|
|
|
152
136
|
return config
|
|
153
137
|
|
|
138
|
+
@event_priority(50)
|
|
139
|
+
def on_files(self, files, config):
|
|
140
|
+
self.data_cached = load_dates_and_authors(Path(config.docs_dir), files)
|
|
141
|
+
return files
|
|
142
|
+
|
|
154
143
|
@event_priority(50)
|
|
155
144
|
def on_page_markdown(self, markdown, page: Page, config, files):
|
|
156
145
|
# 获取相对路径,src_uri 总是以"/"分隔
|
|
157
|
-
rel_path = getattr(page.file, 'src_uri'
|
|
158
|
-
|
|
159
|
-
rel_path = rel_path.replace(os.sep, '/')
|
|
160
|
-
file_path = page.file.abs_src_path
|
|
161
|
-
|
|
146
|
+
rel_path = getattr(page.file, 'src_uri')
|
|
147
|
+
|
|
162
148
|
# 优先获取 page.meta 中的数据
|
|
163
149
|
created = self._load_meta_date(page.meta, self.config['created_field_names'])
|
|
164
150
|
updated = self._load_meta_date(page.meta, self.config['updated_field_names'])
|
|
165
151
|
authors = self._load_meta_author(page.meta, page.url)
|
|
166
152
|
|
|
167
|
-
#
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
153
|
+
# 如果 meta 数据存在,则存储
|
|
154
|
+
cache = self.data_cached.setdefault(rel_path, {})
|
|
155
|
+
if created:
|
|
156
|
+
cache['created'] = created
|
|
157
|
+
else:
|
|
158
|
+
created = cache.get('created')
|
|
159
|
+
|
|
160
|
+
if updated:
|
|
161
|
+
cache['updated'] = updated
|
|
162
|
+
else:
|
|
163
|
+
updated = cache.get('updated')
|
|
164
|
+
|
|
172
165
|
if not authors:
|
|
173
166
|
authors = self._load_author_cached(rel_path, page, config)
|
|
174
167
|
|
|
175
|
-
#
|
|
168
|
+
# 注入数据到模板 (utc datetime -> local datetime)
|
|
176
169
|
page.meta["document_dates"] = {
|
|
177
170
|
"dates": {
|
|
178
|
-
"created": created.isoformat(),
|
|
179
|
-
"updated": updated.isoformat(),
|
|
171
|
+
"created": created.astimezone().isoformat() if created else None,
|
|
172
|
+
"updated": updated.astimezone().isoformat() if updated else None,
|
|
180
173
|
},
|
|
181
174
|
"authors": authors
|
|
182
175
|
}
|
|
@@ -212,7 +205,7 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
212
205
|
|
|
213
206
|
# 获取最近更新的文档数据
|
|
214
207
|
recent_exclude_patterns = compile_exclude_patterns(exclude_list)
|
|
215
|
-
recently_updated_docs = get_recently_updated_files(self.
|
|
208
|
+
recently_updated_docs = get_recently_updated_files(self.data_cached, files, recent_exclude_patterns, limit, self.recent_enable, prefix)
|
|
216
209
|
|
|
217
210
|
# 将数据注入到 config['extra'] 中供全局访问
|
|
218
211
|
if not config.get('extra', {}).get("recently_updated_docs", {}):
|
|
@@ -290,42 +283,31 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
290
283
|
for field in field_names:
|
|
291
284
|
if field in meta:
|
|
292
285
|
try:
|
|
293
|
-
#
|
|
286
|
+
# 移除首尾可能存在的单双引号
|
|
294
287
|
date_str = str(meta[field]).strip("'\"")
|
|
295
|
-
|
|
288
|
+
dt = datetime.fromisoformat(date_str)
|
|
289
|
+
# 如果没时区,则当成本地时间,再转 UTC
|
|
290
|
+
if dt.tzinfo is None:
|
|
291
|
+
local_tz = datetime.now().astimezone().tzinfo
|
|
292
|
+
dt = dt.replace(tzinfo=local_tz)
|
|
293
|
+
return dt.astimezone(timezone.utc)
|
|
294
|
+
# return datetime.fromisoformat(date_str).astimezone()
|
|
296
295
|
except Exception:
|
|
297
296
|
continue
|
|
298
297
|
return None
|
|
299
298
|
|
|
300
|
-
def _load_created_cached(self, file_path, rel_path):
|
|
301
|
-
# 优先从缓存中读取
|
|
302
|
-
if rel_path in self.data_cached:
|
|
303
|
-
return datetime.fromisoformat(self.data_cached[rel_path]['created'])
|
|
304
|
-
# 从文件系统获取
|
|
305
|
-
return load_file_creation_date(file_path).astimezone()
|
|
306
|
-
|
|
307
|
-
def _load_updated_cached(self, file_path, rel_path):
|
|
308
|
-
# 优先从缓存中读取
|
|
309
|
-
if rel_path in self.last_updated_dates:
|
|
310
|
-
return datetime.fromtimestamp(self.last_updated_dates[rel_path]).astimezone()
|
|
311
|
-
# 从文件系统获取最后修改时间
|
|
312
|
-
stat = os.stat(file_path)
|
|
313
|
-
return datetime.fromtimestamp(stat.st_mtime).astimezone()
|
|
314
|
-
|
|
315
|
-
|
|
316
299
|
def _load_author_cached(self, rel_path, page, config):
|
|
317
300
|
# 1. git author
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return authors
|
|
301
|
+
authors_list = self.data_cached.get(rel_path, {}).get('authors', None)
|
|
302
|
+
if authors_list:
|
|
303
|
+
authors = []
|
|
304
|
+
for data in authors_list:
|
|
305
|
+
full_author = self.authors_yml.get(data['name'])
|
|
306
|
+
if full_author:
|
|
307
|
+
authors.append(self._repair_author(full_author, page.url))
|
|
308
|
+
else:
|
|
309
|
+
authors.append(Author(**data))
|
|
310
|
+
return authors
|
|
329
311
|
|
|
330
312
|
# 2. site_author 或 PC username
|
|
331
313
|
name = config.get('site_author') or Path.home().name
|
|
@@ -407,7 +389,7 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
407
389
|
return (
|
|
408
390
|
f"<span class='dd-item' data-tippy-content data-tippy-raw='{formatted}'>"
|
|
409
391
|
f"<span class='material-icons' data-icon='{icon}'></span>"
|
|
410
|
-
f"<time datetime='{time_obj.isoformat()}'>{self._formatting_date(time_obj)}</time>"
|
|
392
|
+
f"<time datetime='{time_obj.astimezone().isoformat()}'>{self._formatting_date(time_obj)}</time>"
|
|
411
393
|
f"</span>"
|
|
412
394
|
)
|
|
413
395
|
|
|
@@ -9,7 +9,7 @@ import re
|
|
|
9
9
|
import math
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from operator import itemgetter
|
|
12
|
-
from datetime import datetime
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
13
|
from collections import defaultdict
|
|
14
14
|
from mkdocs.structure.files import Files
|
|
15
15
|
|
|
@@ -17,6 +17,44 @@ logger = logging.getLogger("mkdocs.plugins.document_dates")
|
|
|
17
17
|
logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def load_dates_and_authors(docs_dir_path: Path, files: Files):
|
|
21
|
+
|
|
22
|
+
# git 创建日期
|
|
23
|
+
created_data = load_git_metadata(docs_dir_path)
|
|
24
|
+
|
|
25
|
+
# 覆盖 jsonl 创建日期
|
|
26
|
+
jsonl_cache_file = docs_dir_path / '.dates_cache.jsonl'
|
|
27
|
+
if jsonl_cache_file.exists():
|
|
28
|
+
jsonl_cache = read_jsonl_cache(jsonl_cache_file)
|
|
29
|
+
for filename, new_info in jsonl_cache.items():
|
|
30
|
+
if filename in created_data:
|
|
31
|
+
created_data[filename].update(new_info)
|
|
32
|
+
|
|
33
|
+
# git 更新日期
|
|
34
|
+
updated_data = load_git_last_updated_dates(docs_dir_path)
|
|
35
|
+
|
|
36
|
+
for file in files:
|
|
37
|
+
if file.inclusion.is_excluded():
|
|
38
|
+
continue
|
|
39
|
+
if not file.src_path.endswith('.md'):
|
|
40
|
+
continue
|
|
41
|
+
rel_path = getattr(file, 'src_uri')
|
|
42
|
+
|
|
43
|
+
# created: timestamp -> datetime
|
|
44
|
+
cache = created_data.setdefault(rel_path, {})
|
|
45
|
+
created_ts = cache.get('created')
|
|
46
|
+
cache['created'] = (
|
|
47
|
+
datetime.fromtimestamp(created_ts, tz=timezone.utc)
|
|
48
|
+
if created_ts is not None
|
|
49
|
+
else load_file_creation_date(file.abs_src_path)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# updated: timestamp -> datetime
|
|
53
|
+
mtime = updated_data.get(rel_path, os.path.getmtime(file.abs_src_path))
|
|
54
|
+
created_data[rel_path]['updated'] = datetime.fromtimestamp(mtime, tz=timezone.utc)
|
|
55
|
+
|
|
56
|
+
return created_data
|
|
57
|
+
|
|
20
58
|
def compile_exclude_patterns(exclude_list):
|
|
21
59
|
if not exclude_list:
|
|
22
60
|
return []
|
|
@@ -36,34 +74,34 @@ def is_excluded(path, patterns):
|
|
|
36
74
|
return True
|
|
37
75
|
return False
|
|
38
76
|
|
|
39
|
-
def load_file_creation_date(file_path):
|
|
77
|
+
def load_file_creation_date(file_path) -> datetime:
|
|
40
78
|
try:
|
|
41
79
|
stat = os.stat(file_path)
|
|
42
80
|
system = platform.system().lower()
|
|
43
81
|
if system.startswith('win'): # Windows
|
|
44
|
-
return datetime.fromtimestamp(stat.st_ctime)
|
|
82
|
+
return datetime.fromtimestamp(stat.st_ctime, tz=timezone.utc)
|
|
45
83
|
elif system == 'darwin': # macOS
|
|
46
84
|
try:
|
|
47
|
-
return datetime.fromtimestamp(stat.st_birthtime)
|
|
85
|
+
return datetime.fromtimestamp(stat.st_birthtime, tz=timezone.utc)
|
|
48
86
|
except AttributeError:
|
|
49
|
-
return datetime.fromtimestamp(stat.st_ctime)
|
|
87
|
+
return datetime.fromtimestamp(stat.st_ctime, tz=timezone.utc)
|
|
50
88
|
else: # Linux, 没有创建时间,使用修改时间
|
|
51
|
-
return datetime.fromtimestamp(stat.st_mtime)
|
|
89
|
+
return datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
|
|
52
90
|
except (OSError, ValueError) as e:
|
|
53
91
|
logger.error(f"Failed to load file creation date for {file_path}: {e}")
|
|
54
|
-
return datetime.now()
|
|
92
|
+
return datetime.now(timezone.utc)
|
|
55
93
|
|
|
56
|
-
def load_git_first_commit_date(file_path):
|
|
94
|
+
def load_git_first_commit_date(file_path) -> datetime:
|
|
57
95
|
try:
|
|
58
96
|
# git log --reverse --format="%aI" -- {file_path} | head -n 1
|
|
59
|
-
cmd_list = ['git', 'log', '--reverse', '--format=%
|
|
97
|
+
cmd_list = ['git', 'log', '--reverse', '--format=%at', '--', file_path]
|
|
60
98
|
process = subprocess.run(cmd_list, capture_output=True, encoding='utf-8')
|
|
61
99
|
if process.returncode == 0 and process.stdout.strip():
|
|
62
|
-
first_line = process.stdout.partition('\n')[0].strip()
|
|
63
|
-
return datetime.
|
|
100
|
+
first_line = int(process.stdout.partition('\n')[0].strip())
|
|
101
|
+
return datetime.fromtimestamp(first_line, tz=timezone.utc)
|
|
64
102
|
except Exception as e:
|
|
65
103
|
logger.info(f"Error load git first commit date for {file_path}: {e}")
|
|
66
|
-
return
|
|
104
|
+
return datetime.now(timezone.utc)
|
|
67
105
|
|
|
68
106
|
def load_git_metadata(docs_dir_path: Path):
|
|
69
107
|
dates_cache = {}
|
|
@@ -74,7 +112,7 @@ def load_git_metadata(docs_dir_path: Path):
|
|
|
74
112
|
).strip())
|
|
75
113
|
rel_docs_path = docs_dir_path.relative_to(git_root).as_posix()
|
|
76
114
|
|
|
77
|
-
cmd = ['git', 'log', '--reverse', '--no-merges', '--use-mailmap', '--name-only', '--format=%aN|%aE|%
|
|
115
|
+
cmd = ['git', '-c', 'core.quotepath=false', 'log', '--reverse', '--no-merges', '--use-mailmap', '--name-only', '--format=%aN|%aE|%at', f'--relative={rel_docs_path}', '--', '*.md']
|
|
78
116
|
process = subprocess.run(cmd, cwd=docs_dir_path, capture_output=True, encoding='utf-8')
|
|
79
117
|
if process.returncode == 0:
|
|
80
118
|
authors_dict = defaultdict(dict)
|
|
@@ -93,7 +131,7 @@ def load_git_metadata(docs_dir_path: Path):
|
|
|
93
131
|
# a.巧用 Python 字典的 setdefault 特性来去重(setdefault 为不存在的键提供初始值,不会覆盖已有值)
|
|
94
132
|
# b.巧用 Python 字典的插入顺序特性来保留内容插入顺序(Python 3.7+ 字典会保持插入顺序)
|
|
95
133
|
authors_dict[line].setdefault((name, email), None)
|
|
96
|
-
first_commit.setdefault(line, created)
|
|
134
|
+
first_commit.setdefault(line, int(created))
|
|
97
135
|
|
|
98
136
|
# 构建最终的缓存数据
|
|
99
137
|
for file_path in first_commit:
|
|
@@ -118,11 +156,11 @@ def load_git_last_updated_dates(docs_dir_path: Path):
|
|
|
118
156
|
).strip())
|
|
119
157
|
rel_docs_path = docs_dir_path.relative_to(git_root).as_posix()
|
|
120
158
|
|
|
121
|
-
cmd = ['git', 'log', '--no-merges', '--use-mailmap', '--format=%aN|%aE|%at', '--name-only', f'--relative={rel_docs_path}', '--', '*.md']
|
|
159
|
+
cmd = ['git', '-c', 'core.quotepath=false', 'log', '--no-merges', '--use-mailmap', '--format=%aN|%aE|%at', '--name-only', f'--relative={rel_docs_path}', '--', '*.md']
|
|
122
160
|
process = subprocess.run(cmd, cwd=docs_dir_path, capture_output=True, encoding='utf-8')
|
|
123
161
|
if process.returncode == 0:
|
|
124
162
|
result = subprocess.run(
|
|
125
|
-
[
|
|
163
|
+
['git', '-c', 'core.quotepath=false', 'ls-files', '*.md'],
|
|
126
164
|
cwd=docs_dir_path, capture_output=True, encoding='utf-8'
|
|
127
165
|
)
|
|
128
166
|
# 只记录已跟踪的文件(还有已删除、重命名、不再跟踪)
|
|
@@ -134,7 +172,7 @@ def load_git_last_updated_dates(docs_dir_path: Path):
|
|
|
134
172
|
if not line:
|
|
135
173
|
continue
|
|
136
174
|
if '|' in line:
|
|
137
|
-
ts =
|
|
175
|
+
ts = int(line.split('|')[2])
|
|
138
176
|
elif line.endswith('.md') and line in tracked_files and ts:
|
|
139
177
|
# 只记录第一次出现的文件,即最近一次提交(setdefault 机制不会覆盖已有值)
|
|
140
178
|
doc_mtime_map.setdefault(line, ts)
|
|
@@ -153,14 +191,13 @@ def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list:
|
|
|
153
191
|
continue
|
|
154
192
|
if not file.src_path.endswith('.md'):
|
|
155
193
|
continue
|
|
156
|
-
rel_path = getattr(file, 'src_uri'
|
|
157
|
-
if os.sep != '/':
|
|
158
|
-
rel_path = rel_path.replace(os.sep, '/')
|
|
194
|
+
rel_path = getattr(file, 'src_uri')
|
|
159
195
|
if is_excluded(rel_path, exclude_list):
|
|
160
196
|
continue
|
|
161
197
|
|
|
162
198
|
# 优先从现有数据获取 mtime,如果不存在则 fallback 到文件系统 mtime
|
|
163
|
-
|
|
199
|
+
exist_updated: datetime = existing_dates.get(rel_path, {}).get('updated')
|
|
200
|
+
mtime = exist_updated.timestamp() if exist_updated else os.path.getmtime(file.abs_src_path)
|
|
164
201
|
|
|
165
202
|
# 获取文档其它信息
|
|
166
203
|
title = file.page.title if file.page and file.page.title else file.name
|
|
@@ -193,23 +230,17 @@ def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list:
|
|
|
193
230
|
"readtime": readtime,
|
|
194
231
|
"tags": tags,
|
|
195
232
|
})
|
|
196
|
-
# existing_map[rel_path] = mtime
|
|
197
233
|
|
|
198
234
|
# 构建最近更新列表
|
|
199
235
|
if files_meta:
|
|
200
|
-
fmt_full = "%Y-%m-%d %H:%M:%S"
|
|
201
|
-
fmt_date = "%Y-%m-%d"
|
|
202
236
|
# heapq 取 top limit
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
for doc in top_results:
|
|
206
|
-
dt = datetime.fromtimestamp(doc["updated_ts"])
|
|
237
|
+
recently_updated_results = heapq.nlargest(limit, files_meta, key=itemgetter("updated_ts"))
|
|
207
238
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
239
|
+
for doc in recently_updated_results:
|
|
240
|
+
# timestamp -> utc datetime -> local datetime
|
|
241
|
+
dt = datetime.fromtimestamp(doc["updated_ts"], tz=timezone.utc).astimezone()
|
|
242
|
+
doc["updated_dt"] = dt.isoformat()
|
|
243
|
+
doc["updated"] = dt.date().isoformat()
|
|
213
244
|
|
|
214
245
|
return recently_updated_results
|
|
215
246
|
|
|
@@ -223,8 +254,11 @@ def read_jsonl_cache(jsonl_file: Path):
|
|
|
223
254
|
entry = json.loads(line.strip())
|
|
224
255
|
if entry and isinstance(entry, dict) and len(entry) == 1:
|
|
225
256
|
file_path, file_info = next(iter(entry.items()))
|
|
257
|
+
if isinstance(file_info, dict):
|
|
258
|
+
if file_info.get('created'):
|
|
259
|
+
file_info['created'] = int(datetime.fromisoformat(file_info['created']).timestamp())
|
|
226
260
|
dates_cache[file_path] = file_info
|
|
227
|
-
except (json.JSONDecodeError, StopIteration) as e:
|
|
261
|
+
except (json.JSONDecodeError, StopIteration, ValueError) as e:
|
|
228
262
|
logger.warning(f"Skipping invalid JSONL line: {e}")
|
|
229
263
|
except IOError as e:
|
|
230
264
|
logger.warning(f"Error reading from '.dates_cache.jsonl': {str(e)}")
|
|
@@ -237,6 +271,9 @@ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
|
|
|
237
271
|
with open(temp_file, 'w', encoding='utf-8') as f:
|
|
238
272
|
for file_path in tracked_files:
|
|
239
273
|
if file_path in dates_cache:
|
|
274
|
+
file_info = dates_cache[file_path].copy()
|
|
275
|
+
if file_info.get('created') is not None:
|
|
276
|
+
file_info['created'] = datetime.fromtimestamp(file_info['created'], tz=timezone.utc).isoformat()
|
|
240
277
|
entry = {file_path: dates_cache[file_path]}
|
|
241
278
|
f.write(json.dumps(entry, ensure_ascii=False) + '\n')
|
|
242
279
|
|
|
@@ -247,7 +284,7 @@ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
|
|
|
247
284
|
subprocess.run(["git", "add", str(jsonl_file)], check=True)
|
|
248
285
|
logger.info(f"Successfully updated JSONL cache file: {jsonl_file}")
|
|
249
286
|
return True
|
|
250
|
-
except
|
|
287
|
+
except IOError as e:
|
|
251
288
|
logger.warning(f"Failed to write JSONL cache file {jsonl_file}: {e}")
|
|
252
289
|
except Exception as e:
|
|
253
290
|
logger.warning(f"Failed to add JSONL cache file to git: {e}")
|
{mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0/mkdocs_document_dates.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-document-dates
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents
|
|
5
5
|
Author-email: Aaron Wang <aaronwqt@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -82,8 +82,8 @@ In addition to the above basic configuration, the plug-in also provides a wealth
|
|
|
82
82
|
- [Structure and Style](https://jaywhj.netlify.app/document-dates-en#Structure-and-Style): You can freely configure the plugin's display structure in mkdocs.yml or Front Matter. You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
|
|
83
83
|
- [Template Variables](https://jaywhj.netlify.app/document-dates-en#Template-Variables): Can be used to optimize `sitemap.xml` for site SEO
|
|
84
84
|
- [Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Recently-Updated-Module): Enable list of recently updated documents (in descending order of update date), this is ideal for sites with a large number of documents, so that readers can quickly see what's new
|
|
85
|
-
- [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
86
|
-
- [
|
|
85
|
+
- [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
86
|
+
- [Developer API](https://jaywhj.netlify.app/document-dates-en#Developer-API): Provides a date data API for developers, making it easy to retrieve exact dates in other plugins or hooks
|
|
87
87
|
|
|
88
88
|
See the documentation for details: https://jaywhj.netlify.app/document-dates-en
|
|
89
89
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mkdocs-document-dates"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.8.0"
|
|
8
8
|
description = "A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.7"
|
|
File without changes
|
|
File without changes
|
{mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/__init__.py
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks/pre-commit
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks_installer.py
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.7.4 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/.DS_Store
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|