Sphinx Documentation

README

For quick start, installation, usage, and contribution guidelines, see README - the complete user guide for the dingx Sphinx documentation repository.


CLAUDE Quick Reference

For development with Claude Code, see CLAUDE - a comprehensive guide on project structure, configuration, JIRA integration, common workflows, and important conventions for AI-assisted development.


Apple Script

The apple script Script Sphinx dingx Docu creates the different build versions.


JIRA Integration

This Sphinx documentation system includes bidirectional synchronization with JIRA for task management:


VM Deployment

Guide for deploying and managing the Sphinx documentation on Google Cloud virtual machines:


Settings

The settings are defined in the conf.py file which is in the root directory of the Sphinx implementation:

conf.py
# Sphinx Configuration

# Configuration file for the Sphinx documentation builder
# Documentation: http://www.sphinx-doc.org/en/master/config
# Coding: UTF-8


# -- Python libraries ------------------------------------------

import os
import pathlib
import sys
import logging
import django
import subprocess
import sphinx_bootstrap_theme
# To obtain timestamp data
from datetime import datetime
# Used when defining custom directives in reStructuredText
from docutils import nodes
# Log custom messages during the Sphinx build process
from sphinx.util.logging import getLogger
# Imports the entire logging module under the name sphinx_logging
from sphinx.util import logging as sphinx_logging
# Modifies Sphinx documents after the initial transformation phase
from sphinx.transforms.post_transforms import SphinxPostTransform
# For the usage of 'literalinclude' directive
from sphinx.highlighting import lexers
# All available syntax highlighters for customizing code block
from pygments.lexers import get_all_lexers
# For the usage of 'Pygments' generic syntax highlighter
from pygments.lexers.configs import IniLexer


# -- Setting the timestamps ------------------------------------

# get the current year
year      = datetime.now().year
# get the current date and time
today_fmt = '%d.%m.%Y at %H:%M'


# -- Project information ---------------------------------------

project   = 'dingx Project'
author    = 'Friedrich Moehring'
subject   = 'Move to Simplicity'
# Base filename for all output formats (no spaces)
filename  = 'dingx'
# set the copyright information for the generated documentation
copyright = f'{year} {project} {author}'
# Major and minor version
major     = '4'
minor     = '8'
patch     = '2'
# The short X version
version   = f'{major}'
# The full version, including alpha/beta/rc tags
release   = f'{major}.{minor}.{patch}'


# -- Important settings ----------------------------------------

# Language for content autogenerated by Sphinx
language     = 'en'
# Master toctree document
master_doc   = 'index'
# html: path to the logo
path_logo    = '_static/logo_dingx.png'
# html: favicon image for the browser tab
html_favicon = '_static/favicon_dingx.png'
# epub: book cover including the title of the cover image
epub_cover   = ('_static/cover_dingx.png', '')


# -- JIRA Integration Configuration ----------------------------

# JIRA credentials for bidirectional sync
jira_user        = 'info@dingx.co'
jira_api_token   = 'ATATT3xFfGF0aCoCqQAtiVsTB7LwK7C09yUUXJNNQPLFS8z297TAIaVNOmUNRgtCFGAkADIHkaqPqZBTgOtyh_FlmCCnPcqrwjR4IRLmfCb3h1EB7nVN-rWMJKKZw63O02JE1wwzy4fY3X694CYKMW-kzPi1E9zi2lCIEVAdzyfmi8ZCPBeizN4=69A65618'
jira_url         = 'https://dingx.atlassian.net'
jira_project_key = 'BUS'


# -- Path setup ------------------------------------------------

# If extensions or modules to document with autodoc are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute.

# Base paths for development projects
REPO_PATH = '/Users/Friedrich/Documents/Project/Development/Python/repo'
VENV_PATH = '/Users/Friedrich/Documents/Logistics/venv'

# Add repository parent path for package-qualified imports
sys.path.insert(0, REPO_PATH)

