Skip to content

Commit ba64220

Browse files
authored
Add support to generate documentation using mkdocs (frequenz-floss#64)
This includes both some tools to generate the docs that can be imported as a Python module, and update the cookiecutter templates to make use of them and to test, build and publish the documentation in the CI. - ci: Generate and publish the documentation - Unify `protobuf` generation configuration - Generate documentation for `proto` files - Generate documentation using `mkdocs` - Add `mkdocs` utility to generate API docs pages - Change copyright to use the `author_name` - Clarify why we don't check `py` with isort Fixes frequenz-floss#12.
2 parents 0496ab4 + 81733bf commit ba64220

File tree

25 files changed

+773
-98
lines changed

25 files changed

+773
-98
lines changed

cookiecutter/cookiecutter.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"local_extensions.keywords",
2929
"local_extensions.pypi_package_name",
3030
"local_extensions.python_package",
31+
"local_extensions.src_path",
3132
"local_extensions.title"
3233
]
3334
}

cookiecutter/hooks/post_gen_project.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ def main() -> None:
6666
print(". .venv/bin/activate")
6767
print("pip install .[dev-noxfile]")
6868
print("nox")
69+
print("# To generate and serve the documentation:")
70+
print("pip install .[dev-mkdocs]")
71+
if cookiecutter.type == "api":
72+
print("# Requires docker")
73+
print("mkdocs serve")
6974
print()
7075
if warnings := do_sanity_checks():
7176
for warning in warnings:

cookiecutter/local_extensions.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,25 @@ def title(cookiecutter: dict[str, str]) -> str:
107107
assert False, f"Unhandled repository type {repo_type!r}"
108108

109109

