Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
3ad110e
Add logging to extension initialization
douden Oct 14, 2025
81da090
Add custom TocTreeCollector and disable built-in collector
douden Oct 14, 2025
498ef7a
Add 'style' field to TocTree and propagate to nodes
douden Oct 14, 2025
97bb7c0
Update validator lambda signature in TocTree
douden Oct 14, 2025
7ada9d8
Add and enforce 'style' option for toctree
douden Oct 14, 2025
ffca368
Replace TocTreeCollectorWithAppendices with TocTreeCollectorWithStyles
douden Oct 15, 2025
a49d589
Remove unused toctree import from collectors.py
douden Oct 15, 2025
4408cea
Refactor to override assign_section_numbers method
douden Oct 15, 2025
77eba84
Add logging for docnames in TocTreeCollectorWithStyles
douden Oct 15, 2025
594ac62
Add logging for found toctree nodes in collector
douden Oct 15, 2025
fba99bd
Log non-numerical toctree styles in collector
douden Oct 15, 2025
e15a4f2
Improve toctree style warning message
douden Oct 15, 2025
50ae7c8
Update collectors.py
douden Oct 15, 2025
c662e73
Update collectors.py
douden Oct 15, 2025
d3c177e
Add __renumber method to TocTreeCollectorWithStyles
douden Oct 15, 2025
07344a2
Update collectors.py
douden Oct 15, 2025
010398f
Add Roman numeral support to TOC numbering styles
douden Oct 15, 2025
b227b4d
Update collectors.py
douden Oct 15, 2025
1b60bd3
Update collectors.py
douden Oct 15, 2025
6a39651
Add alpha section numbering to TocTreeCollectorWithStyles
douden Oct 15, 2025
2211db8
Support numerical style renumbering in TocTreeCollectorWithStyles
douden Oct 15, 2025
9dfe082
Add TOC section renumbering with style support
douden Oct 15, 2025
e9c5c97
Comment out nested toctree RuntimeError
douden Oct 15, 2025
baec699
Update collectors.py
douden Oct 15, 2025
f7889b8
Update collectors.py
douden Oct 15, 2025
dab5bfc
Refactor section number increment logic in collector
douden Oct 15, 2025
83bb1e2
Update collectors.py
douden Oct 15, 2025
8059239
Update collectors.py
douden Oct 15, 2025
ae119ef
Update collectors.py
douden Oct 15, 2025
44ec28e
Update collectors.py
douden Oct 15, 2025
0666bf5
Update collectors.py
douden Oct 15, 2025
fd9a47d
Update collectors.py
douden Oct 15, 2025
4c8b84e
Update collectors.py
douden Oct 15, 2025
70fa360
Update collectors.py
douden Oct 15, 2025
d9b60d0
Update collectors.py
douden Oct 15, 2025
28a98ed
Update collectors.py
douden Oct 15, 2025
6f371fb
Update collectors.py
douden Oct 15, 2025
354ae65
Update collectors.py
douden Oct 15, 2025
36ce81c
Update collectors.py
douden Oct 15, 2025
ba0355b
Update collectors.py
douden Oct 15, 2025
a0c43fc
Update collectors.py
douden Oct 15, 2025
924ccaa
Update collectors.py
douden Oct 15, 2025
6c58b37
Update collectors.py
douden Oct 15, 2025
e1c79ca
Update collectors.py
douden Oct 15, 2025
3986773
Update collectors.py
douden Oct 15, 2025
b8fa42a
Update collectors.py
douden Oct 15, 2025
39c2911
Update collectors.py
douden Oct 15, 2025
687a57a
Update collectors.py
douden Oct 15, 2025
3d6d925
Update collectors.py
douden Oct 15, 2025
ccc639d
Update collectors.py
douden Oct 15, 2025
f380615
Handle nested toctrees in TocTreeCollectorWithStyles
douden Oct 15, 2025
30eb447
Add restart_numbering option to TocTree and parsing
douden Oct 15, 2025
ceb541f
Add support for restarting section numbering in toctree
douden Oct 15, 2025
32bdcc4
Update events.py
douden Oct 15, 2025
69f1ab3
Support list values for toctree style attribute
douden Oct 15, 2025
601f5a1
Improve style conversion for numbered toctree items
douden Oct 15, 2025
ee71be1
Update collectors.py
douden Oct 15, 2025
1c762dc
Update collectors.py
douden Oct 15, 2025
061a845
Update collectors.py
douden Oct 15, 2025
47ff9c6
Improve type checks in renumbering logic
douden Oct 15, 2025
5666533
Remove debug logging from TocTreeCollectorWithStyles
douden Oct 15, 2025
217a776
Update README with section numbering style options
douden Oct 15, 2025
a585672
Fix secnumber handling in TocTreeCollectorWithStyles
douden Oct 15, 2025
eec33bf
Update README with new features and clarifications
douden Oct 15, 2025
0fc6222
Remove unused logger from __init__.py
douden Oct 15, 2025
39f4d36
Update README and improve section numbering logic
douden Oct 16, 2025
a77ace3
Add sphinx-multitoc-numbering to dependencies
douden Oct 16, 2025
f2d6535
Add __version__ and include version in setup return
douden Oct 16, 2025
e58e782
Update README with Jupyter Book configuration info
Tom-van-Woudenberg Oct 20, 2025
06c77c4
Update README.md
douden Oct 20, 2025
90dcded
Merge pull request #1 from TeachBooks/Tom-van-Woudenberg-patch-1
douden Oct 20, 2025
1640204
Update numbering options and defaults for ToC subtrees
douden Oct 20, 2025
98b3408
Remove warnings and notes about numbered option in Sphinx docs
douden Oct 20, 2025
08d86ae
Fix config access for multitoc numbering option
douden Oct 20, 2025
9363eaf
Merge pull request #2 from TeachBooks/patch-multitoc
douden Oct 21, 2025
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
60 changes: 46 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
[![Code style: black][black-badge]][black-link]
[![PyPI][pypi-badge]][pypi-link]