# Add virtual environments FIRST so dependencies are available
# venv django_landing_simple
sys.path.insert(0, os.path.join(VENV_PATH, 'django_landing_simple/lib/python3.12/site-packages'))
# venv django_webapps_fullstack
sys.path.insert(0, os.path.join(VENV_PATH, 'django_webapps_fullstack/lib/python3.12/site-packages'))
# venv odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(VENV_PATH, 'odoo_xmlrpc_twisted/lib/python3.12/site-packages'))
# venv inventory_rfid_node
sys.path.insert(0, os.path.join(VENV_PATH, 'inventory_rfid_node/lib/python3.12/site-packages'))
# venv inventory_camera_node
sys.path.insert(0, os.path.join(VENV_PATH, 'inventory_camera_node/lib/python3.12/site-packages'))
# venv inventory_scale_node
sys.path.insert(0, os.path.join(VENV_PATH, 'inventory_scale_node/lib/python3.12/site-packages'))
# venv inventory_control_system
sys.path.insert(0, os.path.join(VENV_PATH, 'inventory_control_system/lib/python3.12/site-packages'))

# Add the repo paths for the Python modules in the repos
# repo django_landing_simple
sys.path.insert(0, os.path.join(REPO_PATH, 'django_landing_simple'))
# test django_landing_simple
sys.path.insert(0, os.path.join(REPO_PATH, 'django_landing_simple/test'))

# repo django_webapps_fullstack
sys.path.insert(0, os.path.join(REPO_PATH, 'django_webapps_fullstack'))
# test django_webapps_fullstack
sys.path.insert(0, os.path.join(REPO_PATH, 'django_webapps_fullstack/test'))

# root repo odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(REPO_PATH, 'odoo_xmlrpc_twisted'))
# functions odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(REPO_PATH, 'odoo_xmlrpc_twisted/functions'))
# models odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(REPO_PATH, 'odoo_xmlrpc_twisted/models'))
# scripts odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(REPO_PATH, 'odoo_xmlrpc_twisted/scripts'))
# test odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(REPO_PATH, 'odoo_xmlrpc_twisted/test'))
# support odoo_xmlrpc_twisted
sys.path.insert(0, os.path.join(REPO_PATH, 'odoo_xmlrpc_twisted/support'))

# repo inventory_rfid_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_rfid_node'))
# functions inventory_rfid_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_rfid_node/functions'))

# repo inventory_camera_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_camera_node'))
# functions inventory_camera_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_camera_node/functions'))
# test inventory_camera_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_camera_node/test'))

# repo inventory_scale_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_scale_node'))
# functions inventory_scale_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_scale_node/functions'))
# test inventory_scale_node
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_scale_node/test'))

# repo inventory_control_system
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_control_system'))
# functions inventory_control_system
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_control_system/functions'))
# support inventory_control_system
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_control_system/support'))
# test inventory_control_system
sys.path.insert(0, os.path.join(REPO_PATH, 'inventory_control_system/test'))

# For the documentation of the the Django Project
os.environ['DJANGO_SETTINGS_MODULE'] = 'django.settings'
# Indicate building the Sphinx documentation
os.environ['SPHINX_BUILD'] = '1'
from django.conf import settings
settings.configure()
# Setup Django
django.setup()


# -- Setup(app) function (moved to end of file) ---------------

# Dynamically excludes autosummary only for PDF build as autosummary results in build errors
def exclude_autosummary_for_pdf(app):
    """Exclude the table of contents when building HTML documentation."""
    if app.builder.name == "pdf":
        app.config.exclude_patterns.append('development/autosummary/**')

# Avoid the '.. contents:: directive error' when building HTML documentation with the Furo theme
def remove_contents_for_furo_html(app, doctree, docname):
    """Remove ".. contents::" directive only for "furo" "html" builds."""
    if app.builder.name == "html" and app.config.html_theme == "furo":
        for contents in doctree.traverse(nodes.topic):
            if "contents" in contents.get("classes", []):
                contents.parent.remove(contents)

# Configures Furo theme options to hide the sidebar project name, disable keyboard navigation, and set the logo
def modify_furo_options(app, config):
    """Dynamically update theme options to hide the sidebar and set the logo when using Furo."""
    if config.html_theme == "furo":
        # Hide project name in sidebar
        config.html_theme_options["sidebar_hide_name"] = True  
        # Disable sidebar navigation with keys (optional)
        config.html_theme_options["navigation_with_keys"] = False  
        # Places the logo at the top of the left sidebar        
        config.html_logo = path_logo

