Skip to content

Commit 205364c

Browse files
committed
Publish the documentation on GitHub Pages
doc/, README.md, UPGRADE-2.0.md and CHANGELOG.md become a site, built by Zensical with its theme: a light and a dark mode, a search, the fonts of the system, and no request to Google Fonts. Zensical reads the pages from a single directory, which can't be the root of the repository, and runs no hooks: tools/zensical/pages.py copies them to var/docs first, and rewrites the links between the files of the root and doc/, which stay relative paths of the repository, as GitHub shows them. The Documentation workflow builds the site, strictly, on the pull requests changing it, and deploys it to GitHub Pages from main. Dependabot updates its Python dependencies. zensical.toml stays out of the archive Composer installs.
1 parent 9a49754 commit 205364c

8 files changed

Lines changed: 269 additions & 0 deletions

File tree

‎.gitattributes‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
/test-git-versions.php export-ignore
1414
/tests export-ignore
1515
/tools export-ignore
16+
/zensical.toml export-ignore

‎.github/dependabot.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ updates:
1111
- /tools/phpstan
1212
schedule:
1313
interval: monthly
14+
15+
- package-ecosystem: pip
16+
directory: /tools/zensical
17+
schedule:
18+
interval: monthly
19+
groups:
20+
zensical:
21+
patterns: ['*']