> [!NOTE]
> Currently, this is a *forked* version of `sphinx-external-toc` that implements:
>
> - Section numbering styles (e.g. numerical, roman (upper/lower), alphabetic (upper/lower)) per any level in the ToC by providing a new option `style` per subtree.
> - The option to restart the upper level section numbering for each subtree for the selected numbering style by providing a new option `restart_numbering` per subtree.

A sphinx extension that allows the documentation site-map (a.k.a Table of Contents) to be defined external to the documentation files.
As used by [Jupyter Book](https://jupyterbook.org)!
As used by default by [Jupyter Book](https://jupyterbook.org) (no need to manually add this extension to the extensions in `_config.yml` in a JupyterBook)!

In normal Sphinx documentation, the documentation site-map is defined *via* a bottom-up approach - adding [`toctree` directives](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#table-of-contents) within pages of the documentation.

Expand All @@ -24,12 +30,25 @@ Add to your `conf.py`:

```python
extensions = ["sphinx_external_toc"]
use_multitoc_numbering = True # optional, default: True
external_toc_path = "_toc.yml" # optional, default: _toc.yml
external_toc_exclude_missing = False # optional, default: False
```

Note the `external_toc_path` is always read as a Unix path, and can either be specified relative to the source directory (recommended) or as an absolute path.

### Jupyterbook configuration

This extension is included in your jupyterbook configuration by default, so there's need to add it to the list of extensions. The other options can still be added:

```yaml
use_multitoc_numbering: true # optional, default: true
external_toc_path: "_toc.yml" # optional, default: _toc.yml
external_toc_exclude_missing: False # optional, default: False
```

Note the `external_toc_path` is always read as a Unix path, and can either be specified relative to the source directory (recommended) or as an absolute path.

### Basic Structure

A minimal ToC defines the top level `root` key, for a single root document file:
Expand Down Expand Up @@ -113,11 +132,27 @@ Each subtree can be configured with a number of options (see also [sphinx `toctr
By default it is appended to the end of the document, but see also the `tableofcontents` directive for positioning of the ToC.
- `maxdepth` (integer): A maximum nesting depth to use when showing the ToC within the document (default -1, meaning infinite).
- `numbered` (boolean or integer): Automatically add numbers to all documents within a subtree (default `False`).
If set to `True`, all sub-trees will also be numbered based on nesting (e.g. with `1.1` or `1.1.1`),
or if set to an integer then the numbering will only be applied to that depth.
If set to `True`, all subtrees will also be numbered based on nesting (e.g. with `1.1` or `1.1.1`),
or if set to an integer then the numbering will only be applied until that depth. Warning: This can lead to unexpected results if not carefully managed, for example references created using `numref` may fail. Internally this options is always converted to an integer, with `True` -> `999` (effectively unlimited depth) and `False` -> `0` (no numbering).
- `reversed` (boolean): If `True` then the entries in the subtree will be listed in reverse order (default `False`).
This can be useful when using `glob` entries.
- `titlesonly` (boolean): If `True` then only the first heading in the document will be shown in the ToC, not other headings of the same level (default `False`).
- `style` (string or list of strings): The section numbering style to use for this subtree (default `numerical`).
If a single string is given, this will be used for the top level of the subtree.
If a list of strings is given, then each entry will be used for the corresponding level of section numbering.
If styles are not given for all levels, then the remaining levels will be `numerical`.
If too many styles are given, the extra ones will be ignored.
The first time a style is used at the top level in a subtree, the numbering will start from 1, 'a', 'A', 'I' or 'i' depending on the style.
Subsequent times the same style is used at the top level in a subtree, the numbering will continue from the last number used for that style, unless `restart_numbering` is set to `True`.
Available styles:
- `numerical`: 1, 2, 3, ...
- `romanlower`: i, ii, iii, iv, v, ...
- `romanupper`: I, II, III, IV, V, ...
- `alphalower`: a, b, c, d, e, ..., aa, ab, ...
- `alphaupper`: A, B, C, D, E, ..., AA, AB, ...
- `restart_numbering` (boolean): If `True`, the numbering for the top level of this subtree will restart from 1 (or 'a', 'A', 'I' or 'i' depending on the style). If `False` the numbering for the top level of this subtree will continue from the last letter/number/symbol used in a previous subtree with the same style. The default value of this option is `not use_multitoc_numbering`. This means that:
- if `use_multitoc_numbering` is `True` (the default), the numbering for each part will continue from the last letter/number/symbol used in a previous part with the same style, unless `restart_numbering` is explicitly set to `True`.
- if `use_multitoc_numbering` is `False`, the numbering of each subtree will restart from 1 (or 'a', 'A', 'I' or 'i' depending on the style), unless `restart_numbering` is explicitly set to `False`.

These options can be set at the level of the subtree:

Expand All @@ -130,6 +165,8 @@ subtrees:
numbered: True
reversed: False
titlesonly: True
style: [alphaupper, romanlower]
restart_numbering: True
entries:
- file: doc1
subtrees:
Expand All @@ -149,6 +186,8 @@ options:
numbered: True
reversed: False
titlesonly: True
style: [alphaupper, romanlower]
restart_numbering: True
entries:
- file: doc1
options:
Expand All @@ -169,21 +208,14 @@ options:
maxdepth: 1
numbered: True
reversed: False
style: [alphaupper, romanlower]
restart_numbering: True
entries:
- file: doc1
entries:
- file: doc2
```

:::{warning}
`numbered` should not generally be used as a default, since numbering cannot be changed by nested subtrees, and sphinx will log a warning.
:::

:::{note}
By default, title numbering restarts for each subtree.
If you want want this numbering to be continuous, check-out the [sphinx-multitoc-numbering extension](https://github.com/executablebooks/sphinx-multitoc-numbering).
:::

### Using different key-mappings

For certain use-cases, it is helpful to map the `subtrees`/`entries` keys to mirror e.g. an output [LaTeX structure](https://www.overleaf.com/learn/latex/sections_and_chapters).
Expand Down Expand Up @@ -424,13 +456,13 @@ meta: {}

Questions / TODOs:

- Add additional top-level keys, e.g. `appendices` (see https://github.com/sphinx-doc/sphinx/issues/2502) and `bibliography`
- ~~Add additional top-level keys, e.g. `appendices` (see https://github.com/sphinx-doc/sphinx/issues/2502) and `bibliography`.~~ Can be replaced by setting the numbering style and (possibly) restarting the numbering.
- Using `external_toc_exclude_missing` to exclude a certain file suffix:
currently if you had files `doc.md` and `doc.rst`, and put `doc.md` in your ToC,
it will add `doc.rst` to the excluded patterns but then, when looking for `doc.md`,
will still select `doc.rst` (since it is first in `source_suffix`).
Maybe open an issue on sphinx, that `doc2path` should respect exclude patterns.
- Integrate https://github.com/executablebooks/sphinx-multitoc-numbering into this extension? (or upstream PR)
- ~~Integrate https://github.com/executablebooks/sphinx-multitoc-numbering into this extension? (or upstream PR).~~ Included and enforced in this fork.
- document suppressing warnings
- test against orphan file
- https://github.com/executablebooks/sphinx-book-theme/pull/304
Expand Down
9 changes: 0 additions & 9 deletions docs/user_guide/sphinx.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,6 @@ entries:
- file: doc2
```

:::{warning}
`numbered` should not generally be used as a default, since numbering cannot be changed by nested subtrees, and sphinx will log a warning.
:::

:::{note}
By default, title numbering restarts for each subtree.
If you want want this numbering to be continuous, check-out the [sphinx-multitoc-numbering extension](https://github.com/executablebooks/sphinx-multitoc-numbering).
:::

## Using different key-mappings

For certain use-cases, it is helpful to map the `subtrees`/`entries` keys to mirror e.g. an output [LaTeX structure](https://www.overleaf.com/learn/latex/sections_and_chapters).
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"click>=7.1",
"pyyaml",
"sphinx>=5",
"sphinx-multitoc-numbering>=0.1.3"
]

[project.urls]
Expand Down
16 changes: 13 additions & 3 deletions sphinx_external_toc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""A sphinx extension that allows the project toctree to be defined in a single file."""

__version__ = "1.0.1"


from typing import TYPE_CHECKING

if TYPE_CHECKING:
from sphinx.application import Sphinx

__version__ = "1.1.0-dev"

def setup(app: "Sphinx") -> dict:

app.setup_extension("sphinx_multitoc_numbering")

"""Initialize the Sphinx extension."""
from .events import (
InsertToctrees,
Expand All @@ -18,6 +19,14 @@ def setup(app: "Sphinx") -> dict:
ensure_index_file,
parse_toc_to_env,
)
from .collectors import (
disable_builtin_toctree_collector,
TocTreeCollectorWithStyles
)

# collectors
disable_builtin_toctree_collector(app)
app.add_env_collector(TocTreeCollectorWithStyles)

# variables
app.add_config_value("external_toc_path", "_toc.yml", "env")
Expand All @@ -33,3 +42,4 @@ def setup(app: "Sphinx") -> dict:
app.connect("build-finished", ensure_index_file)

return {"version": __version__, "parallel_read_safe": True}

10 changes: 10 additions & 0 deletions sphinx_external_toc/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,13 @@ def findall(node: Element):
# findall replaces traverse in docutils v0.18
# note a difference is that findall is an iterator
return getattr(node, "findall", node.traverse)


def validate_style(instance, attribute, value):
allowed = ["numerical", "romanupper", "romanlower", "alphaupper", "alphalower"]
if isinstance(value, list):
for v in value:
if v not in allowed:
raise ValueError(f"{attribute.name} must be one of {allowed}, not {v!r}")
elif value not in allowed:
raise ValueError(f"{attribute.name} must be one of {allowed}, not {value!r}")
12 changes: 12 additions & 0 deletions sphinx_external_toc/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
matches_re,
optional,
validate_fields,
validate_style,
)

#: Pattern used to match URL items.
Expand Down Expand Up @@ -61,6 +62,17 @@ class TocTree:
)
reversed: bool = field(default=False, kw_only=True, validator=instance_of(bool))
titlesonly: bool = field(default=False, kw_only=True, validator=instance_of(bool))
# Add extra field for style of toctree rendering
style: Union[List[str],str] = field(
default="numerical",
kw_only=True,
validator=validate_style
)
# add extra field for restarting numbering for the set style
# Only allow True, False or None. None is the default value.
restart_numbering: Optional[bool] = field(
default=None, kw_only=True, validator=optional(instance_of(bool))
)

def __post_init__(self):
validate_fields(self)
Expand Down
Loading