mkdocs-document-dates 3.1.6__tar.gz → 3.2__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.
Files changed (45) hide show
  1. {mkdocs_document_dates-3.1.6/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.2}/PKG-INFO +4 -5
  2. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/README.md +3 -3
  3. mkdocs_document_dates-3.2/mkdocs_document_dates/cache_manager.py +126 -0
  4. mkdocs_document_dates-3.2/mkdocs_document_dates/hooks_installer.py +109 -0
  5. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/plugin.py +28 -95
  6. mkdocs_document_dates-3.2/mkdocs_document_dates/utils.py +147 -0
  7. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2/mkdocs_document_dates.egg-info}/PKG-INFO +4 -5
  8. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates.egg-info/SOURCES.txt +1 -0
  9. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/setup.py +1 -2
  10. mkdocs_document_dates-3.1.6/mkdocs_document_dates/cache_manager.py +0 -226
  11. mkdocs_document_dates-3.1.6/mkdocs_document_dates/hooks_installer.py +0 -114
  12. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/LICENSE +0 -0
  13. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/MANIFEST.in +0 -0
  14. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/__init__.py +0 -0
  15. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/hooks/pre-commit +0 -0
  16. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/config/user.config.css +0 -0
  17. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/config/user.config.js +0 -0
  18. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/core/core.css +0 -0
  19. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/core/core.js +0 -0
  20. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/core/timeago-load.js +0 -0
  21. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
  22. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
  23. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/ar.json +0 -0
  24. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/de.json +0 -0
  25. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/en.json +0 -0
  26. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/es.json +0 -0
  27. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/fr.json +0 -0
  28. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/ja.json +0 -0
  29. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/ko.json +0 -0
  30. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/ru.json +0 -0
  31. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/zh.json +0 -0
  32. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/languages/zh_TW.json +0 -0
  33. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
  34. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/light.css +0 -0
  35. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/material.css +0 -0
  36. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
  37. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/scale.css +0 -0
  38. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
  39. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
  40. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
  41. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
  42. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
  43. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates.egg-info/requires.txt +0 -0
  44. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
  45. {mkdocs_document_dates-3.1.6 → mkdocs_document_dates-3.2}/setup.cfg +0 -0
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.1.6
3
+ Version: 3.2
4
4
  Summary: An easy-to-use, lightweight MkDocs plugin for displaying the exact creation time, last modification time and author info of markdown documents.
5
5
  Home-page: https://github.com/jaywhj/mkdocs-document-dates
6
6
  Author: Aaron Wang
7
7
  Author-email: aaronwqt@gmail.com
8
8
  License: MIT
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
10
  Classifier: Operating System :: OS Independent
12
11
  Requires-Python: >=3.7
13
12
  Description-Content-Type: text/markdown
@@ -118,9 +117,9 @@ email: e-name@gmail.com
118
117
 
119
118
  ## Customization
120
119
 
