Skip to content

Commit ec6c880

Browse files
pawamoyoprypin
andcommitted
Migrate Gallery to Catalog
Co-authored-by: Oleh Prypin <[email protected]>
1 parent 7f75c2d commit ec6c880

File tree

12 files changed

+527
-0
lines changed

12 files changed

+527
-0
lines changed

.github/workflows/gallery.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Gallery
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Install Python
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: '3.x'
19+
20+
- uses: actions/cache@v3
21+
name: Cache/restore pip data
22+
with:
23+
path: ~/.cache/pip
24+
key: pip
25+
26+
- name: Install Python dependencies
27+
run: pip install -r requirements.txt
28+
29+
- name: Cache/restore Playwright browsers
30+
uses: actions/cache@v3
31+
with:
32+
path: ~/.cache/ms-playwright/
33+
key: playwright
34+
35+
- name: Install Playwright dependencies
36+
run: shot-scraper install
37+
38+
- name: Build everything
39+
run: python build_gallery.py
40+
41+
- name: Upload artifact
42+
uses: actions/upload-pages-artifact@v2
43+
with:
44+
path: gallery
45+
46+
deploy:
47+
if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch
48+
needs: build
49+
concurrency:
50+
group: "pages"
51+
permissions:
52+
pages: write
53+
id-token: write
54+
runs-on: ubuntu-latest
55+
steps:
56+
- name: Deploy to GitHub Pages
57+
id: deployment
58+
uses: actions/deploy-pages@v2
59+
environment:
60+
name: github-pages
61+
url: ${{ steps.deployment.outputs.page_url }}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# Specific to this project
2+
/logs/
3+
/themes/
4+
/gallery/
5+
/docs/
6+
/mkdocs.yml
7+
18
# IntelliJ
29
target/
310
.idea/

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.PHONY: build-gallery
2+
build-gallery:
3+
python -m venv .venv
4+
.venv/bin/pip install -r requirements.txt
5+
.venv/bin/playwright install
6+
.venv/bin/python build_gallery.py
7+
8+
.PHONY: serve-gallery
9+
serve-gallery:
10+
cd gallery; python -m http.server

