[docs] Zensical docs (#13253)

* add zensical workflow

* rebuild schema dynamically

* load in new module

* Use zensical for docs invoke tasks

* modern

* improved carousel layout

* Increased content width

* fix for app icons

* Ensure schema dir exists

* remove unused requirement

* update readthedocs process

* fix polar.sh link

* adjust link checker pattern

* Revert some of the original style

* remove legacy mkdocs requirements

* fix for ready.py

* reduce db warnings during schema generation

* Adjust padding

* fix index issues

* fix page title

* fix pages with broken or missing headers

* split data import API schema

* cleanup old carousel

* remove djlint link
This commit is contained in:
Oliver authored and GitHub committed 2026-11-14 22:34:18 +11:00
1 parent 80ad822688
commit 19dfb81bfb
52 files changed
+1272 -4885

No files matched your search

+194
View File
@@ -0,0 +1,194 @@
"""Shared data for the documentation build process.
This module loads the generated InvenTree definition files (settings, status codes,
report context, etc) which are used by the custom macros defined in 'main.py'.
It is deliberately separate from 'main.py', as some static site generators
(e.g. zensical) re-execute the macros module for *every* page. As a regular
module, this file is imported (and cached) only once per build process:
- Generated data files are loaded lazily, and reloaded only if they change on disk
- Observed settings / status codes are accumulated across all pages of the build
- The 'on_config' hook values are computed once (for generators without hook support)
"""
import importlib.util
import json
import os
import threading
from pathlib import Path
from typing import Any, Optional
import yaml
HERE = Path(__file__).parent
GENERATED_DIR = HERE.joinpath('generated')
# Files where we *store* information on what has been documented during the build.
# These are checked after the build completes (see 'on_post_build' in docs/hooks.py)
OBSERVED_SETTINGS_FILE = GENERATED_DIR.joinpath('observed_settings.json')
OBSERVED_STATUS_CODES_FILE = GENERATED_DIR.joinpath('observed_status_codes.json')
_lock = threading.RLock()
# Cache of loaded data files: {filename: (mtime, data)}
_file_cache: dict[str, tuple[float, Any]] = {}
# Things which have been rendered somewhere in the documentation
_observed_settings: dict[str, dict[str, bool]] = {'global': {}, 'user': {}}
_observed_status_codes: dict[str, bool] = {}
# Cached result of the 'on_config' hook
_hook_config: Optional[dict] = None
def print_build_environment():
"""Print some useful debugging information - useful for diagnosing CI build issues.
Ref: https://docs.readthedocs.io/en/stable/reference/environment-variables.html
"""
print('loading ./docs/docs_data.py...')
for key in [
'GITHUB_ACTIONS',
'GITHUB_REF',
'READTHEDOCS',
'READTHEDOCS_GIT_IDENTIFIER',
'READTHEDOCS_GIT_CLONE_URL',
'READTHEDOCS_GIT_COMMIT_HASH',
'READTHEDOCS_PROJECT',
'READTHEDOCS_VERSION',
'READTHEDOCS_VERSION_NAME',
'READTHEDOCS_VERSION_TYPE',
]:
val = os.environ.get(key, None) or '-- MISSING --'
print(f' - {key}: {val}')
def _write_json(path: Path, data: Any):
"""Write data to a JSON file."""
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4)
def reset_observations():
"""Reset the record of observed settings and status codes.
Called once when this module is first imported (i.e. once per build process).
"""
with _lock:
_observed_settings.clear()
_observed_settings.update({'global': {}, 'user': {}})
_observed_status_codes.clear()
_write_json(OBSERVED_SETTINGS_FILE, _observed_settings)
_write_json(OBSERVED_STATUS_CODES_FILE, _observed_status_codes)
def observe_setting(key: str, group: str):
"""Record that a particular setting has been documented.
Arguments:
key: The name of the setting
group: The group of the setting (e.g. 'global', 'user', 'config')
"""
with _lock:
group_data = _observed_settings.setdefault(group, {})
if key not in group_data:
group_data[key] = True
_write_json(OBSERVED_SETTINGS_FILE, _observed_settings)
def observe_status_code(class_name: str):
"""Record that a particular status code class has been documented."""
with _lock:
if class_name not in _observed_status_codes:
_observed_status_codes[class_name] = True
_write_json(OBSERVED_STATUS_CODES_FILE, _observed_status_codes)
def load_file(filename: str) -> Any:
"""Load a generated data file (JSON or YAML), caching the result.
The file is reloaded only if it has been modified since it was last loaded.
Arguments:
filename: Name of the file, relative to the 'generated' directory
"""
path = GENERATED_DIR.joinpath(filename)
mtime = path.stat().st_mtime
with _lock:
cached = _file_cache.get(filename)
if cached and cached[0] == mtime:
return cached[1]
with open(path, encoding='utf-8') as f:
if path.suffix in ('.yml', '.yaml'):
data = yaml.load(f, yaml.BaseLoader)
else:
data = json.load(f)
_file_cache[filename] = (mtime, data)
return data
def settings(group: str) -> dict:
"""Return the settings definitions for a given group ('global', 'user', 'config')."""
return load_file('inventree_settings.json')[group]
def tags() -> Any:
"""Return the custom template tags definitions."""
return load_file('inventree_tags.yml')
def filters() -> Any:
"""Return the custom template filters definitions."""
return load_file('inventree_filters.yml')
def status_codes() -> dict:
"""Return the status code class definitions."""
return load_file('inventree_status_codes.json')
def report_context() -> dict:
"""Return the report context definitions."""
return load_file('inventree_report_context.json')
def roles() -> Any:
"""Return the user permission role definitions."""
return load_file('inventree_roles.json')
def audited_models() -> Any:
"""Return the audited model definitions."""
return load_file('inventree_audited_models.json')
def hook_config(site_url: Optional[str] = None) -> dict:
"""Return the config values provided by the 'on_config' hook in docs/hooks.py.
Zensical does not run mkdocs hooks, so
the hook is run once here, and the result cached for subsequent pages.
"""
global _hook_config
with _lock:
if _hook_config is None:
spec = importlib.util.spec_from_file_location(
'inventree_docs_hooks', HERE.joinpath('docs', 'hooks.py')
)
hooks = importlib.util.module_from_spec(spec)
spec.loader.exec_module(hooks)
_hook_config = hooks.on_config({'site_url': site_url})
return _hook_config
print_build_environment()
reset_observations()