# Remove unsupported theme options ["color_primary", "color_accent"] when the Sphinx theme is not "sphinx_material"
def remove_unsupported_theme_options(app, config):
    """Remove unsupported theme options when not using "sphinx_material"."""
    if config.html_theme != "sphinx_material":
        if hasattr(config, "html_theme_options") and config.html_theme_options:
            # Create a new dict without the unsupported keys
            theme_options = dict(config.html_theme_options)
            for key in ["color_primary", "color_accent"]:
                theme_options.pop(key, None)
            # Reassign the cleaned options
            config.html_theme_options = theme_options

# prints a LaTeX SVG note but doesn't actually process SVG files
def setup(app):
    """Setup function for handling LaTeX-specific configurations"""

    def exclude_svg_for_latex(app, env, docnames):
        """Skip SVG processing for LaTeX builder to avoid errors"""
        if hasattr(app.builder, 'name') and app.builder.name == 'latex':
            print("Note: SVG images may need manual conversion for LaTeX output")

    # Connect the handler
    app.connect('env-before-read-docs', exclude_svg_for_latex)

# Suppress the warnings "Ignore unknown node autosummary_table" and "Ignore unknown node autosummary_toc" when build with "docx"
def suppress_autosummary_warnings(app, doctree, docname):
    """Remove Remove autosummary_toc nodes and autosummary_table nodes when building with docx."""
    if app.builder.name == "docx":
        for node in doctree.traverse():
            if node.__class__.__name__ in ["autosummary_toc", "autosummary_table"]:
                node.parent.remove(node)

# Suppress SVG warnings when building DOCX (similar to autosummary suppression)
def suppress_svg_warnings(app, doctree, docname):
    """Remove image nodes with SVG sources when building with docx to avoid warnings."""
    if app.builder.name == "docx":
        from docutils import nodes
        for node in doctree.traverse(nodes.image):
            if node.get('uri', '').lower().endswith('.svg'):
                # Simply remove the SVG image node to avoid warnings
                node.parent.remove(node)

# Suppress duplicate object description warnings
def setup_duplicate_warning_filter(app):
    """Setup warning filter to suppress duplicate object description warnings."""
    import logging
    from sphinx.util.logging import getLogger

    class DuplicateObjectFilter(logging.Filter):
        def filter(self, record):
            message = record.getMessage()
            # Filter out duplicate object description warnings
            if ('duplicate object description' in message and
                'use :no-index: for one of them' in message):
                return False
            # Filter out duplicate label warnings (both formats)
            if ('duplicate label' in message and
                ('autosectionlabel' in message or 'other instance in' in message)):
                return False
            return True

    # Apply filter to multiple logger levels to catch Sphinx warnings
    loggers_to_filter = [
        logging.getLogger('sphinx'),
        logging.getLogger('sphinx.environment'),
        logging.getLogger('sphinx.environment.collectors.asset'),
        logging.getLogger(),  # root logger
    ]

    duplicate_filter = DuplicateObjectFilter()
    for logger in loggers_to_filter:
        logger.addFilter(duplicate_filter)

# JIRA synchronization before build
def run_jira_sync(app):
    """Run JIRA synchronization before building documentation."""
    # Only run for HTML builds to avoid slowing down other builds
    if app.builder.name not in ["html"]:
        return

    print("Running JIRA ↔ Sphinx bidirectional sync...")

    try:
        # Import and run sync directly using this module's configuration
        project_root = pathlib.Path(__file__).parent.parent
        sys.path.insert(0, str(project_root))

        from tools.jira_sphinx_sync import JiraSphinxSync

        # Create a configuration object with our JIRA settings
        class JiraConfig:
            def __init__(self):
                # Use the globals from this module directly
                self.jira_user        = jira_user
                self.jira_api_token   = jira_api_token
                self.jira_url         = jira_url
                self.jira_project_key = jira_project_key

        jira_config = JiraConfig()
        syncer = JiraSphinxSync(sphinx_config=jira_config)

        # Run bidirectional sync
        syncer.sync()

        print("JIRA bidirectional sync completed successfully!")

    except Exception as e:
        print(f"❌ JIRA sync failed: {e}")
        print("⚠️  Continuing with build anyway...")


