Skip to content

Commit e7347ea

Browse files
gauteslandy31415
andauthored
Add Sphinx documentation framework (#24185)
* doc: add sphinx documentation framework Added the basics for a sphinx-based doc portal. Added an extension `external_content` that copies the relevant doc files that are used in the documentation. This extension also modifies files while copying to adjust links and allow syntax that renders on both GitHub and Sphinx. Warnings when building the documentation have been fixed or removed. Fixes include: - Linking errors - Typos - ToC tree errors - Adding MyST configuration data - Removing anchors on the form <a name="my-anchor"></a> as they cannot be used by sphinx - Changed the GitHub rendered emoji `:heavy_check_mark:` to the ascii symbol ✔ so that it renders with Sphinx Signed-off-by: Gaute Svanes Lunde <gaute.lunde@nordicsemi.no> * workflows: expand markdown fences in spellchecker Expand the markdown fences to include MyST for Sphinx syntax like the following: ```{include} my/file.md ``` Signed-off-by: Gaute Svanes Lunde <gaute.lunde@nordicsemi.no> * workflows: add docbuild workflow Add a workflow for building the documentation with Sphinx and deploy the generated html to the `project-chip/connectedhomeip-doc` repository to be used by github.io. This will overwrite the existing Doxygen documentation that is hosted there. Signed-off-by: Gaute Svanes Lunde <gaute.lunde@nordicsemi.no> * Restyle * doc: move link targets to build folder Created a change in the `external_content` extension so that links to content that is also being copied to the _build folder will point to their new location instead. This enables linking to sections on other pages. This change also enables link checking for sections on other pages, and therefore also includes a fix to some previously broken section links. Signed-off-by: Gaute Svanes Lunde <gaute.lunde@nordicsemi.no> Signed-off-by: Gaute Svanes Lunde <gaute.lunde@nordicsemi.no> Co-authored-by: Andrei Litvin <andy314@gmail.com>
1 parent b281d1b commit e7347ea

File tree

112 files changed

+1239
-1084
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+1239
-1084
lines changed

.github/workflows/docbuild.yaml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Documentation Build
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
build-and-deploy:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout the code
17+
uses: actions/checkout@v2
18+
with:
19+
path: matter
20+
fetch-depth: 0
21+
- name: Install Python
22+
uses: actions/setup-python@v2
23+
with:
24+
python-version: 3.8
25+
- name: cache-pip
26+
uses: actions/cache@v1
27+
with:
28+
path: ~/.cache/pip
29+
key: ${{ runner.os }}-doc-pip
30+
- name: Install base dependencies
31+
working-directory: matter
32+
run: |
33+
sudo pip3 install -U pip
34+
pip3 install -r docs/requirements.txt
35+
- name: Build documentation
36+
working-directory: matter/docs
37+
run: |
38+
mkdir -p _build/src
39+
make html
40+
touch _build/html/.nojekyll
41+
- name: Deploy to gh-pages
42+
if: github.repository == 'project-chip/connectedhomeip'
43+
uses: peaceiris/actions-gh-pages@v3
44+
with:
45+
deploy_key: ${{ secrets.DOXYGEN_DEPLOY_KEY }}
46+
external_repository: project-chip/connectedhomeip-doc
47+
publish_dir: matter/docs/_build/html
48+
# Keep only the latest version of the documentation
49+
force_orphan: true

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ __pycache__
5050
# Doxygen outputs
5151
docs/html
5252

53+
# Python venv
54+
.venv
55+
56+
# Documentation
57+
docs/_build
58+
5359
# VSCode java extensions
5460
.project
5561

.spellcheck.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ matrix:
4646
# ```python
4747
# content
4848
# ```
49-
- open: '(?s)^(?P<open> *`{3,})[a-z]*$'
49+
#
50+
# Allow MyST extended syntax like:
51+
# ```{include} my/file.md
52+
# ```
53+
- open: '(?s)^(?P<open> *`{3,})([a-z]*$|{[a-z]*?}\s*[^\n]*?$)'
5054
close: '^(?P=open)$'
5155
# Ignore text between inline back ticks
5256
- open: '(?P<open>`+)'

docs/Doxyfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,7 @@ INPUT = README.md \
830830
docs/guides/BUILDING.md \
831831
docs/VSCODE_DEVELOPMENT.md \
832832
docs/PROJECT_FLOW.md \
833-
docs/STYLE_GUIDE.md \
833+
docs/style/style_guide.md \
834834
src/ble \
835835
src/controller \
836836
src/crypto \

docs/ERROR_CODES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file was **AUTOMATICALLY** generated by
88
- [SDK Core errors: range `0x000..0x0FF`](#sdk-core-errors)
99
- [SDK Inet Layer errors: range `0x100..0x1FF`](#sdk-inet-layer-errors)
1010
- [SDK Device Layer errors: range `0x200..0x2FF`](#sdk-device-layer-errors)
11-
- [ASN.1 Layer errors: range `0x300..0x3FF`](#asn-1-layer-errors)
11+
- [ASN.1 Layer errors: range `0x300..0x3FF`](#asn.1-layer-errors)
1212
- [BLE Layer errors: range `0x400..0x4FF`](#ble-layer-errors)
1313
- [IM Global errors errors: range `0x500..0x5FF`](#im-global-errors-errors)
1414

docs/Makefile

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?= -W -c . -d _build/doctrees
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = _build/src
9+
BUILDDIR = _build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
mkdir -p "$(SOURCEDIR)"
21+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
## Style Guide
2525

2626
- Documentation about style is documented in
27-
[the style guide](./STYLE_GUIDE.md)
27+
[the style guide](./style/style_guide.md)
2828
- Additional documentation about more specific files are in the
2929
[style folder](./style/)
3030

docs/_extensions/external_content.py

+251
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
"""
2+
External content
3+
################
4+
5+
Copyright (c) 2021 Nordic Semiconductor ASA
6+
SPDX-License-Identifier: Apache-2.0
7+
8+
Introduction
9+
============
10+
11+
This extension allows to import sources from directories out of the Sphinx
12+
source directory. They are copied to the source directory before starting the
13+
build. Note that the copy is *smart*, that is, only updated files are actually
14+
copied. Therefore, incremental builds detect changes correctly and behave as
15+
expected.
16+
17+
Links to external content not included in the generated documentation are
18+
transformed to external links as needed.
19+
20+
Configuration options
21+
=====================
22+
23+
- ``external_content_contents``: A list of external contents. Each entry is
24+
a tuple with two fields: the external base directory and a file glob pattern.
25+
- ``external_content_link_prefixes``: A list of link prefixes out of scope.
26+
All links to content with these prefixes are made external.
27+
- ``external_content_link_extensions``: A list of file extensions in scope of
28+
the documentation. All links to content without these file extensions are
29+
made external.
30+
- ``external_content_keep``: A list of file globs (relative to the destination
31+
directory) that should be kept even if they do not exist in the source
32+
directory. This option can be useful for auto-generated files in the
33+
destination directory.
34+
"""
35+
36+
import filecmp
37+
import os
38+
from pathlib import Path
39+
import re
40+
import shutil
41+
import tempfile
42+
from typing import Dict, Any, List, Optional
43+
44+
from sphinx.application import Sphinx
45+
46+
__version__ = "0.1.0"
47+
48+
DIRECTIVES = ("figure", "image", "include", "literalinclude")
49+
"""Default directives for included content."""
50+
51+
EXTERNAL_LINK_URL_PREFIX = (
52+
"https://github.com/project-chip/connectedhomeip/blob/master/"
53+
)
54+
55+
56+
def adjust_includes(
57+
fname: Path,
58+
basepath: Path,
59+
encoding: str,
60+
link_prefixes: List[str],
61+
extensions: List[str],
62+
targets: List[Path],
63+
dstpath: Optional[Path] = None,
64+
) -> None:
65+
"""Adjust included content paths.
66+
67+
Args:
68+
fname: File to be processed.
69+
basepath: Base path to be used to resolve content location.
70+
encoding: Sources encoding.
71+
link_prefixes: Prefixes of links that are made external.
72+
extensions: Filename extensions links to which are not made external.
73+
targets: List of all files that are being copied.
74+
dstpath: Destination path for fname if its path is not the actual destination.
75+
"""
76+
77+
if fname.suffix != ".md":
78+
return
79+
80+
dstpath = dstpath or fname.parent
81+
82+
def _adjust_path(path):
83+
# ignore absolute paths, section links, hyperlinks and same folder
84+
if path.startswith(("/", "#", "http", "www")) or not "/" in path:
85+
return path
86+
87+
# for files that are being copied modify reference to and out of /docs
88+
filepath = path.split("#")[0]
89+
absolute = (basepath / filepath).resolve()
90+
if absolute in targets:
91+
if "docs/" in path:
92+
path = path.replace("docs/", "")
93+
elif "../examples" in path:
94+
path = path.replace("../", "", 1)
95+
return path
96+
97+
# otherwise change links to point to their targets' original location
98+
return Path(os.path.relpath(basepath / path, dstpath)).as_posix()
99+
100+
def _adjust_links(m):
101+
displayed, fpath = m.groups()
102+
fpath_adj = _adjust_path(fpath)
103+
return f"[{displayed}]({fpath_adj})"
104+
105+
def _adjust_external(m):
106+
displayed, target = m.groups()
107+
return f"[{displayed}]({EXTERNAL_LINK_URL_PREFIX}{target})"
108+
109+
def _adjust_filetype(m):
110+
displayed, target, extension = m.groups()
111+
if extension.lower() in extensions or target.startswith("http"):
112+
return m.group(0)
113+
114+
return f"[{displayed}]({EXTERNAL_LINK_URL_PREFIX}{target})"
115+
116+
def _adjust_image_link(m):
117+
prefix, fpath, postfix = m.groups()
118+
fpath_adj = _adjust_path(fpath)
119+
return f"{prefix}{fpath_adj}{postfix}"
120+
121+
rules = [
122+
# Find any links and adjust the path
123+
(r"\[([^\[\]]*)\]\s*\((.*)\)", _adjust_links),
124+
125+
# Find links that lead to an external folder and transform it
126+
# into an external link.
127+
(
128+
r"\[([^\[\]]*)\]\s*\((?:\.\./)*((?:" + "|".join(link_prefixes) + r")[^)]*)\)",
129+
_adjust_external,
130+
),
131+
132+
# Find links that lead to a non-presentable filetype and transform
133+
# it into an external link.
134+
(
135+
r"\[([^\[\]]*)\]\s*\((?:\.\./)*((?:[^()]+?/)*[^.()]+?(\.[^)/#]+))(?:#[^)]+)?\)",
136+
_adjust_filetype,
137+
),
138+
139+
# Find links that lead to a folder and transform it into an external link.
140+
(
141+
r"\[([^\[\]]*)\]\s*\((?:\.\./)*((?:[^()]+?/)+[^).#/]+)(\))",
142+
_adjust_filetype,
143+
),
144+
145+
# Find image links in img tags and adjust them
146+
(r"(<img [^>]*src=[\"'])([^ >]+)([\"'][^>]*>)", _adjust_image_link)
147+
]
148+
149+
with open(fname, "r+", encoding=encoding) as f:
150+
content = f.read()
151+
modified = False
152+
153+
for pattern, sub_func in rules:
154+
content, changes_made = re.subn(pattern, sub_func, content)
155+
modified = modified or changes_made
156+
157+
if modified:
158+
f.seek(0)
159+
f.write(content)
160+
f.truncate()
161+
162+
163+
def sync_contents(app: Sphinx) -> None:
164+
"""Synhronize external contents.
165+
166+
Args:
167+
app: Sphinx application instance.
168+
"""
169+
170+
srcdir = Path(app.srcdir).resolve()
171+
to_copy = []
172+
to_delete = set(f for f in srcdir.glob("**/*") if not f.is_dir())
173+
to_keep = set(
174+
f
175+
for k in app.config.external_content_keep
176+
for f in srcdir.glob(k)
177+
if not f.is_dir()
178+
)
179+
180+
for content in app.config.external_content_contents:
181+
prefix_src, glob = content
182+
for src in prefix_src.glob(glob):
183+
if src.is_dir():
184+
to_copy.extend(
185+
[(f, prefix_src) for f in src.glob("**/*") if not f.is_dir()]
186+
)
187+
else:
188+
to_copy.append((src, prefix_src))
189+
190+
list_of_destinations = [f for f, _ in to_copy]
191+
192+
for entry in to_copy:
193+
src, prefix_src = entry
194+
dst = (srcdir / src.relative_to(prefix_src)).resolve()
195+
196+
if dst in to_delete:
197+
to_delete.remove(dst)
198+
199+
if not dst.parent.exists():
200+
dst.parent.mkdir(parents=True)
201+
202+
# just copy if it does not exist
203+
if not dst.exists():
204+
shutil.copy(src, dst)
205+
adjust_includes(
206+
dst,
207+
src.parent,
208+
app.config.source_encoding,
209+
app.config.external_content_link_prefixes,
210+
app.config.external_content_link_extensions,
211+
list_of_destinations,
212+
)
213+
# if origin file is modified only copy if different
214+
elif src.stat().st_mtime > dst.stat().st_mtime:
215+
with tempfile.TemporaryDirectory() as td:
216+
# adjust origin includes before comparing
217+
src_adjusted = Path(td) / src.name
218+
shutil.copy(src, src_adjusted)
219+
adjust_includes(
220+
src_adjusted,
221+
src.parent,
222+
app.config.source_encoding,
223+
app.config.external_content_link_prefixes,
224+
app.config.external_content_link_extensions,
225+
list_of_destinations,
226+
dstpath=dst.parent,
227+
)
228+
229+
if not filecmp.cmp(src_adjusted, dst):
230+
dst.unlink()
231+
shutil.move(os.fspath(src_adjusted), os.fspath(dst))
232+
233+
# remove any previously copied file not present in the origin folder,
234+
# excepting those marked to be kept.
235+
for file in to_delete - to_keep:
236+
file.unlink()
237+
238+
239+
def setup(app: Sphinx) -> Dict[str, Any]:
240+
app.add_config_value("external_content_contents", [], "env")
241+
app.add_config_value("external_content_keep", [], "")
242+
app.add_config_value("external_content_link_prefixes", [], "env")
243+
app.add_config_value("external_content_link_extensions", [], "env")
244+
245+
app.connect("builder-inited", sync_contents)
246+
247+
return {
248+
"version": __version__,
249+
"parallel_read_safe": True,
250+
"parallel_write_safe": True,
251+
}

docs/_static/images/favicon.ico

4.18 KB
Binary file not shown.

docs/_static/images/logo.png

35.7 KB
Loading

docs/api/index.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# API
2+
3+
```{toctree}
4+
:glob:
5+
6+
*
7+
```

0 commit comments

Comments
 (0)