Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: pip
cache-dependency-path: pyproject.toml

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Expand All @@ -26,12 +33,26 @@ jobs:
- name: Install doxygen
run: sudo apt-get install -y doxygen

# Build with the working copy (not the released yardang) so that the docs,
# and the per-theme previews below, exercise the version in this repo.
- name: Install self
run: pip install -e .[develop]

- uses: actions-ext/yardang@main
- name: Build docs
run: yardang build

# Render the full site once per bundled theme into docs/html/_previews/<theme>/
# so each theme is browsable live at a suburl of the published site.
- name: Build theme previews
run: yardang preview

- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: docs/html
force_orphan: true

- name: Build Wiki
run: yardang wiki --output-dir docs/wiki
Expand Down
55 changes: 50 additions & 5 deletions docs/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,69 @@ authors = "your project authors"
version = "0.1.0"
```

## `theme`
## [`theme`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_theme)

Defaults to `furo`.
The Sphinx HTML theme to build with. Defaults to `furo`.

```toml
[tool.yardang]
theme = "furo"
```

## [`theme`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_theme)
`yardang` ships per-theme defaults (sensible CSS tweaks, and an optional
dependency) for the following themes:

- [`furo`](https://github.com/pradyunsg/furo) (the default, always installed)
- [`sphinxawesome_theme`](https://sphinxawesome.xyz/)
- [`shibuya`](https://shibuya.lepture.com/)

Defaults to `furo`.
`furo` is always available; install the rest with `pip install yardang[themes]`.
Any other installed Sphinx theme works too — you just won't get the bundled
defaults. See [Previewing themes](#previewing-themes) below to compare them live.

## `custom-css` / `custom-js`

Inject a custom stylesheet or script. The value may be a path or raw content.
When unset, `yardang` looks for a bundled per-theme asset named `{theme}.css` /
`{theme}.js`, then falls back to the generic `custom.css` / `custom.js`. This lets
each theme ship sensible defaults — for example, `sphinxawesome_theme` and
`shibuya` hide the duplicate copy button.

```toml
[tool.yardang]
theme = "furo"
custom-css = "docs/_static/my.css"
```

## Previewing themes

Build the docs once per theme to compare them side-by-side:

```bash
yardang preview
```

This renders the documentation into `docs/html/_previews/<theme>/` for each
bundled theme (`furo`, `sphinxawesome_theme`, `shibuya`). Themes whose package is
not installed are skipped. Restrict the set with `--themes`:

```bash
yardang preview --themes furo --themes shibuya
```

Install the optional themes with:

```bash
pip install yardang[themes]
```

When this runs in CI before the GitHub Pages deploy (as in `yardang`'s own
[`docs.yaml`](https://github.com/python-project-templates/yardang/blob/main/.github/workflows/docs.yaml)),
each theme is browsable live at a suburl of the published site:

- [`/_previews/furo/`](https://yardang.python-templates.dev/_previews/furo/)
- [`/_previews/sphinxawesome_theme/`](https://yardang.python-templates.dev/_previews/sphinxawesome_theme/)
- [`/_previews/shibuya/`](https://yardang.python-templates.dev/_previews/shibuya/)

## `root`

The root page to use, defaults to `README.md`.
Expand Down
7 changes: 7 additions & 0 deletions docs/src/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ You can install from PyPI via `pip`:
pip install yardang
```

The `furo` theme is included by default. To pull in the other bundled themes
(`sphinxawesome_theme`, `shibuya`), install the `themes` extra:

```bash
pip install yardang[themes]
```

## Conda