# -- General configuration -------------------------------------

# use 'sphinx.ext.extlinks': markup to shorten external links
extlinks = {
    'mewe':                  ('https://mewe.com/group/5c978b56aeb33427c0511ee9/%s', ''),
    'jira':                  ('https://dingx.atlassian.net/jira/projects/%s', ''),
    'keepass':               ('https://drive.google.com/open?id=11PzaQyIP8zzONCq8rtG87soWG6M1n5ix&usp=drive_fs/%s', ''),
    'planning_folder':       ('https://drive.google.com/open?id=1713JA-BzjdTkXU0I2MBEx6SZj32YF1U-&usp=drive_fs/%s', ''),
    'sprint_folder':         ('https://drive.google.com/open?id=10i680n9SZtZKaXWw_oj7VFLKsnAvMt7z&usp=drive_fs/%s', ''),
    'workflow_processes':    ('https://docs.google.com/spreadsheets/d/17Mxs0iRcqm-jQTPmk_VmWfXfJrxXsZoT?rtpof=true&usp=drive_fs/%s', ''),
    'project_tasks':         ('https://docs.google.com/spreadsheets/d/1GL5-c0Yk-Nt10mgg4j_Ao2pIdoLDN_mW?rtpof=true&usp=drive_fs/%s', ''),
    'project_plan':          ('https://docs.google.com/spreadsheets/d/1GL5-c0Yk-Nt10mgg4j_Ao2pIdoLDN_mW?rtpof=true&usp=drive_fs/%s', ''),
    'google_drive':          ('https://drive.google.com/drive/u/0/folders/1uJN97n5WovhQCGhOzbFOh4suD47wpW9-/%s', ''),
    'google_cloud':          ('https://cloud.google.com/%s', ''),
    'bitbucket':             ('https://bitbucket.org/dingx_workspace/%s', ''),
    # domain registrator and e-mail provider 
    'domain_factory':        ('https://www.df.eu/%s', ''),
    'vadian':                ('https://www.vadian.net/%s', ''),
    'spaceship':             ('https://www.spaceship.com/%s', ''),
    'ionos':                 ('https://www.ionos.com/%s', ''),
    'infomaniak':            ('https://www.infomaniak.com/%s', ''),
    'papaki':                ('https://www.papaki.com/%s', ''),
    'sedo':                  ('https://sedo.com/%s', ''),
    'google_workspace':      ('https://workspace.google.com/%s', ''),
    'zoho':                  ('https://www.zoho.com/%s', ''),
}

# Ignore external library references that Sphinx cannot resolve
# These are references to classes/modules in external packages (Django, FastAPI, unittest, etc.)
nitpick_ignore = [
    # Django framework
    ('py:class', 'django.apps.config.AppConfig'),
    ('py:class', 'django.forms.forms.Form'),
    ('py:class', 'django.test.testcases.TestCase'),
    ('py:mod',   'settings.urls'),

    # Python standard library
    ('py:class', 'unittest.case.TestCase'),
    ('py:class', 'datetime.timedelta'),

    # FastAPI framework
    ('py:class', 'fastapi.Request'),
    ('py:class', 'fastapi.HTTPException'),
    ('py:class', 'fastapi.security.HTTPAuthorizationCredentials'),

    # Twisted framework
    ('py:class', 'twisted.web.xmlrpc.XMLRPC'),

    # Pydantic models (internal to odoo_xmlrpc_twisted)
    ('py:class', 'models.schemas.CreateOrder'),
    ('py:class', 'models.schemas.CreateUser'),
    ('py:class', 'models.schemas.UserLogin'),

    # API modules not included in autosummary (filtered out by generate_autosummary_package.py)
    ('py:mod',   'inventory_camera_node.api'),
    ('py:mod',   'inventory_scale_node.api'),

    # PyQt6 base classes (external GUI library)
    ('py:class', 'PyQt6.QtCore.QThread'),
    ('py:class', 'PyQt6.QtWidgets.QMainWindow'),
    ('py:class', 'PyQt6.QtWidgets.QFrame'),
]

