Skip to content

Commit a313d44

Browse files
authored
Add an automated job to create a list of plugins in the docs. (#222)
1 parent 2ba75a4 commit a313d44

File tree

8 files changed

+486
-172
lines changed

8 files changed

+486
-172
lines changed
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Update Plugin List
2+
3+
on:
4+
schedule:
5+
# At 18:00 on Monday. https://crontab.guru
6+
- cron: '0 18 * * MON'
7+
workflow_dispatch:
8+
9+
# Set permissions at the job level.
10+
permissions: {}
11+
12+
jobs:
13+
createPullRequest:
14+
if: github.repository_owner == 'pytask-dev'
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: write
18+
pull-requests: write
19+
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v2
23+
with:
24+
fetch-depth: 0
25+
26+
- name: Setup Python
27+
uses: actions/setup-python@v2
28+
with:
29+
python-version: 3.8
30+
31+
- name: Install dependencies
32+
run: |
33+
python -m pip install --upgrade pip
34+
pip install packaging requests tabulate[widechars] tqdm
35+
36+
- name: Update Plugin List
37+
run: python scripts/update_plugin_list.py
38+
39+
- name: Create Pull Request
40+
uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6
41+
with:
42+
commit-message: '[automated] Update plugin list'
43+
author: 'Tobias Raabe <[email protected]>'
44+
branch: update-plugin-list/patch
45+
delete-branch: true
46+
branch-suffix: short-commit-hash
47+
title: '[automated] Update plugin list'
48+
body: '[automated] Update plugin list'

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ exclude *.yml
99
exclude tox.ini
1010

1111
prune docs
12+
prune scripts
1213
prune tests

docs/source/changes.rst

+176-169
Large diffs are not rendered by default.

docs/source/conf.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@
7373
copybutton_prompt_text = r"\$ |>>> |In \[\d\]: "
7474
copybutton_prompt_is_regexp = True
7575

76+
_repo = "https://github.com/pytest-dev/pytest"
7677
extlinks = {
77-
"ghuser": ("https://github.com/%s", "@"),
78-
"gh": ("https://github.com/pytask-dev/pytask/pull/%s", "#"),
78+
"pypi": ("https://pypi.org/project/%s/", ""),
79+
"issue": (f"{_repo}/issues/%s", "issue #"),
80+
"pull": (f"{_repo}/pull/%s", "pull request #"),
81+
"user": ("https://github.com/%s", "@"),
7982
}
8083

8184
intersphinx_mapping = {

docs/source/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Furthermore, the documentation includes the following topics.
8585
.. toctree::
8686
:maxdepth: 1
8787

88+
plugin_list
8889
api
8990
developers_guide
9091
glossary

docs/source/plugin_list.rst

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
.. _plugin-list:
3+
4+
Plugin List
5+
===========
6+
7+
PyPI projects that match "pytask-\*" are considered plugins and are listed
8+
automatically. Packages classified as inactive are excluded.
9+
10+
.. The following conditional uses a different format for this list when
11+
creating a PDF, because otherwise the table gets far too wide for the
12+
page.
13+
14+
This list contains 6 plugins.
15+
16+
.. only:: not latex
17+
18+
========================== ==================================================================== ============== ========= ================
19+
name summary last release status requires
20+
========================== ==================================================================== ============== ========= ================
21+
:pypi:`pytask-environment` Detect changes in your pytask environment and abort a project build. Feb 08, 2022 3 - Alpha pytask (>=0.1.7)
22+
:pypi:`pytask-julia` A Pytask plugin for Julia Jan 19, 2022 3 - Alpha pytask (>=0.1)
23+
:pypi:`pytask-latex` Compile LaTeX documents with pytask. Feb 08, 2022 3 - Alpha pytask (>=0.1.7)
24+
:pypi:`pytask-parallel` Parallelize the execution of tasks with pytask. Feb 07, 2022 3 - Alpha pytask (>=0.1.7)
25+
:pypi:`pytask-r` Run R scripts with pytask. Feb 07, 2022 3 - Alpha pytask (>=0.1.7)
26+
:pypi:`pytask-stata` Execute do-files with Stata and pytask. Feb 07, 2022 3 - Alpha pytask (>=0.1.7)
27+
========================== ==================================================================== ============== ========= ================
28+
29+
.. only:: latex
30+
31+
32+
:pypi:`pytask-environment`
33+
*last release*: Feb 08, 2022,
34+
*status*: 3 - Alpha,
35+
*requires*: pytask (>=0.1.7)
36+
37+
Detect changes in your pytask environment and abort a project build.
38+
39+
:pypi:`pytask-julia`
40+
*last release*: Jan 19, 2022,
41+
*status*: 3 - Alpha,
42+
*requires*: pytask (>=0.1)
43+
44+
A Pytask plugin for Julia
45+
46+
:pypi:`pytask-latex`
47+
*last release*: Feb 08, 2022,
48+
*status*: 3 - Alpha,
49+
*requires*: pytask (>=0.1.7)
50+
51+
Compile LaTeX documents with pytask.
52+
53+
:pypi:`pytask-parallel`
54+
*last release*: Feb 07, 2022,
55+
*status*: 3 - Alpha,
56+
*requires*: pytask (>=0.1.7)
57+
58+
Parallelize the execution of tasks with pytask.
59+
60+
:pypi:`pytask-r`
61+
*last release*: Feb 07, 2022,
62+
*status*: 3 - Alpha,
63+
*requires*: pytask (>=0.1.7)
64+
65+
Run R scripts with pytask.
66+
67+
:pypi:`pytask-stata`
68+
*last release*: Feb 07, 2022,
69+
*status*: 3 - Alpha,
70+
*requires*: pytask (>=0.1.7)
71+
72+
Execute do-files with Stata and pytask.

docs/source/tutorials/how_to_use_plugins.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ Where to find plugins
1414

1515
Plugins can be found in many places.
1616

17+
- All plugins should appear in this :doc:`automatically updated list <../plugin_list>`
18+
which is created by scanning packages on PyPI.
19+
1720
- Check out the repositories in the `pytask-dev <https://github.com/pytask-dev>`_ Github
18-
organization which includes a collection of officially supported plugins.
21+
organization for a collection of officially supported plugins.
1922

2023
- Check out the `pytask Github topic <https://github.com/topics/pytask>`_ which shows an
2124
overview of repositories linked to pytask.

scripts/update_plugin_list.py

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"""This script creates a list of plugins for pytask.
2+
3+
It is shamelessly stolen from pytest and therefore includes its license.
4+
5+
https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py
6+
7+
8+
MIT License
9+
10+
Copyright (c) 2004 Holger Krekel and others
11+
12+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
13+
software and associated documentation files (the "Software"), to deal in the Software
14+
without restriction, including without limitation the rights to use, copy, modify,
15+
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
16+
permit persons to whom the Software is furnished to do so, subject to the following
17+
conditions:
18+
19+
The above copyright notice and this permission notice shall be included in all copies or
20+
substantial portions of the Software.
21+
22+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
23+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
24+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
26+
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
OTHER DEALINGS IN THE SOFTWARE.
28+
29+
"""
30+
from __future__ import annotations
31+
32+
import datetime
33+
import pathlib
34+
import re
35+
from textwrap import dedent
36+
from textwrap import indent
37+
38+
import packaging.version
39+
import requests
40+
import tabulate
41+
import wcwidth
42+
from tqdm import tqdm
43+
44+
45+
_FILE_HEAD = r"""
46+
.. _plugin-list:
47+
48+
Plugin List
49+
===========
50+
51+
PyPI projects that match "pytask-\*" are considered plugins and are listed
52+
automatically. Packages classified as inactive are excluded.
53+
54+
.. The following conditional uses a different format for this list when
55+
creating a PDF, because otherwise the table gets far too wide for the
56+
page.
57+
58+
"""
59+
60+
61+
_DEVELOPMENT_STATUS_CLASSIFIERS = (
62+
"Development Status :: 1 - Planning",
63+
"Development Status :: 2 - Pre-Alpha",
64+
"Development Status :: 3 - Alpha",
65+
"Development Status :: 4 - Beta",
66+
"Development Status :: 5 - Production/Stable",
67+
"Development Status :: 6 - Mature",
68+
"Development Status :: 7 - Inactive",
69+
)
70+
71+
72+
_EXCLUDED_PACKAGES = ["pytask-io"]
73+
74+
75+
def _escape_rst(text: str) -> str:
76+
"""Rudimentary attempt to escape special RST characters to appear as plain text."""
77+
text = (
78+
text.replace("*", "\\*")
79+
.replace("<", "\\<")
80+
.replace(">", "\\>")
81+
.replace("`", "\\`")
82+
)
83+
text = re.sub(r"_\b", "", text)
84+
return text
85+
86+
87+
def _iter_plugins():
88+
"""Iterate over all plugins and format entries."""
89+
regex = r">([\d\w-]*)</a>"
90+
response = requests.get("https://pypi.org/simple")
91+
92+
matches = [
93+
match
94+
for match in re.finditer(regex, response.text)
95+
if match.groups()[0].startswith("pytask-")
96+
and match.groups()[0] not in _EXCLUDED_PACKAGES
97+
]
98+
99+
for match in tqdm(matches, smoothing=0):
100+
name = match.groups()[0]
101+
response = requests.get(f"https://pypi.org/pypi/{name}/json")
102+
response.raise_for_status()
103+
info = response.json()["info"]
104+
105+
if "Development Status :: 7 - Inactive" in info["classifiers"]:
106+
continue
107+
for classifier in _DEVELOPMENT_STATUS_CLASSIFIERS:
108+
if classifier in info["classifiers"]:
109+
status = classifier[22:]
110+
break
111+
else:
112+
status = "N/A"
113+
requires = "N/A"
114+
115+
if info["requires_dist"]:
116+
for requirement in info["requires_dist"]:
117+
if requirement == "pytask" or "pytask " in requirement:
118+
requires = requirement
119+
break
120+
releases = response.json()["releases"]
121+
122+
for release in sorted(releases, key=packaging.version.parse, reverse=True):
123+
if releases[release]:
124+
release_date = datetime.date.fromisoformat(
125+
releases[release][-1]["upload_time_iso_8601"].split("T")[0]
126+
)
127+
last_release = release_date.strftime("%b %d, %Y")
128+
break
129+
130+
name = f':pypi:`{info["name"]}`'
131+
summary = _escape_rst(info["summary"].replace("\n", ""))
132+
133+
yield {
134+
"name": name,
135+
"summary": summary.strip(),
136+
"last release": last_release,
137+
"status": status,
138+
"requires": requires,
139+
}
140+
141+
142+
def _plugin_definitions(plugins):
143+
"""Return RST for the plugin list that fits better on a vertical page."""
144+
145+
for plugin in plugins:
146+
yield dedent(
147+
f"""
148+
{plugin['name']}
149+
*last release*: {plugin["last release"]},
150+
*status*: {plugin["status"]},
151+
*requires*: {plugin["requires"]}
152+
153+
{plugin["summary"]}
154+
"""
155+
)
156+
157+
158+
def main():
159+
plugins = list(_iter_plugins())
160+
161+
reference_dir = pathlib.Path("docs", "source")
162+
163+
plugin_list = reference_dir / "plugin_list.rst"
164+
with plugin_list.open("w") as f:
165+
f.write(_FILE_HEAD)
166+
f.write(f"This list contains {len(plugins)} plugins.\n\n")
167+
f.write(".. only:: not latex\n\n")
168+
169+
wcwidth # reference library that must exist for tabulate to work
170+
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
171+
f.write(indent(plugin_table, " "))
172+
f.write("\n\n")
173+
174+
f.write(".. only:: latex\n\n")
175+
f.write(indent("".join(_plugin_definitions(plugins)), " "))
176+
177+
178+
if __name__ == "__main__":
179+
main()

0 commit comments

Comments
 (0)