build_gallery.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import argparse
2+
import os
3+
import shutil
4+
import subprocess
5+
import sys
6+
import venv
7+
from dataclasses import dataclass
8+
from multiprocessing import Pool
9+
from pathlib import Path
10+
11+
import mkdocs_get_deps
12+
import yaml
13+
from jinja2 import Environment
14+
from shot_scraper.cli import cli as shot_scraper
15+
from tqdm import tqdm
16+
17+
18+
@dataclass
19+
class Theme:
20+
name: str
21+
mkdocs_id: str
22+
url: str = ""
23+
pypi_id: str = ""
24+
builtin: bool = False
25+
26+
27+
_builtin_themes = [
28+
Theme(name="MkDocs", mkdocs_id="mkdocs", builtin=True),
29+
Theme(name="ReadTheDocs", mkdocs_id="readthedocs", builtin=True),
30+
]
31+
32+
33+
# Fetch themes from MkDocs catalog.
34+
def get_themes() -> list[Theme]:
35+
with open("projects.yaml") as file:
36+
catalog = yaml.safe_load(file)
37+
projects = catalog["projects"]
38+
theming_category = [project for project in projects if project["category"] == "theming"]
39+
themes = []
40+
for project in theming_category:
41+
if mkdocs_theme := project.get("mkdocs_theme"):
42+
if "github_id" in project:
43+
url = f"https://github.com/{project['github_id']}"
44+
elif "gitlab_id" in project:
45+
url = f"https://gitlab.com/{project['gitlab_id']}"
46+
else:
47+
url = ""
48+
pypi_id = project.get("pypi_id", f"git+{url}")
49+
if isinstance(mkdocs_theme, str):
50+
themes.append(Theme(name=project["name"], url=url, pypi_id=pypi_id, mkdocs_id=mkdocs_theme))
51+
else:
52+
for theme in mkdocs_theme:
53+
themes.append(
54+
Theme(name=f"{project['name']} - {theme.title()}", url=url, pypi_id=pypi_id, mkdocs_id=theme)
55+
)
56+
return _builtin_themes + sorted(themes, key=lambda theme: theme.name.lower())
57+
58+
59+
# Copy files and expand Jinja templates.
60+
def _prepare_site(src_dir: Path, dest_dir: Path, themes: list[Theme], theme: Theme | None = None) -> None:
61+
jinja = Environment(autoescape=False)
62+
dest_dir.mkdir(parents=True, exist_ok=True)
63+
64+
for src_path in src_dir.rglob("*"):
65+
if not src_path.is_file():
66+
continue
67+
dest_path = dest_dir.joinpath(src_path.relative_to(src_dir))
68+
dest_path.parent.mkdir(parents=True, exist_ok=True)
69+
if src_path.suffix in (".md", ".yml"):
70+
content = src_path.read_text()
71+
content = jinja.from_string(content).render(themes=themes, theme=theme)
72+
dest_path.write_text(content)
73+
else:
74+
shutil.copyfile(src_path, dest_path)
75+
76+
77+
# Prepare each theme (docs directory and configuration file).
78+
def prepare_themes(themes: list[Theme]) -> None:
79+
specimen_dir = Path("templates", "specimen")
80+
for theme in themes:
81+
# Copy specific directory, or default to specimen.
82+
theme_dir = Path("themes", theme.mkdocs_id)
83+
theme_conf_dir = Path("templates", "themes", theme.mkdocs_id)
84+
if not theme_conf_dir.exists():
85+
theme_conf_dir = specimen_dir
86+
shutil.copytree(theme_conf_dir, theme_dir, dirs_exist_ok=True)
87+
88+
_prepare_site(specimen_dir, theme_dir, themes, theme=theme)
89+
90+
91+
# Prepare the main documentation site.
92+
def prepare_main(themes: list[Theme]) -> None:
93+
_prepare_site(Path("templates", "main"), Path("."), themes)
94+
95+
96+
# Create virtualenvs and install dependencies.
97+
def install_deps(theme: Theme) -> None:
98+
theme_dir = Path("themes", theme.mkdocs_id)
99+
venv_dir = theme_dir / ".venv"
100+
if not venv_dir.exists():
101+
venv.create(venv_dir, with_pip=True)
102+
deps = mkdocs_get_deps.get_deps(config_file=theme_dir / "mkdocs.yml")
103+
subprocess.run(
104+
[venv_dir / "bin" / "pip", "install", *deps],
105+
check=False,
106+
stdout=subprocess.DEVNULL,
107+
stderr=subprocess.DEVNULL,
108+
)
109+
110+
111+
# Build theme sites.
112+
def build_themes(themes: list[Theme]) -> None:
113+
parser = argparse.ArgumentParser(prog="build_gallery.py")
114+
parser.add_argument(
115+
"-D",
116+
"--no-deps",
117+
dest="install_deps",
118+
action="store_false",
119+
default=True,
120+
help="Don't install Python dependencies.",
121+
)
122+
parser.add_argument(
123+
"-T",
124+
"--no-themes",
125+
dest="build_themes",
126+
action="store_false",
127+
default=True,
128+
help="Don't rebuild each theme site.",
129+
)
130+
parser.add_argument(
131+
"-S",
132+
"--no-shots",
133+
dest="take_screenshots",
134+
action="store_false",
135+
default=True,
136+
help="Don't take screenshots of each theme.",
137+
)
138+
139+
opts = parser.parse_args()
140+
141+
if not opts.install_deps:
142+
print("Skipping dependencies installation")
143+
else:
144+
print("Preparing environments")
145+
146+
with Pool(len(os.sched_getaffinity(0))) as pool:
147+
tuple(tqdm(pool.imap(install_deps, themes), total=len(themes)))
148+
149+
if not opts.build_themes:
150+
print("Skipping themes building")
151+
else:
152+
logs_dir = Path("logs")
153+
logs_dir.mkdir(exist_ok=True)
154+
shutil.rmtree(Path("gallery", "themes"), ignore_errors=True)
155+
Path("gallery", "themes").mkdir(parents=True)
156+
157+
def _build_theme(theme: Theme) -> None:
158+
theme_dir = Path("themes", theme.mkdocs_id).absolute()
159+
dest_dir = Path("gallery", "themes", theme.mkdocs_id).absolute()
160+
print(f"Building {theme.name}")
161+
with logs_dir.joinpath(f"{theme.mkdocs_id}.txt").open("w") as logs_file:
162+
try:
163+
subprocess.run(
164+
[theme_dir.joinpath(".venv", "bin", "mkdocs"), "build", "-d", dest_dir],
165+
stdout=logs_file,
166+
stderr=logs_file,
167+
check=True,
168+
text=True,
169+
cwd=theme_dir,
170+
)
171+
except subprocess.CalledProcessError:
172+
print("FAILED!")
173+
174+
for theme in themes:
175+
_build_theme(theme)
176+
177+
if not opts.take_screenshots:
178+
print("Skipping screenshots")
179+
else:
180+
print("Taking screenshots")
181+
Path("docs", "assets", "img").mkdir(parents=True, exist_ok=True)
182+
for theme in tqdm(themes):
183+
try:
184+
shot_scraper(
185+
[f"gallery/themes/{theme.mkdocs_id}/index.html", "-o", f"docs/assets/img/{theme.mkdocs_id}.png"]
186+
)
187+
except SystemExit:
188+
pass
189+
except Exception as error:
190+
print(error)
191+
192+
193+
# Build main documentation site.
194+
def build_main() -> None:
195+
print("Building gallery's main site")
196+
subprocess.run([sys.executable, "-mmkdocs", "build", "--dirty"], check=True)
197+
198+
199+
# Run everything.
200+
def main() -> None:
201+
themes = get_themes()
202+
prepare_themes(themes)
203+
prepare_main(themes)
204+
build_themes(themes)
205+
build_main()
206+
207+
208+
if __name__ == "__main__":
209+
main()