# Add any Sphinx extension module names here as strings. These can be
# extensions that come with Sphinx (named 'sphinx.ext.*') or custom ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.autosummary',
    'sphinx.ext.doctest',
    'sphinx.ext.coverage',
    'sphinx.ext.viewcode',
    'sphinx.ext.extlinks',
    # enables SVG support for various builders including DOCX
    'sphinx.ext.imgmath',
    # manage requirements and traceability, sync with JIRA
    "sphinx_needs",
    # allows to link to documentation in other project
    'sphinx.ext.intersphinx',
    # for Google-style or NumPy-style docstrings
    'sphinx.ext.napoleon',
    # auto-generate section labels.
    'sphinx.ext.autosectionlabel',
    # enables modern, styled UI elements like colored blocks, cards, and grids
    'sphinx_design',
    # add a "copy" button to code blocks
    # see https://pypi.org/project/sphinx-copybutton/
    'sphinx_copybutton',
    # pdf output from sphinx with rst2pdf
    # see https://sphinx-test-docs.readthedocs.io/en/latest/sphinxTut/render_pdf.html
    'rst2pdf.pdfbuilder',
    # extension to generate Markdown files
    # see https://pypi.org/project/sphinx-markdown-builder/
    "sphinx_markdown_builder",
    # builder extension to generate Office OpenXML (OOXML) document with "docx" file extension
    # see https://docxbuilder.readthedocs.io/en/latest/docxbuilder.html
    'docxbuilder',
    # MyST is a rich and extensible flavor of Markdown meant for technical documentation and publishing
    # see https://myst-parser.readthedocs.io/en/v0.17.2/sphinx/intro.html
    'myst_parser',
]

# Intersphinx mapping for cross-referencing external documentation
intersphinx_mapping = {
    'python': ('https://docs.python.org/3', None),
}

# settings for 'sphinx-needs', see https://sphinx-needs.readthedocs.io/en/
needs_types         = [dict(directive="task", title="Task", prefix="TD", color="#FFCC00", style="node"),]
needs_extra_options = ["assignee", "priority", "sprint", "story_points", "duedate"]
# define the statuses wanted to use
needs_statuses      = [
    {"name": "to-do",       "description": "Not started / planned",     "color": "#d3d3d3"},
    {"name": "in-progress", "description": "Actively being worked on",  "color": "#87ceeb"},
    {"name": "done",        "description": "Completed",                 "color": "#90ee90"},
    {"name": "parking",     "description": "On hold / blocked",         "color": "#ffa500"},
]

# Disable specific warning messages during the build process
suppress_warnings = [
    'epub.unknown_project_files',  # Suppress warnings about missing or extra files
    'epub.duplicated_toc_entry',   # Suppress duplicate ToC entry warnings for EPUB
    'myst.header',                 # Suppress warnings related to MyST headers
    'myst.nested',                 # Suppress nested parsing warnings
    'toc.not_in_toctree',          # Suppress warnings for autosummary files not in toctree
]

# Add any paths that contain templates here, relative to this directory
templates_path = ['_templates']

# If true, 'todo' and 'todoList' produce output, else nothing
todo_include_todos = True
# Hide the ToDo section if it's empty
todo_link_only     = True

# Prefix document path to section labels, otherwise autogenerated labels would look like 'heading'
# rather than 'path/to/file:heading'
autosectionlabel_prefix_document = True

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = []

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# Usage of 'literalinclude' directive to include '.ini' files directly into the Sphinx documentation.
lexers['ini'] = IniLexer()

# Automatically generate stub .rst files for any items listed in an
# '.. autosummary::' directive during the documentation build process.
autosummary_generate = True

# Use custom autosummary templates to show members and source code links
# Templates are in source/_templates/autosummary/
autodoc_default_options = {
    'members': True,
    'undoc-members': True,
    'show-inheritance': True,
}

