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!
In normal Sphinx documentation, the documentation site-map is defined via a bottom-up approach - adding toctree directives within pages of the documentation.
This extension facilitates a top-down approach to defining the site-map structure, within a single YAML file.
It also allows for documents not specified in the ToC to be auto-excluded.
Add to your conf.py:
extensions = ["sphinx_external_toc"]
external_toc_path = "_toc.yml"  # optional, default: _toc.yml
external_toc_exclude_missing = False  # optional, default: FalseNote 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.
A minimal ToC defines the top level root key, for a single root document file:
root: introThe value of the root key will be a path to a file, in Unix format (folders split by /), relative to the source directory, and can be with or without the file extension.
:::{note}
This root file will be set as the master_doc.
:::
Document files can then have a subtrees key - denoting a list of individual toctrees for that document - and in-turn each subtree should have a entries key - denoting a list of children links, that are one of:
- file: path to a single document file in Unix format, with or without the file extension (as for- root)
- glob: path to one or more document files via Unix shell-style wildcards (similar to- fnmatch, but single stars don't match slashes.)
- url: path for an external URL (starting e.g.- httpor- https)
:::{important} Each document file can only occur once in the ToC! :::
This can proceed recursively to any depth.
root: intro
subtrees:
- entries:
  - file: doc1
    subtrees:
    - entries:
      - file: doc2
        subtrees:
        - entries:
          - file: doc3
  - url: https://example.com
  - glob: subfolder/other*This is equivalent to having a single toctree directive in intro, containing doc1,
and a single toctree directive in doc1, with the :glob: flag and containing doc2, https://example.com and subfolder/other*.
As a shorthand, the entries key can be at the same level as the file, which denotes a document with a single subtree.
For example, this file is exactly equivalent to the one above:
root: intro
entries:
- file: doc1
  entries:
  - file: doc2
    entries:
    - file: doc3
- url: https://example.com
- glob: subfolder/other*By default, the initial header within a file document will be used as its title in generated Table of Contents.
With the title key you can set an alternative title for a document. and also for url:
root: intro
subtrees:
- entries:
  - file: doc1
    title: Document 1 Title
  - url: https://example.com
    title: Example URL TitleEach subtree can be configured with a number of options (see also sphinx toctree options):
- caption(string): A title for the whole the subtree, e.g. shown above the subtree in ToCs
- hidden(boolean): Whether to show the ToC within (inline of) the document (default- False). By default it is appended to the end of the document, but see also the- tableofcontentsdirective 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.1or- 1.1.1), or if set to an integer then the numbering will only be applied to that depth.
- reversed(boolean): If- Truethen the entries in the subtree will be listed in reverse order (default- False). This can be useful when using- globentries.
- titlesonly(boolean): If- Truethen only the first heading in the document will be shown in the ToC, not other headings of the same level (default- False).
These options can be set at the level of the subtree:
root: intro
subtrees:
- caption: Subtree Caption
  hidden: False
  maxdepth: 1
  numbered: True
  reversed: False
  titlesonly: True
  entries:
  - file: doc1
    subtrees:
    - titlesonly: True
      entries:
      - file: doc2or, if you are using the shorthand for a single subtree, set options under an options key:
root: intro
options:
  caption: Subtree Caption
  hidden: False
  maxdepth: 1
  numbered: True
  reversed: False
  titlesonly: True
entries:
- file: doc1
  options:
    titlesonly: True
  entries:
  - file: doc2You can also use the top-level defaults key, to set default options for all subtrees:
root: intro
defaults:
  titlesonly: True
options:
  caption: Subtree Caption
  hidden: False
  maxdepth: 1
  numbered: True
  reversed: False
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. :::
For certain use-cases, it is helpful to map the subtrees/entries keys to mirror e.g. an output LaTeX structure.
The format key can be used to provide such mappings (and also initial defaults).
Currently available:
- jb-article:- Maps entries->sections
- Sets the default of titlesonlytotrue
 
- Maps 
- jb-book:- Maps the top-level subtreestoparts
- Maps the top-level entriestochapters
- Maps other levels of entriestosections
- Sets the default of titlesonlytotrue
 
- Maps the top-level 
For example:
defaults:
  titlesonly: true
root: index
subtrees:
- entries:
  - file: doc1
    entries:
    - file: doc2is equivalent to:
format: jb-book
root: index
parts:
- chapters:
  - file: doc1
    sections:
    - file: doc2:::{important} These change in key names do not change the output site-map structure. :::