You can install from `conda-forge` via `conda` (or `mamba`, etc):
Expand Down
6 changes: 5 additions & 1 deletion docs/src/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ Out of the box, it comes with support for several popular Sphinx frameworks:
- [Myst Markdown](https://jupyterbook.org/en/stable/content/myst.html)
- [Sphinx AutoAPI](https://sphinx-autoapi.readthedocs.io/en/latest/)
- [Autodoc Pydantic](https://autodoc-pydantic.readthedocs.io/en/stable/users/examples.html)
- [Furo Theme](https://github.com/pradyunsg/furo)
- [Furo Theme](https://github.com/pradyunsg/furo) (the default), plus bundled defaults for [`sphinxawesome_theme`](https://sphinxawesome.xyz/) and [`shibuya`](https://shibuya.lepture.com/)

`yardang preview` builds the docs once per theme so you can compare them
side-by-side. See [Previewing themes](configuration.md#previewing-themes) for the
live previews.

## Usage

Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ wiki = [
"sphinx-markdown-builder>=0.6.9",

]
themes = [
"shibuya",
"sphinxawesome-theme",
]
develop = [
"build",
"bump-my-version",
Expand All @@ -80,6 +84,9 @@ develop = [
"sphinx-rust",
"sphinx-js>=5.0.0",
"sphinx-markdown-builder>=0.6.9",
# Themes
"shibuya",
"sphinxawesome-theme",
]

[project.scripts]
Expand Down
3 changes: 2 additions & 1 deletion yardang/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
__version__ = "0.6.0"

from .build import generate_docs_configuration, generate_wiki_configuration, run_doxygen_if_needed
from .build import BUNDLED_THEMES, generate_docs_configuration, generate_wiki_configuration, run_doxygen_if_needed
from .wiki import process_wiki_output

__all__ = (
"BUNDLED_THEMES",
"generate_docs_configuration",
"generate_wiki_configuration",
"run_doxygen_if_needed",
Expand Down
98 changes: 56 additions & 42 deletions yardang/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,47 @@
from contextlib import contextmanager
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Callable, Dict, List, Optional
from typing import Callable, Dict, List, Optional, Union

from jinja2 import Environment, FileSystemLoader

from .utils import get_config, get_config_flex

__all__ = ("generate_docs_configuration", "run_doxygen_if_needed", "generate_wiki_configuration")
__all__ = ("generate_docs_configuration", "run_doxygen_if_needed", "generate_wiki_configuration", "BUNDLED_THEMES")

# Themes for which yardang ships per-theme defaults (a bundled ``{theme}.css`` and/or
# an optional dependency). Used as the default set for ``yardang preview``.
BUNDLED_THEMES = ("furo", "sphinxawesome_theme", "shibuya")


def _resolve_custom_asset(value: Optional[Union[str, Path]], theme: Optional[str], extension: str, *, assets_dir: Path) -> Optional[str]:
"""Resolve custom CSS/JS content for the docs build.

Resolution precedence:

1. An explicit ``value`` (an existing file path is read; a path-like value
that does not exist is ignored; anything else is treated as raw content).
2. A bundled theme-specific asset named ``{theme}.{extension}``.
3. A bundled generic ``custom.{extension}``.
4. ``None`` when nothing matches.
"""
if value:
try:
candidate = Path(value)
if candidate.is_file():
return candidate.read_text()
looks_like_path = str(value).endswith(f".{extension}")
except OSError:
looks_like_path = False
if not looks_like_path:
return str(value)
names = [f"{theme}.{extension}"] if theme else []
names.append(f"custom.{extension}")
for name in names:
asset = assets_dir / name
if asset.is_file():
return asset.read_text()
return None


def run_doxygen_if_needed(
Expand Down Expand Up @@ -117,6 +151,7 @@ def generate_docs_configuration(
autoapi_ignore: Optional[List] = None,
custom_css: Optional[Path] = None,
custom_js: Optional[Path] = None,
html_output_dir: Optional[str] = None,
config_base: Optional[str] = None,
previous_versions: Optional[bool] = False,
adjust_arguments: Callable = None,
Expand Down Expand Up @@ -148,8 +183,12 @@ def generate_docs_configuration(
pages: List of page paths to include in the toctree.
use_autoapi: Whether to use sphinx-autoapi for Python API docs.
Defaults to ``None`` (auto-detect).
custom_css: Path to custom CSS file. Defaults to bundled custom.css.
custom_js: Path to custom JavaScript file. Defaults to bundled custom.js.
custom_css: Path or raw content for custom CSS. When unset, falls back to a
bundled per-theme ``{theme}.css`` then the generic ``custom.css``.
custom_js: Path or raw content for custom JS. When unset, falls back to a
bundled per-theme ``{theme}.js`` then the generic ``custom.js``.
html_output_dir: Directory where Sphinx writes HTML output. Custom CSS/JS
are placed under ``<html_output_dir>/_static``. Defaults to ``docs/html``.
config_base: Base key in pyproject.toml for configuration.
Defaults to ``"tool.yardang"``.
previous_versions: Whether to generate previous versions documentation.
Expand All @@ -163,7 +202,6 @@ def generate_docs_configuration(
or the current directory if conf.py already exists.

Raises:
FileNotFoundError: If custom_css or custom_js paths don't exist.
toml.TomlDecodeError: If pyproject.toml is malformed.

Example:
Expand Down Expand Up @@ -242,38 +280,11 @@ def customize(args):
use_autoapi = use_autoapi if use_autoapi is not None else get_config_flex(section="use-autoapi", base=config_base)
autoapi_ignore = autoapi_ignore if autoapi_ignore is not None else get_config_flex(section="autoapi-ignore", base=config_base)

custom_css = (
custom_css
if custom_css is not None
else Path(get_config_flex(section="custom-css", base=config_base) or (Path(__file__).parent / "custom.css"))
)
custom_js = (
custom_js
if custom_js is not None
else Path(get_config_flex(section="custom-js", base=config_base) or (Path(__file__).parent / "custom.js"))
)

# if custom_css and custom_js are strings and they exist as paths, read them as Paths
# otherwise, assume the content is directly provided
if isinstance(custom_css, str):
custom_css_path = Path(custom_css)
# if the path is too long, it will throw
try:
if custom_css_path.exists():
custom_css = custom_css_path.read_text()
except OSError:
pass
else:
custom_css = custom_css.read_text()
if isinstance(custom_js, str):
custom_js_path = Path(custom_js)
try:
if custom_js_path.exists():
custom_js = custom_js_path.read_text()
except OSError:
pass
else:
custom_js = custom_js.read_text()
custom_css = custom_css if custom_css is not None else get_config_flex(section="custom-css", base=config_base)
custom_js = custom_js if custom_js is not None else get_config_flex(section="custom-js", base=config_base)
assets_dir = Path(__file__).parent
custom_css = _resolve_custom_asset(custom_css, theme, "css", assets_dir=assets_dir)
custom_js = _resolve_custom_asset(custom_js, theme, "js", assets_dir=assets_dir)

source_dir = os.path.curdir

Expand Down Expand Up @@ -559,11 +570,14 @@ def customize(args):
template_file = Path(td) / "conf.py"
template_file.write_text(template)

# write custom css and customjs
Path("docs/html/_static/styles").mkdir(parents=True, exist_ok=True)
Path("docs/html/_static/styles/custom.css").write_text(custom_css)
Path("docs/html/_static/js").mkdir(parents=True, exist_ok=True)
Path("docs/html/_static/js/custom.js").write_text(custom_js)
# write custom css and customjs into the html output's static dir
html_output = Path(html_output_dir) if html_output_dir is not None else Path("docs/html")
styles_dir = html_output / "_static" / "styles"
styles_dir.mkdir(parents=True, exist_ok=True)
(styles_dir / "custom.css").write_text(custom_css or "")
js_dir = html_output / "_static" / "js"
js_dir.mkdir(parents=True, exist_ok=True)
(js_dir / "custom.js").write_text(custom_js or "")

# append docs-specific ignores to gitignore
if Path(".gitignore").exists():
Expand Down
Loading
Loading