# Mock imports for modules that can't be imported during documentation build
# This allows FastAPI application files (api.py) to be documented
autodoc_mock_imports = [
    # FastAPI and related
    'uvicorn',
    'fastapi',
    'fastapi.responses',
    'fastapi.middleware',
    'fastapi.middleware.cors',
    'fastapi.middleware.trustedhost',
    'fastapi.testclient',
    # Pydantic
    'pydantic',
    'pydantic.fields',
    'pydantic.main',
    'pydantic_core',
    'typing_extensions',
    # Hardware-specific libraries
    'RPi',
    'RPi.GPIO',
    'mfrc522',
    'hx711',
    'picamera2',
    # GUI libraries
    'PyQt6',
    'PyQt6.QtWidgets',
    'PyQt6.QtCore',
    'PyQt6.QtGui',
]

# Adjust the logging level for autosummary to warning
autosummary_logger = getLogger("sphinx.ext.autosummary")
autosummary_logger.setLevel(logging.WARNING)

# Set the logging level for myst_parser to WARNING or ERROR
sphinx_logging.getLogger('myst_parser').setLevel(logging.ERROR)

# Adjust the global logging level for Sphinx to warning
root_logger = logging.getLogger()
root_logger.setLevel(logging.WARNING)  # or logging.ERROR


# -- Options for Linkcheck Builder -----------------------------

# Adjust the timeout and retry settings for the linkcheck to prevent hanging on slow or unresponsive links.
linkcheck_timeout = 10  # Timeout in seconds
linkcheck_retries = 1   # Number of retries
linkcheck_workers = 10  # Number of workers for link checking

# A list of URL that should not be checked when doing a linkcheck build.
linkcheck_ignore = [
    r'http://localhost',
    r'https://bitbucket.org',
    r'https://stackoverflow.com',
    r'https://loopings.ch',
    r'https://app.dealum.com',
    r'https://www.spaceship.com',
    r'https://www.ifj.ch',
    r'https://kivy.org',
    r'https://www.venturekick.ch',
    r'https://www.innosuisse.venturelab.ch',
    r'https://www.easygov.swiss',
    r'https://docs.google.com/presentation',  # shared documents on Google Drive
    r'https://docs.google.com/document',      # shared documents on Google Drive
    r'https://docs.google.com/spreadsheets',  # shared documents on Google Drive
    r'https://drive.google.com/open\?id=.*',  # private Google Drive files/folders
    r'.*dingx.*',                             # Ignore any link containing 'dingx' as not all domains are active.
#     r'https://zurich.impacthub.ch',           # link not always works
#     r'https://www.figma.com',                 # link not always works
]


# -- Options for HTML output -----------------------------------

# The theme to use for HTML and HTML Help pages.
# See the documentation for a list of builtin themes.
#
# 'alabaster': default theme
# html_theme = 'alabaster'
# 'furo': clean documentation with a sidebar
html_theme = 'furo'
# 'sphinx_material': inspired by Google's Material Design
# html_theme = 'sphinx_material'
# 'bootstrap': responsive, modern design based on Bootstrap
# html_theme = 'bootstrap'
# html_theme = 'classic'
# html_theme = 'nature'

# Path containing custom static files
html_static_path = ['_static']

# Custom CSS files
html_css_files = [
    'custom.css',
]

# Theme options are theme-specific and customize the look and feel of a theme further.
html_theme_options = {
    # Visible levels of the global TOC; -1 means unlimited
    # 'globaltoc_depth': -1,
    # If False, expand all TOC entries
    'globaltoc_collapse':      False,
    # If True, show hidden TOC entries
    'globaltoc_includehidden': False,

    # "Sphinx Material" theme options (only used when html_theme = 'sphinx_material')
    # see https://bashtage.github.io/sphinx-material/
    # Set the color and the accent color
    'color_primary': 'blue',
    'color_accent':  'light-blue',
}

# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself.  Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
html_sidebars = {}


# -- Options for LaTeX output ----------------------------------

# Settings for the creating of the LaTeX documentation
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    ('index',                          # Starting document
     f"{filename}.tex",                # Output filename
     f"{project}",                     # Document title
     author,                           # Author
     'manual',                         # Document class
    ),
]

# explicitly set UTF-8 encoding for LaTeX
latex_engine = 'xelatex'

