Substitutions for Doc
TL;DR: These are mini-templates Sphinx fills in for you
Each |rule_name| gets replaced with the value after replace::.
For example, let’s consider this rule:
.. |example_rule| replace:: "Actual Value Here"
It looks like this when used:
“Actual Value Here”
Why Substitutions?
TL;DR: They save time & effort by single-sourcing values.
For example, we never have to hunt for outdated minimum Python versions in our doc. Each time our doc builds, our conf.py will:
Read
pyproject.tomlParse and compute any necessary values
Generate final substitution rules, including:
Minimum Python version strings
Source install links
Branch links
Our Substitutions
Our Current Substitutions
The only local substitution in the project is one in this file.
The substitutions rules in the source below are global. You can use
them anywhere and everywhere in the project’s reST source. That includes
docstrings in addition to .rst files, but it might not always be a
good idea.
Tip
Be sure to read the comment at the top!
.. # # Global Substitutions
.. # ## Style Intro
.. # These are all comment lines. However, this chunk uses a few
.. # stylistic choices in addition to standard reST. All of them
.. # help beginners by providing something more familiar than the
.. # unusual syntax of reST.
.. # 1. reST's .. comment syntax is followed by Python's #
.. # 2. A Python-like 2 newlines between top-level code segments
.. # 3. Markdown-like title prefixes after the first #
.. # ## General metadata
.. |project_name| replace:: Fontknife
.. |package_name| replace:: fontknife
.. |full_commit_hash| replace:: 50fa447e23e57ebc4ef94c6882aff4f5a776c990
.. # ## Top-of-Page Build Warnings
.. # A link to any GitHub branch if we're on a web build. Local
.. # uses branch and commit instead. See the conditionals after
.. # the substitution rule definitions in conf.py
.. |branch_github_link| replace:: :ghbranch:`main`
.. # ## Install Documentation helpers
.. # #### Core Install Page Helpers
.. |min_py_version| replace:: 3.10
.. |min_py_fullname| replace:: Python 3.10
.. |min_py_fullname_plus| replace:: Python 3.10+
.. |cli_command_min_py| replace:: ``python3.10``
.. # #### Zip Install URLs
.. |cli_pip_install_gh_branch_main| replace:: https://github.com/pushfoo/Fontknife/archive/main.zip
.. |cli_pip_install_gh_commit_curr| replace:: https://github.com/pushfoo/Fontknife/archive/50fa447e23e57ebc4ef94c6882aff4f5a776c990.zip
.. |dep_line_latest_stable| replace:: fontknife == 0.2.0
.. |dep_line_commit_zipball| replace:: fontknife @ https://github.com/pushfoo/Fontknife/zipball/50fa447e23e57ebc4ef94c6882aff4f5a776c990
.. # ### Top-of-Page Suggestion Tables
.. # Substitutions only accept paragraph or sentence level items
.. # according to the errors when trying table parts. Instead,
.. # we'll template the most frequently used cell values.
.. # #### Suggestion Header Rows
.. |if_need| replace:: If you need...
.. |if_need_to| replace:: If you need to...
.. |may_want| replace:: ...you may want:
.. |may_want_to| replace:: ...you may want to:
.. |may_want_see| replace:: ...you may want to see:
.. # #### Re-usable nudges for the top of install pages
.. |task_convert| replace:: Convert font data in the terminal
.. |task_import| replace:: ``import fontknife`` in Python code
.. |task_contribute| replace:: Contribute fixes or new features
.. |redir_to_use| replace:: :ref:`user-install`
.. |redir_library| replace:: :ref:`library-install`
.. |redir_contributing| replace:: :ref:`contributing-setup`
.. # ### UNIX cpython Source Build Helpers
.. # Consider these a test for integrating a tag-based release
.. # setup like the Arcade package's. Otherwise, the pages which
.. # use these are an edge case & reference for the maintainers.
.. |git_tag_min_py_latest_point| replace:: 3.10
.. |git_tag_min_py_specific_point| replace:: v3.10.0
.. |cli_command_git_checkout_tag_latest_point| replace:: ``git checkout v3.10``
.. # ## Globally Predefined External Links
.. # These aren't quite substitutions, but they're useful enough
.. # to be put here for now. You can use them in one of two ways:
.. # 1. `Rye`_
.. # 2. `Alternate link text <Rye>`_
.. # Note that internal links use the :ref: directive instead.
.. # ### Tool Names
.. # For the moment, it seems worth it to break with the
.. # left-to-right specificity rule when it's one recognizable
.. # word used throughout the codebase.
.. _Rye: https://rye-up.com/
.. _Poetry: https://python-poetry.org/
.. # ### TOML doc
.. _guide to writing a pyproject.toml: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
.. _sample project: https://github.com/pypa/sampleproject/
Adding New Substitutions
Do you meet the contributing prerequisites?
Do you have a editable install
Have you tested it?
Does the project really need the substitution?
Signs something should be a substitution include:
It can be derived from project config or git metadata
It’s something which is easy to forget to update
It’s used in multiple places, especially on multiple pages
If you can answer yes to all of the above, you have a new question to answer: global or local?
The Local, the Global, and the Ugly
Substitutions exist in the same spaces as other Sphinx reST objects.
Local Substitutions
You can define local substitutions limited to a specific file.
The example at the top of this page is one of these. Like all local substitutions, it:
can only be used in the
.rstfile they’re defined inwas carefully considered to avoid conflicts with other substitution rules
Global Substitutions
Fontknife’s other substitutions are available in every .rst
file.
For the moment, the details of how that happens aren’t important. You
can think of it as copying and pasting before the source code of every
.rst file, both hand-written and generated API doc.
The Ugly Part: Conflicts
You can define a substitution rule once and only once!
Trying to redefine one anyway causes a build error which looks like the one below:
/home/user/Projects/Fontknife/docs/install/substitutions.rst:10: WARNING: Duplicate explicit target name: "intro".
This applies:
Per file
Globally
Any combination of the two
Adding a Global Substitution Rule
Warning
Substitution rules can only be defined once per context!
Trying anyway will cause a build error. See the previous heading to learn more.
To add a global substitution rule:
Open docs/conf.py in the
Find the
substitution_rules \=variable followed by the definition block f-stringAdd the following:
The new rule itself
Any necessary comments and spacing
Now you need to make sure the doc works. Do so by building it locally:
Switch to your terminal
Make sure you’re in the
docsdirectoryFollow the guide to Building the Doc
Fixing Whitespace Problems
TL;DR: Sphinx is even pickier about whitepsace than Python!
Did make html log a cryptic error like the one below?
/home/user/Projects/Fontknife/docs/contributing/substitutions.rst:184: WARNING: Definition list ends without a blank line; unexpected unindent.
This often happens when you’ve accidentally added whitespace. The most common places are also some of the most frustrating ones:
Lines which look blank
At the ends of certain non-blank lines
Others can be wherever you’ve accidentally pasted it due to mouse or keyboard hotkey accidents.
There may be future git configuration tricks which may fix it automatically. For the moment, see if your editor has a way to enable whitespace visualization.
If not, the following steps may help:
run
git diffYou can start with just
git diff conf.pyIt’s worth checking other files too
Look for blank lines which:
Starting with a green +
Have nothing but blank redness after
Delete all the added whitespace that redness represents
After you eliminate it, try running make html again.
How Doc Build Works Behind the Scenes
Warning
These details may change in the near future.
The project’s conf.py has a custom substitution_rules
variable containing all our rules.
Sphinx doesn’t use it directly. Instead, we use it to help set one of its configuration variables. The current approach is inelegant, but it gets the job done. Continue reading to learn more.
Generating a Prolog
Our conf.py’s
substitution_rulesis templated from various data sources:pyproject.toml
git’s HEAD
a few API calls
We build a number of other values
We
join()them together to set a final value for Sphinx’s rst_prolog configuration variable
How Sphinx Uses It
As Sphinx loads each .rst file into memory, it:
Allocates a buffer to read source into
Writes the rst_prolog into the buffer
Copies the file’s raw contents into the buffer
Applies any plugin transformations bound to the source-read event
Continues to the next file
Once it has processed all files to build its index, it then generates final
HTML from the full data. Note that it caches these unless you run make clean.