110+
@_simple_filter # type: ignore[misc]
111+
def src_path(cookiecutter: dict[str, str]) -> str:
112+
"""Build a default source path for the project.
113+
114+
Args:
115+
cookiecutter: The cookiecutter context.
116+
117+
Returns:
118+
The default source path.
119+
"""
120+
match cookiecutter["type"]:
121+
case "api":
122+
return "py"
123+
case "actor" | "lib" | "app" | "model":
124+
return "src"
125+
case _ as repo_type:
126+
assert False, f"Unhandled repository type {repo_type!r}"
127+
128+
110129
@_simple_filter # type: ignore[misc]
111130
def keywords(cookiecutter: dict[str, str]) -> str:
112131
"""Extend cookiecutter["keywords"] with predefined ones by repository type.

cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci.yaml

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,123 @@ jobs:
8383
path: dist/
8484
if-no-files-found: error
8585

86+
test-docs:
87+
name: Test documentation website generation
88+
if: github.event_name != 'push'
89+
runs-on: ubuntu-{{'${{ env.DEFAULT_UBUNTU_VERSION }}'}}
90+
steps:
91+
- name: Fetch sources
92+
uses: actions/checkout@v3
93+
94+
- name: Setup Git user and e-mail
95+
uses: frequenz-floss/setup-git-user@v2
96+
97+
- name: Set up Python
98+
uses: actions/setup-python@v4
99+
with:
100+
python-version: {{'${{ env.DEFAULT_PYTHON_VERSION }}'}}
101+
cache: 'pip'
102+
103+
- name: Install build dependencies
104+
run: |
105+
python -m pip install -U pip
106+
python -m pip install .[dev-mkdocs]
107+
108+
- name: Generate the documentation
109+
env:
110+
MIKE_VERSION: gh-{{'${{ github.job }}'}}
111+
run: |
112+
mike deploy $MIKE_VERSION
113+
mike set-default $MIKE_VERSION
114+
115+
- name: Upload site
116+
uses: actions/upload-artifact@v3
117+
with:
118+
name: docs-site
119+
path: site/
120+
if-no-files-found: error
121+
122+
publish-docs:
123+
name: Publish documentation website to GitHub pages
124+
needs: ["test", "build"]
125+
if: github.event_name == 'push'
126+
runs-on: ubuntu-{{'${{ env.DEFAULT_UBUNTU_VERSION }}'}}
127+
permissions:
128+
contents: write
129+
steps:
130+
- name: Calculate and check version
131+
id: mike-metadata
132+
env:
133+
REF: {{'${{ github.ref }}'}}
134+
REF_NAME: {{'${{ github.ref_name }}'}}
135+
DEFAULT_BRANCH: {{'${{ github.event.repository.default_branch }}'}}
136+
run: |
137+
aliases=
138+
version=
139+
if test "$REF_NAME" = "$DEFAULT_BRANCH"
140+
then
141+
version=next
142+
# A tag that starts with vX.Y or X.Y
143+
elif echo "$REF" | grep -q '^refs/tags' && echo "$REF_NAME" | grep -Pq '^v?\d+\.\d+\.'
144+
then
145+
if echo "$REF_NAME" | grep -Pq -- "-" # pre-release
146+
then
147+
echo "::notice title=Documentation was not published::" \
148+
"The tag '$REF_NAME' looks like a pre-release."
149+
exit 0
150+
fi
151+
version=$(echo "$REF_NAME" | sed -r 's/^(v?[0-9]+\.[0-9]+)\..*$/\1/') # vX.Y
152+
major=$(echo "$REF_NAME" | sed -r 's/^(v?[0-9]+)\..*$/\1/') # vX
153+
default_major=$(echo "$DEFAULT_BRANCH" | sed -r 's/^(v?[0-9]+)\..*$/\1/') # vX
154+
aliases=$major
155+
if test "$major" = "$default_major"
156+
then
157+
aliases="$aliases latest"
158+
fi
159+
else
160+
echo "::warning title=Documentation was not published::" \
161+
"Don't know how to handle '$REF' to make 'mike' version."
162+
exit 0
163+
fi
164+
echo "version=$version" >> $GITHUB_OUTPUT
165+
echo "aliases=$aliases" >> $GITHUB_OUTPUT
166+
167+
- name: Fetch sources
168+
if: steps.mike-metadata.outputs.version
169+
uses: actions/checkout@v3
170+
171+
- name: Setup Git user and e-mail
172+
if: steps.mike-metadata.outputs.version
173+
uses: frequenz-floss/setup-git-user@v2
174+
175+
- name: Set up Python
176+
if: steps.mike-metadata.outputs.version
177+
uses: actions/setup-python@v4
178+
with:
179+
python-version: {{'${{ env.DEFAULT_PYTHON_VERSION }}'}}
180+
cache: 'pip'
181+
182+
- name: Install build dependencies
183+
if: steps.mike-metadata.outputs.version
184+
run: |
185+
python -m pip install -U pip
186+
python -m pip install .[dev-mkdocs]
187+
188+
- name: Fetch the gh-pages branch
189+
if: steps.mike-metadata.outputs.version
190+
run: git fetch origin gh-pages --depth=1
191+
192+
- name: Publish site
193+
if: steps.mike-metadata.outputs.version
194+
env:
195+
VERSION: {{'${{ steps.mike-metadata.outputs.version }}'}}
196+
ALIASES: {{'${{ steps.mike-metadata.outputs.aliases }}'}}
197+
run: |
198+
mike deploy --push --update-aliases "$VERSION" $ALIASES
199+
86200
create-github-release:
87201
name: Create GitHub release
88-
needs: ["build"]
202+
needs: ["publish-docs"]
89203
# Create a release only on tags creation
90204
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
91205
permissions:

cookiecutter/{{cookiecutter.github_repo_name}}/CONTRIBUTING.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,61 @@ nox -R -s pylint -- test/test_*.py
110110
nox -R -s mypy -- test/test_*.py
111111
```
112112

113+
### Building the documentation
114+
115+
To build the documentation, first install the dependencies (if you didn't
116+
install all `dev` dependencies):
117+
118+
```sh
119+
python -m pip install -e .[dev-mkdocs]
120+
```
121+
122+
Then you can build the documentation (it will be written in the `site/`
123+
directory):
124+
125+
```sh
126+
mkdocs build
127+
```
128+
129+
Or you can just serve the documentation without building it using:
130+
131+
```sh
132+
mkdocs serve
133+
```
134+
135+
Your site will be updated **live** when you change your files (provided that
136+
you used `pip install -e .`, beware of a common pitfall of using `pip install`
137+
without `-e`, in that case the API reference won't change unless you do a new
138+
`pip install`).
139+
140+
To build multi-version documentation, we use
141+
[mike](https://github.com/jimporter/mike). If you want to see how the
142+
multi-version sites looks like locally, you can use:
143+
144+
```sh
145+
mike deploy my-version
146+
mike set-default my-version
147+
mike serve
148+
```
149+
150+
`mike` works in mysterious ways. Some basic information:
151+
152+
* `mike deploy` will do a `mike build` and write the results to your **local**
153+
`gh-pages` branch. `my-version` is an arbitrary name for the local version
154+
you want to preview.
155+
* `mike set-default` is needed so when you serve the documentation, it goes to
156+
your newly produced documentation by default.
157+
* `mike serve` will serve the contents of your **local** `gh-pages` branch. Be
158+
aware that, unlike `mkdocs serve`, changes to the sources won't be shown
159+
live, as the `mike deploy` step is needed to refresh them.
160+
161+
Be careful not to use `--push` with `mike deploy`, otherwise it will push your
162+
local `gh-pages` branch to the `origin` remote.
163+
164+
That said, if you want to test the actual website in **your fork**, you can
165+
always use `mike deploy --push --remote your-fork-remote`, and then access the
166+
GitHub pages produced for your fork.
167+
113168
## Releasing
114169

115170
These are the steps to create a new release:

cookiecutter/{{cookiecutter.github_repo_name}}/LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 Frequenz Energy-as-a-Service GmbH
3+
Copyright © {% now 'utc', '%Y' %} {{cookiecutter.author_name}}
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--8<-- "CONTRIBUTING.md"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
* [Home](index.md)
2+
{%- if cookiecutter.type == "api" %}
3+
* [Protobuf API Reference](protobuf-reference/)
4+
* [Python API Reference](python-reference/)
5+
{%- else %}
6+
* [API Reference](reference/)
7+
{%- endif %}
8+
* [Contributing](CONTRIBUTING.md)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* Recommended style from:
2+
* https://mkdocstrings.github.io/python/customization/#recommended-style-material
3+
* With some additions from:
4+
* https://github.com/mkdocstrings/mkdocstrings/blob/master/docs/css/mkdocstrings.css
5+
*/
6+
7+
/* Indentation. */
8+
div.doc-contents:not(.first) {
9+
padding-left: 25px;
10+
border-left: .05rem solid var(--md-typeset-table-color);
11+
}
12+
13+
/* Indentation. */
14+
div.doc-contents:not(.first) {
15+
padding-left: 25px;
16+
border-left: 4px solid rgba(230, 230, 230);
17+
margin-bottom: 80px;
18+
}
19+
20+
/* Avoid breaking parameters name, etc. in table cells. */
21+
td code {
22+
word-break: normal !important;
23+
}
24+
25+
/* Mark external links as such. */
26+
a.autorefs-external::after {
27+
/* https://primer.style/octicons/arrow-up-right-24 */
28+
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="rgb(0, 0, 0)" d="M18.25 15.5a.75.75 0 00.75-.75v-9a.75.75 0 00-.75-.75h-9a.75.75 0 000 1.5h7.19L6.22 16.72a.75.75 0 101.06 1.06L17.5 7.56v7.19c0 .414.336.75.75.75z"></path></svg>');
29+
content: ' ';
30+
31+
display: inline-block;
32+
position: relative;
33+
top: 0.1em;
34+
margin-left: 0.2em;
35+
margin-right: 0.1em;
36+
37+
height: 1em;
38+
width: 1em;
39+
border-radius: 100%;
40+
background-color: var(--md-typeset-a-color);
41+
}
42+
a.autorefs-external:hover::after {
43+
background-color: var(--md-accent-fg-color);
44+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* Based on:
2+
* https://github.com/mkdocstrings/mkdocstrings/blob/master/docs/css/style.css
3+
*/
4+
5+
/* Increase logo size */
6+
.md-header__button.md-logo {
7+
padding-bottom: 0.2rem;
8+
padding-right: 0;
9+
}
10+
.md-header__button.md-logo img {
11+
height: 1.5rem;
12+
}
13+
14+
/* Mark external links as such (also in nav) */
15+
a.external:hover::after, a.md-nav__link[href^="https:"]:hover::after {
16+
/* https://primer.style/octicons/link-external-16 */
17+
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="rgb(233, 235, 252)" d="M10.604 1h4.146a.25.25 0 01.25.25v4.146a.25.25 0 01-.427.177L13.03 4.03 9.28 7.78a.75.75 0 01-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0110.604 1zM3.75 2A1.75 1.75 0 002 3.75v8.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 12.25v-3.5a.75.75 0 00-1.5 0v3.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25h3.5a.75.75 0 000-1.5h-3.5z"></path></svg>');
18+
height: 0.8em;
19+
width: 0.8em;
20+
margin-left: 0.2em;
21+
content: ' ';
22+
display: inline-block;
23+
}
24+
25+
/* More space at the bottom of the page */
26+
.md-main__inner {
27+
margin-bottom: 1.5rem;
28+
}

0 commit comments

Comments
 (0)