# Configure image converters for LaTeX to handle SVG files
latex_show_pagerefs = True
latex_show_urls     = 'footnote'

# Configure LaTeX compilation to use batchmode (no interactive prompts)
latex_use_latex_multicolumn = True
latex_toplevel_sectioning = 'chapter'

# Configure LaTeX to be more tolerant of errors
latex_show_pagerefs = True
latex_show_urls = 'footnote'

latex_elements = {
    # Disables the inputenc package because xelatex handle UTF-8 natively
    'inputenc': '',
    # Ensures no extra UTF-8 settings are applied, as they are not needed with xelatex
    'utf8extra': '',

    # The paper size ('letterpaper' or 'a4paper').
    'papersize': 'a4paper',

    # The font size ('10pt', '11pt' or '12pt').
    'pointsize': '10pt',

    # Custom LaTeX options
    'fncychap': '\\usepackage[Bjornstrup]{fncychap}',
    'printindex': '\\footnotesize\\raggedright\\printindex',

    # Additional stuff for the LaTeX preamble.
    'preamble': r'''
          % Silence LaTeX Warnings with Silence Package
          \usepackage{silence}
          \WarningsOff*          % Suppress ALL warnings

          % Handle graphics with robust error handling
          \usepackage{caption}

          % Set graphics extensions for xelatex (exclude SVG)
          \DeclareGraphicsExtensions{.pdf,.png,.jpg,.jpeg}

          % Unicode character support for monospace font
          \usepackage{newunicodechar}

          % Define fallbacks for problematic Unicode characters
          \newunicodechar{↔}{\ensuremath{\leftrightarrow}}
          \newunicodechar{❌}{\textcolor{red}{\textbf{X}}}
          \newunicodechar{⚠}{\textcolor{orange}{\textbf{!}}}
          \newunicodechar{️}{}  % Zero-width joiner, ignore

          % Simple approach: just exclude SVG from graphics search path
          % LaTeX will show a simple error message for missing graphics but continue
          
          % Ignore Underfull and Overfull Boxes
          \hbadness=10000        % Suppress underfull hbox warnings
          \hfuzz=9999pt          % Ignore minor overfull hbox warnings
          \vbadness=10000        % Suppress underfull vbox warnings
          \vfuzz=9999pt          % Ignore minor overfull vbox warnings

          \emergencystretch=1em  % Allow more flexible spacing
          \tolerance=5000        % Increase tolerance for bad boxes
          \hbadness=5000         % Suppress badness warnings
          \hfuzz=2pt             % Ignore small overfull boxes

          % Completely Disable Warnings
          \makeatletter
          \renewcommand{\@latex@warning}[1]{}
          \renewcommand{\@latex@warning@no@line}[1]{}
          \makeatother

          % Sloppy typesetting to prevent warnings
          \sloppy

          % Additional tolerance for long words and code blocks
          \tolerance=10000
          \emergencystretch=3em

          \renewcommand{\ttdefault}{lmtt} % Use a better monospace font
      ''',

    # Latex figure (float) alignment
    'figure_align': 'htbp',
    # enable forced wrapping for code:
    'sphinxsetup': 'verbatimforcewraps, verbatimmaxoverfull=0',
}


# -- Options for EPUB output -----------------------------------

# Bibliographic Dublin Core info.
epub_basename    = filename
epub_title       = project
epub_author      = author
epub_publisher   = author
epub_copyright   = copyright
epub_language    = language
epub_description = f'{project} version {release}'

# Individual configurations.
epub_show_urls = 'no'

# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']


# -- Options for DOCX (Docxbuilder) output ---------------------

docx_documents = [
    ('index',
     f"{filename}.docx",
     {'title': project,
     'creator': author,
     'subject': subject,
     'keywords': [''], },
     True,
    ),
]
docx_style = ''
docx_pagebreak_before_section = 1
# Automatically updates TOC (= Table of Contents) when opening the document
docx_update_fields = False


# -- Options for Manual output ---------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc,
     filename,                         # Command name
     f"{project} Documentation",       # Description
     [author],                         # Authors
     1,                                # Manual section
    ),
]