requirements.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
black
2+
jinja2
3+
mkdocs-get-deps
4+
mkdocs-material
5+
pyyaml
6+
shot-scraper
7+
tqdm

templates/main/docs/assets/logo.png

726 Bytes
Loading

templates/main/docs/index.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
hide:
3+
- navigation
4+
---
5+
6+
# Welcome to our gallery of MkDocs themes!
7+
8+
<style>
9+
article img {
10+
-webkit-filter: drop-shadow(0px 16px 10px rgba(100,100,100,0.6));
11+
-moz-filter: drop-shadow(0px 16px 10px rgba(100,100,100,0.6));
12+
-ms-filter: drop-shadow(0px 16px 10px rgba(100,100,100,0.6));
13+
-o-filter: drop-shadow(0px 16px 10px rgba(100,100,100,0.6));
14+
filter: drop-shadow(0px 16px 10px rgba(100,100,100,0.6));
15+
}
16+
</style>
17+
18+
{% for builtin in [true, false] %}
19+
{% if builtin %}## Built-in themes{% else %}## Third-party themes{% endif %}
20+
21+
{% for theme in themes if theme.builtin == builtin %}
22+
### {{theme.name}}
23+
24+
[![{{theme.name}}](assets/img/{{theme.mkdocs_id}}.png)](themes/{{theme.mkdocs_id}}){ title="Click to browse!" }
25+
26+
---
27+
{% endfor %}
28+
{% endfor %}

templates/main/mkdocs.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
site_name: Gallery
2+
site_url: https://mkdocs.github.io/catalog
3+
site_dir: gallery
4+
5+
theme:
6+
name: material
7+
logo: assets/logo.png
8+
palette:
9+
- media: "(prefers-color-scheme: light)"
10+
scheme: default
11+
primary: blue
12+
toggle:
13+
icon: material/brightness-7
14+
name: Switch to dark mode
15+
- media: "(prefers-color-scheme: dark)"
16+
scheme: slate
17+
primary: blue
18+
toggle:
19+
icon: material/brightness-4
20+
name: Switch to light mode
21+
22+
markdown_extensions:
23+
- attr_list
24+
- toc:
25+
permalink: true

templates/specimen/docs/index.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Welcome to {{ theme.name }}
2+
3+
This page is built with the {% if theme.url %}[{{ theme.name }}]({{ theme.url }}){% else %}{{ theme.name }}{% endif %} theme,
4+
and demonstrates how a few Markdown extensions and MkDocs plugins
5+
will look within this theme.
6+
7+
{% if theme.pypi_id %}
8+
To install the theme:
9+
10+
```bash
11+
pip install {{ theme.pypi_id }}
12+
```
13+
{% endif %}
14+
15+
To build your docs with this theme:
16+
17+
```yaml
18+
# mkdocs.yml
19+
theme: {{ theme.mkdocs_id }}
20+
```

templates/specimen/docs/reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: calculator

0 commit comments

Comments
 (0)