‎.github/workflows/docs.yml‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Documentation
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
paths:
8+
- .github/workflows/docs.yml
9+
- CHANGELOG.md
10+
- README.md
11+
- UPGRADE-2.0.md
12+
- doc/**
13+
- tools/zensical/**
14+
- zensical.toml
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
20+
concurrency:
21+
group: ${{ github.workflow }}-${{ github.ref }}
22+
# Let a deployment finish: the next one waits.
23+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
24+
25+
jobs:
26+
build:
27+
name: Build the Documentation
28+
runs-on: ubuntu-24.04
29+
steps:
30+
- name: Checkout Code
31+
uses: actions/checkout@v7
32+
with:
33+
# The site is published: keep the token out of the workspace.
34+
persist-credentials: false
35+
36+
- name: Setup Python
37+
uses: actions/setup-python@v7
38+
with:
39+
python-version: '3.14'
40+
cache: pip
41+
cache-dependency-path: tools/zensical/requirements.txt
42+
43+
- name: Install Zensical
44+
run: pip install -r tools/zensical/requirements.txt
45+
46+
- name: Write the pages
47+
run: python tools/zensical/pages.py
48+
49+
# Strict, as configured in zensical.toml: a broken link or anchor fails.
50+
- name: Build the site
51+
run: zensical build --clean
52+
53+
- name: Upload the site
54+
if: github.ref == 'refs/heads/main'
55+
uses: actions/upload-pages-artifact@v5
56+
with:
57+
path: site
58+
59+
deploy:
60+
name: Deploy to GitHub Pages
61+
if: github.ref == 'refs/heads/main'
62+
needs: build
63+
runs-on: ubuntu-24.04
64+
permissions:
65+
pages: write
66+
id-token: write
67+
environment:
68+
name: github-pages
69+
url: ${{ steps.deployment.outputs.page_url }}
70+
steps:
71+
- name: Setup Pages
72+
uses: actions/configure-pages@v6
73+
74+
- name: Deploy to GitHub Pages
75+
id: deployment
76+
uses: actions/deploy-pages@v5

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/.phpunit.cache
44
/composer.lock
55
/phpunit.xml
6+
/site/
67
/var/
78
/vendor
89
/git-builds/

‎tools/zensical/assets/favicon.svg‎

Lines changed: 1 addition & 0 deletions
Loading

‎tools/zensical/pages.py‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
"""Write the pages of the documentation site, as GitHub shows them, to the docs_dir of zensical.toml.
3+
4+
The site is doc/, with README.md, UPGRADE-2.0.md and CHANGELOG.md, which live at the root of the
5+
repository. Zensical reads its pages from a single directory, which can't be the root, and runs
6+
no hooks: this script copies them there, with the assets of the site. The links between the
7+
pages are relative paths of the repository, like "doc/blob.md" or "../UPGRADE-2.0.md#errors":
8+
they are rewritten to the pages of the site.
9+
10+
Usage, from the root of the repository: python3 tools/zensical/pages.py && zensical build
11+
"""
12+
13+
import posixpath
14+
import re
15+
import shutil
16+
import sys
17+
from pathlib import Path
18+
19+
REPOSITORY_DIR = Path(__file__).resolve().parents[2]
20+
# The docs_dir of zensical.toml: /var/ is ignored by git.
21+
OUTPUT_DIR = REPOSITORY_DIR / 'var/docs'
22+
ASSETS_DIR = Path(__file__).resolve().parent / 'assets'
23+
24+
# The files of the root of the repository published as pages, and their page on the site.
25+
ROOT_PAGES = {
26+
'CHANGELOG.md': 'changelog.md',
27+
'README.md': 'index.md',
28+
'UPGRADE-2.0.md': 'upgrade-2.0.md',
29+
}
30+
31+
# A relative link to a Markdown file: "](doc/blob.md)", "](../UPGRADE-2.0.md#errors)"...
32+
MARKDOWN_LINK = re.compile(r'\]\((?P<path>(?![a-z]+:)[^)\s#]+\.md)(?P<fragment>#[^)\s]*)?\)')
33+
34+
35+
def main() -> None:
36+
if OUTPUT_DIR.exists():
37+
shutil.rmtree(OUTPUT_DIR)
38+
shutil.copytree(REPOSITORY_DIR / 'doc', OUTPUT_DIR)
39+
shutil.copytree(ASSETS_DIR, OUTPUT_DIR / 'assets')
40+
41+
pages = dict(ROOT_PAGES)
42+
for file in sorted((REPOSITORY_DIR / 'doc').rglob('*.md')):
43+
path = file.relative_to(REPOSITORY_DIR).as_posix()
44+
pages[path] = path.removeprefix('doc/')
45+
46+
for path, site_path in pages.items():
47+
markdown = (REPOSITORY_DIR / path).read_text(encoding='utf-8')
48+
(OUTPUT_DIR / site_path).write_text(rewrite_links(markdown, path, site_path), encoding='utf-8')
49+
50+
print(f'{len(pages)} pages written to {OUTPUT_DIR.relative_to(REPOSITORY_DIR)}/.', file=sys.stderr)
51+
52+
53+
def rewrite_links(markdown: str, path: str, site_path: str) -> str:
54+
"""Rewrite the links of the file path of the repository, published as site_path."""
55+
56+
def rewrite(match: re.Match[str]) -> str:
57+
target = posixpath.normpath(posixpath.join(posixpath.dirname(path), match['path']))
58+
if target in ROOT_PAGES:
59+
target_site_path = ROOT_PAGES[target]
60+
elif target.startswith('doc/'):
61+
target_site_path = target.removeprefix('doc/')
62+
else:
63+
# Not a page: the strict build reports the link.
64+
return match[0]
65+
66+
link = posixpath.relpath(target_site_path, posixpath.dirname(site_path) or '.')
67+
68+
return '](' + link + (match['fragment'] or '') + ')'
69+
70+
return MARKDOWN_LINK.sub(rewrite, markdown)
71+
72+
73+
if __name__ == '__main__':
74+
main()

‎tools/zensical/requirements.txt‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# The documentation site (zensical.toml), with Python 3.14: `pip install zensical` in a new
2+
# virtualenv, then `pip freeze`. Dependabot updates them.
3+
click==8.5.0
4+
deepmerge==3.0.1
5+
jinja2==3.1.6
6+
markdown==3.11.1
7+
markupsafe==3.0.4
8+
pathspec==1.1.1
9+
pygments==2.21.0
10+
pymdown-extensions==12.2
11+
pyyaml==6.0.3
12+
tomli==2.5.0
13+
zensical==0.0.70

‎zensical.toml‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# The documentation site, published on GitHub Pages by .github/workflows/docs.yml. Its pages are
2+
# doc/, README.md, UPGRADE-2.0.md and CHANGELOG.md, which tools/zensical/pages.py copies to
3+
# docs_dir first. With the Python packages of tools/zensical/requirements.txt:
4+
#
5+
# python3 tools/zensical/pages.py && zensical serve
6+
7+
[project]
8+
site_name = "gitlib"
9+
site_url = "https://gitonomy.github.io/gitlib/"
10+
site_description = "Access git repositories from PHP, through the git binary or libgit2."
11+
copyright = "gitlib is released under the <a href=\"https://github.com/gitonomy/gitlib/blob/main/LICENSE\">MIT license</a>."
12+
repo_url = "https://github.com/gitonomy/gitlib"
13+
repo_name = "gitonomy/gitlib"
14+
docs_dir = "var/docs"
15+
site_dir = "site"
16+
strict = true
17+
18+
nav = [
19+
{ "Getting started" = "index.md" },
20+
{ "Repository and drivers" = [
21+
{ "Opening a repository" = "repository.md" },
22+
{ "Creating repositories" = "admin.md" },
23+
] },
24+
{ "Objects" = [
25+
{ "Revisions" = "revision.md" },
26+
{ "Commits" = "commit.md" },
27+
{ "Log" = "log.md" },
28+
{ "Trees" = "tree.md" },
29+
{ "Blobs" = "blob.md" },
30+
{ "Diffs" = "diff.md" },
31+
{ "Blame" = "blame.md" },
32+
{ "Tags and branches" = "references.md" },
33+
{ "Branches" = "branch.md" },
34+
] },
35+
{ "Working copy" = "workingcopy.md" },
36+
{ "Hooks" = "hooks.md" },
37+
{ "Upgrade and changelog" = [
38+
{ "Upgrading to 2.0" = "upgrade-2.0.md" },
39+
{ "Changelog" = "changelog.md" },
40+
] },
41+
]
42+
43+
[project.theme]
44+
language = "en"
45+
favicon = "assets/favicon.svg"
46+
# The fonts of the system: no request to Google Fonts.
47+
font = false
48+
features = [
49+
"content.code.copy",
50+
"navigation.footer",
51+
"navigation.instant",
52+
"navigation.path",
53+
"navigation.sections",
54+
"navigation.top",
55+
"navigation.tracking",
56+
"search.highlight",
57+
"toc.follow",
58+
]
59+
60+
[project.theme.icon]
61+
logo = "lucide/git-branch"
62+
repo = "fontawesome/brands/github"
63+
64+
[[project.theme.palette]]
65+
media = "(prefers-color-scheme)"
66+
toggle.icon = "lucide/sun-moon"
67+
toggle.name = "Switch to light mode"
68+
69+
[[project.theme.palette]]
70+
media = "(prefers-color-scheme: light)"
71+
scheme = "default"
72+
primary = "teal"
73+
accent = "teal"
74+
toggle.icon = "lucide/sun"
75+
toggle.name = "Switch to dark mode"
76+
77+
[[project.theme.palette]]
78+
media = "(prefers-color-scheme: dark)"
79+
scheme = "slate"
80+
primary = "teal"
81+
accent = "teal"
82+
toggle.icon = "lucide/moon"
83+
toggle.name = "Switch to system preference"
84+
85+
[project.markdown_extensions]
86+
admonition = {}
87+
attr_list = {}
88+
toc.permalink = true
89+
# The examples start without "<?php".
90+
pymdownx.highlight.extend_pygments_lang = [
91+
{ name = "php", lang = "php", options = { startinline = true } },
92+
]
93+
pymdownx.inlinehilite = {}
94+
# Fenced code blocks in list items, which UPGRADE-2.0.md has.
95+
pymdownx.superfences = {}

0 commit comments

Comments
 (0)