121
- The plugin supports deep customization, such as **icon style, font style, theme color, animation type, dividing line**, etc. Everything is customizable (I've already written the code, you just need to turn on the uncomment switch):
120
+ The plugin supports deep customization, such as **icon style, theme color, font, animation, dividing line**, etc. Everything is customizable (I've already written the code, you just need to turn on the uncomment switch):
122
121
 
123
- | Function: | Location |
122
+ | Category: | Location: |
124
123
  | :----------------------: | ---------------------------------------- |
125
124
  | **Style & Theme** | `docs/assets/document_dates/user.config.css` |
126
125
  | **Properties & Functions** | `docs/assets/document_dates/user.config.js` |
@@ -129,7 +128,7 @@ The plugin supports deep customization, such as **icon style, font style, theme
129
128
  **Tip**: when `type: timeago` is set, timeago.js is enabled to render dynamic time, `timeago.min.js` only contains English and Chinese by default, if you need to load other languages, you can configure it as below (choose one):
130
129
 
131
130
  - In `user.config.js`, refer to [the demo commented out](https://github.com/jaywhj/mkdocs-document-dates/blob/main/mkdocs_document_dates/static/config/user.config.js) at the bottom, translate it into your local language
132
- - In `mkdocs.yml`, add the following two lines to configure the full version of `timeago.full.min.js` to load all languages at once
131
+ - In `mkdocs.yml`, configure the full version of `timeago.full.min.js` to load all languages at once
133
132
  ```yaml
134
133
  extra_javascript:
135
134
  - assets/document_dates/core/timeago.full.min.js
@@ -91,9 +91,9 @@ email: e-name@gmail.com
91
91
 
92
92
  ## Customization
93
93
 
94
- The plugin supports deep customization, such as **icon style, font style, theme color, animation type, dividing line**, etc. Everything is customizable (I've already written the code, you just need to turn on the uncomment switch):
94
+ The plugin supports deep customization, such as **icon style, theme color, font, animation, dividing line**, etc. Everything is customizable (I've already written the code, you just need to turn on the uncomment switch):
95
95
 
96
- | Function: | Location |
96
+ | Category: | Location: |
97
97
  | :----------------------: | ---------------------------------------- |
98
98
  | **Style & Theme** | `docs/assets/document_dates/user.config.css` |
99
99
  | **Properties & Functions** | `docs/assets/document_dates/user.config.js` |
@@ -102,7 +102,7 @@ The plugin supports deep customization, such as **icon style, font style, theme
102
102
  **Tip**: when `type: timeago` is set, timeago.js is enabled to render dynamic time, `timeago.min.js` only contains English and Chinese by default, if you need to load other languages, you can configure it as below (choose one):
103
103
 
104
104
  - In `user.config.js`, refer to [the demo commented out](https://github.com/jaywhj/mkdocs-document-dates/blob/main/mkdocs_document_dates/static/config/user.config.js) at the bottom, translate it into your local language
105
- - In `mkdocs.yml`, add the following two lines to configure the full version of `timeago.full.min.js` to load all languages at once
105
+ - In `mkdocs.yml`, configure the full version of `timeago.full.min.js` to load all languages at once
106
106
  ```yaml
107
107
  extra_javascript:
108
108
  - assets/document_dates/core/timeago.full.min.js
@@ -0,0 +1,126 @@
1
+ import logging
2
+ import subprocess
3
+ from pathlib import Path
4
+ from .utils import read_json_cache, read_jsonl_cache, write_jsonl_cache, get_file_creation_time, get_git_first_commit_time
5
+
6
+ logger = logging.getLogger("mkdocs.plugins.document_dates")
7
+ logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
8
+
9
+ def find_mkdocs_projects():
10
+ projects = []
11
+ try:
12
+ git_root = Path(subprocess.check_output(
13
+ ['git', 'rev-parse', '--show-toplevel'],
14
+ text=True, encoding='utf-8'
15
+ ).strip())
16
+
17
+ # 遍历 git_root 及子目录, 寻找 mkdocs.yml 文件
18
+ for config_file in git_root.rglob('mkdocs.y*ml'):
19
+ if config_file.name.lower() in ('mkdocs.yml', 'mkdocs.yaml'):
20
+ projects.append(config_file.parent)
21
+
22
+ if not projects:
23
+ logger.warning("No MkDocs projects found in the repository")
24
+ except subprocess.CalledProcessError as e:
25
+ logger.error(f"Failed to find the Git repository root: {e}")
26
+ except Exception as e:
27
+ logger.error(f"Unexpected error while searching for MkDocs projects: {e}")
28
+
29
+ return projects
30
+
31
+ def setup_gitattributes(docs_dir: Path):
32
+ try:
33
+ gitattributes_path = docs_dir / '.gitattributes'
34
+ union_merge_line = ".dates_cache.jsonl merge=union"
35
+ # custom_merge_line = ".dates_cache.json merge=custom_json_merge"
36
+ content = gitattributes_path.read_text(encoding='utf-8') if gitattributes_path.exists() else ""
37
+ if union_merge_line not in content:
38
+ if content and not content.endswith('\n'):
39
+ content += '\n'
40
+ content += f"{union_merge_line}\n"
41
+ gitattributes_path.write_text(content, encoding='utf-8')
42
+ subprocess.run(["git", "add", str(gitattributes_path)], check=True)
43
+ logger.info(f"Updated .gitattributes file: {gitattributes_path}")
44
+ return True
45
+ except (IOError, OSError) as e:
46
+ logger.error(f"Failed to read/write .gitattributes file: {e}")
47
+ except Exception as e:
48
+ logger.error(f"Failed to add .gitattributes to git: {e}")
49
+ return False
50
+
51
+ def update_cache():
52
+ global_updated = False
53
+
54
+ for project_dir in find_mkdocs_projects():
55
+ try:
56
+ project_updated = False
57
+
58
+ docs_dir = project_dir / 'docs'
59
+ if not docs_dir.exists():
60
+ logger.error(f"Document directory does not exist: {docs_dir}")
61
+ continue
62
+
63
+ # 设置.gitattributes文件
64
+ global_updated = setup_gitattributes(docs_dir)
65
+
66
+ # 获取docs目录下已跟踪(tracked)的markdown文件
67
+ cmd = ["git", "ls-files", "*.md"]
68
+ result = subprocess.run(cmd, cwd=docs_dir, capture_output=True, text=True)
69
+ tracked_files = result.stdout.splitlines() if result.stdout else []
70
+
71
+ if not tracked_files:
72
+ logger.info(f"No tracked markdown files found in {docs_dir}")
73
+ continue
74
+
75
+ # 读取旧的JSON缓存文件(如果存在)
76
+ json_cache_file = docs_dir / '.dates_cache.json'
77
+ json_dates_cache = read_json_cache(json_cache_file)
78
+
79
+ # 读取新的JSONL缓存文件(如果存在)
80
+ jsonl_cache_file = docs_dir / '.dates_cache.jsonl'
81
+ jsonl_dates_cache = read_jsonl_cache(jsonl_cache_file)
82
+
83
+ # 根据 git已跟踪的文件来更新
84
+ for rel_path in tracked_files:
85
+ try:
86
+ # 如果文件已在JSONL缓存中,跳过
87
+ if rel_path in jsonl_dates_cache:
88
+ continue
89
+
90
+ full_path = docs_dir / rel_path
91
+ # 处理新文件或迁移旧JSON缓存
92
+ if rel_path in json_dates_cache:
93
+ jsonl_dates_cache[rel_path] = json_dates_cache[rel_path]
94
+ project_updated = True
95
+ elif full_path.exists():
96
+ created_time = get_file_creation_time(full_path)
97
+ if not jsonl_cache_file.exists() and not json_cache_file.exists():
98
+ git_time = get_git_first_commit_time(full_path)
99
+ if git_time:
100
+ created_time = min(created_time, git_time)
101
+ jsonl_dates_cache[rel_path] = {
102
+ "created": created_time.isoformat()
103
+ }
104
+ project_updated = True
105
+ except Exception as e:
106
+ logger.error(f"Error processing file {rel_path}: {e}")
107
+ continue
108
+
109
+ # 标记删除不再跟踪的文件
110
+ if len(jsonl_dates_cache) > len(tracked_files):
111
+ project_updated = True
112
+
113
+ # 如果有更新,写入JSONL缓存文件
114
+ if project_updated or not jsonl_cache_file.exists():
115
+ global_updated = write_jsonl_cache(jsonl_cache_file, jsonl_dates_cache, tracked_files)
116
+ except subprocess.CalledProcessError as e:
117
+ logger.error(f"Failed to execute git command: {e}")
118
+ continue
119
+ except Exception as e:
120
+ logger.error(f"Error processing project directory {project_dir}: {e}")
121
+ continue
122
+ return global_updated
123
+
124
+
125
+ if __name__ == "__main__":
126
+ update_cache()
@@ -0,0 +1,109 @@
1
+ import os
2
+ import sys
3
+ import logging
4
+ import subprocess
5
+ from pathlib import Path
6
+ import platform
7
+
8
+ logger = logging.getLogger("mkdocs.plugins.document_dates")
9
+ logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
10
+
11
+ def get_config_dir():
12
+ if platform.system().lower().startswith('win'):
13
+ return Path(os.getenv('APPDATA', str(Path.home() / 'AppData' / 'Roaming')))
14
+ else:
15
+ return Path.home() / '.config'
16
+
17
+ def check_python_version(interpreter):
18
+ try:
19
+ result = subprocess.run(
20
+ [interpreter, "-c", "import sys; print(sys.version_info >= (3, 7))"],
21
+ capture_output=True, text=True)
22
+ if result.returncode == 0 and result.stdout.strip().lower() == 'true':
23
+ return True
24
+ else:
25
+ logger.warning(f"Low python version, requires python_requires >=3.7")
26
+ except Exception as e:
27
+ logger.info(f"Failed to check {interpreter}: {str(e)}")
28
+ return False
29
+
30
+ def detect_python_interpreter():
31
+ # 检查可能的Python解释器
32
+ python_interpreters = ['python3', 'python']
33
+ for interpreter in python_interpreters:
34
+ if check_python_version(interpreter):
35
+ return f'#!/usr/bin/env {interpreter}'
36
+
37
+ # 如果都失败了,使用当前运行的Python解释器
38
+ return f'#!{sys.executable}'
39
+
40
+ def setup_hooks_directory():
41
+ config_dir = get_config_dir() / 'mkdocs-document-dates' / 'hooks'
42
+ try:
43
+ config_dir.mkdir(parents=True, exist_ok=True)
44
+ os.chmod(config_dir, 0o755)
45
+ return config_dir
46
+ except PermissionError:
47
+ logger.error(f"No permission to create directory: {config_dir}")
48
+ except Exception as e:
49
+ logger.error(f"Failed to create directory {config_dir}: {str(e)}")
50
+ return None
51
+
52
+ def install_hook_file(source_dir: Path, target_dir: Path):
53
+ try:
54
+ shebang = detect_python_interpreter()
55
+ for item in source_dir.iterdir():
56
+ # 跳过隐藏文件和目录
57
+ if item.name.startswith('.') or not item.is_file():
58
+ continue
59
+ # 添加 shebang 行
60
+ content = item.read_text(encoding='utf-8')
61
+ if content.startswith('#!'):
62
+ content = shebang + os.linesep + content[content.find('\n')+1:]
63
+ else:
64
+ content = shebang + os.linesep + content
65
+
66
+ target_hook_path = target_dir / item.name
67
+ target_hook_path.write_text(content, encoding='utf-8')
68
+ os.chmod(target_hook_path, 0o755)
69
+
70
+ return True
71
+ except Exception as e:
72
+ logger.error(f"Failed to create hook file {target_hook_path}: {str(e)}")
73
+ return False
74
+
75
+ def configure_git_hooks(hooks_dir):
76
+ try:
77
+ # 配置自定义合并驱动
78
+ # script_path = hooks_dir / 'json_merge_driver.py'
79
+ # subprocess.run(['git', 'config', '--global', 'merge.custom_json_merge.name', 'Custom JSON merge driver'], check=True)
80
+ # subprocess.run(['git', 'config', '--global', 'merge.custom_json_merge.driver', f'"{sys.executable}" "{script_path}" %O %A %B'], check=True)
81
+
82
+ subprocess.run(['git', 'config', '--global', 'core.hooksPath', str(hooks_dir)], check=True)
83
+ logger.info(f"Git hooks successfully installed in: {hooks_dir}")
84
+ return True
85
+ except Exception:
86
+ logger.warning("Git not detected, using plugin in a no-Git environment")
87
+ return False
88
+
89
+ def install():
90
+ try:
91
+ # 创建hooks目录
92
+ target_dir = setup_hooks_directory()
93
+ if not target_dir:
94
+ return False
95
+
96
+ # 安装hook文件
97
+ source_dir = Path(__file__).parent / 'hooks'
98
+ if not install_hook_file(source_dir, target_dir):
99
+ return False
100
+
101
+ # 配置git hooks路径
102
+ return configure_git_hooks(target_dir)
103
+
104
+ except Exception as e:
105
+ logger.error(f"Unexpected error during hooks installation: {str(e)}")
106
+ return False
107
+
108
+ if __name__ == '__main__':
109
+ install()
@@ -2,36 +2,15 @@ import os
2
2
  import json
3
3
  import shutil
4
4
  import logging
5
- import subprocess
6
5
  from datetime import datetime
7
6
  from pathlib import Path
8
7
  from mkdocs.plugins import BasePlugin
9
8
  from mkdocs.config import config_options
10
- from .cache_manager import read_json_cache, read_jsonl_cache, get_file_creation_time, get_git_first_commit_time
11
-
12
- # 配置日志等级 (INFO WARNING ERROR)
13
- logging.basicConfig(
14
- level=logging.WARNING,
15
- format='%(levelname)s: %(message)s'
16
- )
17
-
18
-
19
- class Author:
20
- def __init__(self, name="", email="", **kwargs):
21
- self.name = name
22
- self.email = email
23
- # 扩展属性
24
- self.attributes = kwargs
25
-
26
- def __getattr__(self, name):
27
- return self.attributes.get(name)
28
-
29
- def to_dict(self):
30
- return {
31
- 'name': self.name,
32
- 'email': self.email,
33
- **self.attributes
34
- }
9
+ from mkdocs.structure.pages import Page
10
+ from .utils import Author, read_json_cache, read_jsonl_cache, check_git_repo, get_file_creation_time, get_git_first_commit_time, get_git_authors
11
+
12
+ logger = logging.getLogger("mkdocs.plugins.document_dates")
13
+ logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
35
14
 
36
15
 
37
16
  class DocumentDatesPlugin(BasePlugin):
@@ -66,12 +45,7 @@ class DocumentDatesPlugin(BasePlugin):
66
45
  self.config['locale'] = 'en'
67
46
 
68
47
  # 检查是否为 Git 仓库
69
- try:
70
- check_git = subprocess.run(['git', 'rev-parse', '--is-inside-work-tree'], capture_output=True, text=True)
71
- if check_git.returncode == 0:
72
- self.is_git_repo = True
73
- except Exception as e:
74
- logging.info(f"Not a Git repository: {str(e)}")
48
+ self.is_git_repo = check_git_repo()
75
49
 
76
50
  docs_dir_path = Path(config['docs_dir'])
77
51
 
@@ -158,7 +132,7 @@ class DocumentDatesPlugin(BasePlugin):
158
132
 
159
133
  return config
160
134
 
161
- def on_page_markdown(self, markdown, page, config, files):
135
+ def on_page_markdown(self, markdown, page: Page, config, files):
162
136
  # 获取相对路径,src_uri 总是以"/"分隔
163
137
  rel_path = getattr(page.file, 'src_uri', None)
164
138
  if not rel_path:
@@ -194,7 +168,7 @@ class DocumentDatesPlugin(BasePlugin):
194
168
  return self._insert_date_info(markdown, info_html)
195
169
 
196
170
 
197
- def _load_translation(self, docs_dir_path):
171
+ def _load_translation(self, docs_dir_path: Path):
198
172
  # 内置语言文件目录
199
173
  builtin_dir = Path(__file__).parent / 'static' / 'languages'
200
174
  # 用户自定义语言文件目录
@@ -214,16 +188,16 @@ class DocumentDatesPlugin(BasePlugin):
214
188
  en_json = builtin_dir / 'en.json'
215
189
  shutil.copy2(en_json, custom_en_json)
216
190
 
217
- def _load_lang_file(self, lang_dir):
191
+ def _load_lang_file(self, lang_dir: Path):
218
192
  try:
219
193
  locale_file = lang_dir / f"{self.config['locale']}.json"
220
194
  if locale_file.exists():
221
195
  with locale_file.open('r', encoding='utf-8') as f:
222
196
  self.translation = json.load(f)
223
197
  except json.JSONDecodeError as e:
224
- logging.error(f"Invalid JSON format in language file {locale_file}: {str(e)}")
198
+ logger.error(f"Invalid JSON format in language file {locale_file}: {str(e)}")
225
199
  except Exception as e:
226
- logging.error(f"Error loading language file {locale_file}: {str(e)}")
200
+ logger.error(f"Error loading language file {locale_file}: {str(e)}")
227
201
 
228
202
 
229
203
  def _is_excluded(self, rel_path):
@@ -233,7 +207,7 @@ class DocumentDatesPlugin(BasePlugin):
233
207
  return True
234
208
  return False
235
209
 
236
- def _matches_exclude_pattern(self, rel_path, pattern):
210
+ def _matches_exclude_pattern(self, rel_path: str, pattern: str):
237
211
  if '*' not in pattern:
238
212
  return rel_path == pattern
239
213
  else:
@@ -268,19 +242,9 @@ class DocumentDatesPlugin(BasePlugin):
268
242
  return fs_time
269
243
 
270
244
  def _get_file_modification_time(self, file_path):
271
- """
272
245
  # 从git获取最后修改时间
273
- if self.is_git_repo:
274
- try:
275
- # 获取文件最后修改时间
276
- cmd = f'git log -1 --format="%aI" --date=iso -- "{file_path}"'
277
- process = subprocess.run(cmd, shell=True, capture_output=True, text=True)
278
- if process.returncode == 0 and process.stdout.strip():
279
- git_time = process.stdout.strip()
280
- return datetime.fromisoformat(git_time).replace(tzinfo=None)
281
- except Exception as e:
282
- logging.warning(f"Failed to get git modification time: {str(e)}")
283
- """
246
+ # if self.is_git_repo:
247
+ # return get_git_last_commit_time(file_path)
284
248
 
285
249
  # 从文件系统获取最后修改时间
286
250
  stat = os.stat(file_path)
@@ -295,9 +259,10 @@ class DocumentDatesPlugin(BasePlugin):
295
259
  if authors:
296
260
  return authors
297
261
  # 2. git author
298
- authors = self._get_git_authors(file_path)
299
- if authors:
300
- return authors
262
+ if self.is_git_repo:
263
+ authors = get_git_authors(file_path)
264
+ if authors:
265
+ return authors
301
266
  # 3. site_author 或 PC username
302
267
  return [Author(name=config.get('site_author') or Path.home().name)]
303
268
 
@@ -336,43 +301,18 @@ class DocumentDatesPlugin(BasePlugin):
336
301
  name = email.partition('@')[0]
337
302
  return [Author(name=name, email=email)]
338
303
  except Exception as e:
339
- logging.warning(f"Error processing author meta: {e}")
340
- return None
341
-
342
- def _get_git_authors(self, file_path):
343
- if not self.is_git_repo:
344
- return None
345
- try:
346
- # 获取作者信息(为了兼容性,不采用管道命令,在 python 中处理去重)
347
- # git_log_cmd = f'git log --format="%an|%ae" -- {file_path} | sort | uniq'
348
- # git_log_cmd = f'git log --format="%an|%ae" -- {file_path} | grep -vE "bot|noreply|ci|github-actions|dependabot|renovate" | sort | uniq'
349
- git_log_cmd = ['git', 'log', '--format=%an|%ae', '--', file_path]
350
- git_log_result = subprocess.run(git_log_cmd, capture_output=True, text=True, check=False)
351
- if git_log_result.returncode != 0 or not git_log_result.stdout.strip():
352
- return None
353
-
354
- # 使用字典去重和存储作者
355
- authors_map = {}
356
- for line in git_log_result.stdout.splitlines():
357
- if not line.strip() or line in authors_map:
358
- continue
359
- name, email = line.split('|')
360
- authors_map[line] = Author(name=name, email=email)
361
-
362
- return list(authors_map.values()) or None
363
- except Exception as e:
364
- logging.warning(f"Failed to get git author info: {str(e)}")
304
+ logger.warning(f"Error processing author meta: {e}")
365
305
  return None
366
306
 
367
307
 
368
- def _get_formatted_date(self, date):
308
+ def _get_formatted_date(self, date: datetime):
369
309
  if self.config['type'] == 'timeago':
370
310
  return ""
371
311
  elif self.config['type'] == 'datetime':
372
312
  return date.strftime(f"{self.config['date_format']} {self.config['time_format']}")
373
313
  return date.strftime(self.config['date_format'])
374
314
 
375
- def _generate_html_info(self, created, modified, authors=None):
315
+ def _generate_html_info(self, created: datetime, modified: datetime, authors=None):
376
316
  html = ""
377
317
  try:
378
318
  locale = 'zh_CN' if self.config['locale'] == 'zh' else self.config['locale']
@@ -405,7 +345,6 @@ class DocumentDatesPlugin(BasePlugin):
405
345
  # 多个作者的情况
406
346
  authors_info = ', '.join(a.name for a in authors if a.name)
407
347
  authors_tooltip = ',&nbsp;'.join(f'<a href="mailto:{a.email}">{a.name}</a>' if a.email else a.name for a in authors)
408
-
409
348
  html += (
410
349
  f"<span data-tippy-content='{self.translation.get('authors', 'Authors')}: {authors_tooltip}'>"
411
350
  f"<span class='material-icons' data-icon='doc_authors'></span>"
@@ -416,21 +355,15 @@ class DocumentDatesPlugin(BasePlugin):
416
355
  html += f"</div></div>"
417
356
 
418
357
  except Exception as e:
419
- logging.warning(f"Error generating HTML info: {e}")
358
+ logger.warning(f"Error generating HTML info: {e}")
420
359
  return html
421
360
 
422
361
 
423
- def _insert_date_info(self, markdown, date_info):
424
- if not markdown.strip():
425
- return markdown
426
-
362
+ def _insert_date_info(self, markdown: str, date_info: str):
427
363
  if self.config['position'] == 'top':
428
- lines = markdown.splitlines()
429
- for i, line in enumerate(lines):
430
- if line.startswith('# '):
431
- lines.insert(i + 1, date_info)
432
- else:
433
- lines.insert(0, date_info)
434
- return '\n'.join(lines)
435
- return f"{date_info}\n{markdown}"
364
+ before, _, after = markdown.lstrip().partition('\n')
365
+ if before.startswith('# '):
366
+ return f"{before}\n{date_info}\n{after}"
367
+ else:
368
+ return f"{date_info}\n{markdown}"
436
369
  return f"{markdown}\n\n{date_info}"
@@ -0,0 +1,147 @@
1
+ import os
2
+ import platform
3
+ import json
4
+ import logging
5
+ import subprocess
6
+ from pathlib import Path
7
+ from datetime import datetime
8
+
9
+ logger = logging.getLogger("mkdocs.plugins.document_dates")
10
+ logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
11
+
12
+ class Author:
13
+ def __init__(self, name="", email="", **kwargs):
14
+ self.name = name
15
+ self.email = email
16
+ # 扩展属性
17
+ self.attributes = kwargs
18
+
19
+ def __getattr__(self, name):
20
+ return self.attributes.get(name)
21
+
22
+ def to_dict(self):
23
+ return {
24
+ 'name': self.name,
25
+ 'email': self.email,
26
+ **self.attributes
27
+ }
28
+
29
+
30
+ def get_file_creation_time(file_path):
31
+ try:
32
+ stat = os.stat(file_path)
33
+ system = platform.system().lower()
34
+ if system.startswith('win'): # Windows
35
+ return datetime.fromtimestamp(stat.st_ctime)
36
+ elif system == 'darwin': # macOS
37
+ try:
38
+ return datetime.fromtimestamp(stat.st_birthtime)
39
+ except AttributeError:
40
+ return datetime.fromtimestamp(stat.st_ctime)
41
+ else: # Linux, 没有创建时间,使用修改时间
42
+ return datetime.fromtimestamp(stat.st_mtime)
43
+ except (OSError, ValueError) as e:
44
+ logger.error(f"Failed to get file creation time for {file_path}: {e}")
45
+ return datetime.now()
46
+
47
+ def check_git_repo():
48
+ try:
49
+ check_git = subprocess.run(['git', 'rev-parse', '--is-inside-work-tree'], capture_output=True, text=True)
50
+ if check_git.returncode == 0:
51
+ return True
52
+ except Exception as e:
53
+ logger.info(f"Not a Git repository: {str(e)}")
54
+ return False
55
+
56
+ def get_git_first_commit_time(file_path):
57
+ try:
58
+ # git log --reverse --format="%aI" -- {file_path} | head -n 1
59
+ cmd_list = ['git', 'log', '--reverse', '--format=%aI', '--', file_path]
60
+ process = subprocess.run(cmd_list, capture_output=True, text=True)
61
+ if process.returncode == 0 and process.stdout.strip():
62
+ first_line = process.stdout.partition('\n')[0].strip()
63
+ return datetime.fromisoformat(first_line).replace(tzinfo=None)
64
+ except Exception as e:
65
+ logger.info(f"Error getting git first commit time for {file_path}: {e}")
66
+ return None
67
+
68
+ def get_git_last_commit_time(file_path):
69
+ try:
70
+ cmd_list = ['git', 'log', '-1', '--format=%aI', '--', file_path]
71
+ process = subprocess.run(cmd_list, capture_output=True, text=True)
72
+ if process.returncode == 0 and process.stdout.strip():
73
+ git_time = process.stdout.strip()
74
+ return datetime.fromisoformat(git_time).replace(tzinfo=None)
75
+ except Exception as e:
76
+ logger.info(f"Error getting git last commit time for {file_path}: {e}")
77
+ return None
78
+
79
+ def get_git_authors(file_path):
80
+ try:
81
+ # 为了兼容性,不采用管道命令,在 python 中处理去重
82
+ # git log --format="%an|%ae" -- {file_path} | sort | uniq
83
+ # git log --format="%an|%ae" -- {file_path} | grep -vE "bot|noreply|ci|github-actions|dependabot|renovate" | sort | uniq
84
+ cmd_list = ['git', 'log', '--format=%an|%ae', '--', file_path]
85
+ process = subprocess.run(cmd_list, capture_output=True, text=True)
86
+ if process.returncode == 0 and process.stdout.strip():
87
+ # 使用字典去重和存储作者
88
+ authors_map = {}
89
+ for line in process.stdout.splitlines():
90
+ if not line.strip() or line in authors_map:
91
+ continue
92
+ name, email = line.split('|')
93
+ authors_map[line] = Author(name=name, email=email)
94
+ return list(authors_map.values()) or None
95
+ except Exception as e:
96
+ logger.warning(f"Failed to get git author info: {str(e)}")
97
+ return None
98
+
99
+ def read_json_cache(cache_file: Path):
100
+ dates_cache = {}
101
+ if cache_file.exists():
102
+ try:
103
+ with open(cache_file, "r", encoding='utf-8') as f:
104
+ dates_cache = json.load(f)
105
+ except (IOError, json.JSONDecodeError) as e:
106
+ logger.warning(f"Error reading from '.dates_cache.json': {str(e)}")
107
+ return dates_cache
108
+
109
+ def read_jsonl_cache(jsonl_file: Path):
110
+ dates_cache = {}
111
+ if jsonl_file.exists():
112
+ try:
113
+ with open(jsonl_file, "r", encoding='utf-8') as f:
114
+ for line in f:
115
+ try:
116
+ entry = json.loads(line.strip())
117
+ if entry and isinstance(entry, dict) and len(entry) == 1:
118
+ file_path, file_info = next(iter(entry.items()))
119
+ dates_cache[file_path] = file_info
120
+ except (json.JSONDecodeError, StopIteration) as e:
121
+ logger.warning(f"Skipping invalid JSONL line: {e}")
122
+ except IOError as e:
123
+ logger.warning(f"Error reading from '.dates_cache.jsonl': {str(e)}")
124
+ return dates_cache
125
+
126
+ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
127
+ try:
128
+ # 使用临时文件写入,然后替换原文件,避免写入过程中的问题
129
+ temp_file = jsonl_file.with_suffix('.jsonl.tmp')
130
+ with open(temp_file, "w", encoding='utf-8') as f:
131
+ for file_path in tracked_files:
132
+ if file_path in dates_cache:
133
+ entry = {file_path: dates_cache[file_path]}
134
+ f.write(json.dumps(entry, ensure_ascii=False) + '\n')
135
+
136
+ # 替换原文件
137
+ temp_file.replace(jsonl_file)
138
+
139
+ # 将文件添加到git
140
+ subprocess.run(["git", "add", str(jsonl_file)], check=True)
141
+ logger.info(f"Successfully updated JSONL cache file: {jsonl_file}")
142
+ return True
143
+ except (IOError, json.JSONDecodeError) as e:
144
+ logger.error(f"Failed to write JSONL cache file {jsonl_file}: {e}")
145
+ except Exception as e:
146
+ logger.error(f"Failed to add JSONL cache file to git: {e}")
147
+ return False
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.1.6
3
+ Version: 3.2
4
4
  Summary: An easy-to-use, lightweight MkDocs plugin for displaying the exact creation time, last modification time and author info of markdown documents.
5
5
  Home-page: https://github.com/jaywhj/mkdocs-document-dates
6
6
  Author: Aaron Wang
7
7
  Author-email: aaronwqt@gmail.com
8
8
  License: MIT
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
10
  Classifier: Operating System :: OS Independent
12
11
  Requires-Python: >=3.7
13
12
  Description-Content-Type: text/markdown
@@ -118,9 +117,9 @@ email: e-name@gmail.com
118
117
 
119
118
  ## Customization
120
119
 
121
- The plugin supports deep customization, such as **icon style, font style, theme color, animation type, dividing line**, etc. Everything is customizable (I've already written the code, you just need to turn on the uncomment switch):
120
+ The plugin supports deep customization, such as **icon style, theme color, font, animation, dividing line**, etc. Everything is customizable (I've already written the code, you just need to turn on the uncomment switch):
122
121
 
123
- | Function: | Location |
122
+ | Category: | Location: |
124
123
  | :----------------------: | ---------------------------------------- |
125
124
  | **Style & Theme** | `docs/assets/document_dates/user.config.css` |
126
125
  | **Properties & Functions** | `docs/assets/document_dates/user.config.js` |
@@ -129,7 +128,7 @@ The plugin supports deep customization, such as **icon style, font style, theme
129
128
  **Tip**: when `type: timeago` is set, timeago.js is enabled to render dynamic time, `timeago.min.js` only contains English and Chinese by default, if you need to load other languages, you can configure it as below (choose one):
130
129
 
131
130
  - In `user.config.js`, refer to [the demo commented out](https://github.com/jaywhj/mkdocs-document-dates/blob/main/mkdocs_document_dates/static/config/user.config.js) at the bottom, translate it into your local language
132
- - In `mkdocs.yml`, add the following two lines to configure the full version of `timeago.full.min.js` to load all languages at once
131
+ - In `mkdocs.yml`, configure the full version of `timeago.full.min.js` to load all languages at once
133
132
  ```yaml
134
133
  extra_javascript:
135
134
  - assets/document_dates/core/timeago.full.min.js
@@ -6,6 +6,7 @@ mkdocs_document_dates/__init__.py
6
6
  mkdocs_document_dates/cache_manager.py
7
7
  mkdocs_document_dates/hooks_installer.py
8
8
  mkdocs_document_dates/plugin.py
9
+ mkdocs_document_dates/utils.py
9
10
  mkdocs_document_dates.egg-info/PKG-INFO
10
11
  mkdocs_document_dates.egg-info/SOURCES.txt
11
12
  mkdocs_document_dates.egg-info/dependency_links.txt
@@ -28,7 +28,7 @@ try:
28
28
  except FileNotFoundError:
29
29
  long_description = "An easy-to-use, lightweight MkDocs plugin for displaying the exact creation time, last modification time and author info of markdown documents."
30
30
 
31
- VERSION = '3.1.6'
31
+ VERSION = '3.2'
32
32
 
33
33
  setup(
34
34
  name="mkdocs-document-dates",
@@ -46,7 +46,6 @@ setup(
46
46
  ],
47
47
  classifiers=[
48
48
  "Programming Language :: Python :: 3",
49
- "License :: OSI Approved :: MIT License",
50
49
  "Operating System :: OS Independent",
51
50
  ],
52
51
  cmdclass={
@@ -1,226 +0,0 @@
1
- import os
2
- import json
3
- import logging
4
- import platform
5
- import subprocess
6
- from datetime import datetime
7
- from pathlib import Path
8
-
9
- # 配置日志等级 (INFO WARNING ERROR)
10
- logging.basicConfig(
11
- level=logging.WARNING,
12
- format='%(levelname)s: %(message)s'
13
- )
14
-
15
- def find_mkdocs_projects():
16
- projects = []
17
- try:
18
- git_root = Path(subprocess.check_output(
19
- ['git', 'rev-parse', '--show-toplevel'],
20
- text=True, encoding='utf-8'
21
- ).strip())
22
-
23
- # 遍历 git_root 及子目录, 寻找 mkdocs.yml 文件
24
- for config_file in git_root.rglob('mkdocs.y*ml'):
25
- if config_file.name.lower() in ('mkdocs.yml', 'mkdocs.yaml'):
26
- projects.append(config_file.parent)
27
-
28
- if not projects:
29
- logging.warning("No MkDocs projects found in the repository")
30
- except subprocess.CalledProcessError as e:
31
- logging.error(f"Failed to find the Git repository root: {e}")
32
- except Exception as e:
33
- logging.error(f"Unexpected error while searching for MkDocs projects: {e}")
34
-
35
- return projects
36
-
37
- def get_file_creation_time(file_path):
38
- try:
39
- stat = os.stat(file_path)
40
- system = platform.system().lower()
41
- if system.startswith('win'): # Windows
42
- return datetime.fromtimestamp(stat.st_ctime)
43
- elif system == 'darwin': # macOS
44
- try:
45
- return datetime.fromtimestamp(stat.st_birthtime)
46
- except AttributeError:
47
- return datetime.fromtimestamp(stat.st_ctime)
48
- else: # Linux, 没有创建时间,使用修改时间
49
- return datetime.fromtimestamp(stat.st_mtime)
50
- except (OSError, ValueError) as e:
51
- logging.error(f"Failed to get file creation time for {file_path}: {e}")
52
- return datetime.now()
53
-
54
- def get_git_first_commit_time(file_path):
55
- try:
56
- # git log --reverse --format="%aI" --date=iso -- {file_path} | head -n 1
57
- result = subprocess.run(['git', 'log', '--reverse', '--format=%aI', '--', file_path], capture_output=True, text=True)
58
- if result.returncode == 0:
59
- first_line = result.stdout.partition('\n')[0].strip()
60
- if first_line:
61
- return datetime.fromisoformat(first_line).replace(tzinfo=None)
62
- except Exception as e:
63
- logging.info(f"Error getting git first commit time for {file_path}: {e}")
64
- return None
65
-
66
- def setup_gitattributes(docs_dir):
67
- updated = False
68
- try:
69
- gitattributes_path = docs_dir / '.gitattributes'
70
- union_config_line = ".dates_cache.jsonl merge=union"
71
- if gitattributes_path.exists():
72
- with open(gitattributes_path, 'r', encoding='utf-8') as f:
73
- content = f.read()
74
-
75
- if union_config_line not in content:
76
- with open(gitattributes_path, 'a', encoding='utf-8') as f:
77
- f.write(f"\n{union_config_line}\n")
78
- updated = True
79
- else:
80
- with open(gitattributes_path, 'w', encoding='utf-8') as f:
81
- f.write(f"{union_config_line}\n")
82
- updated = True
83
-
84
- if updated:
85
- subprocess.run(["git", "add", str(gitattributes_path)], check=True)
86
- logging.info(f"Updated .gitattributes file: {gitattributes_path}")
87
- except (IOError, OSError) as e:
88
- logging.error(f"Failed to read/write .gitattributes file: {e}")
89
- except Exception as e:
90
- logging.error(f"Failed to add .gitattributes to git: {e}")
91
-
92
- return updated
93
-
94
- def read_json_cache(cache_file):
95
- dates_cache = {}
96
- if cache_file.exists():
97
- try:
98
- with open(cache_file, "r", encoding='utf-8') as f:
99
- dates_cache = json.load(f)
100
- except (IOError, json.JSONDecodeError) as e:
101
- logging.warning(f"Error reading from '.dates_cache.json': {str(e)}")
102
- return dates_cache
103
-
104
- def read_jsonl_cache(jsonl_file):
105
- dates_cache = {}
106
- if jsonl_file.exists():
107
- try:
108
- with open(jsonl_file, "r", encoding='utf-8') as f:
109
- for line in f:
110
- try:
111
- entry = json.loads(line.strip())
112
- if entry and isinstance(entry, dict) and len(entry) == 1:
113
- file_path, file_info = next(iter(entry.items()))
114
- dates_cache[file_path] = file_info
115
- except (json.JSONDecodeError, StopIteration) as e:
116
- logging.warning(f"Skipping invalid JSONL line: {e}")
117
- except IOError as e:
118
- logging.warning(f"Error reading from '.dates_cache.jsonl': {str(e)}")
119
- return dates_cache
120
-
121
- def write_jsonl_cache(jsonl_file, dates_cache, tracked_files):
122
- try:
123
- # 使用临时文件写入,然后替换原文件,避免写入过程中的问题
124
- temp_file = jsonl_file.with_suffix('.jsonl.tmp')
125
- with open(temp_file, "w", encoding='utf-8') as f:
126
- for file_path in tracked_files:
127
- if file_path in dates_cache:
128
- entry = {file_path: dates_cache[file_path]}
129
- f.write(json.dumps(entry, ensure_ascii=False) + '\n')
130
-
131
- # 替换原文件
132
- temp_file.replace(jsonl_file)
133
-
134
- # 将文件添加到git
135
- subprocess.run(["git", "add", str(jsonl_file)], check=True)
136
- logging.info(f"Successfully updated JSONL cache file: {jsonl_file}")
137
- return True
138
- except (IOError, json.JSONDecodeError) as e:
139
- logging.error(f"Failed to write JSONL cache file {jsonl_file}: {e}")
140
- return False
141
- except subprocess.CalledProcessError as e:
142
- logging.error(f"Failed to add JSONL cache file to git: {e}")
143
- return False
144
-
145
- def update_cache():
146
- global_updated = False
147
-
148
- for project_dir in find_mkdocs_projects():
149
- try:
150
- project_updated = False
151
-
152
- docs_dir = project_dir / 'docs'
153
- if not docs_dir.exists():
154
- logging.error(f"Document directory does not exist: {docs_dir}")
155
- continue
156
-
157
- # 设置.gitattributes文件
158
- gitattributes_updated = setup_gitattributes(docs_dir)
159
- if gitattributes_updated:
160
- global_updated = True
161
-
162
- # 获取docs目录下已跟踪(tracked)的markdown文件
163
- cmd = ["git", "ls-files", "*.md"]
164
- result = subprocess.run(cmd, cwd=docs_dir, capture_output=True, text=True, check=True)
165
- tracked_files = result.stdout.splitlines() if result.stdout else []
166
-
167
- if not tracked_files:
168
- logging.info(f"No tracked markdown files found in {docs_dir}")
169
- continue
170
-
171
- # 读取旧的JSON缓存文件(如果存在)
172
- json_cache_file = docs_dir / '.dates_cache.json'
173
- json_dates_cache = read_json_cache(json_cache_file)
174
-
175
- # 读取新的JSONL缓存文件(如果存在)
176
- jsonl_cache_file = docs_dir / '.dates_cache.jsonl'
177
- jsonl_dates_cache = read_jsonl_cache(jsonl_cache_file)
178
-
179
- # 根据 git已跟踪的文件来更新
180
- for rel_path in tracked_files:
181
- try:
182
- # 如果文件已在JSONL缓存中,跳过
183
- if rel_path in jsonl_dates_cache:
184
- continue
185
-
186
- full_path = docs_dir / rel_path
187
- # 处理新文件或迁移旧JSON缓存
188
- if rel_path in json_dates_cache:
189
- jsonl_dates_cache[rel_path] = json_dates_cache[rel_path]
190
- project_updated = True
191
- elif full_path.exists():
192
- created_time = get_file_creation_time(full_path)
193
- if not jsonl_cache_file.exists() and not json_cache_file.exists():
194
- git_time = get_git_first_commit_time(full_path)
195
- if git_time:
196
- created_time = min(created_time, git_time)
197
- jsonl_dates_cache[rel_path] = {
198
- "created": created_time.isoformat()
199
- }
200
- project_updated = True
201
- except Exception as e:
202
- logging.error(f"Error processing file {rel_path}: {e}")
203
- continue
204
-
205
- # 标记删除不再跟踪的文件
206
- files_to_remove = set(jsonl_dates_cache.keys()) - set(tracked_files)
207
- if files_to_remove:
208
- project_updated = True
209
- logging.info(f"Removing {len(files_to_remove)} untracked files from cache")
210
-
211
- # 如果有更新,写入JSONL缓存文件
212
- if project_updated or not jsonl_cache_file.exists():
213
- if write_jsonl_cache(jsonl_cache_file, jsonl_dates_cache, tracked_files):
214
- global_updated = True
215
- except subprocess.CalledProcessError as e:
216
- logging.error(f"Failed to execute git command: {e}")
217
- continue
218
- except Exception as e:
219
- logging.error(f"Error processing project directory {project_dir}: {e}")
220
- continue
221
-
222
- return global_updated
223
-
224
-
225
- if __name__ == "__main__":
226
- update_cache()
@@ -1,114 +0,0 @@
1
- import os
2
- import sys
3
- import logging
4
- import subprocess
5
- from pathlib import Path
6
- import platform
7
-
8
- # 配置日志等级 (INFO WARNING ERROR)
9
- logging.basicConfig(
10
- level=logging.WARNING,
11
- format='%(levelname)s: %(message)s'
12
- )
13
-
14
- def get_config_dir():
15
- if platform.system().lower().startswith('win'):
16
- return Path(os.getenv('APPDATA', str(Path.home() / 'AppData' / 'Roaming')))
17
- else:
18
- return Path.home() / '.config'
19
-
20
- def check_python_version(interpreter):
21
- try:
22
- result = subprocess.run(
23
- [interpreter, "-c", "import sys; print(sys.version_info >= (3, 7))"],
24
- capture_output=True, text=True, check=False
25
- )
26
- if result.returncode == 0 and result.stdout.strip().lower() == 'true':
27
- return True
28
- else:
29
- logging.warning(f"Low python version, requires python_requires >=3.7")
30
- return False
31
- except Exception as e:
32
- logging.debug(f"Failed to check {interpreter}: {str(e)}")
33
- return False
34
-
35
- def detect_python_interpreter():
36
- # 检查可能的Python解释器
37
- python_interpreters = ['python3', 'python']
38
-
39
- for interpreter in python_interpreters:
40
- if check_python_version(interpreter):
41
- return f'#!/usr/bin/env {interpreter}'
42
-
43
- # 如果都失败了,使用当前运行的Python解释器
44
- return f'#!{sys.executable}'
45
-
46
- def setup_hooks_directory():
47
- config_dir = get_config_dir() / 'mkdocs-document-dates' / 'hooks'
48
- try:
49
- config_dir.mkdir(parents=True, exist_ok=True)
50
- os.chmod(config_dir, 0o755)
51
- return config_dir
52
- except PermissionError:
53
- logging.error(f"No permission to create directory: {config_dir}")
54
- return None
55
- except Exception as e:
56
- logging.error(f"Failed to create directory {config_dir}: {str(e)}")
57
- return None
58
-
59
- def install_hook_file(source_hook, target_dir):
60
- target_hook_path = target_dir / source_hook.name
61
- try:
62
- # 读取并更新hook文件内容
63
- with open(source_hook, 'r', encoding='utf-8') as f_in:
64
- content = f_in.read()
65
-
66
- # 更新shebang行
67
- shebang = detect_python_interpreter()
68
- if content.startswith('#!'):
69
- content = shebang + os.linesep + content[content.find('\n'):]
70
- else:
71
- content = shebang + os.linesep + content
72
-
73
- # 写入并设置权限
74
- with open(target_hook_path, 'w', encoding='utf-8') as f_out:
75
- f_out.write(content)
76
- os.chmod(target_hook_path, 0o755)
77
- return True
78
- except Exception as e:
79
- logging.error(f"Failed to create hook file {target_hook_path}: {str(e)}")
80
- return False
81
-
82
- def configure_git_hooks(hooks_dir):
83
- try:
84
- subprocess.run(
85
- ['git', 'config', '--global', 'core.hooksPath', str(hooks_dir)],
86
- check=True, capture_output=True, encoding='utf-8'
87
- )
88
- logging.info(f"Git hooks successfully installed in: {hooks_dir}")
89
- return True
90
- except Exception:
91
- logging.warning("Git not detected, failed to set git hooks path")
92
- return False
93
-
94
- def install():
95
- try:
96
- # 创建hooks目录
97
- hooks_dir = setup_hooks_directory()
98
- if not hooks_dir:
99
- return False
100
-
101
- # 安装hook文件
102
- source_hook = Path(__file__).parent / 'hooks' / 'pre-commit'
103
- if not install_hook_file(source_hook, hooks_dir):
104
- return False
105
-
106
- # 配置git hooks路径
107
- return configure_git_hooks(hooks_dir)
108
-
109
- except Exception as e:
110
- logging.error(f"Unexpected error during hooks installation: {str(e)}")
111
- return False
112
-
113
- if __name__ == '__main__':
114
- install()