From b1723a7363505f17a0cb847a2dfa1848ff73ef60 Mon Sep 17 00:00:00 2001 From: Samuel Hinton Date: Tue, 21 Oct 2025 13:56:27 +1000 Subject: [PATCH 01/21] Initial mkdocs additions --- cookiecutter.json | 6 + docs/pages/guides/docs.md | 226 +++++++++++++++++- .../.readthedocs.yaml | 6 +- {{cookiecutter.project_name}}/noxfile.py | 16 +- {{cookiecutter.project_name}}/pyproject.toml | 13 + .../api.md | 1 + .../examples/README.md | 3 + .../examples/example_1.py | 13 + .../index.md | 16 ++ .../conf.py | 0 .../index.md | 0 ...ter.docs=='mkdocs' %}mkdocs.yml{% endif %} | 58 +++++ 12 files changed, 349 insertions(+), 9 deletions(-) create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/index.md rename {{cookiecutter.project_name}}/{docs => {% if cookiecutter.docs='sphinx' %}docs{% endif %}}/conf.py (100%) rename {{cookiecutter.project_name}}/{docs => {% if cookiecutter.docs='sphinx' %}docs{% endif %}}/index.md (100%) create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} diff --git a/cookiecutter.json b/cookiecutter.json index 558ecc07..6635b1a5 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -18,6 +18,7 @@ "mesonpy", "maturin" ], + "docs": ["sphinx", "mkdocs"], "vcs": true, "__year": "{% now 'utc', '%Y' %}", "__project_slug": "{{ cookiecutter.project_name | lower | replace('-', '_') | replace('.', '_') }}", @@ -45,6 +46,11 @@ "mesonpy": "Meson-python - Compiled C++ (also good)", "maturin": "Maturin - Compiled Rust (recommended)" }, + "docs": { + "__prompt__": "Chose your documentation tool", + "sphinx": "Sphinx", + "mkdocs": "mkdocs-material" + }, "vcs": "Use version control for versioning" } } diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index cd5571e4..85cd43dd 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -12,20 +12,23 @@ parent: Topical Guides Documentation used to require learning reStructuredText (sometimes referred to as reST / rST), but today we have great choices for documentation in markdown, -the same format used by GitHub, Wikipedia, and others. This guide covers Sphinx, +the same format used by GitHub, Wikipedia, and others. This guide covers Sphinx and Mkdocs, and uses the modern MyST plugin to get Markdown support. {: .note-title } -> Other frameworks +> Popular frameworks > > There are other frameworks as well; these often are simpler, but are not as > commonly used, and have somewhat fewer examples and plugins. They are: > +> - [Sphinx](https://www.sphinx-doc.org/en/master/): A popular documentation +> framework for scientific libraries with a history of close usage with scientific +> tools like LaTeX. > - [JupyterBook](https://jupyterbook.org): A powerful system for rendering a > collection of notebooks using Sphinx internally. Can also be used for docs, > though, see [echopype](https://echopype.readthedocs.io). -> - [MkDocs](https://www.mkdocs.org): a from-scratch new documentation system +> - [MkDocs](https://www.mkdocs.org): A from-scratch new documentation system > based on markdown and HTML. Less support for man pages & PDFs than Sphinx, > since it doesn't use docutils. Has over > [200 plugins](https://github.com/mkdocs/catalog) - they are much easier to @@ -66,8 +69,13 @@ with render_cookie() as package: ## Hand-written docs -Create `docs/` directory within your project (i.e. next to `src/`). There is a -sphinx-quickstart tool, but it creates unnecessary files (make/bat, we recommend +Create `docs/` directory within your project (i.e. next to `src/`). From here, +Sphinx and MkDocs diverge. + +{% tabs %} {% tab sphinx Sphinx %} + + +There is a sphinx-quickstart tool, but it creates unnecessary files (make/bat, we recommend a cross-platform noxfile instead), and uses rST instead of Markdown. Instead, this is our recommended starting point for `conf.py`: @@ -246,15 +254,190 @@ docs = [ "sphinx-autodoc-typehints", ] ``` - You should use `--group=docs` when using uv or pip to install. +{% endtab %} +{% tab mkdocs MkDocs %} + +While the cookie cutter creates a basic structure for your MkDocs (a top level `mkdocs.yml` file and the `docs` directory), you can also follow the official [Getting started](https://squidfunk.github.io/mkdocs-material/getting-started/) guide instead. Note, however, instead of the `pip` install, it is better practise install your documentation dependencies via `pyproject.toml` and then when you run your `uv sync` to install dependencies, you can explicitly ask for the `docs` group to be installed via `uv sync --group=docs` or `uv sync --all-groups`. + +If you selected the `mkdocs` option when using the template cookie-cutter repository, you will already have this group. Otherwise, add to your `pyproject.toml`: + +```toml +[dependency-groups] +docs = docs = [ + "Markdown>=3.9", + "mkdocs>=1.1.2,<2", + "mkdocs-material>=9.1.19,<10", + "pyyaml>=6.0.1", + "mdx-include>=1.4.2", + "mkdocstrings-python>=1.18.2", + "mkdocs-gallery>=0.10.4", +] +``` + +These dependencies include several common plugins---such as generating reference API documentation from docstrings---to make life easier. + +Similar to Sphinx, MkDocs puts your written documentation into the `/docs` directory, but also has a top-level `mkdocs.yml` configuration file. You can see the [minimal configuration for the file here](https://squidfunk.github.io/mkdocs-material/creating-your-site/#minimal-configuration), which is only four lines. However, the `mkdocs.yml` file bundled with the template repository have many options pre-configured. Let's run through an example configuration now. + +Here's the whole file for completeness. We'll break it into sections underneath. + +```yaml +site_name: some_project +site_url: https://some_project.readthedocs.io/ +site_author: "Bruce Wayne" +repo_name: "wayne_industries/some_project" +repo_url: "https://github.com/wayne_industries/some_project" + +theme: + name: material + icon: + repo: fontawesome/brands/github + features: + - search.suggest + - search.highlight + - navigation.expand + - navigation.tracking + - toc.follow + palette: + # See options to customise your colour scheme here: + # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/weather-sunny + name: Switch to light mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/weather-night + name: Switch to dark mode + +plugins: + autorefs: {} + mkdocstrings: + handlers: + python: + paths: [.] + import: + - https://docs.python.org/3/objects.inv + - https://docs.pydantic.dev/latest/objects.inv + options: + members_order: source + separate_signature: true + filters: ["!^_"] + show_root_heading: true + show_if_no_docstring: true + show_signature_annotations: true + gallery: + examples_dirs: docs/examples # path to your example scripts + gallery_dirs: docs/generated/gallery # where to save generated gallery + image_srcset: ['2x'] + within_subsection_order: FileNameSortKey + search: {} + + +nav: + - Home: index.md + - Python API: api.md + - Examples: generated/gallery +``` + +First, the basic site metadata contains authors, repository details, URLs, etc: + +```yaml +site_name: some_project +site_url: https://some_project.readthedocs.io/ +site_author: "Bruce Wayne" +repo_name: "wayne_industries/some_project" +repo_url: "https://github.com/wayne_industries/some_project" +``` + +After that, we can configure the visual theming for the the site. The repo icon is what appears in the top-right of the site next to the link to your GitHub/GitLab/etc, and you can peruse [other FontAwesome icons here](https://fontawesome.com/icons) if the default GitHub or GitLab brand is unwanted. + +Extra search features are documented [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/), and the three enabled are autocomplete for search suggestions (`search.suggest`) and highlighting search terms after a user clicks on a search result (`search.highlight`). + +For navigation plugins (documented [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/)), we request the side navigation to be expanded by default (`naviation.expand`) and that the URL autoupdate to the latest anchor as a user scrolls through the page (`naviation.tracking`). Finally, we request that the current user section is always shown and highlight in the sidebar via `toc.follow`. + +In the palette section (documented [here](https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/)) you can easily modify the scheme, icons, primary colours, and accents for both light and dark themes. + +```yaml +theme: + name: material + icon: + repo: fontawesome/brands/github + features: + - search.suggest + - search.highlight + - navigation.expand + - navigation.tracking + - toc.follow + palette: + # See options to customise your colour scheme here: + # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/weather-sunny + name: Switch to light mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/weather-night + name: Switch to dark mode +``` + +Onto the best part of MkDocs: it's many plugins! + +* `search` enabled search functionality. +* [`autorefs`](https://mkdocstrings.github.io/autorefs/) allows easier linking across pages and anchors. +* [`mkdocstrings`](https://mkdocstrings.github.io/) lets you generate reference API documentation from your docstring. +* [`gallery`](https://smarie.github.io/mkdocs-gallery/) turns an `example` directory inside your `docs` folder into accessible documentation, capturing output (such as print statements or generated `matplotlib` figures). + +```yaml +plugins: + autorefs: {} + mkdocstrings: + handlers: + python: + paths: [.] + import: + - https://docs.python.org/3/objects.inv + - https://docs.pydantic.dev/latest/objects.inv + options: + members_order: source + separate_signature: true + filters: ["!^_"] + show_root_heading: true + show_if_no_docstring: true + show_signature_annotations: true + gallery: + examples_dirs: docs/examples # path to your example scripts + gallery_dirs: docs/generated/gallery # where to save generated gallery + image_srcset: ['2x'] + within_subsection_order: FileNameSortKey + search: {} +``` + +Finally, we have to define the actual structure of our site by providing the primary navigation sidebar layout. Here we have three top-level links, one for the home page, one where all the generated API documentation from `mkdocstrings` will live, and a final entry for any examples showing how to use the package. + +```yaml +nav: + - Home: index.md + - Python API: api.md + - Examples: generated/gallery +``` + +{% endtab %} {% endtabs %} + ### .readthedocs.yaml In order to use to build, host, and preview your documentation, you must have a `.readthedocs.yaml` file {% rr RTD100 %} like this: +{% tabs %} {% tab sphinx Sphinx %} + +{% endtab %} +{% tab mkdocs MkDocs %} + + + +```yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.13" + commands: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv sync --group docs + - uv run mkdocs build --site-dir $READTHEDOCS_OUTPUT/html +``` + + + + +{% endtab %} {% endtabs %} + This sets the Read the Docs config version (2 is required) {% rr RTD101 %}. The `build` table is the modern way to specify a runner. You need an `os` (a diff --git a/{{cookiecutter.project_name}}/.readthedocs.yaml b/{{cookiecutter.project_name}}/.readthedocs.yaml index 172d2d8b..173da630 100644 --- a/{{cookiecutter.project_name}}/.readthedocs.yaml +++ b/{{cookiecutter.project_name}}/.readthedocs.yaml @@ -11,6 +11,10 @@ build: - asdf plugin add uv - asdf install uv latest - asdf global uv latest - - uv sync --group docs + - uv sync --group docs + {%- if cookiecutter.docs == 'sphinx' %} - uv run python -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html + {%- elif cookiecutter.docs == 'mkdocs' %} + - uv run mkdocs build --site-dir $READTHEDOCS_OUTPUT/html + {%- endif %} diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index d56b538a..1d5bf457 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -44,7 +44,7 @@ def tests(session: nox.Session) -> None: session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *test_deps) session.run("pytest", *session.posargs) - +{%- if cookiecutter.docs == 'sphinx' %} @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: """ @@ -76,7 +76,6 @@ def docs(session: nox.Session) -> None: else: session.run("sphinx-build", "--keep-going", *shared_args) - @nox.session(default=False) def build_api_docs(session: nox.Session) -> None: """ @@ -93,6 +92,19 @@ def build_api_docs(session: nox.Session) -> None: "--force", "src/{{ cookiecutter.__project_slug }}", ) +{%- elif cookiecutter.docs == 'mkdocs' %} +@nox.session(reuse_venv=True, default=False) +def docs(session: nox.Session) -> None: + """ + Serve the docs + """ + + doc_deps = nox.project.dependency_groups(PROJECT, "docs") + session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *doc_deps) + session.run("mkdocs", "serve", "--clean") +{%- endif %} + + @nox.session(default=False) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 3f0e52fc..2507cd18 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -125,6 +125,7 @@ test = [ dev = [ { include-group = "test" }, ] +{%- if cookiecutter.docs == 'sphinx' %} docs = [ "sphinx>=7.0", "myst_parser>=0.13", @@ -132,6 +133,15 @@ docs = [ "sphinx_autodoc_typehints", "furo>=2023.08.17", ] +{%- elif cookiecutter.docs == "mkdocs" %} +docs = [ + "Markdown>=3.9", + "mkdocs>=1.1.2,<2", + "mkdocs-material>=9.1.19,<10", + "pyyaml>=6.0.1", + "mkdocstrings-python>=1.18.2", + "mkdocs-gallery>=0.10.4", +]{%- endif %} {%- if cookiecutter.backend == "skbuild" %} @@ -312,6 +322,9 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "tests/**" = ["T20"] "noxfile.py" = ["T20"] +{%- if cookiecutter.docs == 'mkdocs' %} +"docs/examples/**" = ["T20"] +{%- endif %} [tool.pylint] diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md new file mode 100644 index 00000000..fad054f0 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md @@ -0,0 +1 @@ +# ::: {{cookiecutter.__project_slug}} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md new file mode 100644 index 00000000..557bc634 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md @@ -0,0 +1,3 @@ +# Basic Examples + +Here are some basic examples. \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py new file mode 100644 index 00000000..7428bb16 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py @@ -0,0 +1,13 @@ +""" +# A simple example + +Import your package and show what it can do. +""" + + +def square(x: int) -> int: + """Return the square of a number.""" + return x * x + + +print("Square of 5 is:", square(5)) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/index.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/index.md new file mode 100644 index 00000000..62a1e751 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/index.md @@ -0,0 +1,16 @@ +# {{ cookiecutter.project_name }} + +Here you can document whatever you'd like on your main page. Common choices include installation instructions, a minimal usage example, BibTex citations, and contribution guidelines. + +See [this link](https://squidfunk.github.io/mkdocs-material/reference/) for all the easy references and components you can use with mkdocs-material, or feel free to go through through [from the top](https://squidfunk.github.io/mkdocs-material/). + +## Installation + +You can install this package via running: + +```bash +pip install {{ cookiecutter.__project_slug }} +``` + + + diff --git a/{{cookiecutter.project_name}}/docs/conf.py b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/conf.py similarity index 100% rename from {{cookiecutter.project_name}}/docs/conf.py rename to {{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/conf.py diff --git a/{{cookiecutter.project_name}}/docs/index.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/index.md similarity index 100% rename from {{cookiecutter.project_name}}/docs/index.md rename to {{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/index.md diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} new file mode 100644 index 00000000..6ec327eb --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} @@ -0,0 +1,58 @@ +site_name: {{ cookiecutter.project_name }} +site_url: https://{{cookiecutter.project_name}}.readthedocs.io/ +site_author: "{{ cookiecutter.full_name }}" + +repo_name: "{{ cookiecutter.org }}/{{ cookiecutter.__project_slug }}" +repo_url: "{{ cookiecutter.url }}" + +theme: + name: material + icon: + repo: fontawesome/brands/github + features: + - search.suggest + - search.highlight + - navigation.expand + - navigation.tracking + - toc.follow + palette: + # See options to customise your colour scheme here: + # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/weather-sunny + name: Switch to light mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/weather-night + name: Switch to dark mode + +plugins: + autorefs: {} + mkdocstrings: + handlers: + python: + paths: [.] + import: + - https://docs.python.org/3/objects.inv + - https://docs.pydantic.dev/latest/objects.inv + options: + members_order: source + separate_signature: true + filters: ["!^_"] + show_root_heading: true + show_if_no_docstring: true + show_signature_annotations: true + gallery: + examples_dirs: docs/examples # path to your example scripts + gallery_dirs: docs/generated/gallery # where to save generated gallery + image_srcset: ['2x'] + within_subsection_order: FileNameSortKey + search: {} + +nav: + - Home: index.md + - Python API: api.md + - Examples: generated/gallery From 8e3ebf185cea1fa19209c3956ddb1a603224e371 Mon Sep 17 00:00:00 2001 From: Samuel Hinton Date: Wed, 22 Oct 2025 10:31:18 +1000 Subject: [PATCH 02/21] Updating mkdocs documentation --- README.md | 1 - docs/pages/guides/docs.md | 85 ++++++++++++++++++++++-- src/sp_repo_review/checks/readthedocs.py | 28 -------- tests/test_readthedocs.py | 31 --------- 4 files changed, 78 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 32255bf4..7d109b8d 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,6 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1 - [`RTD101`](https://learn.scientific-python.org/development/guides/docs#RTD101): You have to set the RTD version number to 2 - [`RTD102`](https://learn.scientific-python.org/development/guides/docs#RTD102): You have to set the RTD build image - [`RTD103`](https://learn.scientific-python.org/development/guides/docs#RTD103): You have to set the RTD python version -- [`RTD104`](https://learn.scientific-python.org/development/guides/docs#RTD104): You have to specify a build configuration now for readthedocs. ### GitHub Actions - [`GH100`](https://learn.scientific-python.org/development/guides/gha-basic#GH100): Has GitHub Actions config diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 85cd43dd..35b5ae0e 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -498,20 +498,19 @@ build: This sets the Read the Docs config version (2 is required) {% rr RTD101 %}. The `build` table is the modern way to specify a runner. You need an `os` (a -modern Ubuntu should be fine) {% rr RTD102 %}, a `tools` table (we'll use Python +modern Ubuntu should be fine) {% rr RTD102 %} and a `tools` table (we'll use Python {% rr RTD103 %}, several languages are supported here). -Adding a `sphinx` table tells Read the Docs to enable Sphinx integration. MkDocs -is supported too. You must include one of these unless you use build commands -{% rr RTD104 %}. - -Finally, we have a `python` table with an `install` key to describe how to -install our project. This will enable our "docs" extra. +Finally, we have a `commands` table which describes how to install our dependencies and +build the documentation into the ReadTheDocs output directory. ### noxfile.py additions Add a session to your `noxfile.py` to generate docs: +{% tabs %} {% tab sphinx Sphinx %} + + + This is a more complex Nox job just because it's taking some options (the ability to build and serve instead of just build). The first portion is just setting up argument parsing so we can serve if building `html`. Then it does @@ -565,8 +565,38 @@ and run either the autobuild (for `--serve`) or regular build. We could have just added `python -m http.server` pointing at the built documentation, but autobuild will rebuild if you change a file while serving. +{% endtab %} +{% tab mkdocs MkDocs %} + + + + +```python +@nox.session(reuse_venv=True, default=False) +def docs(session: nox.Session) -> None: + """ + Serve the docs + """ + + doc_deps = nox.project.dependency_groups(PROJECT, "docs") + session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *doc_deps) + session.run("mkdocs", "serve", "--clean") +``` + + + +This Nox job will invoke MkDocs to serve a live copy of your documentation under a local endpoint, such as `http://localhost:8080` (the link will be in the job output). By requesting a `serve` instead of a `build`, any time documentation or the source code is changed, the documentation will automatically update. For documentation on how to configure what directories are watched for changes, [consult the MkDocs configuration page](https://www.mkdocs.org/user-guide/configuration/#live-reloading). + +{% endtab %} {% endtabs %} + + ## API docs +{% tabs %} {% tab sphinx Sphinx %} + To build API docs, you need to add the following Nox job. It will rerun `sphinx-apidoc` to generate the sphinx autodoc pages for each of your public modules. @@ -614,8 +644,29 @@ api/ Note that your docstrings are still parsed as reStructuredText. +{% endtab %} +{% tab mkdocs MkDocs %} + +API documentation can be built from your docstring using the `mkdocstrings` plugin, as referenced previously. Unlike with Sphinx, which requires a direct invocation of `sphinx-apidoc`, MkDocs plugins are integrated into the MkDocs build. + +All `mkdocstrings` requires is your markdown files to specify what package, module, or class you would like documented in said file. See the [`mkdocstring` Usage page](https://mkdocstrings.github.io/usage/) for more details, but for a minimal example, if you add an `api.md` file and set its contents to: + +```markdown +# Documentation for `my_package.MyClass` + +::: my_package.MyClass +``` + +Where the triple colon syntax is used to specify what documentation you would like built. In this case, we are asking for the class `MyClass` which is located in `my_package`, and a user would be able to import this class via `from my_package` import `MyClass`. If the class is not exposed in the package `__init__.py` and is only importable from a module (such that a user would have to write `from my_package.my_module import MyClass`) then you would update the identifier to `my_package.my_module.MyClass`. + +{% endtab %} +{% endtabs %} + ## Notebooks in docs +{% tabs %} {% tab sphinx Sphinx %} + + You can combine notebooks into your docs. The tool for this is `nbsphinx`. If you want to use it, add `nbsphinx` and `ipykernel` to your documentation requirements, add `"nbsphinx"` to your `conf.py`'s `extensions =` list, and add @@ -643,6 +694,26 @@ for this to work. CI services like readthedocs usually have it installed. If you want to use Markdown instead of notebooks, you can use jupytext (see [here](https://nbsphinx.readthedocs.io/en/0.9.2/a-markdown-file.html)). +{% endtab %} +{% tab mkdocs MkDocs %} + +You can combine notebooks into your docs. The plugin for this is `mkdocs-jupyter`, and configuration is detailed [here](https://github.com/danielfrg/mkdocs-jupyter) and you can find examples [here](https://mkdocs-jupyter.danielfrg.com/). + +Once you have a notebook (which has been run and populated with results, as the plugin will not execute your notebooks for you), you simply need to add a link to the notebook in your `mkdocs.yml` navigation. + +```yaml +nav: + - Home: index.md + - Notebook page: notebook.ipynb + - Python file: python_script.py +plugins: + - mkdocs-jupyter +``` + +Note that the `mkdocs-jupyter` plugin allows you to include both python scripts and notebooks. If you have a directory of example python files to run, consider [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/) as an alternative. + +{% endtab %} {% endtabs %} + [diátaxis]: https://diataxis.fr/ [sphinx]: https://www.sphinx-doc.org/ diff --git a/src/sp_repo_review/checks/readthedocs.py b/src/sp_repo_review/checks/readthedocs.py index 4f69c56d..898b35cf 100644 --- a/src/sp_repo_review/checks/readthedocs.py +++ b/src/sp_repo_review/checks/readthedocs.py @@ -104,34 +104,6 @@ def check(readthedocs: dict[str, Any]) -> bool: return False -class RTD104(ReadTheDocs): - "You have to specify a build configuration now for readthedocs." - - requires = {"RTD100"} - - @staticmethod - def check(readthedocs: dict[str, Any]) -> bool: - """ - You must set `sphinx: configuration:`, `mkdocs: configuration:` or - `build: commands:`. Skipping it is no longer allowed. Example: - - ```yaml - sphinx: - configuration: docs/conf.py - ``` - """ - - match readthedocs: - case {"build": {"commands": list()}}: - return True - case {"sphinx": {"configuration": str()}}: - return True - case {"mkdocs": {"configuration": str()}}: - return True - case _: - return False - - def readthedocs(root: Traversable) -> dict[str, Any]: for path in (".readthedocs.yaml", ".readthedocs.yml"): readthedocs_path = root.joinpath(path) diff --git a/tests/test_readthedocs.py b/tests/test_readthedocs.py index 9a4575b7..7b6a11e2 100644 --- a/tests/test_readthedocs.py +++ b/tests/test_readthedocs.py @@ -67,34 +67,3 @@ def test_rtd103_false() -> None: os: ubuntu-22.04 """) assert not compute_check("RTD103", readthedocs=readthedocs).result - - -def test_rtd104_commands() -> None: - readthedocs = yaml.safe_load(""" - build: - commands: [] - """) - assert compute_check("RTD104", readthedocs=readthedocs).result - - -def test_rtd104_sphinx() -> None: - readthedocs = yaml.safe_load(""" - sphinx: - configuration: docs/conf.py - """) - assert compute_check("RTD104", readthedocs=readthedocs).result - - -def test_rtd104_mkdocs() -> None: - readthedocs = yaml.safe_load(""" - mkdocs: - configuration: docs/mkdocs.yml - """) - assert compute_check("RTD104", readthedocs=readthedocs).result - - -def test_rtd104_false() -> None: - readthedocs = yaml.safe_load(""" - build: - """) - assert not compute_check("RTD104", readthedocs=readthedocs).result From d61709a1072f23c085409553b7b8a3e1fff313fe Mon Sep 17 00:00:00 2001 From: Samuel Hinton Date: Fri, 24 Oct 2025 13:04:54 +1000 Subject: [PATCH 03/21] Updating mkdocs --- docs/README.md | 2 +- docs/pages/guides/docs.md | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0c23adc0..12be755d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,7 @@ scientists and research software engineers. The repository contains: ## Contributing to the documentation To build locally, install rbenv (remember to run `rbenv init` after installing, -and `rbenv install 3.1.2`). Then: +and `rbenv install 3.4.1`). Then: ```bash bundle install diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 35b5ae0e..09c2d2c5 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -24,10 +24,8 @@ and uses the modern MyST plugin to get Markdown support. > > - [Sphinx](https://www.sphinx-doc.org/en/master/): A popular documentation > framework for scientific libraries with a history of close usage with scientific -> tools like LaTeX. -> - [JupyterBook](https://jupyterbook.org): A powerful system for rendering a -> collection of notebooks using Sphinx internally. Can also be used for docs, -> though, see [echopype](https://echopype.readthedocs.io). +> tools like LaTeX. Examples include [astropy](https://docs.astropy.org/en/stable/index_user_docs.html) +> and [corner](https://docs.astropy.org/en/stable/index_user_docs.html). > - [MkDocs](https://www.mkdocs.org): A from-scratch new documentation system > based on markdown and HTML. Less support for man pages & PDFs than Sphinx, > since it doesn't use docutils. Has over @@ -35,8 +33,12 @@ and uses the modern MyST plugin to get Markdown support. > write than Sphinx. Example sites include [hatch](https://hatch.pypa.io), > [PDM](https://pdm.fming.dev), > [cibuildwheel](https://cibuildwheel.readthedocs.io), -> [Textual](https://textual.textualize.io), and -> [pipx](https://pypa.github.io/pipx/). +> [Textual](https://textual.textualize.io), +> [pipx](https://pypa.github.io/pipx/), [Pydantic](https://docs.pydantic.dev/latest/), +> [Polars](https://docs.pola.rs/), and [FastAPI](https://fastapi.tiangolo.com/) +> - [JupyterBook](https://jupyterbook.org): A powerful system for rendering a +> collection of notebooks using Sphinx internally. Can also be used for docs, +> though, see [echopype](https://echopype.readthedocs.io). ## What to include @@ -69,16 +71,16 @@ with render_cookie() as package: ## Hand-written docs -Create `docs/` directory within your project (i.e. next to `src/`). From here, +Create `docs/` directory within your project (next to `src/`). From here, Sphinx and MkDocs diverge. -{% tabs %} {% tab sphinx Sphinx %} - +{% tabs %}{% tab sphinx Sphinx %} There is a sphinx-quickstart tool, but it creates unnecessary files (make/bat, we recommend a cross-platform noxfile instead), and uses rST instead of Markdown. Instead, this is our recommended starting point for `conf.py`: + ### conf.py From 7f3770fbde44161d951ce57bbfbe42b06604bea0 Mon Sep 17 00:00:00 2001 From: Samuel Hinton Date: Fri, 24 Oct 2025 13:54:31 +1000 Subject: [PATCH 05/21] I am dumb --- copier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copier.yml b/copier.yml index 2b649053..ae1f36f9 100644 --- a/copier.yml +++ b/copier.yml @@ -83,7 +83,7 @@ backend: # [[[end]]] # [[[cog print(cc.docs.yaml()) ]]] -backend: +docs: help: Choose your documentation tool choices: "Sphinx": sphinx From 9e066bcd2dd5a331957ae21efe017aa025a8f2be Mon Sep 17 00:00:00 2001 From: Samuel Hinton Date: Fri, 24 Oct 2025 14:09:36 +1000 Subject: [PATCH 06/21] Newline --- {{cookiecutter.project_name}}/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 2507cd18..ec6c9e8b 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -141,7 +141,8 @@ docs = [ "pyyaml>=6.0.1", "mkdocstrings-python>=1.18.2", "mkdocs-gallery>=0.10.4", -]{%- endif %} +] +{%- endif %} {%- if cookiecutter.backend == "skbuild" %} From 7b4047af49363f2c47666482254578a9ba129e43 Mon Sep 17 00:00:00 2001 From: Samuel Hinton Date: Fri, 24 Oct 2025 14:55:54 +1000 Subject: [PATCH 07/21] Ensuring copier and cookiecutter work --- docs/pages/guides/docs.md | 31 ++++++------------ {{cookiecutter.project_name}}/README.md | 1 - {{cookiecutter.project_name}}/noxfile.py | 7 ++-- {{cookiecutter.project_name}}/pyproject.toml | 3 +- .../example.py | 32 +++++++++++++++++++ .../api.md | 1 + .../index.md | 0 .../conf.py | 0 .../index.md | 0 .../api.md | 1 - .../examples/README.md | 3 -- .../examples/example_1.py | 13 -------- ...ter.docs=='mkdocs' %}mkdocs.yml{% endif %} | 8 +---- 13 files changed, 48 insertions(+), 52 deletions(-) create mode 100644 {{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/example.py create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy => {% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}}/index.md (100%) rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs='sphinx' %}docs{% endif %} => {% if cookiecutter.docs == 'sphinx' %}docs{% endif %}}/conf.py (100%) rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs='sphinx' %}docs{% endif %} => {% if cookiecutter.docs == 'sphinx' %}docs{% endif %}}/index.md (100%) delete mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md delete mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md delete mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 09c2d2c5..79227e21 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -274,7 +274,6 @@ docs = [ "pyyaml>=6.0.1", "mdx-include>=1.4.2", "mkdocstrings-python>=1.18.2", - "mkdocs-gallery>=0.10.4", ] ``` @@ -321,7 +320,7 @@ plugins: handlers: python: paths: [.] - import: + inventories: - https://docs.python.org/3/objects.inv - https://docs.pydantic.dev/latest/objects.inv options: @@ -331,18 +330,12 @@ plugins: show_root_heading: true show_if_no_docstring: true show_signature_annotations: true - gallery: - examples_dirs: docs/examples # path to your example scripts - gallery_dirs: docs/generated/gallery # where to save generated gallery - image_srcset: ['2x'] - within_subsection_order: FileNameSortKey search: {} nav: - Home: index.md - Python API: api.md - - Examples: generated/gallery ``` First, the basic site metadata contains authors, repository details, URLs, etc: @@ -394,7 +387,7 @@ Onto the best part of MkDocs: it's many plugins! * `search` enabled search functionality. * [`autorefs`](https://mkdocstrings.github.io/autorefs/) allows easier linking across pages and anchors. * [`mkdocstrings`](https://mkdocstrings.github.io/) lets you generate reference API documentation from your docstring. -* [`gallery`](https://smarie.github.io/mkdocs-gallery/) turns an `example` directory inside your `docs` folder into accessible documentation, capturing output (such as print statements or generated `matplotlib` figures). + ```yaml plugins: @@ -403,7 +396,7 @@ plugins: handlers: python: paths: [.] - import: + inventories: - https://docs.python.org/3/objects.inv - https://docs.pydantic.dev/latest/objects.inv options: @@ -413,21 +406,15 @@ plugins: show_root_heading: true show_if_no_docstring: true show_signature_annotations: true - gallery: - examples_dirs: docs/examples # path to your example scripts - gallery_dirs: docs/generated/gallery # where to save generated gallery - image_srcset: ['2x'] - within_subsection_order: FileNameSortKey search: {} ``` -Finally, we have to define the actual structure of our site by providing the primary navigation sidebar layout. Here we have three top-level links, one for the home page, one where all the generated API documentation from `mkdocstrings` will live, and a final entry for any examples showing how to use the package. +Finally, we have to define the actual structure of our site by providing the primary navigation sidebar layout. Here we have three top-level links, one for the home page and one where all the generated API documentation from `mkdocstrings` will live. ```yaml nav: - Home: index.md - Python API: api.md - - Examples: generated/gallery ``` {% endtab %} {% endtabs %} @@ -651,15 +638,15 @@ Note that your docstrings are still parsed as reStructuredText. API documentation can be built from your docstring using the `mkdocstrings` plugin, as referenced previously. Unlike with Sphinx, which requires a direct invocation of `sphinx-apidoc`, MkDocs plugins are integrated into the MkDocs build. -All `mkdocstrings` requires is your markdown files to specify what package, module, or class you would like documented in said file. See the [`mkdocstring` Usage page](https://mkdocstrings.github.io/usage/) for more details, but for a minimal example, if you add an `api.md` file and set its contents to: +All `mkdocstrings` requires is your markdown files to specify what module, class, or function you would like documented in said file. See the [`mkdocstring` Usage page](https://mkdocstrings.github.io/usage/) for more details, but for a minimal example, if you add an `api.md` file and set its contents to: ```markdown -# Documentation for `my_package.MyClass` +# Documentation for `my_package.my_module` -::: my_package.MyClass +::: my_package.my_module ``` -Where the triple colon syntax is used to specify what documentation you would like built. In this case, we are asking for the class `MyClass` which is located in `my_package`, and a user would be able to import this class via `from my_package` import `MyClass`. If the class is not exposed in the package `__init__.py` and is only importable from a module (such that a user would have to write `from my_package.my_module import MyClass`) then you would update the identifier to `my_package.my_module.MyClass`. +Where the triple colon syntax is used to specify what documentation you would like built. In this case, we are asking to document the entire module `my_class` (and all classes and functions within it) which is located in `my_package`. You could instead ask for only a single component inside your module by being more specific, like `::: my_package.my_module.MyClass`. {% endtab %} {% endtabs %} @@ -712,7 +699,7 @@ plugins: - mkdocs-jupyter ``` -Note that the `mkdocs-jupyter` plugin allows you to include both python scripts and notebooks. If you have a directory of example python files to run, consider [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/) as an alternative. +Note that the `mkdocs-jupyter` plugin allows you to include both python scripts and notebooks. If you have a directory of example python files to run, consider [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/) as an alternative. For an external example, the [ChainConsumer docs](https://samreay.github.io/ChainConsumer/generated/gallery/) show `mkdocs-gallery` in action. {% endtab %} {% endtabs %} diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index ff45a1c0..f50e6fe1 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -11,7 +11,6 @@ {%- if cookiecutter.org | lower == "scikit-hep" %} [![Scikit-HEP][sk-badge]](https://scikit-hep.org/) {%- endif %} - [actions-badge]: {{cookiecutter.url}}/workflows/CI/badge.svg diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 1d5bf457..74c9c0e4 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -44,6 +44,7 @@ def tests(session: nox.Session) -> None: session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *test_deps) session.run("pytest", *session.posargs) + {%- if cookiecutter.docs == 'sphinx' %} @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: @@ -92,6 +93,8 @@ def build_api_docs(session: nox.Session) -> None: "--force", "src/{{ cookiecutter.__project_slug }}", ) + + {%- elif cookiecutter.docs == 'mkdocs' %} @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: @@ -102,11 +105,9 @@ def docs(session: nox.Session) -> None: doc_deps = nox.project.dependency_groups(PROJECT, "docs") session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *doc_deps) session.run("mkdocs", "serve", "--clean") -{%- endif %} - - +{%- endif %} @nox.session(default=False) def build(session: nox.Session) -> None: """ diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index ec6c9e8b..b5e4a49d 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -140,7 +140,6 @@ docs = [ "mkdocs-material>=9.1.19,<10", "pyyaml>=6.0.1", "mkdocstrings-python>=1.18.2", - "mkdocs-gallery>=0.10.4", ] {%- endif %} @@ -323,7 +322,7 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "tests/**" = ["T20"] "noxfile.py" = ["T20"] -{%- if cookiecutter.docs == 'mkdocs' %} +{%- if cookiecutter.docs == "mkdocs" %} "docs/examples/**" = ["T20"] {%- endif %} diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/example.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/example.py new file mode 100644 index 00000000..99f366b4 --- /dev/null +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/example.py @@ -0,0 +1,32 @@ +"""Behold, module level docstring.""" + + +class Example: + """This class exists to show off autodocstring generation.""" + + def add(self, a: int, b: int) -> int: + """Add two integers. + + Notes: + Docstring can be useful. I promise. + + Parameters: + a: First integer to add. + b: Second integer to add. + + Returns: + The sum of the two integers. + """ + return a + b + + def subtract(self, a: int, b: int) -> int: + """Subtract two integers. + + Parameters: + a: Integer to subtract from. + b: Integer to subtract. + + Returns: + The difference of the two integers. + """ + return a - b diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md new file mode 100644 index 00000000..d9df5579 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md @@ -0,0 +1 @@ +# ::: {{cookiecutter.__project_slug}}.example \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/index.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/index.md similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/index.md rename to {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/index.md diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/conf.py b/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/conf.py similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/conf.py rename to {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/conf.py diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/index.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/index.md similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs='sphinx' %}docs{% endif %}/index.md rename to {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/index.md diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md deleted file mode 100644 index fad054f0..00000000 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/api.md +++ /dev/null @@ -1 +0,0 @@ -# ::: {{cookiecutter.__project_slug}} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md deleted file mode 100644 index 557bc634..00000000 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Basic Examples - -Here are some basic examples. \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py b/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py deleted file mode 100644 index 7428bb16..00000000 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.docs='mkdocs' %}docs{% endif %} copy/examples/example_1.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -# A simple example - -Import your package and show what it can do. -""" - - -def square(x: int) -> int: - """Return the square of a number.""" - return x * x - - -print("Square of 5 is:", square(5)) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} index 6ec327eb..95e3bd14 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} @@ -35,7 +35,7 @@ plugins: handlers: python: paths: [.] - import: + inventories: - https://docs.python.org/3/objects.inv - https://docs.pydantic.dev/latest/objects.inv options: @@ -45,14 +45,8 @@ plugins: show_root_heading: true show_if_no_docstring: true show_signature_annotations: true - gallery: - examples_dirs: docs/examples # path to your example scripts - gallery_dirs: docs/generated/gallery # where to save generated gallery - image_srcset: ['2x'] - within_subsection_order: FileNameSortKey search: {} nav: - Home: index.md - Python API: api.md - - Examples: generated/gallery From f23a60a5ff2221617b81da2d35615883bbd26b95 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:01:10 +0000 Subject: [PATCH 08/21] style: pre-commit fixes --- docs/pages/guides/docs.md | 184 ++++++++++++++++++++++------------- docs/pages/tutorials/docs.md | 27 +++-- 2 files changed, 131 insertions(+), 80 deletions(-) diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 79227e21..0bcdbc19 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -12,8 +12,8 @@ parent: Topical Guides Documentation used to require learning reStructuredText (sometimes referred to as reST / rST), but today we have great choices for documentation in markdown, -the same format used by GitHub, Wikipedia, and others. This guide covers Sphinx and Mkdocs, -and uses the modern MyST plugin to get Markdown support. +the same format used by GitHub, Wikipedia, and others. This guide covers Sphinx +and Mkdocs, and uses the modern MyST plugin to get Markdown support. {: .note-title } @@ -23,9 +23,10 @@ and uses the modern MyST plugin to get Markdown support. > commonly used, and have somewhat fewer examples and plugins. They are: > > - [Sphinx](https://www.sphinx-doc.org/en/master/): A popular documentation -> framework for scientific libraries with a history of close usage with scientific -> tools like LaTeX. Examples include [astropy](https://docs.astropy.org/en/stable/index_user_docs.html) -> and [corner](https://docs.astropy.org/en/stable/index_user_docs.html). +> framework for scientific libraries with a history of close usage with +> scientific tools like LaTeX. Examples include +> [astropy](https://docs.astropy.org/en/stable/index_user_docs.html) and +> [corner](https://docs.astropy.org/en/stable/index_user_docs.html). > - [MkDocs](https://www.mkdocs.org): A from-scratch new documentation system > based on markdown and HTML. Less support for man pages & PDFs than Sphinx, > since it doesn't use docutils. Has over @@ -33,9 +34,11 @@ and uses the modern MyST plugin to get Markdown support. > write than Sphinx. Example sites include [hatch](https://hatch.pypa.io), > [PDM](https://pdm.fming.dev), > [cibuildwheel](https://cibuildwheel.readthedocs.io), -> [Textual](https://textual.textualize.io), -> [pipx](https://pypa.github.io/pipx/), [Pydantic](https://docs.pydantic.dev/latest/), -> [Polars](https://docs.pola.rs/), and [FastAPI](https://fastapi.tiangolo.com/) +> [Textual](https://textual.textualize.io), +> [pipx](https://pypa.github.io/pipx/), +> [Pydantic](https://docs.pydantic.dev/latest/), +> [Polars](https://docs.pola.rs/), and +> [FastAPI](https://fastapi.tiangolo.com/) > - [JupyterBook](https://jupyterbook.org): A powerful system for rendering a > collection of notebooks using Sphinx internally. Can also be used for docs, > though, see [echopype](https://echopype.readthedocs.io). @@ -71,15 +74,14 @@ with render_cookie() as package: ## Hand-written docs -Create `docs/` directory within your project (next to `src/`). From here, -Sphinx and MkDocs diverge. +Create `docs/` directory within your project (next to `src/`). From here, Sphinx +and MkDocs diverge. {% tabs %}{% tab sphinx Sphinx %} -There is a sphinx-quickstart tool, but it creates unnecessary files (make/bat, we recommend -a cross-platform noxfile instead), and uses rST instead of Markdown. Instead, -this is our recommended starting point for `conf.py`: - +There is a sphinx-quickstart tool, but it creates unnecessary files (make/bat, +we recommend a cross-platform noxfile instead), and uses rST instead of +Markdown. Instead, this is our recommended starting point for `conf.py`: ### conf.py @@ -256,14 +258,23 @@ docs = [ "sphinx-autodoc-typehints", ] ``` + You should use `--group=docs` when using uv or pip to install. -{% endtab %} -{% tab mkdocs MkDocs %} +{% endtab %} {% tab mkdocs MkDocs %} -While the cookie cutter creates a basic structure for your MkDocs (a top level `mkdocs.yml` file and the `docs` directory), you can also follow the official [Getting started](https://squidfunk.github.io/mkdocs-material/getting-started/) guide instead. Note, however, instead of the `pip` install, it is better practise install your documentation dependencies via `pyproject.toml` and then when you run your `uv sync` to install dependencies, you can explicitly ask for the `docs` group to be installed via `uv sync --group=docs` or `uv sync --all-groups`. +While the cookie cutter creates a basic structure for your MkDocs (a top level +`mkdocs.yml` file and the `docs` directory), you can also follow the official +[Getting started](https://squidfunk.github.io/mkdocs-material/getting-started/) +guide instead. Note, however, instead of the `pip` install, it is better +practise install your documentation dependencies via `pyproject.toml` and then +when you run your `uv sync` to install dependencies, you can explicitly ask for +the `docs` group to be installed via `uv sync --group=docs` or +`uv sync --all-groups`. -If you selected the `mkdocs` option when using the template cookie-cutter repository, you will already have this group. Otherwise, add to your `pyproject.toml`: +If you selected the `mkdocs` option when using the template cookie-cutter +repository, you will already have this group. Otherwise, add to your +`pyproject.toml`: ```toml [dependency-groups] @@ -277,9 +288,16 @@ docs = [ ] ``` -These dependencies include several common plugins---such as generating reference API documentation from docstrings---to make life easier. +These dependencies include several common plugins---such as generating reference +API documentation from docstrings---to make life easier. -Similar to Sphinx, MkDocs puts your written documentation into the `/docs` directory, but also has a top-level `mkdocs.yml` configuration file. You can see the [minimal configuration for the file here](https://squidfunk.github.io/mkdocs-material/creating-your-site/#minimal-configuration), which is only four lines. However, the `mkdocs.yml` file bundled with the template repository have many options pre-configured. Let's run through an example configuration now. +Similar to Sphinx, MkDocs puts your written documentation into the `/docs` +directory, but also has a top-level `mkdocs.yml` configuration file. You can see +the +[minimal configuration for the file here](https://squidfunk.github.io/mkdocs-material/creating-your-site/#minimal-configuration), +which is only four lines. However, the `mkdocs.yml` file bundled with the +template repository have many options pre-configured. Let's run through an +example configuration now. Here's the whole file for completeness. We'll break it into sections underneath. @@ -301,7 +319,7 @@ theme: - navigation.tracking - toc.follow palette: - # See options to customise your colour scheme here: + # See options to customise your colour scheme here: # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ - media: "(prefers-color-scheme: light)" scheme: default @@ -332,7 +350,6 @@ plugins: show_signature_annotations: true search: {} - nav: - Home: index.md - Python API: api.md @@ -348,13 +365,29 @@ repo_name: "wayne_industries/some_project" repo_url: "https://github.com/wayne_industries/some_project" ``` -After that, we can configure the visual theming for the the site. The repo icon is what appears in the top-right of the site next to the link to your GitHub/GitLab/etc, and you can peruse [other FontAwesome icons here](https://fontawesome.com/icons) if the default GitHub or GitLab brand is unwanted. - -Extra search features are documented [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/), and the three enabled are autocomplete for search suggestions (`search.suggest`) and highlighting search terms after a user clicks on a search result (`search.highlight`). - -For navigation plugins (documented [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/)), we request the side navigation to be expanded by default (`naviation.expand`) and that the URL autoupdate to the latest anchor as a user scrolls through the page (`naviation.tracking`). Finally, we request that the current user section is always shown and highlight in the sidebar via `toc.follow`. - -In the palette section (documented [here](https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/)) you can easily modify the scheme, icons, primary colours, and accents for both light and dark themes. +After that, we can configure the visual theming for the the site. The repo icon +is what appears in the top-right of the site next to the link to your +GitHub/GitLab/etc, and you can peruse +[other FontAwesome icons here](https://fontawesome.com/icons) if the default +GitHub or GitLab brand is unwanted. + +Extra search features are documented +[here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/), +and the three enabled are autocomplete for search suggestions (`search.suggest`) +and highlighting search terms after a user clicks on a search result +(`search.highlight`). + +For navigation plugins (documented +[here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/)), +we request the side navigation to be expanded by default (`naviation.expand`) +and that the URL autoupdate to the latest anchor as a user scrolls through the +page (`naviation.tracking`). Finally, we request that the current user section +is always shown and highlight in the sidebar via `toc.follow`. + +In the palette section (documented +[here](https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/)) +you can easily modify the scheme, icons, primary colours, and accents for both +light and dark themes. ```yaml theme: @@ -368,7 +401,7 @@ theme: - navigation.tracking - toc.follow palette: - # See options to customise your colour scheme here: + # See options to customise your colour scheme here: # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ - media: "(prefers-color-scheme: light)" scheme: default @@ -384,10 +417,11 @@ theme: Onto the best part of MkDocs: it's many plugins! -* `search` enabled search functionality. -* [`autorefs`](https://mkdocstrings.github.io/autorefs/) allows easier linking across pages and anchors. -* [`mkdocstrings`](https://mkdocstrings.github.io/) lets you generate reference API documentation from your docstring. - +- `search` enabled search functionality. +- [`autorefs`](https://mkdocstrings.github.io/autorefs/) allows easier linking + across pages and anchors. +- [`mkdocstrings`](https://mkdocstrings.github.io/) lets you generate reference + API documentation from your docstring. ```yaml plugins: @@ -409,7 +443,10 @@ plugins: search: {} ``` -Finally, we have to define the actual structure of our site by providing the primary navigation sidebar layout. Here we have three top-level links, one for the home page and one where all the generated API documentation from `mkdocstrings` will live. +Finally, we have to define the actual structure of our site by providing the +primary navigation sidebar layout. Here we have three top-level links, one for +the home page and one where all the generated API documentation from +`mkdocstrings` will live. ```yaml nav: @@ -453,8 +490,7 @@ build: -{% endtab %} -{% tab mkdocs MkDocs %} +{% endtab %} {% tab mkdocs MkDocs %} - {% endtab %} {% endtabs %} This sets the Read the Docs config version (2 is required) {% rr RTD101 %}. The `build` table is the modern way to specify a runner. You need an `os` (a -modern Ubuntu should be fine) {% rr RTD102 %} and a `tools` table (we'll use Python -{% rr RTD103 %}, several languages are supported here). +modern Ubuntu should be fine) {% rr RTD102 %} and a `tools` table (we'll use +Python {% rr RTD103 %}, several languages are supported here). -Finally, we have a `commands` table which describes how to install our dependencies and -build the documentation into the ReadTheDocs output directory. +Finally, we have a `commands` table which describes how to install our +dependencies and build the documentation into the ReadTheDocs output directory. ### noxfile.py additions @@ -499,7 +534,6 @@ Add a session to your `noxfile.py` to generate docs: {% tabs %} {% tab sphinx Sphinx %} - - This is a more complex Nox job just because it's taking some options (the ability to build and serve instead of just build). The first portion is just setting up argument parsing so we can serve if building `html`. Then it does @@ -554,9 +587,7 @@ and run either the autobuild (for `--serve`) or regular build. We could have just added `python -m http.server` pointing at the built documentation, but autobuild will rebuild if you change a file while serving. -{% endtab %} -{% tab mkdocs MkDocs %} - +{% endtab %} {% tab mkdocs MkDocs %} -This Nox job will invoke MkDocs to serve a live copy of your documentation under a local endpoint, such as `http://localhost:8080` (the link will be in the job output). By requesting a `serve` instead of a `build`, any time documentation or the source code is changed, the documentation will automatically update. For documentation on how to configure what directories are watched for changes, [consult the MkDocs configuration page](https://www.mkdocs.org/user-guide/configuration/#live-reloading). +This Nox job will invoke MkDocs to serve a live copy of your documentation under +a local endpoint, such as `http://localhost:8080` (the link will be in the job +output). By requesting a `serve` instead of a `build`, any time documentation or +the source code is changed, the documentation will automatically update. For +documentation on how to configure what directories are watched for changes, +[consult the MkDocs configuration page](https://www.mkdocs.org/user-guide/configuration/#live-reloading). {% endtab %} {% endtabs %} - ## API docs {% tabs %} {% tab sphinx Sphinx %} @@ -633,12 +668,18 @@ api/ Note that your docstrings are still parsed as reStructuredText. -{% endtab %} -{% tab mkdocs MkDocs %} +{% endtab %} {% tab mkdocs MkDocs %} -API documentation can be built from your docstring using the `mkdocstrings` plugin, as referenced previously. Unlike with Sphinx, which requires a direct invocation of `sphinx-apidoc`, MkDocs plugins are integrated into the MkDocs build. +API documentation can be built from your docstring using the `mkdocstrings` +plugin, as referenced previously. Unlike with Sphinx, which requires a direct +invocation of `sphinx-apidoc`, MkDocs plugins are integrated into the MkDocs +build. -All `mkdocstrings` requires is your markdown files to specify what module, class, or function you would like documented in said file. See the [`mkdocstring` Usage page](https://mkdocstrings.github.io/usage/) for more details, but for a minimal example, if you add an `api.md` file and set its contents to: +All `mkdocstrings` requires is your markdown files to specify what module, +class, or function you would like documented in said file. See the +[`mkdocstring` Usage page](https://mkdocstrings.github.io/usage/) for more +details, but for a minimal example, if you add an `api.md` file and set its +contents to: ```markdown # Documentation for `my_package.my_module` @@ -646,16 +687,18 @@ All `mkdocstrings` requires is your markdown files to specify what module, class ::: my_package.my_module ``` -Where the triple colon syntax is used to specify what documentation you would like built. In this case, we are asking to document the entire module `my_class` (and all classes and functions within it) which is located in `my_package`. You could instead ask for only a single component inside your module by being more specific, like `::: my_package.my_module.MyClass`. +Where the triple colon syntax is used to specify what documentation you would +like built. In this case, we are asking to document the entire module `my_class` +(and all classes and functions within it) which is located in `my_package`. You +could instead ask for only a single component inside your module by being more +specific, like `::: my_package.my_module.MyClass`. -{% endtab %} -{% endtabs %} +{% endtab %} {% endtabs %} ## Notebooks in docs {% tabs %} {% tab sphinx Sphinx %} - You can combine notebooks into your docs. The tool for this is `nbsphinx`. If you want to use it, add `nbsphinx` and `ipykernel` to your documentation requirements, add `"nbsphinx"` to your `conf.py`'s `extensions =` list, and add @@ -683,23 +726,32 @@ for this to work. CI services like readthedocs usually have it installed. If you want to use Markdown instead of notebooks, you can use jupytext (see [here](https://nbsphinx.readthedocs.io/en/0.9.2/a-markdown-file.html)). -{% endtab %} -{% tab mkdocs MkDocs %} +{% endtab %} {% tab mkdocs MkDocs %} -You can combine notebooks into your docs. The plugin for this is `mkdocs-jupyter`, and configuration is detailed [here](https://github.com/danielfrg/mkdocs-jupyter) and you can find examples [here](https://mkdocs-jupyter.danielfrg.com/). +You can combine notebooks into your docs. The plugin for this is +`mkdocs-jupyter`, and configuration is detailed +[here](https://github.com/danielfrg/mkdocs-jupyter) and you can find examples +[here](https://mkdocs-jupyter.danielfrg.com/). -Once you have a notebook (which has been run and populated with results, as the plugin will not execute your notebooks for you), you simply need to add a link to the notebook in your `mkdocs.yml` navigation. +Once you have a notebook (which has been run and populated with results, as the +plugin will not execute your notebooks for you), you simply need to add a link +to the notebook in your `mkdocs.yml` navigation. ```yaml nav: - - Home: index.md - - Notebook page: notebook.ipynb - - Python file: python_script.py + - Home: index.md + - Notebook page: notebook.ipynb + - Python file: python_script.py plugins: - - mkdocs-jupyter + - mkdocs-jupyter ``` -Note that the `mkdocs-jupyter` plugin allows you to include both python scripts and notebooks. If you have a directory of example python files to run, consider [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/) as an alternative. For an external example, the [ChainConsumer docs](https://samreay.github.io/ChainConsumer/generated/gallery/) show `mkdocs-gallery` in action. +Note that the `mkdocs-jupyter` plugin allows you to include both python scripts +and notebooks. If you have a directory of example python files to run, consider +[`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/) as an alternative. +For an external example, the +[ChainConsumer docs](https://samreay.github.io/ChainConsumer/generated/gallery/) +show `mkdocs-gallery` in action. {% endtab %} {% endtabs %} diff --git a/docs/pages/tutorials/docs.md b/docs/pages/tutorials/docs.md index 9fd9e587..336aedd5 100644 --- a/docs/pages/tutorials/docs.md +++ b/docs/pages/tutorials/docs.md @@ -34,20 +34,19 @@ Some **bold** or _italicized_ text! This documentation "source code" is then _built_ into a formats like HTML or PDF to be displayed to the user. -There are a variety of tools that can do this. In this guide we will present two approaches that are mainstream in -the scientific Python community: the [Sphinx][] -documentation generator with the [MyST][] plugin, and the [MkDocs][] generator -via [mkdocs-material][]. - -For more details, examples to help you pick between Sphinx and MkDocs (and instructions for the latter), -see the [documentation guide][]. For this simple introduction, we will use Sphinx as it is still -more popular with scientific libraries, even though MkDocs is simpler to set up and more popular in -general with Python documentation. - - -Please refer to the MyST -documentation for more information on the Markdown syntax in general and MyST's -flavor of Markdown in particular. +There are a variety of tools that can do this. In this guide we will present two +approaches that are mainstream in the scientific Python community: the +[Sphinx][] documentation generator with the [MyST][] plugin, and the [MkDocs][] +generator via [mkdocs-material][]. + +For more details, examples to help you pick between Sphinx and MkDocs (and +instructions for the latter), see the [documentation guide][]. For this simple +introduction, we will use Sphinx as it is still more popular with scientific +libraries, even though MkDocs is simpler to set up and more popular in general +with Python documentation. + +Please refer to the MyST documentation for more information on the Markdown +syntax in general and MyST's flavor of Markdown in particular. We'll start with a very basic template. Start by create a `docs/` directory within your project (i.e. next to `src/`). From 3f6575dc54599e76ae8986797880a86cd4b8d754 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 5 Nov 2025 23:54:08 -0500 Subject: [PATCH 09/21] WIP: some additions Signed-off-by: Henry Schreiner --- README.md | 1 + docs/pages/guides/docs.md | 9 ++- noxfile.py | 74 ++++++++++++++---------- src/sp_repo_review/checks/readthedocs.py | 28 +++++++++ 4 files changed, 80 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7d109b8d..32255bf4 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,7 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1 - [`RTD101`](https://learn.scientific-python.org/development/guides/docs#RTD101): You have to set the RTD version number to 2 - [`RTD102`](https://learn.scientific-python.org/development/guides/docs#RTD102): You have to set the RTD build image - [`RTD103`](https://learn.scientific-python.org/development/guides/docs#RTD103): You have to set the RTD python version +- [`RTD104`](https://learn.scientific-python.org/development/guides/docs#RTD104): You have to specify a build configuration now for readthedocs. ### GitHub Actions - [`GH100`](https://learn.scientific-python.org/development/guides/gha-basic#GH100): Has GitHub Actions config diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 0bcdbc19..aeffcea3 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -64,11 +64,14 @@ Ideally, software documentation should include: @@ -494,7 +497,7 @@ build: ```yaml @@ -591,7 +594,7 @@ autobuild will rebuild if you change a file while serving. ```python diff --git a/noxfile.py b/noxfile.py index f60c0be1..aab7e662 100755 --- a/noxfile.py +++ b/noxfile.py @@ -15,6 +15,7 @@ import difflib import email.parser import email.policy +import enum import functools import json import os @@ -38,6 +39,11 @@ nox.options.default_venv_backend = "uv|virtualenv" +class Docs(enum.Enum): + Sphinx = "sphinx" + MkDocs = "mkdocs" + + DIR = Path(__file__).parent.resolve() with DIR.joinpath("cookiecutter.json").open() as f: BACKENDS = json.load(f)["backend"] @@ -47,6 +53,7 @@ project_name: cookie-{backend} backend: {backend} vcs: {vcs} + docs: {docs} """ @@ -66,7 +73,7 @@ def get_expected_version(backend: str, vcs: bool) -> str: return "0.2.3" if vcs and backend not in {"maturin", "mesonpy", "uv"} else "0.1.0" -def make_copier(session: nox.Session, backend: str, vcs: bool) -> Path: +def make_copier(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> Path: package_dir = Path(f"copy-{backend}") if package_dir.exists(): rmtree_ro(package_dir) @@ -86,6 +93,7 @@ def make_copier(session: nox.Session, backend: str, vcs: bool) -> Path: "--data=email=me@email.com", "--data=license=BSD", f"--data=vcs={vcs}", + f"--data=docs={docs.value}", ) init_git(session, package_dir) @@ -93,13 +101,13 @@ def make_copier(session: nox.Session, backend: str, vcs: bool) -> Path: return package_dir -def make_cookie(session: nox.Session, backend: str, vcs: bool) -> Path: +def make_cookie(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> Path: package_dir = Path(f"cookie-{backend}") if package_dir.exists(): rmtree_ro(package_dir) Path("input.yml").write_text( - JOB_FILE.format(backend=backend, vcs=vcs), encoding="utf-8" + JOB_FILE.format(backend=backend, vcs=vcs, docs=docs.value), encoding="utf-8" ) session.run( @@ -114,7 +122,7 @@ def make_cookie(session: nox.Session, backend: str, vcs: bool) -> Path: return package_dir -def make_cruft(session: nox.Session, backend: str, vcs: bool) -> Path: +def make_cruft(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> Path: package_dir = Path(f"cruft-{backend}") if package_dir.exists(): rmtree_ro(package_dir) @@ -124,7 +132,8 @@ def make_cruft(session: nox.Session, backend: str, vcs: bool) -> Path: session.cd(tmp_dir) Path("input.yml").write_text( - JOB_FILE.format(backend=backend, pkg=package_dir, vcs=vcs), encoding="utf-8" + JOB_FILE.format(backend=backend, pkg=package_dir, vcs=vcs, docs=docs), + encoding="utf-8", ) session.run( "cruft", @@ -189,14 +198,15 @@ def diff_files(p1: Path, p2: Path) -> bool: @nox.session(default=False) +@nox.parametrize("docs", list(Docs), ids=[d.value for d in Docs]) @nox.parametrize("vcs", [False, True], ids=["novcs", "vcs"]) @nox.parametrize("backend", BACKENDS, ids=BACKENDS) -def lint(session: nox.Session, backend: str, vcs: bool) -> None: +def lint(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> None: session.install("cookiecutter", "pre-commit") tmp_dir = session.create_tmp() session.cd(tmp_dir) - cookie = make_cookie(session, backend, vcs) + cookie = make_cookie(session, backend, vcs, docs) session.chdir(cookie) session.run( @@ -215,7 +225,7 @@ def autoupdate(session: nox.Session, backend: str) -> None: tmp_dir = session.create_tmp() session.cd(tmp_dir) - cookie = make_cookie(session, backend, True) + cookie = make_cookie(session, backend, True, Docs.Sphinx) session.chdir(cookie) session.run("pre-commit", "autoupdate") @@ -223,14 +233,15 @@ def autoupdate(session: nox.Session, backend: str) -> None: @nox.session(default=False) +@nox.parametrize("docs", list(Docs), ids=[d.value for d in Docs]) @nox.parametrize("vcs", [False, True], ids=["novcs", "vcs"]) @nox.parametrize("backend", BACKENDS, ids=BACKENDS) -def tests(session: nox.Session, backend: str, vcs: bool) -> None: +def tests(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> None: session.install("cookiecutter") tmp_dir = session.create_tmp() session.cd(tmp_dir) - cookie = make_cookie(session, backend, vcs) + cookie = make_cookie(session, backend, vcs, docs) session.chdir(cookie) name = f"cookie-{backend}" @@ -249,14 +260,15 @@ def tests(session: nox.Session, backend: str, vcs: bool) -> None: @nox.session(default=False) +@nox.parametrize("docs", list(Docs), ids=[d.value for d in Docs]) @nox.parametrize("vcs", [False, True], ids=["novcs", "vcs"]) @nox.parametrize("backend", ("poetry", "pdm", "hatch"), ids=("poetry", "pdm", "hatch")) -def native(session: nox.Session, backend: str, vcs: bool) -> None: +def native(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> None: session.install("cookiecutter", backend) tmp_dir = session.create_tmp() session.cd(tmp_dir) - cookie = make_cookie(session, backend, vcs) + cookie = make_cookie(session, backend, vcs, docs) session.chdir(cookie) if backend == "hatch": @@ -270,14 +282,15 @@ def native(session: nox.Session, backend: str, vcs: bool) -> None: @nox.session(default=False) +@nox.parametrize("docs", list(Docs), ids=[d.value for d in Docs]) @nox.parametrize("vcs", [False, True], ids=["novcs", "vcs"]) @nox.parametrize("backend", BACKENDS, ids=BACKENDS) -def dist(session: nox.Session, backend: str, vcs: bool) -> None: +def dist(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> None: session.install("cookiecutter", "build", "twine") tmp_dir = session.create_tmp() session.cd(tmp_dir) - cookie = make_cookie(session, backend, vcs) + cookie = make_cookie(session, backend, vcs, docs) session.chdir(cookie) session.run("python", "-m", "build", silent=True) @@ -333,14 +346,15 @@ def dist(session: nox.Session, backend: str, vcs: bool) -> None: @nox.session(name="nox", default=False) +@nox.parametrize("docs", list(Docs), ids=[d.value for d in Docs]) @nox.parametrize("vcs", [False, True], ids=["novcs", "vcs"]) @nox.parametrize("backend", BACKENDS, ids=BACKENDS) -def nox_session(session: nox.Session, backend: str, vcs: bool) -> None: +def nox_session(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> None: session.install("cookiecutter", "nox") tmp_dir = session.create_tmp() session.cd(tmp_dir) - cookie = make_cookie(session, backend, vcs) + cookie = make_cookie(session, backend, vcs, docs) session.chdir(cookie) if session.posargs: @@ -358,13 +372,14 @@ def compare_copier(session): for backend in BACKENDS: for vcs in (False, True): - cookie = make_cookie(session, backend, vcs) - copier = make_copier(session, backend, vcs) + for docs in Docs: + cookie = make_cookie(session, backend, vcs, docs) + copier = make_copier(session, backend, vcs, docs) - if diff_files(cookie, copier): - session.log(f"{backend} {vcs=} passed") - else: - session.error(f"{backend} {vcs=} files are not the same!") + if diff_files(cookie, copier): + session.log(f"{backend} {vcs=} passed") + else: + session.error(f"{backend} {vcs=} files are not the same!") @nox.session(default=False) @@ -376,13 +391,14 @@ def compare_cruft(session): for backend in BACKENDS: for vcs in (False, True): - cookie = make_cookie(session, backend, vcs) - cruft = make_cruft(session, backend, vcs) - - if diff_files(cookie, cruft): - session.log(f"{backend} {vcs=} passed") - else: - session.error(f"{backend} {vcs=} files are not the same!") + for docs in Docs: + cookie = make_cookie(session, backend, vcs, docs) + cruft = make_cruft(session, backend, vcs, docs) + + if diff_files(cookie, cruft): + session.log(f"{backend} {vcs=} passed") + else: + session.error(f"{backend} {vcs=} files are not the same!") PC_VERS = re.compile( diff --git a/src/sp_repo_review/checks/readthedocs.py b/src/sp_repo_review/checks/readthedocs.py index 898b35cf..4f69c56d 100644 --- a/src/sp_repo_review/checks/readthedocs.py +++ b/src/sp_repo_review/checks/readthedocs.py @@ -104,6 +104,34 @@ def check(readthedocs: dict[str, Any]) -> bool: return False +class RTD104(ReadTheDocs): + "You have to specify a build configuration now for readthedocs." + + requires = {"RTD100"} + + @staticmethod + def check(readthedocs: dict[str, Any]) -> bool: + """ + You must set `sphinx: configuration:`, `mkdocs: configuration:` or + `build: commands:`. Skipping it is no longer allowed. Example: + + ```yaml + sphinx: + configuration: docs/conf.py + ``` + """ + + match readthedocs: + case {"build": {"commands": list()}}: + return True + case {"sphinx": {"configuration": str()}}: + return True + case {"mkdocs": {"configuration": str()}}: + return True + case _: + return False + + def readthedocs(root: Traversable) -> dict[str, Any]: for path in (".readthedocs.yaml", ".readthedocs.yml"): readthedocs_path = root.joinpath(path) From e80e7f0b0c4f506c1ed51c38fe3a059dd9d91593 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 00:21:48 -0500 Subject: [PATCH 10/21] fix: can't make a directory conditional Signed-off-by: Henry Schreiner --- cookiecutter.json | 2 +- docs/pages/guides/docs.md | 23 +++++++++++++------ pyproject.toml | 5 ++++ ...tter.docs == 'mkdocs' %}api.md{% endif %}} | 0 ...er.docs == 'mkdocs' %}index.md{% endif %}} | 0 ...ter.docs == 'sphinx' %}conf.py{% endif %}} | 0 ...er.docs == 'sphinx' %}index.md{% endif %}} | 0 ...ter.docs=='mkdocs' %}mkdocs.yml{% endif %} | 2 +- 8 files changed, 23 insertions(+), 9 deletions(-) rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md => docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %}} (100%) rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/index.md => docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %}} (100%) rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/conf.py => docs/{% if cookiecutter.docs == 'sphinx' %}conf.py{% endif %}} (100%) rename {{cookiecutter.project_name}}/{{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/index.md => docs/{% if cookiecutter.docs == 'sphinx' %}index.md{% endif %}} (100%) diff --git a/cookiecutter.json b/cookiecutter.json index f6bef0ce..7379fec5 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -47,7 +47,7 @@ "maturin": "Maturin - Compiled Rust (recommended)" }, "docs": { - "__prompt__": "Chose your documentation tool", + "__prompt__": "Choose your documentation tool", "sphinx": "Sphinx", "mkdocs": "MkDocs" }, diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index aeffcea3..53562b6f 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -69,7 +69,8 @@ with render_cookie(backend="hatch", docs="sphinx") as package: docs_index_md = package.joinpath("docs/index.md").read_text(encoding="utf-8").strip() readthedocs_yaml = package.joinpath(".readthedocs.yaml").read_text(encoding="utf-8").strip() noxfile = Matcher.from_file(package / "noxfile.py") -with render_cookie(backend="hatch", docs="sphinx") as package: +with render_cookie(backend="hatch", docs="mkdocs") as package: + mkdocs_conf_yaml = package.joinpath("mkdocs.yml").read_text(encoding="utf-8").strip() noxfile_mkdocs = Matcher.from_file(package / "noxfile.py") readthedocs_yaml_mkdocs = package.joinpath(".readthedocs.yaml").read_text(encoding="utf-8").strip() ]]] --> @@ -304,12 +305,18 @@ example configuration now. Here's the whole file for completeness. We'll break it into sections underneath. + + ```yaml -site_name: some_project -site_url: https://some_project.readthedocs.io/ -site_author: "Bruce Wayne" -repo_name: "wayne_industries/some_project" -repo_url: "https://github.com/wayne_industries/some_project" +site_name: package +site_url: https://package.readthedocs.io/ +site_author: "My Name" + +repo_name: "org/package" +repo_url: "https://github.com/org/package" theme: name: material @@ -357,6 +364,8 @@ nav: - Home: index.md - Python API: api.md ``` + + First, the basic site metadata contains authors, repository details, URLs, etc: @@ -605,7 +614,7 @@ def docs(session: nox.Session) -> None: """ doc_deps = nox.project.dependency_groups(PROJECT, "docs") - session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *doc_deps) + session.install("-e.", *doc_deps) session.run("mkdocs", "serve", "--clean") ``` diff --git a/pyproject.toml b/pyproject.toml index f6af1144..a72047f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ scikit-hep = "sp_repo_review.families:get_families" [dependency-groups] dev = [ { include-group = "test" }, + { include-group = "cog" }, "repo-review[cli]", "validate-pyproject-schema-store[all]", ] @@ -90,6 +91,10 @@ test = [ "pytest >=7", "repo-review >=0.10.6", ] +cog = [ + "cogapp", + "cookiecutter", +] [tool.hatch] version.source = "vcs" diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/api.md rename to {{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/index.md b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'mkdocs' %}docs{% endif %}/index.md rename to {{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/conf.py b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'sphinx' %}conf.py{% endif %} similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/conf.py rename to {{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'sphinx' %}conf.py{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/index.md b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'sphinx' %}index.md{% endif %} similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.docs == 'sphinx' %}docs{% endif %}/index.md rename to {{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'sphinx' %}index.md{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} index 95e3bd14..f67a2e2c 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} @@ -16,7 +16,7 @@ theme: - navigation.tracking - toc.follow palette: - # See options to customise your colour scheme here: + # See options to customise your colour scheme here: # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ - media: "(prefers-color-scheme: light)" scheme: default From e635e8039e4c4f4073c65850eef9e605bcf474a8 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 00:31:21 -0500 Subject: [PATCH 11/21] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/pages/guides/docs.md | 6 +++--- noxfile.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 53562b6f..f42ebde7 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -391,9 +391,9 @@ and highlighting search terms after a user clicks on a search result For navigation plugins (documented [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/)), -we request the side navigation to be expanded by default (`naviation.expand`) +we request the side navigation to be expanded by default (`navigation.expand`) and that the URL autoupdate to the latest anchor as a user scrolls through the -page (`naviation.tracking`). Finally, we request that the current user section +page (`navigation.tracking`). Finally, we request that the current user section is always shown and highlight in the sidebar via `toc.follow`. In the palette section (documented @@ -700,7 +700,7 @@ contents to: ``` Where the triple colon syntax is used to specify what documentation you would -like built. In this case, we are asking to document the entire module `my_class` +like built. In this case, we are asking to document the entire module `my_module` (and all classes and functions within it) which is located in `my_package`. You could instead ask for only a single component inside your module by being more specific, like `::: my_package.my_module.MyClass`. diff --git a/noxfile.py b/noxfile.py index aab7e662..fcdad23f 100755 --- a/noxfile.py +++ b/noxfile.py @@ -132,7 +132,7 @@ def make_cruft(session: nox.Session, backend: str, vcs: bool, docs: Docs) -> Pat session.cd(tmp_dir) Path("input.yml").write_text( - JOB_FILE.format(backend=backend, pkg=package_dir, vcs=vcs, docs=docs), + JOB_FILE.format(backend=backend, pkg=package_dir, vcs=vcs, docs=docs.value), encoding="utf-8", ) session.run( From 494e6576411435c5073d826a656cf6f7a803f664 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 05:31:57 +0000 Subject: [PATCH 12/21] style: pre-commit fixes --- docs/pages/guides/docs.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index f42ebde7..87b8a5ab 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -700,10 +700,10 @@ contents to: ``` Where the triple colon syntax is used to specify what documentation you would -like built. In this case, we are asking to document the entire module `my_module` -(and all classes and functions within it) which is located in `my_package`. You -could instead ask for only a single component inside your module by being more -specific, like `::: my_package.my_module.MyClass`. +like built. In this case, we are asking to document the entire module +`my_module` (and all classes and functions within it) which is located in +`my_package`. You could instead ask for only a single component inside your +module by being more specific, like `::: my_package.my_module.MyClass`. {% endtab %} {% endtabs %} From e9dafb543d4dbc6716e4551d71b7b68c4414a4c7 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 00:34:58 -0500 Subject: [PATCH 13/21] fix: minor fixup from looking at the diff Signed-off-by: Henry Schreiner --- {{cookiecutter.project_name}}/README.md | 3 +++ {{cookiecutter.project_name}}/noxfile.py | 1 + 2 files changed, 4 insertions(+) diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index f50e6fe1..8881b983 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -11,6 +11,9 @@ {%- if cookiecutter.org | lower == "scikit-hep" %} [![Scikit-HEP][sk-badge]](https://scikit-hep.org/) {%- endif %} +{%- if cookiecutter.docs == "sphinx" %} + +{%- endif %} [actions-badge]: {{cookiecutter.url}}/workflows/CI/badge.svg diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 74c9c0e4..5ca17682 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -77,6 +77,7 @@ def docs(session: nox.Session) -> None: else: session.run("sphinx-build", "--keep-going", *shared_args) + @nox.session(default=False) def build_api_docs(session: nox.Session) -> None: """ From 77113d3b7ee193bbc2e16f59cf71faeb4464a5af Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 09:12:58 -0500 Subject: [PATCH 14/21] fix: some styling corrections Signed-off-by: Henry Schreiner --- ...okiecutter.docs == 'mkdocs' %}index.md{% endif %} | 12 +++++++----- {{cookiecutter.project_name}}/noxfile.py | 8 ++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} index 62a1e751..231f058c 100644 --- a/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} +++ b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} @@ -1,8 +1,13 @@ # {{ cookiecutter.project_name }} -Here you can document whatever you'd like on your main page. Common choices include installation instructions, a minimal usage example, BibTex citations, and contribution guidelines. +Here you can document whatever you'd like on your main page. Common choices +include installation instructions, a minimal usage example, BibTex citations, +and contribution guidelines. -See [this link](https://squidfunk.github.io/mkdocs-material/reference/) for all the easy references and components you can use with mkdocs-material, or feel free to go through through [from the top](https://squidfunk.github.io/mkdocs-material/). +See [this link](https://squidfunk.github.io/mkdocs-material/reference/) for all +the easy references and components you can use with mkdocs-material, or feel +free to go through through [from the +top](https://squidfunk.github.io/mkdocs-material/). ## Installation @@ -11,6 +16,3 @@ You can install this package via running: ```bash pip install {{ cookiecutter.__project_slug }} ``` - - - diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 5ca17682..45109d58 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -1,6 +1,8 @@ from __future__ import annotations +{%- if cookiecutter.docs == 'sphinx' %} import argparse +{%- endif %} import shutil from pathlib import Path @@ -46,6 +48,8 @@ def tests(session: nox.Session) -> None: {%- if cookiecutter.docs == 'sphinx' %} + + @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: """ @@ -97,6 +101,8 @@ def build_api_docs(session: nox.Session) -> None: {%- elif cookiecutter.docs == 'mkdocs' %} + + @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: """ @@ -109,6 +115,8 @@ def docs(session: nox.Session) -> None: {%- endif %} + + @nox.session(default=False) def build(session: nox.Session) -> None: """ From 3216b94007461e16ebd5d692335558e70a427c3f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 09:13:30 -0500 Subject: [PATCH 15/21] ci: update for new option Signed-off-by: Henry Schreiner --- .github/workflows/reusable-cookie.yml | 64 ++++++++++++++------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/.github/workflows/reusable-cookie.yml b/.github/workflows/reusable-cookie.yml index fcd8b8a4..343ed718 100644 --- a/.github/workflows/reusable-cookie.yml +++ b/.github/workflows/reusable-cookie.yml @@ -46,47 +46,49 @@ jobs: run: uv tool install nox - name: Test pybind11 - run: nox -s 'tests(pybind11, novcs)' -s 'tests(pybind11, vcs)' + run: nox -s 'tests(pybind11, novcs)' -s 'tests(pybind11, vcs, sphinx)' - name: Test scikit-build - run: nox -s 'tests(skbuild, novcs)' -s 'tests(skbuild, vcs)' + run: nox -s 'tests(skbuild, novcs)' -s 'tests(skbuild, vcs, sphinx)' - name: Test poetry - run: nox -s 'tests(poetry, novcs)' -s 'tests(poetry, vcs)' + run: nox -s 'tests(poetry, novcs)' -s 'tests(poetry, vcs, sphinx)' - name: Test flit - run: nox -s 'tests(flit, novcs)' -s 'tests(flit, vcs)' + run: nox -s 'tests(flit, novcs)' -s 'tests(flit, vcs, mkdocs)' - name: Test uv - run: nox -s 'tests(uv, novcs)' + run: nox -s 'tests(uv, novcs, sphinx)' - name: Test pdm - run: nox -s 'tests(pdm, novcs)' -s 'tests(pdm, vcs)' + run: nox -s 'tests(pdm, novcs)' -s 'tests(pdm, vcs, sphinx)' - name: Test maturin - run: nox -s 'tests(maturin, novcs)' + run: nox -s 'tests(maturin, novcs, sphinx)' - name: Test hatch - run: nox -s 'tests(hatch, novcs)' -s 'tests(hatch, vcs)' + run: nox -s 'tests(hatch, novcs, sphinx)' -s 'tests(hatch, vcs, sphinx)' - name: Test setuptools PEP 621 - run: nox -s 'tests(setuptools, novcs)' -s 'tests(setuptools, vcs)' + run: + nox -s 'tests(setuptools, novcs, sphinx)' -s 'tests(setuptools, vcs, + sphinx)' - name: Native poetry tooling if: matrix.python-version != 'pypy-3.11' run: | - nox -s 'native(poetry, novcs)' - nox -s 'native(poetry, vcs)' + nox -s 'native(poetry, novcs, sphinx)' + nox -s 'native(poetry, vcs, sphinx)' - name: Native pdm tooling - run: nox -s 'native(pdm, novcs)' -s 'native(pdm, vcs)' + run: nox -s 'native(pdm, novcs, sphinx)' -s 'native(pdm, vcs, sphinx)' - name: Activate MSVC for Meson if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 - name: Test meson-python - run: nox -s 'tests(mesonpy, novcs)' + run: nox -s 'tests(mesonpy, novcs, sphinx)' - name: Compare copier template generation run: nox -s compare_copier @@ -114,43 +116,43 @@ jobs: - name: Test pybind11 run: | - nox -s 'nox(pybind11, vcs)' - nox -s 'nox(pybind11, vcs)' -- docs + nox -s 'nox(pybind11, vcs, sphinx)' + nox -s 'nox(pybind11, vcs, sphinx)' -- docs - name: Test scikit-build run: | - nox -s 'nox(skbuild, vcs)' - nox -s 'nox(skbuild, vcs)' -- docs + nox -s 'nox(skbuild, vcs, sphinx)' + nox -s 'nox(skbuild, vcs, mkdocs)' -- docs - name: Test poetry run: | - nox -s 'nox(poetry, novcs)' - nox -s 'nox(poetry, novcs)' -- docs + nox -s 'nox(poetry, novcs, sphinx)' + nox -s 'nox(poetry, novcs, mkdocs)' -- docs - name: Test flit run: | - nox -s 'nox(flit, novcs)' - nox -s 'nox(flit, novcs)' -- docs + nox -s 'nox(flit, novcs, mkdocs)' + nox -s 'nox(flit, novcs, mkdocs)' -- docs - name: Test pdm run: | - nox -s 'nox(pdm, vcs)' - nox -s 'nox(pdm, vcs)' -- docs + nox -s 'nox(pdm, vcs, sphinx)' + nox -s 'nox(pdm, vcs, sphinx)' -- docs - name: Test maturin run: | - nox -s 'nox(maturin, novcs)' - nox -s 'nox(maturin, novcs)' -- docs + nox -s 'nox(maturin, novcs, sphinx)' + nox -s 'nox(maturin, novcs, sphinx)' -- docs - name: Test hatch run: | - nox -s 'nox(hatch, vcs)' - nox -s 'nox(hatch, vcs)' -- docs + nox -s 'nox(hatch, vcs, mkdocs)' + nox -s 'nox(hatch, vcs, mkdocs)' -- docs - name: Test setuptools PEP 621 run: | - nox -s 'nox(setuptools, vcs)' - nox -s 'nox(setuptools, vcs)' -- docs + nox -s 'nox(setuptools, vcs, sphinx)' + nox -s 'nox(setuptools, vcs, sphinx)' -- docs - name: Activate MSVC for Meson if: runner.os == 'Windows' @@ -158,8 +160,8 @@ jobs: - name: Test meson-python run: | - nox -s 'nox(mesonpy, novcs)' - nox -s 'nox(mesonpy, novcs)' -- docs + nox -s 'nox(mesonpy, novcs, sphinx)' + nox -s 'nox(mesonpy, novcs, sphinx)' -- docs dist: name: Distribution build From 8037c2c23db86ac841cc4ee0cd5ac60027a05e3e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 10:15:00 -0500 Subject: [PATCH 16/21] chore: more style adjustments Signed-off-by: Henry Schreiner --- {{cookiecutter.project_name}}/README.md | 1 + .../{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} | 2 +- .../{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} | 4 ++-- {{cookiecutter.project_name}}/noxfile.py | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index 8881b983..8fab55ce 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -12,6 +12,7 @@ [![Scikit-HEP][sk-badge]](https://scikit-hep.org/) {%- endif %} {%- if cookiecutter.docs == "sphinx" %} + {%- endif %} diff --git a/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} index d9df5579..aa2f887b 100644 --- a/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} +++ b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}api.md{% endif %} @@ -1 +1 @@ -# ::: {{cookiecutter.__project_slug}}.example \ No newline at end of file +# ::: {{cookiecutter.__project_slug}}.example diff --git a/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} index 231f058c..96c94cb2 100644 --- a/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} +++ b/{{cookiecutter.project_name}}/docs/{% if cookiecutter.docs == 'mkdocs' %}index.md{% endif %} @@ -6,8 +6,8 @@ and contribution guidelines. See [this link](https://squidfunk.github.io/mkdocs-material/reference/) for all the easy references and components you can use with mkdocs-material, or feel -free to go through through [from the -top](https://squidfunk.github.io/mkdocs-material/). +free to go through through +[from the top](https://squidfunk.github.io/mkdocs-material/). ## Installation diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 45109d58..6adc83b2 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -1,6 +1,7 @@ from __future__ import annotations {%- if cookiecutter.docs == 'sphinx' %} + import argparse {%- endif %} import shutil From d883c53093fdfb3618a7c449b2a32349c8452e71 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 11:09:32 -0500 Subject: [PATCH 17/21] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index fcdad23f..f0d0104f 100755 --- a/noxfile.py +++ b/noxfile.py @@ -379,7 +379,7 @@ def compare_copier(session): if diff_files(cookie, copier): session.log(f"{backend} {vcs=} passed") else: - session.error(f"{backend} {vcs=} files are not the same!") + session.error(f"{backend} {vcs=} {docs=} files are not the same!") @nox.session(default=False) @@ -398,7 +398,7 @@ def compare_cruft(session): if diff_files(cookie, cruft): session.log(f"{backend} {vcs=} passed") else: - session.error(f"{backend} {vcs=} files are not the same!") + session.error(f"{backend} {vcs=} {docs=} files are not the same!") PC_VERS = re.compile( From 7e938b188b7b30d3bcd2d309588df7790f02250c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 11:14:38 -0500 Subject: [PATCH 18/21] chore: colour -> color Signed-off-by: Henry Schreiner --- docs/pages/guides/docs.md | 6 +++--- ...% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 87b8a5ab..262ee3f5 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -329,7 +329,7 @@ theme: - navigation.tracking - toc.follow palette: - # See options to customise your colour scheme here: + # See options to customise your color scheme here: # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ - media: "(prefers-color-scheme: light)" scheme: default @@ -398,7 +398,7 @@ is always shown and highlight in the sidebar via `toc.follow`. In the palette section (documented [here](https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/)) -you can easily modify the scheme, icons, primary colours, and accents for both +you can easily modify the scheme, icons, primary colors, and accents for both light and dark themes. ```yaml @@ -413,7 +413,7 @@ theme: - navigation.tracking - toc.follow palette: - # See options to customise your colour scheme here: + # See options to customise your color scheme here: # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ - media: "(prefers-color-scheme: light)" scheme: default diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} index f67a2e2c..979d0fa3 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.docs=='mkdocs' %}mkdocs.yml{% endif %} @@ -16,7 +16,7 @@ theme: - navigation.tracking - toc.follow palette: - # See options to customise your colour scheme here: + # See options to customise your color scheme here: # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ - media: "(prefers-color-scheme: light)" scheme: default From 9b4cd8c6a2529dda37fad0d675346ea0b5b3f036 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 11:18:21 -0500 Subject: [PATCH 19/21] chore: a few more updates inspired by copilot Signed-off-by: Henry Schreiner --- docs/pages/guides/docs.md | 10 +++++----- {{cookiecutter.project_name}}/pyproject.toml | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index 262ee3f5..e00e444a 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -271,7 +271,7 @@ While the cookie cutter creates a basic structure for your MkDocs (a top level `mkdocs.yml` file and the `docs` directory), you can also follow the official [Getting started](https://squidfunk.github.io/mkdocs-material/getting-started/) guide instead. Note, however, instead of the `pip` install, it is better -practise install your documentation dependencies via `pyproject.toml` and then +practice install your documentation dependencies via `pyproject.toml` and then when you run your `uv sync` to install dependencies, you can explicitly ask for the `docs` group to be installed via `uv sync --group=docs` or `uv sync --all-groups`. @@ -283,12 +283,12 @@ repository, you will already have this group. Otherwise, add to your ```toml [dependency-groups] docs = [ - "Markdown>=3.9", - "mkdocs>=1.1.2,<2", - "mkdocs-material>=9.1.19,<10", - "pyyaml>=6.0.1", + "markdown>=3.9", "mdx-include>=1.4.2", + "mkdocs-material>=9.1.19", + "mkdocs>=1.1.2,", "mkdocstrings-python>=1.18.2", + "pyyaml>=6.0.1", ] ``` diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index b5e4a49d..ab699889 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -135,11 +135,12 @@ docs = [ ] {%- elif cookiecutter.docs == "mkdocs" %} docs = [ - "Markdown>=3.9", - "mkdocs>=1.1.2,<2", - "mkdocs-material>=9.1.19,<10", - "pyyaml>=6.0.1", + "markdown>=3.9", + "mdx-include>=1.4.2", + "mkdocs-material>=9.1.19", + "mkdocs>=1.1.2", "mkdocstrings-python>=1.18.2", + "pyyaml>=6.0.1", ] {%- endif %} From 065e1d207a44dbb6581f7ccdf246b6f787877c42 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 11:59:24 -0500 Subject: [PATCH 20/21] chore: fix one more space Signed-off-by: Henry Schreiner --- {{cookiecutter.project_name}}/noxfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 6adc83b2..173dcf7d 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -3,7 +3,9 @@ {%- if cookiecutter.docs == 'sphinx' %} import argparse + {%- endif %} + import shutil from pathlib import Path From 2722f8da02a3a77117960c0e97efa5991cb2670a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 6 Nov 2025 12:02:09 -0500 Subject: [PATCH 21/21] fix: serve or build mkdocs Signed-off-by: Henry Schreiner --- docs/pages/guides/docs.md | 8 ++++++-- {{cookiecutter.project_name}}/noxfile.py | 15 ++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/pages/guides/docs.md b/docs/pages/guides/docs.md index e00e444a..c42a9a0b 100644 --- a/docs/pages/guides/docs.md +++ b/docs/pages/guides/docs.md @@ -610,12 +610,16 @@ with code_fence("python"): @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: """ - Serve the docs + Make or serve the docs. Pass --non-interactive to avoid serving. """ doc_deps = nox.project.dependency_groups(PROJECT, "docs") session.install("-e.", *doc_deps) - session.run("mkdocs", "serve", "--clean") + + if session.interactive: + session.run("mkdocs", "serve", "--clean", *session.posargs) + else: + session.run("mkdocs", "build", "--clean", *session.posargs) ``` diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 173dcf7d..2c139679 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -1,11 +1,8 @@ from __future__ import annotations -{%- if cookiecutter.docs == 'sphinx' %} - +{% if cookiecutter.docs == 'sphinx' -%} import argparse - -{%- endif %} - +{% endif -%} import shutil from pathlib import Path @@ -109,12 +106,16 @@ def build_api_docs(session: nox.Session) -> None: @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: """ - Serve the docs + Make or serve the docs. Pass --non-interactive to avoid serving. """ doc_deps = nox.project.dependency_groups(PROJECT, "docs") session.install("{% if cookiecutter.backend != "mesonpy" %}-e{% endif %}.", *doc_deps) - session.run("mkdocs", "serve", "--clean") + + if session.interactive: + session.run("mkdocs", "serve", "--clean", *session.posargs) + else: + session.run("mkdocs", "build", "--clean", *session.posargs) {%- endif %}