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”

Note

This substitution is unique in Fontknife’s doc.

This one is the only local substitution.

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:

  1. Read pyproject.toml

  2. Parse and compute any necessary values

  3. 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

  1. Do you meet the contributing prerequisites?

  2. Do you have a editable install

  3. Have you tested it?

  4. 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 .rst file they’re defined in

  • was 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:

  1. Open docs/conf.py in the

  2. Find the substitution_rules \= variable followed by the definition block f-string

  3. Add the following:

    1. The new rule itself

    2. Any necessary comments and spacing

Now you need to make sure the doc works. Do so by building it locally:

  1. Switch to your terminal

  2. Make sure you’re in the docs directory

  3. Follow 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:

  1. run git diff

    • You can start with just git diff conf.py

    • It’s worth checking other files too

  2. Look for blank lines which:

    1. Starting with a green +

    2. Have nothing but blank redness after

  3. 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

  1. Our conf.py’s substitution_rules is templated from various data sources:

    • pyproject.toml

    • git’s HEAD

    • a few API calls

  2. We build a number of other values

  3. 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:

  1. Allocates a buffer to read source into

  2. Writes the rst_prolog into the buffer

  3. Copies the file’s raw contents into the buffer

  4. Applies any plugin transformations bound to the source-read event

  5. 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.