By default, the toctree generated per document (one per subtree) are appended to the end of the document and hidden (then, for example, most HTML themes show them in a side-bar).
But if you would like them to be visible at a certain place within the document body, you may do so by using the tableofcontents directive:
ReStructuredText:
.. tableofcontents::MyST Markdown:
```{tableofcontents}
```Currently, only one tableofcontents should be used per page (all toctree will be added here), and only if it is a page with child/descendant documents.
Note, this will override the hidden option set for a subtree.
By default, Sphinx will build all document files, regardless of whether they are specified in the Table of Contents, if they:
- Have a file extension relating to a loaded parser (e.g. .rstor.md)
- Do not match a pattern in exclude_patterns
To automatically add any document files that do not match a file or glob in the ToC to the exclude_patterns list, add to your conf.py:
external_toc_exclude_missing = TrueNote that, for performance, files that are in hidden folders (e.g. in .tox or .venv) will not be added to exclude_patterns even if they are not specified in the ToC.
You should exclude these folders explicitly.
:::{important} This feature is not currently compatible with orphan files. :::
This package comes with the sphinx-etoc command-line program, with some additional tools.
To see all options:
$ sphinx-etoc --help
Usage: sphinx-etoc [OPTIONS] COMMAND [ARGS]...
  Command-line for sphinx-external-toc.
Options:
  --version   Show the version and exit.
  -h, --help  Show this message and exit.
Commands:
  from-project  Create a ToC file from a project directory.
  migrate    Migrate a ToC from a previous revision.
  parse      Parse a ToC file to a site-map YAML.
  to-project    Create a project directory from a ToC file.To build a template project from only a ToC file:
$ sphinx-etoc to-project -p path/to/site -e rst path/to/_toc.ymlNote, you can also add additional files in meta/create_files amd append text to the end of files with meta/create_append, e.g.
root: intro
entries:
- glob: doc*
meta:
  create_append:
    intro: |
      This is some
      appended text
  create_files:
  - doc1
  - doc2
  - doc3To build a ToC file from an existing site:
$ sphinx-etoc from-project path/to/folderSome rules used:
- Files/folders will be skipped if they match a pattern added by -s(based on fnmatch Unix shell-style wildcards)
- Sub-folders with no content files inside will be skipped
- File and folder names will be sorted by natural order
- If there is a file called index(or the name set by-i) in any folder, it will be treated as the index file, otherwise the first file by ordering will be used.
The command can also guess a title for each file, based on its path:
- The folder name is used for index files, otherwise the file name
- Words are split by _
- The first "word" is removed if it is an integer
For example, for a project with files:
index.rst
1_a_title.rst
11_another_title.rst
.hidden_file.rst
.hidden_folder/index.rst
1_a_subfolder/index.rst
2_another_subfolder/index.rst
2_another_subfolder/other.rst
3_subfolder/1_no_index.rst
3_subfolder/2_no_index.rst
14_subfolder/index.rst
14_subfolder/subsubfolder/index.rst
14_subfolder/subsubfolder/other.rst
will create the ToC:
$ sphinx-etoc from-project path/to/folder -i index -s ".*" -e ".rst" -t
root: index
entries:
- file: 1_a_title
  title: A title
- file: 11_another_title
  title: Another title
- file: 1_a_subfolder/index
  title: A subfolder
- file: 2_another_subfolder/index
  title: Another subfolder
  entries:
  - file: 2_another_subfolder/other
    title: Other
- file: 3_subfolder/1_no_index
  title: No index
  entries:
  - file: 3_subfolder/2_no_index
    title: No index
- file: 14_subfolder/index
  title: Subfolder
  entries:
  - file: 14_subfolder/subsubfolder/index
    title: Subsubfolder
    entries:
    - file: 14_subfolder/subsubfolder/other
      title: OtherThe ToC file is parsed to a SiteMap, which is a MutableMapping subclass, with keys representing docnames mapping to a Document that stores information on the toctrees it should contain:
import yaml
from sphinx_external_toc.parsing import parse_toc_yaml
path = "path/to/_toc.yml"
site_map = parse_toc_yaml(path)
yaml.dump(site_map.as_json())Would produce e.g.
root: intro
documents:
  doc1:
    docname: doc1
    subtrees: []
    title: null
  intro:
    docname: intro
    subtrees:
    - caption: Subtree Caption
      numbered: true
      reversed: false
      items:
      - doc1
      titlesonly: true
    title: null
meta: {}Questions / TODOs:
- Add additional top-level keys, e.g. appendices(see sphinx-doc/sphinx#2502) andbibliography
- Using external_toc_exclude_missingto exclude a certain file suffix: currently if you had filesdoc.mdanddoc.rst, and putdoc.mdin your ToC, it will adddoc.rstto the excluded patterns but then, when looking fordoc.md, will still selectdoc.rst(since it is first insource_suffix). Maybe open an issue on sphinx, thatdoc2pathshould respect exclude patterns.
- Integrate https://github.com/executablebooks/sphinx-multitoc-numbering into this extension? (or upstream PR)
- document suppressing warnings
- test against orphan file
- executablebooks/sphinx-book-theme#304
- CLI command to generate toc from existing documentation toctrees(and then remove toctree directives)
- test rebuild on toc changes (and document how rebuilds are controlled when toc changes)
- some jupyter-book issues point to potential changes in numbering, based on where the toctreeis in the document. So could look into placing it e.g. under the first heading/title
