mkdocs-ultralytics-plugin 0.2.4__tar.gz → 0.2.6__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_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/PKG-INFO +13 -10
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/README.md +11 -9
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/PKG-INFO +13 -10
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/__init__.py +1 -1
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/main.py +28 -3
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/postprocess.py +137 -19
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/processor.py +19 -15
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/plugin/utils.py +43 -7
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/pyproject.toml +1 -3
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/LICENSE +0 -0
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/SOURCES.txt +0 -0
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/dependency_links.txt +0 -0
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/entry_points.txt +0 -0
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/requires.txt +0 -0
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/mkdocs_ultralytics_plugin.egg-info/top_level.txt +0 -0
- {mkdocs_ultralytics_plugin-0.2.4 → mkdocs_ultralytics_plugin-0.2.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-ultralytics-plugin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: An MkDocs plugin that provides Ultralytics Docs customizations at https://docs.ultralytics.com.
|
|
5
5
|
Author-email: Glenn Jocher <hello@ultralytics.com>
|
|
6
6
|
Maintainer-email: Ultralytics <hello@ultralytics.com>
|
|
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.13
|
|
26
26
|
Classifier: Programming Language :: Python :: 3.14
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
27
28
|
Classifier: Topic :: Software Development
|
|
28
29
|
Classifier: Topic :: Scientific/Engineering
|
|
29
30
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
@@ -41,11 +42,11 @@ Provides-Extra: dev
|
|
|
41
42
|
Requires-Dist: pytest; extra == "dev"
|
|
42
43
|
Dynamic: license-file
|
|
43
44
|
|
|
44
|
-
<a href="https://www.ultralytics.com
|
|
45
|
+
<a href="https://www.ultralytics.com"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
|
|
45
46
|
|
|
46
47
|
# 🚀 MkDocs Ultralytics Plugin
|
|
47
48
|
|
|
48
|
-
Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.
|
|
49
|
+
Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.org/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
|
|
49
50
|
|
|
50
51
|
**Two modes available:**
|
|
51
52
|
|
|
@@ -55,8 +56,8 @@ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your
|
|
|
55
56
|
[](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml)
|
|
56
57
|
|
|
57
58
|
[](https://discord.com/invite/ultralytics)
|
|
58
|
-
[](https://community.ultralytics.com
|
|
59
|
-
[](https://reddit.com/r/ultralytics)
|
|
59
|
+
[](https://community.ultralytics.com)
|
|
60
|
+
[](https://www.reddit.com/r/ultralytics/)
|
|
60
61
|
|
|
61
62
|
## ✨ Features
|
|
62
63
|
|
|
@@ -64,12 +65,13 @@ This tool seamlessly integrates valuable features into your documentation site:
|
|
|
64
65
|
|
|
65
66
|
- **Meta Tag Generation**: Automatically creates meta description and image tags using the first paragraph and image found on each page, crucial for SEO and social previews.
|
|
66
67
|
- **Keyword Customization**: Allows you to define specific meta keywords directly within your Markdown front matter for targeted SEO.
|
|
67
|
-
- **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://
|
|
68
|
+
- **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://docs.x.com/overview) meta tags to ensure your content looks great when shared on social platforms.
|
|
68
69
|
- **Simple Sharing**: Inserts convenient share buttons for Twitter and LinkedIn at the end of your content, encouraging readers to share.
|
|
69
70
|
- **Git Insights**: Gathers and displays [Git](https://git-scm.com/) commit information, including update dates and authors, directly within the page footer for transparency.
|
|
70
71
|
- **JSON-LD Support**: Adds structured data in JSON-LD format, helping search engines understand your content better and potentially enabling rich results.
|
|
71
72
|
- **FAQ Parsing**: Automatically parses FAQ sections (if present) and includes them in the structured data for enhanced search visibility.
|
|
72
73
|
- **Copy for LLM**: Adds a button to copy page content in Markdown format, optimized for sharing with AI assistants.
|
|
74
|
+
- **LLMs.txt Generation**: Generates an `llms.txt` index after builds for LLM-friendly site discovery.
|
|
73
75
|
- **Customizable Styling**: Includes optional inline CSS to maintain consistent styling across your documentation, aligning with themes like [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
|
|
74
76
|
|
|
75
77
|
## 🛠️ Installation
|
|
@@ -161,7 +163,7 @@ jekyll build && python postprocess.py
|
|
|
161
163
|
|
|
162
164
|
## ⚙️ Configuration Options
|
|
163
165
|
|
|
164
|
-
Both modes support the
|
|
166
|
+
Both modes support the feature options below. Differences: `enabled` applies to plugin mode only, while postprocess mode reads `site_dir`, `docs_dir`, and `site_url` as arguments (plugin mode gets these from `mkdocs.yml`) and additionally accepts `use_processes` (default `True`) and `workers` (default: CPU count) to control parallelism.
|
|
165
167
|
|
|
166
168
|
| Option | Type | Default | Description |
|
|
167
169
|
| ------------------- | ---- | ------- | -------------------------------- |
|
|
@@ -177,6 +179,7 @@ Both modes support the same configuration options:
|
|
|
177
179
|
| `add_json_ld` | bool | `False` | Add JSON-LD structured data |
|
|
178
180
|
| `add_css` | bool | `True` | Include inline CSS styles |
|
|
179
181
|
| `add_copy_llm` | bool | `True` | Add "Copy for LLM" button |
|
|
182
|
+
| `add_llms_txt` | bool | `True` | Generate an `llms.txt` index |
|
|
180
183
|
|
|
181
184
|
## 🧩 How It Works
|
|
182
185
|
|
|
@@ -236,7 +239,7 @@ Both plugin mode and postprocess mode use the same `process_html()` function in
|
|
|
236
239
|
|
|
237
240
|
Collaboration fuels innovation! 🤗 The success of Ultralytics' open-source projects thrives on community contributions. We welcome your involvement, whether it's fixing bugs, proposing new features, improving documentation, engaging in discussions, or sharing how you use Ultralytics tools.
|
|
238
241
|
|
|
239
|
-
Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing
|
|
242
|
+
Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇♂️ every contribution!
|
|
240
243
|
|
|
241
244
|
[](https://github.com/ultralytics/ultralytics/graphs/contributors)
|
|
242
245
|
|
|
@@ -244,7 +247,7 @@ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributi
|
|
|
244
247
|
|
|
245
248
|
Ultralytics provides two licensing options:
|
|
246
249
|
|
|
247
|
-
- **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-
|
|
250
|
+
- **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-3.0) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
|
|
248
251
|
- **Enterprise License**: Designed for commercial applications, this license allows seamless integration of Ultralytics software into commercial products, bypassing AGPL-3.0 requirements. Visit [Ultralytics Licensing](https://www.ultralytics.com/license) for details.
|
|
249
252
|
|
|
250
253
|
## ✉️ Connect with Us
|
|
@@ -259,7 +262,7 @@ Encountered a bug or have an idea? Visit [GitHub Issues](https://github.com/ultr
|
|
|
259
262
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
260
263
|
<a href="https://twitter.com/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-twitter.png" width="3%" alt="Ultralytics Twitter"></a>
|
|
261
264
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
262
|
-
<a href="https://youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
|
|
265
|
+
<a href="https://www.youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
|
|
263
266
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
264
267
|
<a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
|
|
265
268
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<a href="https://www.ultralytics.com
|
|
1
|
+
<a href="https://www.ultralytics.com"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
|
|
2
2
|
|
|
3
3
|
# 🚀 MkDocs Ultralytics Plugin
|
|
4
4
|
|
|
5
|
-
Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.
|
|
5
|
+
Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.org/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
|
|
6
6
|
|
|
7
7
|
**Two modes available:**
|
|
8
8
|
|
|
@@ -12,8 +12,8 @@ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your
|
|
|
12
12
|
[](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml)
|
|
13
13
|
|
|
14
14
|
[](https://discord.com/invite/ultralytics)
|
|
15
|
-
[](https://community.ultralytics.com
|
|
16
|
-
[](https://reddit.com/r/ultralytics)
|
|
15
|
+
[](https://community.ultralytics.com)
|
|
16
|
+
[](https://www.reddit.com/r/ultralytics/)
|
|
17
17
|
|
|
18
18
|
## ✨ Features
|
|
19
19
|
|
|
@@ -21,12 +21,13 @@ This tool seamlessly integrates valuable features into your documentation site:
|
|
|
21
21
|
|
|
22
22
|
- **Meta Tag Generation**: Automatically creates meta description and image tags using the first paragraph and image found on each page, crucial for SEO and social previews.
|
|
23
23
|
- **Keyword Customization**: Allows you to define specific meta keywords directly within your Markdown front matter for targeted SEO.
|
|
24
|
-
- **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://
|
|
24
|
+
- **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://docs.x.com/overview) meta tags to ensure your content looks great when shared on social platforms.
|
|
25
25
|
- **Simple Sharing**: Inserts convenient share buttons for Twitter and LinkedIn at the end of your content, encouraging readers to share.
|
|
26
26
|
- **Git Insights**: Gathers and displays [Git](https://git-scm.com/) commit information, including update dates and authors, directly within the page footer for transparency.
|
|
27
27
|
- **JSON-LD Support**: Adds structured data in JSON-LD format, helping search engines understand your content better and potentially enabling rich results.
|
|
28
28
|
- **FAQ Parsing**: Automatically parses FAQ sections (if present) and includes them in the structured data for enhanced search visibility.
|
|
29
29
|
- **Copy for LLM**: Adds a button to copy page content in Markdown format, optimized for sharing with AI assistants.
|
|
30
|
+
- **LLMs.txt Generation**: Generates an `llms.txt` index after builds for LLM-friendly site discovery.
|
|
30
31
|
- **Customizable Styling**: Includes optional inline CSS to maintain consistent styling across your documentation, aligning with themes like [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
|
|
31
32
|
|
|
32
33
|
## 🛠️ Installation
|
|
@@ -118,7 +119,7 @@ jekyll build && python postprocess.py
|
|
|
118
119
|
|
|
119
120
|
## ⚙️ Configuration Options
|
|
120
121
|
|
|
121
|
-
Both modes support the
|
|
122
|
+
Both modes support the feature options below. Differences: `enabled` applies to plugin mode only, while postprocess mode reads `site_dir`, `docs_dir`, and `site_url` as arguments (plugin mode gets these from `mkdocs.yml`) and additionally accepts `use_processes` (default `True`) and `workers` (default: CPU count) to control parallelism.
|
|
122
123
|
|
|
123
124
|
| Option | Type | Default | Description |
|
|
124
125
|
| ------------------- | ---- | ------- | -------------------------------- |
|
|
@@ -134,6 +135,7 @@ Both modes support the same configuration options:
|
|
|
134
135
|
| `add_json_ld` | bool | `False` | Add JSON-LD structured data |
|
|
135
136
|
| `add_css` | bool | `True` | Include inline CSS styles |
|
|
136
137
|
| `add_copy_llm` | bool | `True` | Add "Copy for LLM" button |
|
|
138
|
+
| `add_llms_txt` | bool | `True` | Generate an `llms.txt` index |
|
|
137
139
|
|
|
138
140
|
## 🧩 How It Works
|
|
139
141
|
|
|
@@ -193,7 +195,7 @@ Both plugin mode and postprocess mode use the same `process_html()` function in
|
|
|
193
195
|
|
|
194
196
|
Collaboration fuels innovation! 🤗 The success of Ultralytics' open-source projects thrives on community contributions. We welcome your involvement, whether it's fixing bugs, proposing new features, improving documentation, engaging in discussions, or sharing how you use Ultralytics tools.
|
|
195
197
|
|
|
196
|
-
Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing
|
|
198
|
+
Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇♂️ every contribution!
|
|
197
199
|
|
|
198
200
|
[](https://github.com/ultralytics/ultralytics/graphs/contributors)
|
|
199
201
|
|
|
@@ -201,7 +203,7 @@ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributi
|
|
|
201
203
|
|
|
202
204
|
Ultralytics provides two licensing options:
|
|
203
205
|
|
|
204
|
-
- **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-
|
|
206
|
+
- **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-3.0) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
|
|
205
207
|
- **Enterprise License**: Designed for commercial applications, this license allows seamless integration of Ultralytics software into commercial products, bypassing AGPL-3.0 requirements. Visit [Ultralytics Licensing](https://www.ultralytics.com/license) for details.
|
|
206
208
|
|
|
207
209
|
## ✉️ Connect with Us
|
|
@@ -216,7 +218,7 @@ Encountered a bug or have an idea? Visit [GitHub Issues](https://github.com/ultr
|
|
|
216
218
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
217
219
|
<a href="https://twitter.com/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-twitter.png" width="3%" alt="Ultralytics Twitter"></a>
|
|
218
220
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
219
|
-
<a href="https://youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
|
|
221
|
+
<a href="https://www.youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
|
|
220
222
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
221
223
|
<a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
|
|
222
224
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-ultralytics-plugin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: An MkDocs plugin that provides Ultralytics Docs customizations at https://docs.ultralytics.com.
|
|
5
5
|
Author-email: Glenn Jocher <hello@ultralytics.com>
|
|
6
6
|
Maintainer-email: Ultralytics <hello@ultralytics.com>
|
|
@@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.13
|
|
26
26
|
Classifier: Programming Language :: Python :: 3.14
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
27
28
|
Classifier: Topic :: Software Development
|
|
28
29
|
Classifier: Topic :: Scientific/Engineering
|
|
29
30
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
@@ -41,11 +42,11 @@ Provides-Extra: dev
|
|
|
41
42
|
Requires-Dist: pytest; extra == "dev"
|
|
42
43
|
Dynamic: license-file
|
|
43
44
|
|
|
44
|
-
<a href="https://www.ultralytics.com
|
|
45
|
+
<a href="https://www.ultralytics.com"><img src="https://raw.githubusercontent.com/ultralytics/assets/main/logo/Ultralytics_Logotype_Original.svg" width="320" alt="Ultralytics logo"></a>
|
|
45
46
|
|
|
46
47
|
# 🚀 MkDocs Ultralytics Plugin
|
|
47
48
|
|
|
48
|
-
Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.
|
|
49
|
+
Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your [MkDocs](https://www.mkdocs.org/), [Zensical](https://zensical.org/), or any static site documentation with advanced Search Engine Optimization (SEO) features, interactive social elements, and structured data support. It automates the generation of essential meta tags, incorporates social sharing capabilities, and adds [JSON-LD](https://json-ld.org/) structured data to elevate user engagement and improve your documentation's visibility on the web.
|
|
49
50
|
|
|
50
51
|
**Two modes available:**
|
|
51
52
|
|
|
@@ -55,8 +56,8 @@ Welcome to the MkDocs Ultralytics Plugin! 📄 This powerful tool enhances your
|
|
|
55
56
|
[](https://github.com/ultralytics/mkdocs/actions/workflows/format.yml)
|
|
56
57
|
|
|
57
58
|
[](https://discord.com/invite/ultralytics)
|
|
58
|
-
[](https://community.ultralytics.com
|
|
59
|
-
[](https://reddit.com/r/ultralytics)
|
|
59
|
+
[](https://community.ultralytics.com)
|
|
60
|
+
[](https://www.reddit.com/r/ultralytics/)
|
|
60
61
|
|
|
61
62
|
## ✨ Features
|
|
62
63
|
|
|
@@ -64,12 +65,13 @@ This tool seamlessly integrates valuable features into your documentation site:
|
|
|
64
65
|
|
|
65
66
|
- **Meta Tag Generation**: Automatically creates meta description and image tags using the first paragraph and image found on each page, crucial for SEO and social previews.
|
|
66
67
|
- **Keyword Customization**: Allows you to define specific meta keywords directly within your Markdown front matter for targeted SEO.
|
|
67
|
-
- **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://
|
|
68
|
+
- **Social Media Optimization**: Generates [Open Graph](https://ogp.me/) and [Twitter Card](https://docs.x.com/overview) meta tags to ensure your content looks great when shared on social platforms.
|
|
68
69
|
- **Simple Sharing**: Inserts convenient share buttons for Twitter and LinkedIn at the end of your content, encouraging readers to share.
|
|
69
70
|
- **Git Insights**: Gathers and displays [Git](https://git-scm.com/) commit information, including update dates and authors, directly within the page footer for transparency.
|
|
70
71
|
- **JSON-LD Support**: Adds structured data in JSON-LD format, helping search engines understand your content better and potentially enabling rich results.
|
|
71
72
|
- **FAQ Parsing**: Automatically parses FAQ sections (if present) and includes them in the structured data for enhanced search visibility.
|
|
72
73
|
- **Copy for LLM**: Adds a button to copy page content in Markdown format, optimized for sharing with AI assistants.
|
|
74
|
+
- **LLMs.txt Generation**: Generates an `llms.txt` index after builds for LLM-friendly site discovery.
|
|
73
75
|
- **Customizable Styling**: Includes optional inline CSS to maintain consistent styling across your documentation, aligning with themes like [MkDocs Material](https://squidfunk.github.io/mkdocs-material/).
|
|
74
76
|
|
|
75
77
|
## 🛠️ Installation
|
|
@@ -161,7 +163,7 @@ jekyll build && python postprocess.py
|
|
|
161
163
|
|
|
162
164
|
## ⚙️ Configuration Options
|
|
163
165
|
|
|
164
|
-
Both modes support the
|
|
166
|
+
Both modes support the feature options below. Differences: `enabled` applies to plugin mode only, while postprocess mode reads `site_dir`, `docs_dir`, and `site_url` as arguments (plugin mode gets these from `mkdocs.yml`) and additionally accepts `use_processes` (default `True`) and `workers` (default: CPU count) to control parallelism.
|
|
165
167
|
|
|
166
168
|
| Option | Type | Default | Description |
|
|
167
169
|
| ------------------- | ---- | ------- | -------------------------------- |
|
|
@@ -177,6 +179,7 @@ Both modes support the same configuration options:
|
|
|
177
179
|
| `add_json_ld` | bool | `False` | Add JSON-LD structured data |
|
|
178
180
|
| `add_css` | bool | `True` | Include inline CSS styles |
|
|
179
181
|
| `add_copy_llm` | bool | `True` | Add "Copy for LLM" button |
|
|
182
|
+
| `add_llms_txt` | bool | `True` | Generate an `llms.txt` index |
|
|
180
183
|
|
|
181
184
|
## 🧩 How It Works
|
|
182
185
|
|
|
@@ -236,7 +239,7 @@ Both plugin mode and postprocess mode use the same `process_html()` function in
|
|
|
236
239
|
|
|
237
240
|
Collaboration fuels innovation! 🤗 The success of Ultralytics' open-source projects thrives on community contributions. We welcome your involvement, whether it's fixing bugs, proposing new features, improving documentation, engaging in discussions, or sharing how you use Ultralytics tools.
|
|
238
241
|
|
|
239
|
-
Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing
|
|
242
|
+
Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributing) for details. Filling out our [Survey](https://www.ultralytics.com/survey?utm_source=github&utm_medium=social&utm_campaign=Survey) also provides valuable feedback. We sincerely appreciate 🙇♂️ every contribution!
|
|
240
243
|
|
|
241
244
|
[](https://github.com/ultralytics/ultralytics/graphs/contributors)
|
|
242
245
|
|
|
@@ -244,7 +247,7 @@ Please see our [Contributing Guide](https://docs.ultralytics.com/help/contributi
|
|
|
244
247
|
|
|
245
248
|
Ultralytics provides two licensing options:
|
|
246
249
|
|
|
247
|
-
- **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-
|
|
250
|
+
- **AGPL-3.0 License**: Ideal for students, researchers, and enthusiasts, this [OSI-approved](https://opensource.org/license/agpl-3.0) license promotes open collaboration and knowledge sharing. See the [LICENSE](https://github.com/ultralytics/mkdocs/blob/main/LICENSE) file for details.
|
|
248
251
|
- **Enterprise License**: Designed for commercial applications, this license allows seamless integration of Ultralytics software into commercial products, bypassing AGPL-3.0 requirements. Visit [Ultralytics Licensing](https://www.ultralytics.com/license) for details.
|
|
249
252
|
|
|
250
253
|
## ✉️ Connect with Us
|
|
@@ -259,7 +262,7 @@ Encountered a bug or have an idea? Visit [GitHub Issues](https://github.com/ultr
|
|
|
259
262
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
260
263
|
<a href="https://twitter.com/ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-twitter.png" width="3%" alt="Ultralytics Twitter"></a>
|
|
261
264
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
262
|
-
<a href="https://youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
|
|
265
|
+
<a href="https://www.youtube.com/ultralytics?sub_confirmation=1"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-youtube.png" width="3%" alt="Ultralytics YouTube"></a>
|
|
263
266
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
264
267
|
<a href="https://www.tiktok.com/@ultralytics"><img src="https://github.com/ultralytics/assets/raw/main/social/logo-social-tiktok.png" width="3%" alt="Ultralytics TikTok"></a>
|
|
265
268
|
<img src="https://github.com/ultralytics/assets/raw/main/social/logo-transparent.png" width="3%" alt="space">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
2
|
+
"""MkDocs plugin entrypoint for Ultralytics documentation metadata."""
|
|
2
3
|
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
|
|
@@ -7,8 +8,9 @@ from pathlib import Path
|
|
|
7
8
|
from mkdocs.config import config_options
|
|
8
9
|
from mkdocs.plugins import BasePlugin
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
from plugin import processor
|
|
11
12
|
from plugin.processor import process_html
|
|
13
|
+
from plugin.utils import resolve_all_authors
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class MetaPlugin(BasePlugin):
|
|
@@ -27,9 +29,11 @@ class MetaPlugin(BasePlugin):
|
|
|
27
29
|
("add_json_ld", config_options.Type(bool, default=False)),
|
|
28
30
|
("add_css", config_options.Type(bool, default=True)),
|
|
29
31
|
("add_copy_llm", config_options.Type(bool, default=True)),
|
|
32
|
+
("add_llms_txt", config_options.Type(bool, default=True)),
|
|
30
33
|
)
|
|
31
34
|
|
|
32
35
|
def __init__(self):
|
|
36
|
+
"""Initialize cached repository metadata for page processing."""
|
|
33
37
|
super().__init__()
|
|
34
38
|
self.git_repo_url = None
|
|
35
39
|
self.git_data = None
|
|
@@ -43,6 +47,12 @@ class MetaPlugin(BasePlugin):
|
|
|
43
47
|
docs_dir = Path(config["docs_dir"])
|
|
44
48
|
md_files = [str(p) for p in docs_dir.rglob("*.md")] if docs_dir.exists() else []
|
|
45
49
|
self.git_repo_url, self.git_data = processor.build_git_map(md_files)
|
|
50
|
+
self.git_data = resolve_all_authors(
|
|
51
|
+
self.git_data,
|
|
52
|
+
default_author=self.config.get("default_author"),
|
|
53
|
+
repo_url=self.git_repo_url,
|
|
54
|
+
verbose=self.config.get("verbose", True),
|
|
55
|
+
)
|
|
46
56
|
return config
|
|
47
57
|
|
|
48
58
|
def on_post_page(self, output: str, page, config) -> str:
|
|
@@ -57,7 +67,8 @@ class MetaPlugin(BasePlugin):
|
|
|
57
67
|
)
|
|
58
68
|
|
|
59
69
|
try:
|
|
60
|
-
|
|
70
|
+
# MkDocs normalizes site_url to end in "/" and gives the home page url "./"
|
|
71
|
+
page_url = f"{(config['site_url'] or '').rstrip('/')}/{page.url}".rstrip("/.") or "/"
|
|
61
72
|
title = page.title
|
|
62
73
|
keywords = page.meta.get("keywords", None) if hasattr(page, "meta") else None
|
|
63
74
|
|
|
@@ -69,7 +80,6 @@ class MetaPlugin(BasePlugin):
|
|
|
69
80
|
git_data=self.git_data,
|
|
70
81
|
repo_url=self.git_repo_url,
|
|
71
82
|
default_image=self.config["default_image"],
|
|
72
|
-
default_author=self.config["default_author"],
|
|
73
83
|
keywords=keywords,
|
|
74
84
|
add_desc=self.config["add_desc"],
|
|
75
85
|
add_image=self.config["add_image"],
|
|
@@ -84,3 +94,18 @@ class MetaPlugin(BasePlugin):
|
|
|
84
94
|
if self.config["verbose"]:
|
|
85
95
|
print(f"ERROR - mkdocs-ultralytics-plugin: Failed to process {page.file.src_path}: {e}")
|
|
86
96
|
return output # Return original output on error
|
|
97
|
+
|
|
98
|
+
def on_post_build(self, config):
|
|
99
|
+
"""Generate llms.txt after build completes. Added for mkdocs build compatibility. Not needed for zensical build."""
|
|
100
|
+
if not self.config.get("enabled", True) or not self.config.get("add_llms_txt", True):
|
|
101
|
+
return
|
|
102
|
+
from plugin.postprocess import generate_llms_txt
|
|
103
|
+
|
|
104
|
+
generate_llms_txt(
|
|
105
|
+
site_dir=Path(config["site_dir"]),
|
|
106
|
+
docs_dir=Path(config["docs_dir"]),
|
|
107
|
+
site_url=config.get("site_url", ""),
|
|
108
|
+
site_name=config.get("site_name"),
|
|
109
|
+
site_description=config.get("site_description"),
|
|
110
|
+
nav=config.get("nav"),
|
|
111
|
+
)
|
|
@@ -14,7 +14,7 @@ try:
|
|
|
14
14
|
except ImportError:
|
|
15
15
|
TQDM = None
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
from plugin import processor
|
|
18
18
|
from plugin.processor import process_html
|
|
19
19
|
from plugin.utils import resolve_all_authors
|
|
20
20
|
|
|
@@ -88,7 +88,7 @@ def process_html_file(
|
|
|
88
88
|
|
|
89
89
|
# Get page URL - calculate relative path from site_dir
|
|
90
90
|
rel_path = html_path.relative_to(site_dir).as_posix()
|
|
91
|
-
page_url = f"{site_url.rstrip('/')}/{rel_path}".replace("/index.html", "/"
|
|
91
|
+
page_url = f"{site_url.rstrip('/')}/{rel_path}".replace("/index.html", "") or "/"
|
|
92
92
|
|
|
93
93
|
# Get title
|
|
94
94
|
title = soup.find("h1").text if soup.find("h1") else soup.title.string if soup.title else ""
|
|
@@ -134,6 +134,120 @@ def process_html_file(
|
|
|
134
134
|
return False
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
def generate_llms_txt(
|
|
138
|
+
site_dir: Path,
|
|
139
|
+
docs_dir: Path,
|
|
140
|
+
site_url: str,
|
|
141
|
+
site_name: str | None = None,
|
|
142
|
+
site_description: str | None = None,
|
|
143
|
+
nav: list | None = None,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Generate llms.txt file for LLM consumption."""
|
|
146
|
+
import yaml
|
|
147
|
+
|
|
148
|
+
# Fallback to reading mkdocs.yml if config values not provided (standalone postprocess mode)
|
|
149
|
+
if site_name is None or nav is None:
|
|
150
|
+
|
|
151
|
+
class _Loader(yaml.SafeLoader):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
_Loader.add_multi_constructor("", lambda loader, suffix, node: None)
|
|
155
|
+
|
|
156
|
+
mkdocs_yml = site_dir.parent / "mkdocs.yml"
|
|
157
|
+
if mkdocs_yml.exists():
|
|
158
|
+
config = yaml.load(mkdocs_yml.read_text(), Loader=_Loader) or {}
|
|
159
|
+
site_name = site_name or config.get("site_name", "Documentation")
|
|
160
|
+
site_description = site_description or config.get("site_description", "")
|
|
161
|
+
nav = nav or config.get("nav")
|
|
162
|
+
site_name = site_name or "Documentation"
|
|
163
|
+
site_description = site_description or ""
|
|
164
|
+
|
|
165
|
+
lines = [f"# {site_name}", f"> {site_description}"]
|
|
166
|
+
seen_urls: set[str] = set()
|
|
167
|
+
site_url = site_url.rstrip("/")
|
|
168
|
+
|
|
169
|
+
def get_description(md_path: Path) -> str:
|
|
170
|
+
"""Extract description from markdown frontmatter."""
|
|
171
|
+
try:
|
|
172
|
+
content = md_path.read_text()
|
|
173
|
+
if content.startswith("---"):
|
|
174
|
+
end = content.find("\n---\n", 3)
|
|
175
|
+
if end != -1:
|
|
176
|
+
fm = yaml.safe_load(content[4:end]) or {}
|
|
177
|
+
return fm.get("description", "")
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
return ""
|
|
181
|
+
|
|
182
|
+
def md_to_url(md_path: str) -> str:
|
|
183
|
+
"""Convert markdown path to HTML URL."""
|
|
184
|
+
url = md_path[:-3] if md_path.endswith(".md") else md_path
|
|
185
|
+
url = url[:-6] if url.endswith("/index") else url
|
|
186
|
+
return (site_url or "/") if url == "index" else f"{site_url}/{url}"
|
|
187
|
+
|
|
188
|
+
if nav:
|
|
189
|
+
|
|
190
|
+
def process_items(items, indent=0):
|
|
191
|
+
"""Recursively process nav items with indentation (Vercel-style)."""
|
|
192
|
+
prefix = " " * indent + "- "
|
|
193
|
+
for item in items:
|
|
194
|
+
if isinstance(item, str):
|
|
195
|
+
md = docs_dir / item
|
|
196
|
+
if md.exists():
|
|
197
|
+
url = md_to_url(item)
|
|
198
|
+
if url in seen_urls:
|
|
199
|
+
continue
|
|
200
|
+
seen_urls.add(url)
|
|
201
|
+
desc = get_description(md)
|
|
202
|
+
# Use parent dir name for index.md, else filename
|
|
203
|
+
title = md.parent.name if md.stem == "index" else md.stem
|
|
204
|
+
title = title.replace("-", " ").replace("_", " ").title()
|
|
205
|
+
desc_part = f": {desc}" if desc else ""
|
|
206
|
+
lines.append(f"{prefix}[{title}]({url}){desc_part}")
|
|
207
|
+
elif isinstance(item, dict):
|
|
208
|
+
for k, v in item.items():
|
|
209
|
+
if isinstance(v, str):
|
|
210
|
+
md = docs_dir / v
|
|
211
|
+
if md.exists():
|
|
212
|
+
url = md_to_url(v)
|
|
213
|
+
if url in seen_urls:
|
|
214
|
+
continue
|
|
215
|
+
seen_urls.add(url)
|
|
216
|
+
desc = get_description(md)
|
|
217
|
+
desc_part = f": {desc}" if desc else ""
|
|
218
|
+
lines.append(f"{prefix}[{k}]({url}){desc_part}")
|
|
219
|
+
elif isinstance(v, list):
|
|
220
|
+
# Nested section - plain text header, then recurse
|
|
221
|
+
lines.append(f"{prefix}{k}")
|
|
222
|
+
process_items(v, indent + 1)
|
|
223
|
+
|
|
224
|
+
# Top-level nav items become ## sections
|
|
225
|
+
for item in nav:
|
|
226
|
+
if isinstance(item, str):
|
|
227
|
+
process_items([item], indent=0)
|
|
228
|
+
elif isinstance(item, dict):
|
|
229
|
+
for k, v in item.items():
|
|
230
|
+
if isinstance(v, list):
|
|
231
|
+
lines.extend(["", f"## {k}"])
|
|
232
|
+
process_items(v, indent=0)
|
|
233
|
+
else:
|
|
234
|
+
process_items([{k: v}], indent=0)
|
|
235
|
+
else:
|
|
236
|
+
for md in sorted(docs_dir.rglob("*.md")):
|
|
237
|
+
rel = md.relative_to(docs_dir).as_posix()
|
|
238
|
+
url = md_to_url(rel)
|
|
239
|
+
if url in seen_urls:
|
|
240
|
+
continue
|
|
241
|
+
seen_urls.add(url)
|
|
242
|
+
desc = get_description(md)
|
|
243
|
+
title = md.stem.replace("-", " ").replace("_", " ").title()
|
|
244
|
+
desc_part = f": {desc}" if desc else ""
|
|
245
|
+
lines.append(f"- [{title}]({url}){desc_part}")
|
|
246
|
+
|
|
247
|
+
(site_dir / "llms.txt").write_text("\n".join(lines))
|
|
248
|
+
print("Generated llms.txt")
|
|
249
|
+
|
|
250
|
+
|
|
137
251
|
def postprocess_site(
|
|
138
252
|
site_dir: str | Path = "site",
|
|
139
253
|
docs_dir: str | Path = "docs",
|
|
@@ -148,6 +262,7 @@ def postprocess_site(
|
|
|
148
262
|
add_json_ld: bool = False,
|
|
149
263
|
add_css: bool = True,
|
|
150
264
|
add_copy_llm: bool = True,
|
|
265
|
+
add_llms_txt: bool = True,
|
|
151
266
|
verbose: bool = True,
|
|
152
267
|
use_processes: bool = True,
|
|
153
268
|
workers: int | None = None,
|
|
@@ -190,23 +305,23 @@ def postprocess_site(
|
|
|
190
305
|
# Enable logging only for the synchronous path; pools run without per-task log_fn to remain pickle-safe.
|
|
191
306
|
log_fn = (progress.write if verbose and progress else print if verbose else None) if worker_count == 1 else None
|
|
192
307
|
|
|
193
|
-
task_kwargs =
|
|
194
|
-
site_dir
|
|
195
|
-
md_index
|
|
196
|
-
git_data
|
|
197
|
-
repo_url
|
|
198
|
-
site_url
|
|
199
|
-
default_image
|
|
200
|
-
add_desc
|
|
201
|
-
add_image
|
|
202
|
-
add_keywords
|
|
203
|
-
add_share_buttons
|
|
204
|
-
add_authors
|
|
205
|
-
add_json_ld
|
|
206
|
-
add_css
|
|
207
|
-
add_copy_llm
|
|
208
|
-
verbose
|
|
209
|
-
|
|
308
|
+
task_kwargs = {
|
|
309
|
+
"site_dir": site_dir,
|
|
310
|
+
"md_index": md_index,
|
|
311
|
+
"git_data": git_data,
|
|
312
|
+
"repo_url": repo_url,
|
|
313
|
+
"site_url": site_url,
|
|
314
|
+
"default_image": default_image,
|
|
315
|
+
"add_desc": add_desc,
|
|
316
|
+
"add_image": add_image,
|
|
317
|
+
"add_keywords": add_keywords,
|
|
318
|
+
"add_share_buttons": add_share_buttons,
|
|
319
|
+
"add_authors": add_authors,
|
|
320
|
+
"add_json_ld": add_json_ld,
|
|
321
|
+
"add_css": add_css,
|
|
322
|
+
"add_copy_llm": add_copy_llm,
|
|
323
|
+
"verbose": verbose,
|
|
324
|
+
}
|
|
210
325
|
|
|
211
326
|
if worker_count == 1:
|
|
212
327
|
for html_file in html_files:
|
|
@@ -250,6 +365,9 @@ def postprocess_site(
|
|
|
250
365
|
|
|
251
366
|
print(f"✅ Postprocessing complete: {processed}/{len(html_files)} files processed")
|
|
252
367
|
|
|
368
|
+
if add_llms_txt:
|
|
369
|
+
generate_llms_txt(site_dir, docs_dir, site_url)
|
|
370
|
+
|
|
253
371
|
|
|
254
372
|
if __name__ == "__main__":
|
|
255
373
|
postprocess_site()
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
8
|
import subprocess
|
|
9
|
-
from datetime import datetime, timedelta
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any
|
|
12
12
|
from urllib.parse import quote
|
|
@@ -15,7 +15,7 @@ from bs4 import BeautifulSoup
|
|
|
15
15
|
|
|
16
16
|
from plugin.utils import calculate_time_difference, get_youtube_video_ids
|
|
17
17
|
|
|
18
|
-
today = datetime.now()
|
|
18
|
+
today = datetime.now(timezone.utc)
|
|
19
19
|
DEFAULT_CREATION_DATE = (today - timedelta(days=365)).strftime("%Y-%m-%d %H:%M:%S +0000")
|
|
20
20
|
DEFAULT_MODIFIED_DATE = (today - timedelta(days=40)).strftime("%Y-%m-%d %H:%M:%S +0000")
|
|
21
21
|
|
|
@@ -136,7 +136,7 @@ def build_git_map(file_paths: list[str] | list[Path]) -> tuple[str | None, dict[
|
|
|
136
136
|
str(repo_root),
|
|
137
137
|
"log",
|
|
138
138
|
"--name-only",
|
|
139
|
-
"--pretty=format:%ad\t%ae",
|
|
139
|
+
"--pretty=format:%H\t%ad\t%ae",
|
|
140
140
|
"--date=format:%Y-%m-%d %H:%M:%S %z",
|
|
141
141
|
"--",
|
|
142
142
|
*[str(p) for p in rel_paths],
|
|
@@ -147,17 +147,18 @@ def build_git_map(file_paths: list[str] | list[Path]) -> tuple[str | None, dict[
|
|
|
147
147
|
except subprocess.CalledProcessError:
|
|
148
148
|
return repo_url, git_data
|
|
149
149
|
|
|
150
|
+
current_commit = None
|
|
150
151
|
current_date = None
|
|
151
152
|
current_email = None
|
|
152
153
|
for line in output:
|
|
153
154
|
if not line.strip():
|
|
154
155
|
continue
|
|
155
156
|
parts = line.split("\t")
|
|
156
|
-
if len(parts) ==
|
|
157
|
-
current_date, current_email = parts
|
|
157
|
+
if len(parts) == 3:
|
|
158
|
+
current_commit, current_date, current_email = parts
|
|
158
159
|
continue
|
|
159
160
|
|
|
160
|
-
if current_date and current_email:
|
|
161
|
+
if current_commit and current_date and current_email:
|
|
161
162
|
abs_path = (repo_root / line.strip()).resolve()
|
|
162
163
|
key = str(abs_path)
|
|
163
164
|
entry = git_data.setdefault(
|
|
@@ -166,11 +167,13 @@ def build_git_map(file_paths: list[str] | list[Path]) -> tuple[str | None, dict[
|
|
|
166
167
|
"creation_date": current_date,
|
|
167
168
|
"last_modified_date": current_date,
|
|
168
169
|
"emails": {},
|
|
170
|
+
"commits": {},
|
|
169
171
|
},
|
|
170
172
|
)
|
|
171
173
|
entry.setdefault("last_modified_date", current_date)
|
|
172
174
|
entry["creation_date"] = current_date
|
|
173
175
|
entry["emails"][current_email] = entry["emails"].get(current_email, 0) + 1
|
|
176
|
+
entry["commits"].setdefault(current_email, current_commit)
|
|
174
177
|
|
|
175
178
|
return repo_url, git_data
|
|
176
179
|
|
|
@@ -436,21 +439,22 @@ def process_html(
|
|
|
436
439
|
let rawUrl = editBtn.href.replace('github.com', 'raw.githubusercontent.com');
|
|
437
440
|
rawUrl = rawUrl.replace('/blob/', '/').replace('/tree/', '/');
|
|
438
441
|
|
|
439
|
-
|
|
442
|
+
async function getContent() {{
|
|
440
443
|
const response = await fetch(rawUrl);
|
|
441
444
|
let markdown = await response.text();
|
|
442
|
-
|
|
443
445
|
if (markdown.startsWith('---')) {{
|
|
444
446
|
const frontMatterEnd = markdown.indexOf('\\n---\\n', 3);
|
|
445
|
-
if (frontMatterEnd !== -1)
|
|
446
|
-
markdown = markdown.substring(frontMatterEnd + 5).trim();
|
|
447
|
-
}}
|
|
447
|
+
if (frontMatterEnd !== -1) markdown = markdown.substring(frontMatterEnd + 5).trim();
|
|
448
448
|
}}
|
|
449
|
-
|
|
450
449
|
const title = document.querySelector('h1')?.textContent || document.title;
|
|
451
|
-
|
|
450
|
+
return `# ${{title}}\\n\\nSource: ${{window.location.href}}\\n\\n---\\n\\n${{markdown}}`;
|
|
451
|
+
}}
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
try {{
|
|
454
|
+
const clipboardItem = new ClipboardItem({{
|
|
455
|
+
'text/plain': getContent().then(text => new Blob([text], {{ type: 'text/plain' }}))
|
|
456
|
+
}});
|
|
457
|
+
await navigator.clipboard.write([clipboardItem]);
|
|
454
458
|
button.innerHTML = checkIcon + ' Copied!';
|
|
455
459
|
setTimeout(() => {{ button.innerHTML = originalHTML; }}, 2000);
|
|
456
460
|
}} catch (err) {{
|
|
@@ -525,7 +529,7 @@ def process_html(
|
|
|
525
529
|
{
|
|
526
530
|
"@type": "Organization",
|
|
527
531
|
"name": "Ultralytics",
|
|
528
|
-
"url": "https://ultralytics.com
|
|
532
|
+
"url": "https://ultralytics.com",
|
|
529
533
|
}
|
|
530
534
|
],
|
|
531
535
|
"abstract": meta.get("description", ""),
|
|
@@ -6,6 +6,7 @@ import re
|
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
9
10
|
|
|
10
11
|
import requests
|
|
11
12
|
import yaml
|
|
@@ -97,14 +98,31 @@ def save_author_cache(cache: dict[str, dict[str, str | None]]) -> None:
|
|
|
97
98
|
print(f"{WARNING} Failed to save author cache: {e}")
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
def _github_repo_path(repo_url: str | None) -> str | None:
|
|
102
|
+
"""Return the owner/repo path for a GitHub repository URL."""
|
|
103
|
+
if not repo_url:
|
|
104
|
+
return None
|
|
105
|
+
parsed = urlparse(repo_url)
|
|
106
|
+
if parsed.hostname != "github.com":
|
|
107
|
+
return None
|
|
108
|
+
path = parsed.path.strip("/")
|
|
109
|
+
return path[:-4] if path.endswith(".git") else path or None
|
|
110
|
+
|
|
111
|
+
|
|
100
112
|
def resolve_github_user(
|
|
101
|
-
email: str,
|
|
113
|
+
email: str,
|
|
114
|
+
cache: dict[str, dict[str, str | None]],
|
|
115
|
+
repo_url: str | None = None,
|
|
116
|
+
commit_sha: str | None = None,
|
|
117
|
+
verbose: bool = True,
|
|
102
118
|
) -> dict[str, str | None]:
|
|
103
119
|
"""Resolve a single email to GitHub username and avatar, updating cache in-place.
|
|
104
120
|
|
|
105
121
|
Args:
|
|
106
122
|
email (str): The email address to resolve.
|
|
107
123
|
cache (dict): The author cache dict (modified in-place if new entry added).
|
|
124
|
+
repo_url (str, optional): GitHub repository URL used for commit API fallback.
|
|
125
|
+
commit_sha (str, optional): Commit SHA authored by the email.
|
|
108
126
|
verbose (bool): Whether to print API call info.
|
|
109
127
|
|
|
110
128
|
Returns:
|
|
@@ -113,8 +131,8 @@ def resolve_github_user(
|
|
|
113
131
|
if not email or not email.strip():
|
|
114
132
|
return {"username": None, "avatar": None}
|
|
115
133
|
|
|
116
|
-
# Return cached
|
|
117
|
-
if email in cache:
|
|
134
|
+
# Return complete cached results immediately. Incomplete cached entries may be refreshed from commit metadata.
|
|
135
|
+
if email in cache and cache[email].get("username") and cache[email].get("avatar"):
|
|
118
136
|
return cache[email]
|
|
119
137
|
|
|
120
138
|
# Parse username directly from GitHub noreply emails
|
|
@@ -127,6 +145,20 @@ def resolve_github_user(
|
|
|
127
145
|
cache[email] = {"username": username, "avatar": avatar}
|
|
128
146
|
return cache[email]
|
|
129
147
|
|
|
148
|
+
# Query the commit API when git history provides a commit for this email. This resolves authors whose commit email
|
|
149
|
+
# is linked to a GitHub account but hidden from user search.
|
|
150
|
+
if (repo_path := _github_repo_path(repo_url)) and commit_sha:
|
|
151
|
+
try:
|
|
152
|
+
response = requests.get(f"https://api.github.com/repos/{repo_path}/commits/{commit_sha}", timeout=TIMEOUT)
|
|
153
|
+
if response.status_code == 200:
|
|
154
|
+
data = response.json()
|
|
155
|
+
author = data.get("author") or {}
|
|
156
|
+
if author.get("login") and author.get("avatar_url"):
|
|
157
|
+
cache[email] = {"username": author["login"], "avatar": author["avatar_url"]}
|
|
158
|
+
return cache[email]
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
|
|
130
162
|
# Query GitHub REST API
|
|
131
163
|
if verbose:
|
|
132
164
|
print(f"Running GitHub REST API for author {email}")
|
|
@@ -173,10 +205,13 @@ def resolve_all_authors(
|
|
|
173
205
|
if not git_data:
|
|
174
206
|
return git_data
|
|
175
207
|
|
|
176
|
-
# Collect all unique emails across all files
|
|
208
|
+
# Collect all unique emails across all files, with one representative commit SHA per email.
|
|
177
209
|
all_emails: set[str] = set()
|
|
210
|
+
commits: dict[str, str] = {}
|
|
178
211
|
for entry in git_data.values():
|
|
179
212
|
all_emails.update(entry.get("emails", {}).keys())
|
|
213
|
+
for email, commit in entry.get("commits", {}).items():
|
|
214
|
+
commits.setdefault(email, commit)
|
|
180
215
|
if default_author:
|
|
181
216
|
all_emails.add(default_author)
|
|
182
217
|
all_emails.discard("")
|
|
@@ -189,8 +224,9 @@ def resolve_all_authors(
|
|
|
189
224
|
cache_modified = False
|
|
190
225
|
|
|
191
226
|
for email in sorted(all_emails):
|
|
192
|
-
|
|
193
|
-
|
|
227
|
+
cached = cache.get(email, {})
|
|
228
|
+
if email not in cache or (commits.get(email) and not (cached.get("username") and cached.get("avatar"))):
|
|
229
|
+
resolve_github_user(email, cache, repo_url=repo_url, commit_sha=commits.get(email), verbose=verbose)
|
|
194
230
|
cache_modified = True
|
|
195
231
|
|
|
196
232
|
if cache_modified:
|
|
@@ -199,7 +235,7 @@ def resolve_all_authors(
|
|
|
199
235
|
# Build authors list for each file entry
|
|
200
236
|
github_repo_url = repo_url or "https://github.com/ultralytics/ultralytics"
|
|
201
237
|
|
|
202
|
-
for
|
|
238
|
+
for entry in git_data.values():
|
|
203
239
|
emails = entry.get("emails", {})
|
|
204
240
|
if not emails and default_author:
|
|
205
241
|
emails = {default_author: 1}
|
|
@@ -50,6 +50,7 @@ classifiers = [
|
|
|
50
50
|
"Programming Language :: Python :: 3.12",
|
|
51
51
|
"Programming Language :: Python :: 3.13",
|
|
52
52
|
"Programming Language :: Python :: 3.14",
|
|
53
|
+
"Programming Language :: Python :: 3.15",
|
|
53
54
|
"Topic :: Software Development",
|
|
54
55
|
"Topic :: Scientific/Engineering",
|
|
55
56
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
@@ -77,8 +78,5 @@ ultralytics = "plugin:MetaPlugin" # executes the function `MetaPlugin` from thi
|
|
|
77
78
|
[project.optional-dependencies] # Optional
|
|
78
79
|
dev = ["pytest"]
|
|
79
80
|
|
|
80
|
-
[tool.setuptools] # This is configuration specific to the `setuptools` build backend.
|
|
81
|
-
package-data = { "sample" = ["*.yaml"] }
|
|
82
|
-
|
|
83
81
|
[tool.setuptools.dynamic]
|
|
84
82
|
version = {attr = "plugin.__version__"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|