# -- Options for Texinfo output --------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
    (master_doc,
     filename,                         # Target name
     f"{project}",                     # Title
     author,                           # Author
     filename,                         # Dir menu entry
     subject,                          # Description
     'Miscellaneous',                  # Category
    ),
]


# -- Setup(app) function ---------------------------------------

# The setup(app) function is a special function in Sphinx that allows
# to connect custom functions (event handlers) to specific Sphinx events
def setup(app):
    app.connect("builder-inited",   exclude_autosummary_for_pdf)      # Exclude autosummary only for PDF build
    app.connect("builder-inited",   run_jira_sync)                    # Add JIRA sync before build
    app.connect("builder-inited",   setup_duplicate_warning_filter)   # Filter duplicate warnings
    app.connect("doctree-resolved", remove_contents_for_furo_html)    # Avoid the '.. contents:: directive error'
    app.connect("config-inited",    modify_furo_options)              # Configure Furo theme options
    app.connect("config-inited",    remove_unsupported_theme_options) # If theme is not "sphinx_material"
    app.connect("doctree-resolved", suppress_autosummary_warnings)    # When build with "docx"   
    app.connect("doctree-resolved", suppress_svg_warnings)            # Suppress SVG warnings for DOCX

    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }

Build Automation

The Makefile provides build automation commands for generating documentation in multiple formats and managing JIRA synchronization:

Makefile
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS    ?=
SPHINXBUILD   ?= /Users/Friedrich/Documents/Logistics/venv/sphinx/bin/sphinx-build
SOURCEDIR     = source
BUILDDIR      = build
PYTHON        = /Users/Friedrich/Documents/Logistics/venv/sphinx/bin/python

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
	@echo ""
	@echo "JIRA Synchronization Commands:"
	@echo "  jira-pull           Pull tasks from JIRA into Sphinx"
	@echo "  jira-push           Push Sphinx changes to JIRA"
	@echo "  jira-sync           Bidirectional synchronization"
	@echo "  jira-status         Show synchronization status"
	@echo "  html-with-sync      Build HTML with JIRA sync"
	@echo ""
	@echo "Link Checking Commands:"
	@echo "  linkcheck           Check all links (full output)"

.PHONY: help Makefile jira-pull jira-push jira-sync jira-status html-with-sync

# JIRA synchronization targets
jira-pull:
	@echo "🔄 Pulling tasks from JIRA..."
	@$(PYTHON) tools/jira_sphinx_sync.py pull

jira-push:
	@echo "🔄 Pushing changes to JIRA..."
	@$(PYTHON) tools/jira_sphinx_sync.py push

jira-sync:
	@echo "🔄 Running bidirectional JIRA sync..."
	@$(PYTHON) tools/jira_sphinx_sync.py sync

jira-status:
	@echo "📊 JIRA Sync Status:"
	@if [ -f "tools/sync_state.json" ]; then \
		echo "   Last sync: Never"; \
		echo "   JIRA issues: 0"; \
	else \
		echo "   No sync history found"; \
	fi
	@echo "   JIRA credentials: $$([ -n \"$$JIRA_USER\" ] && echo \"✅ Set\" || echo \"❌ Not set\")"

html-with-sync: jira-pull html

# Custom latexpdf target that handles SVG issues gracefully
latexpdf: latex
	@echo "Building PDF from LaTeX (removing SVG files and references)..."
	@echo "Removing problematic SVG files from LaTeX build..."
	@find "$(BUILDDIR)/latex" -name "*.svg" -delete 2>/dev/null || true
	@echo "   Removing SVG references from LaTeX source..."
	@cd "$(BUILDDIR)/latex" && \
	sed -i.bak '/includegraphics.*\.svg/d' dingx.tex && \
	latexmk -pdf -f -interaction=nonstopmode dingx.tex; \
	if [ -f "dingx.pdf" ]; then \
		echo "PDF successfully created: $$(ls -lh dingx.pdf | awk '{print $$5}')"; \
		echo "Location: $(BUILDDIR)/latex/dingx.pdf"; \
	else \
		echo "❌ PDF creation failed"; \
		exit 1; \
	fi

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)