diff --git a/.github/workflows/mkdoxy-test-demos.yaml b/.github/workflows/mkdoxy-test-demos.yaml
index 8d09ba83..9fedfc55 100644
--- a/.github/workflows/mkdoxy-test-demos.yaml
+++ b/.github/workflows/mkdoxy-test-demos.yaml
@@ -24,7 +24,7 @@ jobs:
python -m pip install -e ".[dev]"
sudo apt-get install doxygen
- name: Clone test repo
- run: git clone https://github.com/JakubAndrysek/MkDoxy-demo.git demo
+ run: git clone --branch core-update https://github.com/JakubAndrysek/MkDoxy-demo.git demo
- name: Build docs
run: |
cd demo
diff --git a/.gitignore b/.gitignore
index 0aad63ce..0dde6add 100644
--- a/.gitignore
+++ b/.gitignore
@@ -289,3 +289,5 @@ tests/files/.mkdoxy
.mkdoxy/
temp/
local/
+
+.idea/
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 1a0176ac..00000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index d678e147..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index cc5462da..00000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/.idea/main-mkdoxy.iml b/.idea/main-mkdoxy.iml
deleted file mode 100644
index 76f7618c..00000000
--- a/.idea/main-mkdoxy.iml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 97b30fe4..00000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index a3ab9e8d..00000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 5ace414d..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 6a4636cb..00000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,653 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "associatedIndex": 3
-}
-
-
-
-
-
-
-
- {
- "keyToString": {
- "Python tests.pytest in /.executor": "Run",
- "Python.mkdocs-serve-vPy3.11.executor": "Run",
- "Python.mkdoxy-demo-vPy3.11.executor": "Debug",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "SHARE_PROJECT_CONFIGURATION_FILES": "true",
- "WebServerToolWindowFactoryState": "false",
- "git-widget-placeholder": "main",
- "last_opened_file_path": "/Users/kuba/Documents/git/kuba/mkdoxy/mkdoxy-base/mkdoxy-main",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
- "vue.rearranger.settings.migration": "true"
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1636831708250
-
-
- 1636831708250
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1674630349699
-
-
-
- 1674630349699
-
-
- 1674631616905
-
-
-
- 1674631616905
-
-
- 1679404123954
-
-
-
- 1679404123954
-
-
- 1679405696796
-
-
-
- 1679405696796
-
-
- 1679406849683
-
-
-
- 1679406849683
-
-
- 1679412445947
-
-
-
- 1679412445947
-
-
- 1679412916914
-
-
-
- 1679412916914
-
-
- 1679412944638
-
-
-
- 1679412944638
-
-
- 1679475560538
-
-
-
- 1679475560538
-
-
- 1680348700460
-
-
-
- 1680348700460
-
-
- 1680349313071
-
-
-
- 1680349313071
-
-
- 1680349715098
-
-
-
- 1680349715098
-
-
- 1681656417972
-
-
-
- 1681656417972
-
-
- 1681656572465
-
-
-
- 1681656572465
-
-
- 1681657852137
-
-
-
- 1681657852137
-
-
- 1681663034138
-
-
-
- 1681663034138
-
-
- 1683158225350
-
-
-
- 1683158225350
-
-
- 1683158771920
-
-
-
- 1683158771920
-
-
- 1683158793244
-
-
-
- 1683158793244
-
-
- 1683242202337
-
-
-
- 1683242202337
-
-
- 1686746284046
-
-
-
- 1686746284046
-
-
- 1686746715966
-
-
-
- 1686746715966
-
-
- 1686746897657
-
-
-
- 1686746897657
-
-
- 1689593119646
-
-
-
- 1689593119646
-
-
- 1689777297262
-
-
-
- 1689777297262
-
-
-
- 1715439268944
-
-
-
- 1715439268944
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- file://$PROJECT_DIR$/mkdoxy/generatorAuto.py
- 235
-
-
-
- file://$PROJECT_DIR$/mkdoxy/node.py
- 221
-
-
-
- file://$PROJECT_DIR$/mkdoxy/node.py
- 224
-
-
-
- file://$PROJECT_DIR$/tests-old/snippetsTest-examples.py
- 49
-
-
-
- file://$PROJECT_DIR$/mkdoxy/node.py
- 839
-
-
-
- file://$PROJECT_DIR$/mkdoxy/DoxyTagParser.py
- 41
-
-
-
- file://$PROJECT_DIR$/mkdoxy/DoxyTagParser.py
- 21
-
-
-
- file://$PROJECT_DIR$/mkdoxy/DoxyTagParser.py
- 34
-
-
-
- file://$PROJECT_DIR$/local/test_generatorSnippets.py
- 22
-
-
-
- file://$PROJECT_DIR$/mkdoxy/doxyrun.py
- 94
-
-
-
- file://$PROJECT_DIR$/mkdoxy/doxyrun.py
- 111
-
-
-
- file://$PROJECT_DIR$/tests/test_doxyrun.py
- 14
-
-
-
- file://$PROJECT_DIR$/mkdoxy/generatorBase.py
- 67
-
-
-
- file://$PROJECT_DIR$/mkdoxy/templates/relatedPages.jinja2
- 5
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a1d7c8fe..9592a6cd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,15 @@
---
repos:
+ - repo: https://github.com/sourcery-ai/sourcery
+ rev: v1.19.0
+ hooks:
+ - id: sourcery
+ # The best way to use Sourcery in a pre-commit hook:
+ # * review only changed lines:
+ # * omit the summary
+ args: [--diff=git diff HEAD, --no-summary]
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.5.0
+ rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -9,8 +17,12 @@ repos:
rev: 23.9.1
hooks:
- id: black
- - repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: 'v0.0.292'
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ # Ruff version.
+ rev: v0.9.6
hooks:
+ # Run the linter.
- id: ruff
- args: [--fix, --exit-non-zero-on-fix]
+ args: [ --fix, --exit-non-zero-on-fix ]
+ # Run the formatter.
+ - id: ruff-format
diff --git a/Makefile b/Makefile
index 312f42b4..aaea103f 100755
--- a/Makefile
+++ b/Makefile
@@ -3,20 +3,20 @@
# Packaging
package:
rm -f dist/*
- python3 -m build --wheel # Updated to use python3 build tool for wheel
- python3 setup.py sdist
+ hatch build
install: package
- python3 -m pip install --no-deps --force dist/*.whl
+ hatch install
release: package
- twine upload --repository pypi dist/*
+ hatch publish
release-test: package
- twine upload --repository testpypi dist/*
+ hatch publish --index test
clean:
rm -rf dist build
+ hatch clean
# Testing
@@ -24,7 +24,7 @@ reviewCode:
sourcery review mkdoxy --in-place
install-dev:
- python3 -m pip install --force --editable .
+ hatch env create
# Documentation
docs-serve:
@@ -35,3 +35,13 @@ docs-build: # results in site directory
pre-commit:
pre-commit run --show-diff-on-failure --color=always --all-files
+
+# Linting
+lint:
+ ruff check mkdoxy
+
+format:
+ ruff format mkdoxy
+
+lint-fix:
+ ruff check --fix mkdoxy
diff --git a/README.md b/README.md
index 70cbe63e..fa21878e 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ pip install mkdoxy
```
Development version with all dependencies:
```bash
+python -m venv .venv
python -m pip install mkdoxy ".[dev]"
```
diff --git a/devdeps.txt b/devdeps.txt
deleted file mode 100644
index 43234dad..00000000
--- a/devdeps.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-mkdocs-material~=9.5.18
-mkdocs-open-in-new-tab~=1.0.2
-pathlib~=1.0.1
-isort~=5.13.2
-pytest~=8.2.2
-pre-commit~=3.7.0
-setuptools~=70.0.0
-build~=1.2.2
-twine~=5.1.1
diff --git a/mkdocs.yml b/mkdocs.yml
index dc5a3e34..0e7b75c1 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -25,9 +25,23 @@ theme:
repo: fontawesome/brands/github
palette:
- - scheme: slate
+ # Palette toggle for dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
primary: orange
accent: orange
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+ # Palette toggle for light mode
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: orange
+ accent: orange
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
extra:
social:
@@ -64,11 +78,11 @@ plugins:
enabled: !ENV [ENABLE_MKDOXY, True]
projects:
mkdoxyApi:
- src-dirs: mkdoxy
- full-doc: True
- template-dir: templates-custom
- doxy-cfg-file: demo-projects/animal/Doxyfile
- doxy-cfg:
+ src_dirs: mkdoxy
+ full_doc: True
+ template_dir: templates-custom
+# doxy_config_file: demo-projects/animal/Doxyfile
+ doxy_config_dict:
FILE_PATTERNS: "*.py"
EXAMPLE_PATH: ""
RECURSIVE: True
@@ -76,17 +90,44 @@ plugins:
JAVADOC_AUTOBRIEF: True
EXTRACT_ALL: True
animal:
- src-dirs: demo-projects/animal
- full-doc: True
- doxy-cfg:
+ src_dirs: demo-projects/animal
+ full_doc: True
+ doxy_config_dict:
FILE_PATTERNS: "*.cpp *.h*"
EXAMPLE_PATH: examples
RECURSIVE: True
- save-api: .mkdoxy
- full-doc: True
+# save_api: .mkdoxy
+ full_doc: True
debug: False
- ignore-errors: False
- emojis-enabled: True
+ ignore_errors: False
+
+# - mkdoxy:
+# enabled: !ENV [ENABLE_MKDOXY, True]
+# projects:
+# mkdoxyApi:
+# src-dirs: mkdoxy
+# full-doc: True
+# template-dir: templates-custom
+# doxy-cfg-file: demo-projects/animal/Doxyfile
+# doxy-cfg:
+# FILE_PATTERNS: "*.py"
+# EXAMPLE_PATH: ""
+# RECURSIVE: True
+# OPTIMIZE_OUTPUT_JAVA: True
+# JAVADOC_AUTOBRIEF: True
+# EXTRACT_ALL: True
+# animal:
+# src-dirs: demo-projects/animal
+# full-doc: True
+# doxy-cfg:
+# FILE_PATTERNS: "*.cpp *.h*"
+# EXAMPLE_PATH: examples
+# RECURSIVE: True
+# save-api: .mkdoxy
+# full-doc: True
+# debug: False
+# ignore-errors: False
+# emojis-enabled: True
markdown_extensions:
- pymdownx.highlight
diff --git a/mkdoxy/DoxyTagParser.py b/mkdoxy/DoxyTagParser.py
index db450f56..8d35d4a1 100644
--- a/mkdoxy/DoxyTagParser.py
+++ b/mkdoxy/DoxyTagParser.py
@@ -2,36 +2,36 @@
class DoxyTagParser:
- def __init__(self, markdown_page: str, debug: bool = False):
+ def __init__(self, markdown_page: str, debug: bool = False) -> None:
self.markdown_page = markdown_page
self.debug = debug
self.doxy_key = "::: doxy"
- self.indent = "(?P[\t ]*)"
+ self.indent = "(?P[\\t ]*)"
self.project = "(?P[a-zA-Z]+)"
self.key = "(?P[a-zA-Z.-_]+)"
- self.dot = "\."
+ self.dot = "\\."
self.optional_dot = "[.]?"
- self.look_ahead = "(?=\n)" # it's a look ahead because we don't want to capture the newline
+ self.look_ahead = "(?=\\n)" # it's a look ahead because we don't want to capture the newline
- def replaceMarkdown(self, start: int, end: int, replace_format: str, **kwargs):
+ def replace_markdown(self, start: int, end: int, replace_format: str, **kwargs) -> None:
self.markdown_page = self.markdown_page.replace(self.markdown_page[start:end], replace_format.format(**kwargs))
- def returnMarkdown(self):
+ def return_markdown(self):
return self.markdown_page
- def parseEmptyTag(self, replacement: str):
+ def parse_empty_tag(self, replacement: str) -> None:
empty_tag = (
rf"{self.indent}{self.doxy_key}{self.optional_dot}{self.look_ahead}" # https://regex101.com/r/Zh38uo/1
)
matches = re.finditer(empty_tag, self.markdown_page, re.MULTILINE)
for match in reversed(list(matches)):
- self.replaceMarkdown(match.start(), match.end(), replacement, indent=match.group("indent"))
+ self.replace_markdown(match.start(), match.end(), replacement, indent=match.group("indent"))
- def parseProject(self, replacement: str):
+ def parse_project(self, replacement: str) -> None:
project_tag = rf"{self.indent}{self.doxy_key}{self.dot}{self.project}{self.optional_dot}{self.look_ahead}" # https://regex101.com/r/TfAsmE/1
matches = re.finditer(project_tag, self.markdown_page, re.MULTILINE)
for match in reversed(list(matches)):
- self.replaceMarkdown(
+ self.replace_markdown(
match.start(),
match.end(),
replacement,
@@ -39,13 +39,13 @@ def parseProject(self, replacement: str):
project=match.group("project"),
)
- def parseProjectTagSingle(self, replacement: str):
+ def parse_project_tag_single(self, replacement: str) -> None:
project_tag = (
rf"{self.indent}{self.doxy_key}{self.dot}{self.project}{self.dot}(?P[a-zA-Z-_]+){self.look_ahead}"
)
matches = re.finditer(project_tag, self.markdown_page, re.MULTILINE)
for match in reversed(list(matches)):
- self.replaceMarkdown(
+ self.replace_markdown(
match.start(),
match.end(),
replacement,
@@ -53,12 +53,12 @@ def parseProjectTagSingle(self, replacement: str):
key=match.group("key"),
)
- def parseProjectTagMulti(self, replacement: str):
- project_tag = rf"{self.indent}{self.doxy_key}{self.dot}{self.project}{self.dot}(?P[a-zA-Z-_]+)\s*\n(?:(?=\n)|(?=:::)|\Z)" # noqa: E501
+ def parse_project_tag_multi(self, replacement: str) -> None:
+ project_tag = rf"{self.indent}{self.doxy_key}{self.dot}{self.project}{self.dot}(?P[a-zA-Z-_]+)\\s*\\n(?:(?=\\n)|(?=:::)|\\Z)" # noqa: E501
matches = re.finditer(project_tag, self.markdown_page, re.MULTILINE)
for match in reversed(list(matches)):
list_keys = match.group("key").split(".") # split keys by . to allow for nested keys
- self.replaceMarkdown(
+ self.replace_markdown(
match.start(),
match.end(),
replacement,
diff --git a/mkdoxy/__main__.py b/mkdoxy/__main__.py
new file mode 100644
index 00000000..0b3242c6
--- /dev/null
+++ b/mkdoxy/__main__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+from mkdoxy.cli import main
+
+if __name__ == "__main__":
+ main()
diff --git a/mkdoxy/cache.py b/mkdoxy/cache.py
index b0c93fc6..30a98239 100644
--- a/mkdoxy/cache.py
+++ b/mkdoxy/cache.py
@@ -1,8 +1,8 @@
class Cache:
- def __init__(self):
+ def __init__(self) -> None:
self.cache = {}
- def add(self, key: str, value):
+ def add(self, key: str, value) -> None:
self.cache[key] = value
def get(self, key: str):
diff --git a/mkdoxy/cli.py b/mkdoxy/cli.py
new file mode 100644
index 00000000..600e5701
--- /dev/null
+++ b/mkdoxy/cli.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+from pathlib import Path
+
+import click
+
+from mkdoxy.migration import update_new_config
+
+
+@click.group()
+def main() -> None:
+ """mkdoxy - Command line tool for managing Doxygen configuration migration."""
+
+
+@click.command()
+@click.argument("yaml_file", type=click.Path(exists=True))
+@click.option("--no-backup", is_flag=True, help="Do not backup old config to mkdocs.1_old.yaml")
+def migrate(yaml_file, no_backup) -> None:
+ """
+ Migrate mkdoxy configuration to a new version.
+
+ :param yaml_file: Path to the mkdocs.yaml file.
+ :param no_backup: Do not backup the old config to mkdocs.1_old.yaml.
+ """
+ backup_file_name = "mkdocs.1_old.yaml"
+ update_new_config(Path(yaml_file), not no_backup, backup_file_name)
+ click.echo("Migration completed successfully")
+ if not no_backup:
+ click.echo(f"Old config was backed up as '{backup_file_name}'")
+
+
+@click.command()
+def version() -> None:
+ """
+ Display the version of the mkdoxy package.
+ """
+ try:
+ import importlib.metadata
+
+ package_version = importlib.metadata.version("mkdoxy")
+ except Exception:
+ package_version = "Unknown"
+ click.echo("MkDoxy: https://github.com/JakubAndrysek/MkDoxy")
+ click.echo(f"Version: {package_version}")
+
+
+main.add_command(migrate)
+main.add_command(version)
+
+if __name__ == "__main__":
+ main()
diff --git a/mkdoxy/constants.py b/mkdoxy/constants.py
index c9be98ea..dff41b19 100644
--- a/mkdoxy/constants.py
+++ b/mkdoxy/constants.py
@@ -37,6 +37,7 @@
"operator<<=",
"operator>>=",
"operator[]",
+ "operator()",
"operator*",
"operator&",
"operator->",
@@ -168,3 +169,6 @@ class Visibility(Enum):
PACKAGE = "package"
PROTECTED = "protected"
PRIVATE = "private"
+
+
+JINJA_EXTENSIONS = (".jinja2", ".j2", ".jinja")
diff --git a/mkdoxy/doxy_config.py b/mkdoxy/doxy_config.py
new file mode 100644
index 00000000..f5b316c8
--- /dev/null
+++ b/mkdoxy/doxy_config.py
@@ -0,0 +1,227 @@
+import logging
+from pathlib import Path
+
+from mkdocs.config import Config
+from mkdocs.config import config_options as c
+
+log: logging.Logger = logging.getLogger("mkdocs")
+
+config_scheme_legacy = {
+ "full-doc": "full_doc",
+ "ignore-errors": "ignore_errors",
+ "save-api": "custom_api_folder",
+ "doxygen-bin-path": "doxygen_bin_path",
+}
+
+config_project_legacy = {
+ "src-dirs": "src_dirs",
+ "full-doc": "full_doc",
+ "ignore-errors": "ignore_errors",
+ "doxy-cfg": "doxy_config_dict",
+ "doxy-cfg-file": "doxy_config_file",
+ "template-dir": "custom_template_dir",
+}
+
+
+class MkDoxyConfigProject(Config):
+ """! Configuration for each project in the MkDoxy configuration file.
+ @details New type of configuration for each project in the MkDoxy configuration file.
+ It will replace the old configuration type.
+
+ @param src_dirs: (str) Source directories for Doxygen - INPUT
+ @param full_doc: (bool) Generate full documentation
+ @param debug: (bool) Debug mode
+ @param ignore_errors: (bool) Ignore errors
+ @param doxy_config_dict: (dict) Doxygen additional configuration
+ @param doxy_config_default: (bool) Use default MkDoxy Doxygen configuration
+ @param doxy_config_file: (str) Doxygen configuration file
+ @param doxy_config_file_force: (bool) Do not use default MkDoxy Doxygen configuration, use only Doxygen configuration file
+ @param custom_template_dir: (str) Custom template directory
+ """
+
+ src_dirs = c.Type(str)
+ full_doc = c.Type(bool, default=True)
+ debug = c.Type(bool, default=False)
+ ignore_errors = c.Type(bool, default=False)
+ doxy_config_dict = c.Type(dict, default={})
+ doxy_config_default = c.Type(bool, default=True)
+ doxy_config_file = c.Optional(c.Type(Path))
+ doxy_config_file_force = c.Type(bool, default=False)
+ custom_template_dir = c.Optional(c.Type(str))
+
+ def validate(self):
+ failed, warnings = super().validate()
+
+ # Add a warning for deprecated configuration keys
+ unused_keys = set(self.keys()) - self._schema_keys
+ for k in unused_keys.intersection(config_project_legacy.keys()):
+ warnings.append((k, f"Deprecated configuration name: {k} -> {config_project_legacy[k]}"))
+
+ return failed, warnings
+
+
+class MkDoxyConfig(Config):
+ """! Global configuration for the MkDoxy plugin.
+ @details New type of global configuration for the MkDoxy plugin. It will replace the old configuration type.
+ @param projects: (dict) Project configuration - multiple projects
+ @param full_doc: (bool) Generate full documentation - global (all projects)
+ @param debug: (bool) Debug mode
+ @param ignore_errors: (bool) Ignore errors
+ @param custom_api_folder: (str) Custom API folder for Doxygen and MD output (default in temp folder)
+ @param doxygen_bin_path: (str) Path to Doxygen binary - default "doxygen"
+ """
+
+ projects = c.DictOfItems(c.SubConfig(MkDoxyConfigProject), default={}) # project configuration - multiple projects
+ full_doc = c.Type(bool, default=True) # generate full documentation - global (all projects)
+ debug = c.Type(bool, default=False) # debug mode
+ ignore_errors = c.Type(bool, default=False) # ignore errors
+ custom_api_folder = c.Optional(c.Type(str)) # custom API folder for Doxygen and MD output (default in temp folder)
+ doxy_config_dict = c.Type(
+ dict, default={}
+ ) # Doxygen additional configuration - it is overwritten by project config
+ doxygen_bin_path = c.Type(Path, default=Path("doxygen")) # path to Doxygen binary (default "doxygen"
+
+ generate_diagrams = c.Type(bool, default=False) # generate diagrams
+ generate_diagrams_format = c.Choice(("svg", "png", "jpg", "gif"), default="svg") # diagram format
+ generate_diagrams_type = c.Choice(("dot", "uml"), default="dot") # diagram type
+
+ def validate(self):
+ failed, warnings = super().validate()
+
+ # Add a warning for deprecated configuration keys
+ unused_keys = set(self.keys()) - self._schema_keys
+ for k in unused_keys.intersection(config_scheme_legacy.keys()):
+ warnings.append((k, f"Deprecated configuration name: {k} -> {config_scheme_legacy[k]}"))
+
+ # Include warnings from sub-config options in mkdoxy.projects
+ warnings.extend(next(s.option_type.warnings for k, s in self._schema if k == "projects"))
+
+ return failed, warnings
+
+
+# def load_config_by_key(key: str, legacy_key: str, config: Config, legacy: list) -> any:
+# """! Load the configuration value from the global configuration
+# @details Legacy config option is by default None, but if it is not None, it will print a warning and return value.
+# @param key: (str) The new configuration key.
+# @param legacy_key: (str) The legacy configuration key.
+# @param config: (Config) The global configuration object.
+# @param legacy: (list) The list of legacy configuration options.
+# @return: (Optional[str]) The configuration value.
+# """
+# if config.get(legacy_key) is not None:
+# legacy.append(f"Found legacy configuration options: '{legacy_key}' -> replace with '{key}'")
+# return config.get(legacy_key)
+# return config.get(key)
+#
+#
+# def process_configuration(config: Config) -> MkDoxyConfig:
+# """! Process the configuration for the MkDoxy plugin
+# @details Process the configuration for the MkDoxy plugin and validate the configuration.
+# It will try to load new configuration, but it will also check for legacy configuration options.
+# @param config: (Config) The global configuration object.
+# @return: (MkDoxyConfig) The new validated configuration object.
+# @throws ConfigurationError: If the configuration is invalid.
+# """
+# legacy_options = []
+# doxy_config = MkDoxyConfig()
+# doxy_config.full_doc = load_config_by_key("full_doc", "full-doc", config, legacy_options)
+# doxy_config.debug = config.get("debug", False)
+# doxy_config.ignore_errors = load_config_by_key("ignore_errors", "ignore-errors", config, legacy_options)
+# doxy_config.custom_api_folder = load_config_by_key("custom_api_folder", "save-api", config, legacy_options)
+# doxy_config.doxygen_bin_path = load_config_by_key("doxygen_bin_path", "doxygen-bin-path", config, legacy_options)
+#
+# doxy_config.generate_diagrams = config.get("generate_diagrams")
+# doxy_config.generate_diagrams_format = config.get("generate_diagrams_format")
+# doxy_config.generate_diagrams_type = config.get("generate_diagrams_type")
+#
+# # Validate the global configuration
+# validate_project_config(doxy_config, legacy_options)
+#
+# # Validate and load project configuration
+# for project_name, project_cfg in config.get("projects", {}).items():
+# doxy_config.projects[project_name] = load_project_config(project_cfg, project_name)
+#
+# return doxy_config
+#
+#
+# def validate_project_config(doxy_cfg: Config, legacy_options: list[str]) -> None:
+# """! Validate the project configuration for the MkDoxy plugin
+# @details Validate the project configuration for the MkDoxy plugin and check for errors and warnings.
+# @param doxy_cfg: (MkDoxyConfig) The project configuration object.
+# @param legacy_options: (list) The list of problems.
+# @return: None
+# @throws ConfigurationError: If the configuration is invalid.
+# """
+# if legacy_options:
+# log.warning("Found some legacy configuration options, please update your configuration!")
+# log.warning("Run command 'mkdoxy migrate mkdocs.yaml' to update your configuration to the new format!")
+# log.warning("More information in the documentation: https://mkdoxy.kubaandrysek.cz/")
+# for problem in legacy_options:
+# log.warning(f" -> {problem}")
+#
+# failed, warnings = doxy_cfg.validate()
+#
+# for config_name, warning in warnings:
+# log.warning(f" -> Config value: '{config_name}'. Warning: {warning}")
+#
+# for config_name, error in failed:
+# log.error(f" -> Config value: '{config_name}'. Error: {error}")
+# raise exceptions.ConfigurationError(f"Config value: '{config_name}'. Error: {error}")
+#
+#
+# def load_project_config_by_key(key: str, legacy_key: str, project_cfg: dict, project_name: str, problems: list) -> any:
+# """! Load the project configuration value from the project configuration
+# @details Legacy project config option is by default None, but if it is not None,
+# it will print a warning and return the value.
+# @param key: (str) The new project configuration key.
+# @param legacy_key: (str) The legacy project configuration key.
+# @param project_cfg: (dict) The project configuration object.
+# @param project_name: (str) The project name.
+# @param problems: (list) The list of problems.
+# @return: (Optional[str]) The project configuration value.
+# """
+# if project_cfg.get(legacy_key) is not None:
+# problems.append(
+# f"Found legacy configuration options: '{legacy_key}' -> replace with '{key}'"
+# f" in project '{project_name}'"
+# )
+# return project_cfg.get(legacy_key)
+# return project_cfg.get(key)
+#
+#
+# def load_project_config(project_cfg: dict, project_name: str) -> MkDoxyConfigProject:
+# """! Load the project configuration for the MkDoxy plugin
+# @details Load the project configuration for the MkDoxy plugin and validate the configuration.
+# @param project_cfg: (dict) The project configuration object.
+# @param project_name: (str) The project name.
+# @return: (MkDoxyConfigProject) The new validated project configuration object.
+# """
+# legacy_options = []
+# doxy_project_cfg = MkDoxyConfigProject()
+# doxy_project_cfg.src_dirs = load_project_config_by_key(
+# "src_dirs", "src-dirs", project_cfg, project_name, legacy_options
+# )
+#
+# doxy_project_cfg.full_doc = load_project_config_by_key(
+# "full_doc", "full-doc", project_cfg, project_name, legacy_options
+# )
+# doxy_project_cfg.debug = project_cfg.get("debug", False)
+# doxy_project_cfg.ignore_errors = load_project_config_by_key(
+# "ignore_errors", "ignore-errors", project_cfg, project_name, legacy_options
+# )
+# doxy_project_cfg.doxy_config_dict = load_project_config_by_key(
+# "doxy_config_dict", "doxy-cfg", project_cfg, project_name, legacy_options
+# )
+#
+# validate_config_file: Optional[str] = load_project_config_by_key(
+# "doxy_config_file", "doxy-cfg-file", project_cfg, project_name, legacy_options
+# )
+# doxy_project_cfg.doxy_config_file = None if validate_config_file is None else Path(validate_config_file)
+#
+# validate_template_dir: Optional[str] = load_project_config_by_key(
+# "custom_template_dir", "template-dir", project_cfg, project_name, legacy_options
+# )
+# doxy_project_cfg.custom_template_dir = None if validate_template_dir is None else Path(validate_template_dir)
+#
+# validate_project_config(doxy_project_cfg, legacy_options)
+# return doxy_project_cfg
diff --git a/mkdoxy/doxygen.py b/mkdoxy/doxygen.py
index 39c70c00..50c17330 100644
--- a/mkdoxy/doxygen.py
+++ b/mkdoxy/doxygen.py
@@ -1,6 +1,6 @@
import logging
import os
-from xml.etree import ElementTree
+from xml.etree import ElementTree as ET
from mkdoxy.cache import Cache
from mkdoxy.constants import Kind, Visibility
@@ -12,12 +12,12 @@
class Doxygen:
- def __init__(self, index_path: str, parser: XmlParser, cache: Cache):
+ def __init__(self, index_path: str, parser: XmlParser, cache: Cache) -> None:
self.debug = parser.debug
path_xml = os.path.join(index_path, "index.xml")
if self.debug:
- log.info(f"Loading XML from: {path_xml}")
- xml = ElementTree.parse(path_xml).getroot()
+ log.info("Loading XML from: %s", path_xml)
+ xml = ET.parse(path_xml).getroot()
self.parser = parser
self.ctx = ProjectContext(cache)
@@ -106,7 +106,7 @@ def __init__(self, index_path: str, parser: XmlParser, cache: Cache):
self._recursive_sort(self.pages)
self._recursive_sort(self.examples)
- def _fix_parents(self, node: Node):
+ def _fix_parents(self, node: Node) -> None:
if node.is_dir or node.is_root:
for child in node.children:
if child.is_file:
@@ -114,7 +114,7 @@ def _fix_parents(self, node: Node):
if child.is_dir:
self._fix_parents(child)
- def _recursive_sort(self, node: Node):
+ def _recursive_sort(self, node: Node) -> None:
node.sort_children()
for child in node.children:
self._recursive_sort(child)
@@ -122,13 +122,13 @@ def _recursive_sort(self, node: Node):
def _is_in_root(self, node: Node, root: Node):
return any(node.refid == child.refid for child in root.children)
- def _remove_from_root(self, refid: str, root: Node):
+ def _remove_from_root(self, refid: str, root: Node) -> None:
for i, child in enumerate(root.children):
if child.refid == refid:
root.children.pop(i)
return
- def _fix_duplicates(self, node: Node, root: Node, filter: [Kind]):
+ def _fix_duplicates(self, node: Node, root: Node, filter: [Kind]) -> None:
for child in node.children:
if len(filter) > 0 and child.kind not in filter:
continue
@@ -136,7 +136,7 @@ def _fix_duplicates(self, node: Node, root: Node, filter: [Kind]):
self._remove_from_root(child.refid, root)
self._fix_duplicates(child, root, filter)
- def printStructure(self):
+ def printStructure(self) -> None:
if not self.debug:
return
print("\n")
@@ -154,8 +154,8 @@ def printStructure(self):
for node in self.files.children:
self.print_node(node, "")
- def print_node(self, node: Node, indent: str):
+ def print_node(self, node: Node, indent: str) -> None:
if self.debug:
- log.info(f"{indent} {node.kind} {node.name}")
+ log.info("%s %s %s", indent, node.kind, node.name)
for child in node.children:
self.print_node(child, f"{indent} ")
diff --git a/mkdoxy/doxygen_generator.py b/mkdoxy/doxygen_generator.py
new file mode 100644
index 00000000..f0ca72f6
--- /dev/null
+++ b/mkdoxy/doxygen_generator.py
@@ -0,0 +1,401 @@
+import hashlib
+import logging
+import os
+import re
+import shutil
+from pathlib import Path
+from subprocess import PIPE, Popen
+
+from mkdocs import exceptions
+
+from mkdoxy.doxy_config import MkDoxyConfig, MkDoxyConfigProject
+
+log: logging.Logger = logging.getLogger("mkdocs")
+
+
+class DoxygenGenerator:
+ """! Class for running Doxygen.
+ @details This class is used to run Doxygen and parse the XML output.
+ """
+
+ def __init__(
+ self,
+ doxy_config: MkDoxyConfig,
+ project_config: MkDoxyConfigProject,
+ temp_doxy_folder: Path,
+ ) -> None:
+ """! Constructor.
+ @details
+ @param doxy_config: (MkDoxyConfig) Doxygen configuration.
+ @param project_config: (MkDoxyConfigProject) Project configuration.
+ @param temp_doxy_folder: (Path) Temporary Doxygen folder.
+ """
+ self.doxy_config = doxy_config
+ self.project_config = project_config
+ self.temp_doxy_folder = temp_doxy_folder
+
+ if not self.is_doxygen_valid_path(doxy_config.doxygen_bin_path):
+ raise DoxygenBinPathNotValid(
+ f"Invalid Doxygen binary path: {doxy_config.doxygen_bin_path}\n"
+ f"Make sure Doxygen is installed and the path is correct.\n"
+ f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/#configure-custom-doxygen-binary."
+ )
+
+ @staticmethod
+ def get_doxy_format_config() -> dict:
+ """
+ @brief Get the default Doxygen format configuration.
+ @details Default Doxygen configuration options:
+ @details - GENERATE_XML: YES
+ @details - GENERATE_HTML: NO
+ @details - GENERATE_LATEX: NO
+ """
+ return {
+ "GENERATE_XML": True,
+ "GENERATE_HTML": False,
+ "GENERATE_LATEX": False,
+ }
+
+ @staticmethod
+ def get_doxy_default_config() -> dict:
+ """
+ @brief Get the default Doxygen configuration.
+ @details Default Doxygen configuration options:
+ @details - DOXYFILE_ENCODING: UTF-8
+ @details - RECURSIVE: YES
+ @details - EXAMPLE_PATH: examples
+ @details - SHOW_NAMESPACES: YES
+ """
+ return {
+ "DOXYFILE_ENCODING": "UTF-8",
+ "RECURSIVE": True,
+ "EXAMPLE_PATH": "examples",
+ "SHOW_NAMESPACES": True,
+ }
+
+ def get_doxy_diagrams_config(self) -> dict:
+ """
+ @brief Get the Doxygen diagrams configuration.
+ @details Doxygen diagrams configuration options:
+ @details - HAVE_DOT: YES
+ @details - DOT_IMAGE_FORMATS:
+ @details - UML_LOOK: YES if is "uml", NO otherwise
+ @details - DOT_CLEANUP: NO
+ @details - GENERATE_LEGEND: NO
+ @details - SEARCHENGINE: NO
+ @details - GENERATE_HTML: YES (required for diagrams)
+ """
+ return {
+ "HAVE_DOT": True,
+ "DOT_IMAGE_FORMATS": self.doxy_config.generate_diagrams_format,
+ "UML_LOOK": self.doxy_config.generate_diagrams_type == "uml",
+ "DOT_CLEANUP": False,
+ "GENERATE_LEGEND": False,
+ "SEARCHENGINE": False,
+ "GENERATE_HTML": True,
+ }
+
+ # have to be tested
+ # doxy_config["CLASS_DIAGRAMS"] = "YES"
+ # doxy_config["COLLABORATION_GRAPH"] = "YES"
+ # doxy_config["INCLUDE_GRAPH"] = "YES"
+ # doxy_config["GRAPHICAL_HIERARCHY"] = "YES"
+ # doxy_config["CALL_GRAPH"] = "YES"
+ # doxy_config["CALLER_GRAPH"] = "YES"
+
+ def get_doxy_config_file(self):
+ """! Get the Doxygen configuration from the provided file.
+ @details
+ @return: (dict) Doxygen configuration from the provided file.
+ """
+ return self.str2dox_dict(self.get_doxy_config_file_raw(), self.project_config.doxy_config_file)
+
+ def get_doxy_config_file_raw(self):
+ """! Get the Doxygen configuration from the provided file.
+ @details
+ @return: (str) Doxygen configuration from the provided file.
+ """
+ try:
+ with open(self.project_config.doxy_config_file) as file:
+ return file.read()
+ except FileNotFoundError as e:
+ raise DoxygenCustomConfigNotFound(
+ f"Custom Doxygen config file not found\n"
+ f"Make sure the path is correct."
+ f"Loaded path: '{self.project_config.doxy_config_file}'\n"
+ f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/#configure-custom-doxygen-configuration-file.\n"
+ ) from e
+
+ def get_merged_doxy_dict(self) -> dict:
+ """! Get the merged Doxygen configuration.
+ @details The merged Doxygen configuration is created by merging multiple configurations.
+ @details The hierarchy is as follows:
+ @details - If a Doxygen config file is provided, it is used.
+ @details - If not, the default Doxygen configuration is used.
+ @details - Merge the INPUT directories from the mkdocs.yml file with the Doxygen configuration.
+ @details - Add the OUTPUT_DIRECTORY to the temporary Doxygen folder.
+ @details - Update configuration with the project format configuration.
+ @details - Update configuration with the default configuration.
+ @details - Update configuration with the project configuration.
+ @details - Update configuration with the diagrams configuration if enabled.
+ @return: (dict) Merged Doxygen configuration.
+ """
+ doxy_dict = {}
+
+ # Update with Doxygen config file if provided
+ if self.project_config.doxy_config_file:
+ doxy_dict.update(self.get_doxy_config_file())
+ else:
+ doxy_dict.update(self.get_doxy_default_config())
+
+ # Merge INPUT directories from the mkdocs.yml file with the Doxygen configuration
+ doxy_dict["INPUT"] = self.merge_doxygen_input(
+ self.project_config.src_dirs, doxy_dict.get("INPUT", ""), self.get_doxygen_run_folder()
+ )
+
+ # OUTPUT_DIRECTORY is always the temporary Doxygen folder
+ doxy_dict["OUTPUT_DIRECTORY"] = str(self.temp_doxy_folder)
+
+ # Update with the project format configuration
+ doxy_dict.update(self.get_doxy_format_config())
+
+ # Update with the default configuration
+ doxy_dict.update(self.doxy_config.doxy_config_dict)
+
+ # Update with the project configuration
+ doxy_dict.update(self.project_config.doxy_config_dict)
+
+ if self.doxy_config.generate_diagrams:
+ doxy_dict.update(self.get_doxy_diagrams_config())
+
+ if doxy_dict["INPUT"] == "":
+ raise exceptions.PluginError(
+ "No INPUT directories provided for Doxygen.\n"
+ "Make sure to provide at least one source directory."
+ "Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/#configure-custom-doxygen-configuration-file."
+ )
+
+ log.debug(f"- Doxygen INPUT: {doxy_dict['INPUT']}")
+
+ return doxy_dict
+
+ @staticmethod
+ def merge_doxygen_input(src_dirs: str, doxy_input: str, doxygen_run_folder: Path) -> str:
+ """! Merge the INPUT directories from the mkdocs.yml file with the Doxygen configuration.
+
+ @details Both `src_dirs` and `doxy_input` should be space-separated strings.
+ Each path is resolved relative to `doxygen_run_folder`.
+ The function returns a space-separated string of unique relative paths.
+
+ @param src_dirs: (str) Source directories from the mkdocs.yml file.
+ @param doxy_input: (str) Doxygen INPUT directories.
+ @param doxygen_run_folder: (Path) The folder to execute
+ @return: (str) Merged INPUT directories.
+ """
+ # If either input is empty, return the other.
+ if not src_dirs:
+ return doxy_input
+ if not doxy_input:
+ return src_dirs
+
+ base_dir = doxygen_run_folder.resolve()
+
+ abs_paths = {(base_dir / path_str).resolve() for path_str in src_dirs.split()}
+ for path_str in doxy_input.split():
+ abs_paths.add((base_dir / path_str).resolve())
+
+ # Convert absolute paths back to relative ones and sort for consistency
+ relative_paths = sorted(os.path.relpath(p, base_dir) for p in abs_paths)
+
+ return " ".join(relative_paths)
+
+ @staticmethod
+ def is_doxygen_valid_path(doxygen_bin_path: Path) -> bool:
+ """! Check if the Doxygen binary path is valid.
+ @details Accepts a full path or just 'doxygen' if it exists in the system's PATH.
+ @param doxygen_bin_path: (str) The path to the Doxygen binary or just 'doxygen'.
+ @return: (bool) True if the Doxygen binary path is valid, False otherwise.
+ """
+ # If the path is just 'doxygen', search for it in the system's PATH
+ if str(doxygen_bin_path) == "doxygen":
+ return shutil.which("doxygen") is not None
+
+ # Use pathlib to check if the provided full path is a file and executable
+ return doxygen_bin_path.is_file() and os.access(doxygen_bin_path, os.X_OK)
+
+ # Source of dox_dict2str: https://xdress-fabio.readthedocs.io/en/latest/_modules/xdress/doxygen.html#XDressPlugin
+
+ @staticmethod
+ def str2dox_dict(dox_str: str, config_file: str = "???") -> dict:
+ """! Convert a string from a doxygen config file to a dictionary.
+ @details
+ @param dox_str: (str) String from a doxygen config file.
+ @return: (dict) Dictionary.
+ """
+ dox_dict = {}
+ dox_str = re.sub(r"\\\s*\n\s*", "", dox_str)
+ pattern = r"^\s*([^=\s]+)\s*(=|\+=)\s*(.*)$"
+
+ try:
+ for line in dox_str.split("\n"):
+ if line.strip() == "" or line.startswith("#"):
+ continue
+ match = re.match(pattern, line)
+ if not match:
+ raise DoxygenCustomConfigNotValid(
+ f"Invalid line: '{line}'"
+ f"In custom Doxygen config file: {config_file}\n"
+ f"Make sure the file is in standard Doxygen format."
+ f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
+ )
+ key, operator, value = match.groups()
+ value = value.strip()
+ if operator == "=":
+ if value == "YES":
+ dox_dict[key] = True
+ elif value == "NO":
+ dox_dict[key] = False
+ else:
+ dox_dict[key] = value
+ if operator == "+=":
+ dox_dict[key] = f"{dox_dict[key]} {value}"
+ except ValueError as e:
+ raise DoxygenCustomConfigNotValid(
+ f"Invalid custom Doxygen config file: {config_file}\n"
+ f"Make sure the file is in standard Doxygen format."
+ f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
+ ) from e
+ return dox_dict
+
+ @staticmethod
+ def dox_dict2str(dox_dict: dict) -> str:
+ """! Convert a dictionary to a string that can be written to a doxygen config file.
+ @details
+ @param dox_dict: (dict) Dictionary to convert.
+ @return: (str) String that can be written to a doxygen config file.
+ """
+ string = ""
+ new_line = "{option} = {value}\n"
+ items = sorted(dox_dict.items())
+ for key, value in items:
+ if value is True:
+ value_transformed = "YES"
+ elif value is False:
+ value_transformed = "NO"
+ else:
+ value_transformed = value
+
+ string += new_line.format(option=key.upper(), value=value_transformed)
+
+ # Don't need an empty line at the end
+ return string.strip()
+
+ @staticmethod
+ def hash_write(file_name: Path, hash_key: str) -> None:
+ """! Write the hash to the file.
+ @details
+ @param file_name: (Path) Path to the file where the hash will be saved.
+ @param hash_key: (str) Hash.
+ """
+ with open(file_name, "w") as hash_file:
+ hash_file.write(hash_key)
+
+ @staticmethod
+ def hash_read(file_name: Path) -> str:
+ """! Read the hash from the file.
+ @details
+ @param file_name: (Path) Path to the file with the hash.
+ @return: (str) Hash.
+ """
+ with open(file_name) as hash_file:
+ return str(hash_file.read())
+
+ def has_changes(self) -> bool:
+ """! Check if the source files have changed since the last run.
+ @details
+ @return: (bool) True if the source files have changed since the last run.
+ """
+ sha1 = hashlib.sha1()
+ sources = self.project_config.src_dirs.split(" ")
+ # Code from https://stackoverflow.com/a/22058673/15411117
+ BUF_SIZE = 65536 # let's read stuff in 64kb chunks!
+ for source in sources:
+ for path in Path(source).rglob("*.*"):
+ if path.is_file():
+ with open(path, "rb") as file:
+ while True:
+ data = file.read(BUF_SIZE)
+ if not data:
+ break
+ sha1.update(data)
+
+ hash_new = sha1.hexdigest()
+ hash_file_name: Path = Path("mkdoxy_hash.txt")
+ hash_file_path = Path.joinpath(self.temp_doxy_folder, hash_file_name)
+ if hash_file_path.is_file():
+ hash_old = self.hash_read(hash_file_path)
+ if hash_new == hash_old:
+ return False # No changes in the source files
+
+ self.hash_write(hash_file_path, hash_new)
+ return True
+
+ def run(self) -> None:
+ """! Run Doxygen with the current configuration using the Popen class.
+ @details
+ """
+ doxy_builder = Popen(
+ [self.doxy_config.doxygen_bin_path, "-"],
+ stdout=PIPE,
+ stdin=PIPE,
+ stderr=PIPE,
+ )
+
+ if self.project_config.doxy_config_file_force:
+ doxy_str = self.get_doxy_config_file_raw()
+ else:
+ doxy_str = self.dox_dict2str(self.get_merged_doxy_dict())
+ stdout_data, stderr_data = doxy_builder.communicate(input=doxy_str.encode("utf-8"))
+ if doxy_builder.returncode != 0:
+ error_message = (
+ f"Error running Doxygen (exit code {doxy_builder.returncode}): {stderr_data.decode('utf-8')}"
+ )
+ raise exceptions.PluginError(error_message)
+
+ def get_output_xml_folder(self) -> Path:
+ """! Get the path to the XML output folder.
+ @details
+ @return: (Path) Path to the XML output folder.
+ """
+ return Path.joinpath(self.temp_doxy_folder, Path("xml"))
+
+ def get_output_html_folder(self) -> Path:
+ """! Get the path to the HTML output folder.
+ @details
+ @return: (Path) Path to the HTML output folder.
+ """
+ return Path.joinpath(self.temp_doxy_folder, Path("html"))
+
+ def get_doxygen_run_folder(self):
+ """! Get the working directory to execute Doxygen in. Important to resolve relative paths.
+ @details When a doxygen config file is provided, this is its containing directory. Otherwise, it's the current
+ working directory.
+ @return: (Path) Path to the folder to execute Doxygen in.
+ """
+ if not self.project_config.doxy_config_file:
+ return Path.cwd()
+
+ return Path(self.project_config.doxy_config_file).parent
+
+
+# not valid path exception
+class DoxygenBinPathNotValid(exceptions.PluginError):
+ pass
+
+
+class DoxygenCustomConfigNotFound(exceptions.PluginError):
+ pass
+
+
+class DoxygenCustomConfigNotValid(exceptions.PluginError):
+ pass
diff --git a/mkdoxy/doxyrun.py b/mkdoxy/doxyrun.py
deleted file mode 100644
index ffd1473a..00000000
--- a/mkdoxy/doxyrun.py
+++ /dev/null
@@ -1,275 +0,0 @@
-import hashlib
-import logging
-import os
-import shutil
-import re
-
-from pathlib import Path, PurePath
-from subprocess import PIPE, Popen
-from typing import Optional
-
-log: logging.Logger = logging.getLogger("mkdocs")
-
-
-class DoxygenRun:
- """! Class for running Doxygen.
- @details This class is used to run Doxygen and parse the XML output.
- """
-
- def __init__(
- self,
- doxygenBinPath: str,
- doxygenSource: str,
- tempDoxyFolder: str,
- doxyCfgNew,
- doxyConfigFile: Optional[str] = None,
- ):
- """! Constructor.
- Default Doxygen config options:
-
- - INPUT:
- - OUTPUT_DIRECTORY:
- - DOXYFILE_ENCODING: UTF-8
- - GENERATE_XML: YES
- - RECURSIVE: YES
- - EXAMPLE_PATH: examples
- - SHOW_NAMESPACES: YES
- - GENERATE_HTML: NO
- - GENERATE_LATEX: NO
-
- @details
- @param doxygenBinPath: (str) Path to the Doxygen binary.
- @param doxygenSource: (str) Source files for Doxygen.
- @param tempDoxyFolder: (str) Temporary folder for Doxygen.
- @param doxyConfigFile: (str) Path to a Doxygen config file.
- @param doxyCfgNew: (dict) New Doxygen config options that will be added to the default config (new options will overwrite default options)
- """ # noqa: E501
-
- if not self.is_doxygen_valid_path(doxygenBinPath):
- raise DoxygenBinPathNotValid(
- f"Invalid Doxygen binary path: {doxygenBinPath}\n"
- f"Make sure Doxygen is installed and the path is correct.\n"
- f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/#configure-custom-doxygen-binary."
- )
-
- self.doxygenBinPath: str = doxygenBinPath
- self.doxygenSource: str = doxygenSource
- self.tempDoxyFolder: str = tempDoxyFolder
- self.doxyConfigFile: Optional[str] = doxyConfigFile
- self.hashFileName: str = "hashChanges.yaml"
- self.hashFilePath: PurePath = PurePath.joinpath(Path(self.tempDoxyFolder), Path(self.hashFileName))
- self.doxyCfg: dict = self.setDoxyCfg(doxyCfgNew)
-
- def setDoxyCfg(self, doxyCfgNew: dict) -> dict:
- """! Set the Doxygen configuration.
- @details If a custom Doxygen config file is provided, it will be used. Otherwise, default options will be used.
- @details Order of application of parameters:
- @details 1. Custom Doxygen config file
- @details 2. If not provided, default options - in documentation
- @details 3. New Doxygen config options from mkdocs.yml
- @details 3. Overwrite INPUT and OUTPUT_DIRECTORY with the provided values for correct plugin operation.
-
- @details Overwrite options description:
- @details - INPUT:
- @details - OUTPUT_DIRECTORY:
-
- @details Default Doxygen config options:
- @details - DOXYFILE_ENCODING: UTF-8
- @details - GENERATE_XML: YES
- @details - RECURSIVE: YES
- @details - EXAMPLE_PATH: examples
- @details - SHOW_NAMESPACES: YES
- @details - GENERATE_HTML: NO
- @details - GENERATE_LATEX: NO
- @param doxyCfgNew: (dict) New Doxygen config options that will be
- added to the default config (new options will overwrite default options)
- @return: (dict) Doxygen configuration.
- """
- doxyCfg = {}
-
- if self.doxyConfigFile is not None and self.doxyConfigFile != "":
- try:
- with open(self.doxyConfigFile, "r") as file:
- doxyCfg.update(self.str2dox_dict(file.read()))
- except FileNotFoundError as e:
- raise DoxygenCustomConfigNotFound(
- f"Custom Doxygen config file not found: {self.doxyConfigFile}\n"
- f"Make sure the path is correct."
- f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/#configure-custom-doxygen-configuration-file."
- ) from e
- else:
- doxyCfg = {
- "DOXYFILE_ENCODING": "UTF-8",
- "GENERATE_XML": "YES",
- "RECURSIVE": "YES",
- "EXAMPLE_PATH": "examples",
- "SHOW_NAMESPACES": "YES",
- "GENERATE_HTML": "NO",
- "GENERATE_LATEX": "NO",
- }
-
- doxyCfg.update(doxyCfgNew)
- doxyCfg["INPUT"] = self.doxygenSource
- doxyCfg["OUTPUT_DIRECTORY"] = self.tempDoxyFolder
- return doxyCfg
-
- def is_doxygen_valid_path(self, doxygen_bin_path: str) -> bool:
- """! Check if the Doxygen binary path is valid.
- @details Accepts a full path or just 'doxygen' if it exists in the system's PATH.
- @param doxygen_bin_path: (str) The path to the Doxygen binary or just 'doxygen'.
- @return: (bool) True if the Doxygen binary path is valid, False otherwise.
- """
- # If the path is just 'doxygen', search for it in the system's PATH
- if doxygen_bin_path.lower() == "doxygen":
- return shutil.which("doxygen") is not None
-
- # Use pathlib to check if the provided full path is a file and executable
- path = Path(doxygen_bin_path)
- return path.is_file() and os.access(path, os.X_OK)
-
- # Source of dox_dict2str: https://xdress-fabio.readthedocs.io/en/latest/_modules/xdress/doxygen.html#XDressPlugin
- def dox_dict2str(self, dox_dict: dict) -> str:
- """! Convert a dictionary to a string that can be written to a doxygen config file.
- @details
- @param dox_dict: (dict) Dictionary to convert.
- @return: (str) String that can be written to a doxygen config file.
- """
- s = ""
- new_line = "{option} = {value}\n"
- for key, value in dox_dict.items():
- if value is True:
- _value = "YES"
- elif value is False:
- _value = "NO"
- else:
- _value = value
-
- s += new_line.format(option=key.upper(), value=_value)
-
- # Don't need an empty line at the end
- return s.strip()
-
- def str2dox_dict(self, dox_str: str) -> dict:
- """! Convert a string from a doxygen config file to a dictionary.
- @details
- @param dox_str: (str) String from a doxygen config file.
- @return: (dict) Dictionary.
- """
- dox_dict = {}
- dox_str = re.sub(r"\\\s*\n\s*", "", dox_str)
- pattern = r"^\s*([^=\s]+)\s*(=|\+=)\s*(.*)$"
-
- try:
- for line in dox_str.split("\n"):
- if line.strip() == "" or line.startswith("#"):
- continue
- match = re.match(pattern, line)
- if not match:
- raise DoxygenCustomConfigNotValid(
- f"Invalid line: '{line}'"
- f"In custom Doxygen config file: {self.doxyConfigFile}\n"
- f"Make sure the file is in standard Doxygen format."
- f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
- )
- key, operator, value = match.groups()
- value = value.strip()
- if operator == "=":
- if value == "YES":
- dox_dict[key] = True
- elif value == "NO":
- dox_dict[key] = False
- else:
- dox_dict[key] = value
- if operator == "+=":
- dox_dict[key] = f"{dox_dict[key]} {value}"
- except ValueError as e:
- raise DoxygenCustomConfigNotValid(
- f"Invalid custom Doxygen config file: {self.doxyConfigFile}\n"
- f"Make sure the file is in standard Doxygen format."
- f"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
- ) from e
- return dox_dict
-
- def hasChanged(self) -> bool:
- """! Check if the source files have changed since the last run.
- @details
- @return: (bool) True if the source files have changed since the last run.
- """
-
- def hashWrite(filename: PurePath, hash: str):
- with open(filename, "w") as file:
- file.write(hash)
-
- def hashRead(filename: PurePath) -> str:
- with open(filename, "r") as file:
- return str(file.read())
-
- sha1 = hashlib.sha1()
- srcs = self.doxygenSource.split(" ")
- for src in srcs:
- for path in Path(src).rglob("*.*"):
- # # Code from https://stackoverflow.com/a/22058673/15411117
- # # BUF_SIZE is totally arbitrary, change for your app!
- BUF_SIZE = 65536 # let's read stuff in 64kb chunks!
- if path.is_file():
- with open(path, "rb") as f:
- while True:
- data = f.read(BUF_SIZE)
- if not data:
- break
- sha1.update(data)
- # print(f"{path}: {sha1.hexdigest()}")
-
- hashNew = sha1.hexdigest()
- if Path(self.hashFilePath).is_file():
- hashOld = hashRead(self.hashFilePath)
- if hashNew == hashOld:
- return False
-
- hashWrite(self.hashFilePath, hashNew)
- return True
-
- def run(self):
- """! Run Doxygen with the current configuration using the Popen class.
- @details
- """
- doxyBuilder = Popen(
- [self.doxygenBinPath, "-"],
- stdout=PIPE,
- stdin=PIPE,
- stderr=PIPE,
- )
- (doxyBuilder.communicate(self.dox_dict2str(self.doxyCfg).encode("utf-8"))[0].decode().strip())
- # log.info(self.destinationDir)
- # log.info(stdout_data)
-
- def checkAndRun(self):
- """! Check if the source files have changed since the last run and run Doxygen if they have.
- @details
- @return: (bool) True if Doxygen was run.
- """
- if self.hasChanged():
- self.run()
- return True
- else:
- return False
-
- def getOutputFolder(self) -> PurePath:
- """! Get the path to the XML output folder.
- @details
- @return: (PurePath) Path to the XML output folder.
- """
- return Path.joinpath(Path(self.tempDoxyFolder), Path("xml"))
-
-
-# not valid path exception
-class DoxygenBinPathNotValid(Exception):
- pass
-
-
-class DoxygenCustomConfigNotFound(Exception):
- pass
-
-
-class DoxygenCustomConfigNotValid(Exception):
- pass
diff --git a/mkdoxy/finder.py b/mkdoxy/finder.py
index 10621465..053d4643 100644
--- a/mkdoxy/finder.py
+++ b/mkdoxy/finder.py
@@ -1,76 +1,74 @@
-from typing import Dict
-
from mkdoxy.constants import Kind
from mkdoxy.doxygen import Doxygen
from mkdoxy.utils import recursive_find, recursive_find_with_parent
class Finder:
- def __init__(self, doxygen: Dict[str, Doxygen], debug: bool = False):
+ def __init__(self, doxygen: dict[str, Doxygen], debug: bool = False) -> None:
self.doxygen = doxygen
self.debug = debug
def _normalize(self, name: str) -> str:
return name.replace(" ", "")
- def listToNames(self, list):
+ def list_to_names(self, list):
return [part.name_params for part in list]
- def _doxyParent(self, project, parent: str, kind: Kind):
+ def _doxy_parent(self, project, parent: str, kind: Kind):
if not kind.is_parent():
return None
parents = recursive_find(self.doxygen[project].root.children, kind)
if parents:
- for findParent in parents:
- if findParent.name_long == parent:
- return findParent
- return self.listToNames(parents)
+ for find_parent in parents:
+ if find_parent.name_long == parent:
+ return find_parent
+ return self.list_to_names(parents)
return None
- def _doxyMemberInParent(self, project, parent: str, parentKind: Kind, memberName: str, memberKind: Kind):
- findParent = self._doxyParent(project, parent, parentKind)
- if findParent:
- if isinstance(findParent, list):
- for member in findParent:
- if self._normalize(memberName) in self._normalize(member):
+ def _doxy_member_in_parent(self, project, parent: str, parent_kind: Kind, member_name: str, member_kind: Kind):
+ find_parent = self._doxy_parent(project, parent, parent_kind)
+ if find_parent:
+ if isinstance(find_parent, list):
+ for member in find_parent:
+ if self._normalize(member_name) in self._normalize(member):
return member
- return findParent
+ return find_parent
else:
- members = recursive_find(findParent.children, memberKind)
+ members = recursive_find(find_parent.children, member_kind)
if members:
for member in members:
- if self._normalize(memberName) in self._normalize(member.name_params):
+ if self._normalize(member_name) in self._normalize(member.name_params):
return member
- return self.listToNames(members)
+ return self.list_to_names(members)
return None
return None
- def doxyClass(self, project, className: str):
- return self._doxyParent(project, className, Kind.CLASS)
+ def doxy_class(self, project, class_name: str):
+ return self._doxy_parent(project, class_name, Kind.CLASS)
- def doxyNamespace(self, project, namespace: str):
- return self._doxyParent(project, namespace, Kind.NAMESPACE)
+ def doxy_namespace(self, project, namespace: str):
+ return self._doxy_parent(project, namespace, Kind.NAMESPACE)
- def doxyClassMethod(self, project, className: str, methodName: str):
- return self._doxyMemberInParent(project, className, Kind.CLASS, methodName, Kind.FUNCTION)
+ def doxy_class_method(self, project, class_name: str, method_name: str):
+ return self._doxy_member_in_parent(project, class_name, Kind.CLASS, method_name, Kind.FUNCTION)
- def doxyNamespaceFunction(self, project, namespace: str, functionName: str):
- return self._doxyMemberInParent(project, namespace, Kind.NAMESPACE, functionName, Kind.FUNCTION)
+ def doxy_namespace_function(self, project, namespace: str, function_name: str):
+ return self._doxy_member_in_parent(project, namespace, Kind.NAMESPACE, function_name, Kind.FUNCTION)
- def doxyFunction(self, project, functionName: str):
+ def doxy_function(self, project, function_name: str):
functions = recursive_find_with_parent(self.doxygen[project].files.children, [Kind.FUNCTION], [Kind.FILE])
if functions:
for function in functions:
- if self._normalize(functionName) == self._normalize(function.name_params):
+ if self._normalize(function_name) == self._normalize(function.name_params):
return function
- return self.listToNames(functions)
+ return self.list_to_names(functions)
return None
- def doxyCode(self, project, fileName):
+ def doxy_code(self, project, file_name):
files = recursive_find_with_parent(self.doxygen[project].files.children, [Kind.FILE], [Kind.DIR])
if files:
for file in files:
- if self._normalize(fileName) == self._normalize(file.name_long):
+ if self._normalize(file_name) == self._normalize(file.name_long):
return file
- return self.listToNames(files)
+ return self.list_to_names(files)
return None
diff --git a/mkdoxy/generatorAuto.py b/mkdoxy/generatorAuto.py
index 8f5d77d6..91add8b6 100644
--- a/mkdoxy/generatorAuto.py
+++ b/mkdoxy/generatorAuto.py
@@ -1,5 +1,6 @@
import logging
import os
+from typing import Optional
from mkdocs.structure import files
@@ -41,40 +42,40 @@ def normalize(name):
class GeneratorAuto:
def __init__(
self,
- generatorBase: GeneratorBase,
- tempDoxyDir: str,
- siteDir: str,
- apiPath: str,
+ generator_base: GeneratorBase,
+ temp_doxy_dir: str,
+ site_dir: str,
+ api_path: str,
doxygen: Doxygen,
- useDirectoryUrls: bool,
- ):
- self.generatorBase = generatorBase
- self.tempDoxyDir = tempDoxyDir
- self.siteDir = siteDir
- self.apiPath = apiPath
+ use_directory_urls: bool,
+ ) -> None:
+ self.generator_base = generator_base
+ self.temp_doxy_dir = temp_doxy_dir
+ self.site_dir = site_dir
+ self.api_path = api_path
self.doxygen = doxygen
- self.useDirectoryUrls = useDirectoryUrls
- self.fullDocFiles = []
- self.debug = generatorBase.debug
- os.makedirs(os.path.join(self.tempDoxyDir, self.apiPath), exist_ok=True)
-
- def save(self, path: str, output: str):
- pathRel = os.path.join(self.apiPath, path)
- self.fullDocFiles.append(files.File(pathRel, self.tempDoxyDir, self.siteDir, self.useDirectoryUrls))
- with open(os.path.join(self.tempDoxyDir, pathRel), "w", encoding="utf-8") as file:
+ self.use_directory_urls = use_directory_urls
+ self.full_doc_files = []
+ self.debug = generator_base.debug
+ os.makedirs(os.path.join(self.temp_doxy_dir, self.api_path), exist_ok=True)
+
+ def save(self, path: str, output: str) -> None:
+ path_rel = os.path.join(self.api_path, path)
+ self.full_doc_files.append(files.File(path_rel, self.temp_doxy_dir, self.site_dir, self.use_directory_urls))
+ with open(os.path.join(self.temp_doxy_dir, path_rel), "w", encoding="utf-8") as file:
file.write(output)
- def fullDoc(self, defaultTemplateConfig: dict):
- self.annotated(self.doxygen.root.children, defaultTemplateConfig)
- self.fileindex(self.doxygen.files.children, defaultTemplateConfig)
- self.members(self.doxygen.root.children, defaultTemplateConfig)
- self.members(self.doxygen.groups.children, defaultTemplateConfig)
- self.files(self.doxygen.files.children, defaultTemplateConfig)
- self.namespaces(self.doxygen.root.children, defaultTemplateConfig)
- self.classes(self.doxygen.root.children, defaultTemplateConfig)
- self.hierarchy(self.doxygen.root.children, defaultTemplateConfig)
- self.modules(self.doxygen.groups.children, defaultTemplateConfig)
- self.pages(self.doxygen.pages.children, defaultTemplateConfig)
+ def full_doc(self, default_template_config: dict) -> None:
+ self.annotated(self.doxygen.root.children, default_template_config)
+ self.fileindex(self.doxygen.files.children, default_template_config)
+ self.members(self.doxygen.root.children, default_template_config)
+ self.members(self.doxygen.groups.children, default_template_config)
+ self.files(self.doxygen.files.children, default_template_config)
+ self.namespaces(self.doxygen.root.children, default_template_config)
+ self.classes(self.doxygen.root.children, default_template_config)
+ self.hierarchy(self.doxygen.root.children, default_template_config)
+ self.modules(self.doxygen.groups.children, default_template_config)
+ self.pages(self.doxygen.pages.children, default_template_config)
# self.examples(self.doxygen.examples.children) # TODO examples
self.relatedpages(self.doxygen.pages.children)
self.index(
@@ -82,139 +83,139 @@ def fullDoc(self, defaultTemplateConfig: dict):
[Kind.FUNCTION, Kind.VARIABLE, Kind.TYPEDEF, Kind.ENUM],
[Kind.CLASS, Kind.STRUCT, Kind.INTERFACE],
"Class Members",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.FUNCTION],
[Kind.CLASS, Kind.STRUCT, Kind.INTERFACE],
"Class Member Functions",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.VARIABLE],
[Kind.CLASS, Kind.STRUCT, Kind.INTERFACE],
"Class Member Variables",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.TYPEDEF],
[Kind.CLASS, Kind.STRUCT, Kind.INTERFACE],
"Class Member Typedefs",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.ENUM],
[Kind.CLASS, Kind.STRUCT, Kind.INTERFACE],
"Class Member Enums",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.FUNCTION, Kind.VARIABLE, Kind.TYPEDEF, Kind.ENUM],
[Kind.NAMESPACE],
"Namespace Members",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.FUNCTION],
[Kind.NAMESPACE],
"Namespace Member Functions",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.VARIABLE],
[Kind.NAMESPACE],
"Namespace Member Variables",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.TYPEDEF],
[Kind.NAMESPACE],
"Namespace Member Typedefs",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.root.children,
[Kind.ENUM],
[Kind.NAMESPACE],
"Namespace Member Enums",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.files.children,
[Kind.FUNCTION],
[Kind.FILE],
"Functions",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.files.children,
[Kind.DEFINE],
[Kind.FILE],
"Macros",
- defaultTemplateConfig,
+ default_template_config,
)
self.index(
self.doxygen.files.children,
[Kind.VARIABLE, Kind.UNION, Kind.TYPEDEF, Kind.ENUM],
[Kind.FILE],
"Variables",
- defaultTemplateConfig,
+ default_template_config,
)
- def annotated(self, nodes: [Node], config: dict = None):
+ def annotated(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "annotated.md"
- output = self.generatorBase.annotated(nodes, config)
+ output = self.generator_base.annotated(nodes, config)
self.save(path, output)
- def programlisting(self, node: [Node], config: dict = None):
+ def programlisting(self, node: [Node], config: Optional[dict] = None) -> None:
path = f"{node.refid}_source.md"
- output = self.generatorBase.programlisting(node, config)
+ output = self.generator_base.programlisting(node, config)
self.save(path, output)
- def fileindex(self, nodes: [Node], config: dict = None):
+ def fileindex(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "files.md"
- output = self.generatorBase.fileindex(nodes, config)
+ output = self.generator_base.fileindex(nodes, config)
self.save(path, output)
- def namespaces(self, nodes: [Node], config: dict = None):
+ def namespaces(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "namespaces.md"
- output = self.generatorBase.namespaces(nodes, config)
+ output = self.generator_base.namespaces(nodes, config)
self.save(path, output)
- def page(self, node: Node, config: dict = None):
+ def page(self, node: Node, config: Optional[dict] = None) -> None:
path = f"{node.name}.md"
- output = self.generatorBase.page(node, config)
+ output = self.generator_base.page(node, config)
self.save(path, output)
- def pages(self, nodes: [Node], config: dict = None):
+ def pages(self, nodes: [Node], config: Optional[dict] = None) -> None:
for node in nodes:
self.page(node, config)
- def relatedpages(self, nodes: [Node], config: dict = None):
+ def relatedpages(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "pages.md"
- output = self.generatorBase.relatedpages(nodes)
+ output = self.generator_base.relatedpages(nodes)
self.save(path, output)
- def example(self, node: Node, config: dict = None):
+ def example(self, node: Node, config: Optional[dict] = None) -> None:
path = f"{node.refid}.md"
- output = self.generatorBase.example(node, config)
+ output = self.generator_base.example(node, config)
self.save(path, output)
- def examples(self, nodes: [Node], config: dict = None):
+ def examples(self, nodes: [Node], config: Optional[dict] = None) -> None:
for node in nodes:
if node.is_example:
if node.has_programlisting:
@@ -223,40 +224,40 @@ def examples(self, nodes: [Node], config: dict = None):
path = "examples.md"
- output = self.generatorBase.examples(nodes, config)
+ output = self.generator_base.examples(nodes, config)
self.save(path, output)
- def classes(self, nodes: [Node], config: dict = None):
+ def classes(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "classes.md"
- output = self.generatorBase.classes(nodes, config)
+ output = self.generator_base.classes(nodes, config)
self.save(path, output)
- def modules(self, nodes: [Node], config: dict = None):
+ def modules(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "modules.md"
- output = self.generatorBase.modules(nodes, config)
+ output = self.generator_base.modules(nodes, config)
self.save(path, output)
- def hierarchy(self, nodes: [Node], config: dict = None):
+ def hierarchy(self, nodes: [Node], config: Optional[dict] = None) -> None:
path = "hierarchy.md"
- output = self.generatorBase.hierarchy(nodes, config)
+ output = self.generator_base.hierarchy(nodes, config)
self.save(path, output)
- def member(self, node: Node, config: dict = None):
+ def member(self, node: Node, config: Optional[dict] = None) -> None:
path = node.filename
- output = self.generatorBase.member(node, config)
+ output = self.generator_base.member(node, config)
self.save(path, output)
if node.is_language or node.is_group or node.is_file or node.is_dir:
self.members(node.children, config)
- def file(self, node: Node, config: dict = None):
+ def file(self, node: Node, config: Optional[dict] = None) -> None:
path = node.filename
- output = self.generatorBase.file(node, config)
+ output = self.generator_base.file(node, config)
self.save(path, output)
if node.is_file and node.has_programlisting:
@@ -265,12 +266,12 @@ def file(self, node: Node, config: dict = None):
if node.is_file or node.is_dir:
self.files(node.children, config)
- def members(self, nodes: [Node], config: dict = None):
+ def members(self, nodes: [Node], config: Optional[dict] = None) -> None:
for node in nodes:
if node.is_parent or node.is_group or node.is_file or node.is_dir:
self.member(node, config)
- def files(self, nodes: [Node], config: dict = None):
+ def files(self, nodes: [Node], config: Optional[dict] = None) -> None:
for node in nodes:
if node.is_file or node.is_dir:
self.file(node, config)
@@ -281,20 +282,22 @@ def index(
kind_filters: Kind,
kind_parents: [Kind],
title: str,
- config: dict = None,
- ):
+ config: Optional[dict] = None,
+ ) -> None:
path = title.lower().replace(" ", "_") + ".md"
- output = self.generatorBase.index(nodes, kind_filters, kind_parents, title, config)
+ output = self.generator_base.index(nodes, kind_filters, kind_parents, title, config)
self.save(path, output)
- def _generate_recursive(self, output_summary: str, node: Node, level: int):
+ def _generate_recursive(self, output_summary: str, node: Node, level: int) -> None:
if node.kind.is_parent():
output_summary += str(" " * level + generate_link(f"{node.kind.value} {node.name}", f"{node.refid}.md"))
for child in node.children:
self._generate_recursive(output_summary, child, level + 2)
- def _generate_recursive_files(self, output_summary: str, node: Node, level: int, config: dict = None):
+ def _generate_recursive_files(
+ self, output_summary: str, node: Node, level: int, config: Optional[dict] = None
+ ) -> None:
if config is None:
config = []
if node.kind.is_file() or node.kind.is_dir():
@@ -308,25 +311,25 @@ def _generate_recursive_files(self, output_summary: str, node: Node, level: int,
for child in node.children:
self._generate_recursive_files(output_summary, child, level + 2, config)
- def _generate_recursive_examples(self, output_summary: str, node: Node, level: int):
+ def _generate_recursive_examples(self, output_summary: str, node: Node, level: int) -> None:
if node.kind.is_example():
output_summary += str(" " * level + generate_link(node.name, f"{node.refid}.md"))
for child in node.children:
self._generate_recursive_examples(output_summary, child, level + 2)
- def _generate_recursive_groups(self, output_summary: str, node: Node, level: int):
+ def _generate_recursive_groups(self, output_summary: str, node: Node, level: int) -> None:
if node.kind.is_group():
output_summary += str(" " * level + generate_link(node.title, f"{node.refid}.md"))
for child in node.children:
self._generate_recursive_groups(output_summary, child, level + 2)
- def _generate_recursive_pages(self, output_summary: str, node: Node, level: int):
+ def _generate_recursive_pages(self, output_summary: str, node: Node, level: int) -> None:
if node.kind.is_page():
output_summary += str(" " * level + generate_link(node.title, f"{node.refid}.md"))
for child in node.children:
self._generate_recursive_pages(output_summary, child, level + 2)
- def summary(self, defaultTemplateConfig: dict):
+ def summary(self, default_template_config: dict) -> None:
offset = 0
output_summary = "" + str(" " * (offset + 2) + generate_link("Related Pages", "pages.md"))
for node in self.doxygen.pages.children:
@@ -345,7 +348,7 @@ def summary(self, defaultTemplateConfig: dict):
output_summary += str(" " * (offset + 2) + generate_link("Files", "files.md", end="\n"))
for node in self.doxygen.files.children:
- self._generate_recursive_files(output_summary, node, offset + 4, defaultTemplateConfig)
+ self._generate_recursive_files(output_summary, node, offset + 4, default_template_config)
# output_summary += str(' ' * (offset + 2) + generate_link('Examples', 'examples.md'))
# for node in self.doxygen.examples.children:
diff --git a/mkdoxy/generatorBase.py b/mkdoxy/generatorBase.py
index 6e2f6905..fc7c3d3e 100644
--- a/mkdoxy/generatorBase.py
+++ b/mkdoxy/generatorBase.py
@@ -1,19 +1,19 @@
import logging
import os
import string
-from typing import Dict
+from typing import Optional
from jinja2 import BaseLoader, Environment, Template
from jinja2.exceptions import TemplateError
from mkdocs import exceptions
import mkdoxy
-from mkdoxy.constants import Kind
+from mkdoxy.constants import JINJA_EXTENSIONS, Kind
from mkdoxy.filters import use_code_language
from mkdoxy.node import DummyNode, Node
from mkdoxy.utils import (
merge_two_dicts,
- parseTemplateFile,
+ parse_template_file,
recursive_find,
recursive_find_with_parent,
)
@@ -21,91 +21,92 @@
log: logging.Logger = logging.getLogger("mkdocs")
-LETTERS = string.ascii_lowercase + "~_@\\"
+LETTERS = string.ascii_lowercase + "~_@\\\\"
class GeneratorBase:
- """! Base class for all generators."""
+ \"\"\"! Base class for all generators.\"\"\"
- def __init__(self, templateDir: str = "", ignore_errors: bool = False, debug: bool = False):
- """! Constructor.
+ def __init__(self, template_dir: str = \"\", ignore_errors: bool = False, debug: bool = False) -> None:
+ \"\"\"! Constructor.
@details
- @param templateDir (str): Path to the directory with custom templates (default: "")
+ @param template_dir (str): Path to the directory with custom templates (default: \"\")
@param ignore_errors (bool): If True, errors will be ignored (default: False)
@param debug (bool): If True, debug messages will be printed (default: False)
- """
+ \"\"\"
self.debug: bool = debug # if True, debug messages will be printed
- self.templates: Dict[str, Template] = {}
- self.metaData: Dict[str, list[str]] = {}
+ self.templates: dict[str, Template] = {}
+ self.meta_data: dict[str, list[str]] = {}
environment = Environment(loader=BaseLoader())
- environment.filters["use_code_language"] = use_code_language
+ environment.filters[\"use_code_language\"] = use_code_language
# code from https://github.com/daizutabi/mkapi/blob/master/mkapi/core/renderer.py#L29-L38
- path = os.path.join(os.path.dirname(mkdoxy.__file__), "templates")
- ENDING = (".jinja2", ".j2", ".jinja")
- for fileName in os.listdir(path):
- filePath = os.path.join(path, fileName)
+ path = os.path.join(os.path.dirname(mkdoxy.__file__), \"templates\")
+ for file_name in os.listdir(path):
+ file_path = os.path.join(path, file_name)
# accept any case of the file ending
- if fileName.lower().endswith(ENDING):
- with open(filePath, "r") as file:
- name = os.path.splitext(fileName)[0]
- fileTemplate, metaData = parseTemplateFile(file.read())
- self.templates[name] = environment.from_string(fileTemplate)
- self.metaData[name] = metaData
+ if file_name.lower().endswith(JINJA_EXTENSIONS):
+ with open(file_path) as file:
+ name = os.path.splitext(file_name)[0]
+ file_template, meta_data = parse_template_file(file.read())
+ self.templates[name] = environment.from_string(file_template)
+ self.meta_data[name] = meta_data
else:
log.error(
- f"Trying to load unsupported file '{filePath}'. Supported file ends with {ENDING}."
- f"Look at documentation: https://mkdoxy.kubaandrysek.cz/usage/#custom-jinja-templates."
+ \"Trying to load unsupported file '%s'. Supported file ends with %s.\"
+ \"Look at documentation: https://mkdoxy.kubaandrysek.cz/usage/#custom-jinja-templates.\",
+ file_path, JINJA_EXTENSIONS
)
- # test if templateDir is existing
- if templateDir:
- if not os.path.exists(templateDir):
- raise exceptions.ConfigurationError(f"Custom template directory '{templateDir}' does not exist.")
+ # test if template_dir is existing
+ if template_dir:
+ if not os.path.exists(template_dir):
+ raise exceptions.ConfigurationError(f\"Custom template directory '{template_dir}' does not exist.\")
# load custom templates and overwrite default templates - if they exist
- for fileName in os.listdir(templateDir):
- filePath = os.path.join(templateDir, fileName)
- if fileName.lower().endswith(ENDING):
- with open(filePath, "r") as file:
- name = os.path.splitext(fileName)[0]
- fileTemplate, metaData = parseTemplateFile(file.read())
- self.templates[name] = environment.from_string(fileTemplate)
- self.metaData[name] = metaData
- log.info(f"Overwriting template '{name}' with custom template.")
+ for file_name in os.listdir(template_dir):
+ file_path = os.path.join(template_dir, file_name)
+ if file_name.lower().endswith(JINJA_EXTENSIONS):
+ with open(file_path) as file:
+ name = os.path.splitext(file_name)[0]
+ file_template, meta_data = parse_template_file(file.read())
+ self.templates[name] = environment.from_string(file_template)
+ self.meta_data[name] = meta_data
+ log.info(\"Overwriting template '%s' with custom template.\", name)
else:
log.error(
- f"Trying to load unsupported file '{filePath}'. Supported file ends with {ENDING}."
- f"Look at documentation: https://mkdoxy.kubaandrysek.cz/usage/#custom-jinja-templates."
+ \"Trying to load unsupported file '%s'. Supported file ends with %s.\"
+ \"Look at documentation: https://mkdoxy.kubaandrysek.cz/usage/#custom-jinja-templates.\",
+ file_path, JINJA_EXTENSIONS
)
@staticmethod
- def shift_each_line(value: str, shift_char: str = "\t") -> str:
- """! Shift each line of a given string for a given character.
+ def shift_each_line(value: str, shift_char: str = \"\t\") -> str:
+ \"\"\"! Shift each line of a given string for a given character.
@details It is used to shift the content for Markdown code blocks or other content that should be shifted.
@param value (str): String to shift.
@param shift_char (str): Character to shift the string (default: '\t').
@return (str): Shifted string.
- """
- return "\n".join(shift_char + line for line in value.split("\n"))
+ \"\"\"
+ return \"\n\".join(shift_char + line for line in value.split(\"\\n\"))
- def loadConfigAndTemplate(self, name: str) -> [Template, dict]:
+ def load_config_and_template(self, name: str) -> [Template, dict]:
template = self.templates.get(name)
if not template:
raise exceptions.Abort(
- f"Trying to load unexciting template '{name}'. Please create a new template file with name '{name}.jinja2'" # noqa: E501
+ f\"Trying to load unexciting template '{name}'. Please create a new template file with name '{name}.jinja2'\" # noqa: E501
)
- metaData = self.metaData.get(name, {})
- return template, metaData
+ meta_data = self.meta_data.get(name, {})
+ return template, meta_data
def render(self, tmpl: Template, data: dict) -> str:
- """! Render a template with given data.
+ \"\"\"! Render a template with given data.
@details
@param tmpl (Template): Template to render.
@param data (dict): Data to render the template.
@return (str): Rendered template.
- """
+ \"\"\"
try:
# if self.debug:
# print('Generating', path) # TODO: add path to data
@@ -119,195 +120,195 @@ def error(
config: dict,
title: str,
description: str,
- code_header: str = "",
- code: str = "",
- code_language: str = "",
- snippet_code: str = "",
+ code_header: str = \"\",
+ code: str = \"\",
+ code_language: str = \"\",
+ snippet_code: str = \"\",
):
- """! Render an error page.
+ \"\"\"! Render an error page.
@details
@param config (dict): Config for the template.
@param title (str): Title of the error.
@param description (str): Description of the error.
- @param code_header (str): Header of the code (default: "")
- @param code (str): Code (default: "")
- @param code_language (str): Language of the code (default: "")
- @param snippet_code (str): Snippet code (default: "")
- """
+ @param code_header (str): Header of the code (default: \"\")
+ @param code (str): Code (default: \"\")
+ @param code_language (str): Language of the code (default: \"\")
+ @param snippet_code (str): Snippet code (default: \"\")
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("error")
+ template, meta_config = self.load_config_and_template(\"error\")
data = {
- "title": title,
- "description": description,
- "code": code,
- "code_header": code_header,
- "code_language": code_language,
- "snippet_code": snippet_code,
- "config": merge_two_dicts(config, metaConfig),
+ \"title\": title,
+ \"description\": description,
+ \"code\": code,
+ \"code_header\": code_header,
+ \"code_language\": code_language,
+ \"snippet_code\": snippet_code,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def annotated(self, nodes: [Node], config: dict = None):
- """! Render an annotated page.
+ def annotated(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render an annotated page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template (default: None)
@return (str): Rendered annotated page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("annotated")
+ template, meta_config = self.load_config_and_template(\"annotated\")
data = {
- "nodes": nodes,
- "config": merge_two_dicts(config, metaConfig),
+ \"nodes\": nodes,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
def examples(self, nodes: [Node], config=None):
- """! Render an examples page.
+ \"\"\"! Render an examples page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template (default: None)
@return (str): Rendered examples page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("examples")
+ template, meta_config = self.load_config_and_template(\"examples\")
data = {
- "nodes": nodes,
- "config": merge_two_dicts(config, metaConfig),
+ \"nodes\": nodes,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def programlisting(self, node: [Node], config: dict = None):
- """! Render a programlisting page.
+ def programlisting(self, node: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a programlisting page.
@details
@param node ([Node]): Node to render.
@param config (dict): Config for the template (default: None)
@return (str): Rendered programlisting page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("programlisting")
+ template, meta_config = self.load_config_and_template(\"programlisting\")
data = {
- "node": node,
- "config": merge_two_dicts(config, metaConfig),
+ \"node\": node,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def code(self, node: [Node], config: dict = None, code: str = ""):
- """! Render a code page.
+ def code(self, node: [Node], config: Optional[dict] = None, code: str = \"\"):
+ \"\"\"! Render a code page.
@details
@param node ([Node]): Node to render.
@param config (dict): Config for the template (default: None)
- @param code (str): Code to render (default: "")
+ @param code (str): Code to render (default: \"\")
@return (str): Rendered code page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("code")
+ template, meta_config = self.load_config_and_template(\"code\")
# newConfig = merge_two_dicts(CODE_CONFIG, config)
data = {
- "node": node,
- "config": merge_two_dicts(config, metaConfig),
- "code": code,
+ \"node\": node,
+ \"config\": merge_two_dicts(config, meta_config),
+ \"code\": code,
}
return self.render(template, data)
- def fileindex(self, nodes: [Node], config: dict = None):
- """! Render a fileindex page.
+ def fileindex(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a fileindex page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template (default: None)
@return (str): Rendered fileindex page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("files")
+ template, meta_config = self.load_config_and_template(\"files\")
data = {
- "nodes": nodes,
- "config": merge_two_dicts(config, metaConfig),
+ \"nodes\": nodes,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def namespaces(self, nodes: [Node], config: dict = None):
- """! Render a namespaces page.
+ def namespaces(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a namespaces page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered namespaces page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("namespaces")
+ template, meta_config = self.load_config_and_template(\"namespaces\")
data = {
- "nodes": nodes,
- "config": merge_two_dicts(config, metaConfig),
+ \"nodes\": nodes,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def page(self, node: Node, config: dict = None):
- """! Render a page.
+ def page(self, node: Node, config: Optional[dict] = None):
+ \"\"\"! Render a page.
@details
@param node (Node): Node to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("page")
+ template, meta_config = self.load_config_and_template(\"page\")
data = {
- "node": node,
- "config": merge_two_dicts(config, metaConfig),
+ \"node\": node,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def example(self, node: Node, config: dict = None):
- """! Render an example page.
+ def example(self, node: Node, config: Optional[dict] = None):
+ \"\"\"! Render an example page.
@details
@param node (Node): Node to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered example page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("example")
+ template, meta_config = self.load_config_and_template(\"example\")
data = {
- "node": node,
- "config": merge_two_dicts(config, metaConfig),
+ \"node\": node,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def relatedpages(self, nodes: [Node], config: dict = None):
- """! Render a related pages page.
+ def relatedpages(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a related pages page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered related pages page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("relatedPages")
+ template, meta_config = self.load_config_and_template(\"relatedPages\")
data = {
- "nodes": nodes,
- "config": merge_two_dicts(config, metaConfig),
+ \"nodes\": nodes,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def classes(self, nodes: [Node], config: dict = None):
- """! Render a classes page.
+ def classes(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a classes page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered classes page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("classes")
+ template, meta_config = self.load_config_and_template(\"classes\")
classes = recursive_find(nodes, Kind.CLASS)
classes.extend(recursive_find(nodes, Kind.STRUCT))
@@ -323,22 +324,22 @@ def classes(self, nodes: [Node], config: dict = None):
del dictionary[letter]
data = {
- "dictionary": dictionary,
- "config": merge_two_dicts(config, metaConfig),
+ \"dictionary\": dictionary,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
def _find_base_classes(self, nodes: [Node], derived: Node):
- """! Find base classes of a node.
+ \"\"\"! Find base classes of a node.
@details
@param nodes ([Node]): List of nodes to search.
@param derived (Node): Derived node.
@return ([Node]): List of base classes.
- """
+ \"\"\"
ret = []
for node in nodes:
if isinstance(node, str):
- ret.append({"refid": node, "derived": derived})
+ ret.append({\"refid\": node, \"derived\": derived})
elif node.kind.is_parent() and not node.kind.is_namespace():
bases = node.base_classes
if len(bases) == 0:
@@ -347,32 +348,32 @@ def _find_base_classes(self, nodes: [Node], derived: Node):
ret.extend(self._find_base_classes(bases, node))
return ret
- def modules(self, nodes: [Node], config: dict = None):
- """! Render a modules page.
+ def modules(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a modules page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered modules page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("modules")
+ template, meta_config = self.load_config_and_template(\"modules\")
data = {
- "nodes": nodes,
- "config": merge_two_dicts(config, metaConfig),
+ \"nodes\": nodes,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def hierarchy(self, nodes: [Node], config: dict = None):
- """! Render a hierarchy page.
+ def hierarchy(self, nodes: [Node], config: Optional[dict] = None):
+ \"\"\"! Render a hierarchy page.
@details
@param nodes ([Node]): List of nodes to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered hierarchy page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("hierarchy")
+ template, meta_config = self.load_config_and_template(\"hierarchy\")
classes = recursive_find(nodes, Kind.CLASS)
classes.extend(recursive_find(nodes, Kind.STRUCT))
@@ -383,92 +384,92 @@ def hierarchy(self, nodes: [Node], config: dict = None):
for base in bases:
if isinstance(base, dict):
- if base["refid"] not in deduplicated:
- deduplicated[base["refid"]] = []
- deduplicated[base["refid"]].append(base)
+ if base[\"refid\"] not in deduplicated:
+ deduplicated[base[\"refid\"]] = []
+ deduplicated[base[\"refid\"]].append(base)
deduplicated_arr = []
for key, children in deduplicated.items():
if isinstance(children, list):
- deduplicated_arr.append(DummyNode(key, list(map(lambda x: x["derived"], children)), Kind.CLASS))
+ deduplicated_arr.append(DummyNode(key, [x[\"derived\"] for x in children], Kind.CLASS))
else:
found: Node = next((klass for klass in classes if klass.refid == key), None)
if found:
deduplicated_arr.append(found)
data = {
- "classes": deduplicated_arr,
- "config": merge_two_dicts(config, metaConfig),
+ \"classes\": deduplicated_arr,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def function(self, node: Node, config: dict = None):
- """! Render a function page.
+ def function(self, node: Node, config: Optional[dict] = None):
+ \"\"\"! Render a function page.
@details
@param node (Node): Node to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered function page.
- """
+ \"\"\"
if config is None:
config = {}
- templateMemDef, metaConfigMemDef = self.loadConfigAndTemplate("memDef")
- templateCode, metaConfigCode = self.loadConfigAndTemplate("code")
+ template_mem_def, meta_config_mem_def = self.load_config_and_template(\"memDef\")
+ template_code, meta_config_code = self.load_config_and_template(\"code\")
data = {
- "node": node,
- "configMemDef": merge_two_dicts(config, metaConfigMemDef),
- "templateCode": templateCode,
- "configCode": metaConfigCode,
- "config": merge_two_dicts(config, metaConfigMemDef),
+ \"node\": node,
+ \"config_mem_def\": merge_two_dicts(config, meta_config_mem_def),
+ \"template_code\": template_code,
+ \"config_code\": meta_config_code,
+ \"config\": merge_two_dicts(config, meta_config_mem_def),
}
- return self.render(templateMemDef, data)
+ return self.render(template_mem_def, data)
- def member(self, node: Node, config: dict = None):
- """! Render a member page.
+ def member(self, node: Node, config: Optional[dict] = None):
+ \"\"\"! Render a member page.
@details
@param node (Node): Node to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered member page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("member")
- templateMemDef, metaConfigMemDef = self.loadConfigAndTemplate("memDef")
- templateMemTab, metaConfigMemTab = self.loadConfigAndTemplate("memTab")
- templateCode, metaConfigCode = self.loadConfigAndTemplate("code")
+ template, meta_config = self.load_config_and_template(\"member\")
+ template_mem_def, meta_config_mem_def = self.load_config_and_template(\"memDef\")
+ template_mem_tab, meta_config_mem_tab = self.load_config_and_template(\"memTab\")
+ template_code, meta_config_code = self.load_config_and_template(\"code\")
data = {
- "node": node,
- "templateMemDef": templateMemDef,
- "configMemDef": metaConfigMemDef,
- "templateMemTab": templateMemTab,
- "configMemTab": metaConfigMemTab,
- "templateCode": templateCode,
- "configCode": metaConfigCode,
- "config": merge_two_dicts(config, metaConfig),
+ \"node\": node,
+ \"template_mem_def\": template_mem_def,
+ \"config_mem_def\": meta_config_mem_def,
+ \"template_mem_tab\": template_mem_tab,
+ \"config_mem_tab\": meta_config_mem_tab,
+ \"template_code\": template_code,
+ \"config_code\": meta_config_code,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
- def file(self, node: Node, config: dict = None):
- """! Render a file page.
+ def file(self, node: Node, config: Optional[dict] = None):
+ \"\"\"! Render a file page.
@details
@param node (Node): Node to render.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered file page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("member")
- templateMemDef, metaConfigMemDef = self.loadConfigAndTemplate("memDef")
- templateMemTab, metaConfigMemTab = self.loadConfigAndTemplate("memTab")
+ template, meta_config = self.load_config_and_template(\"member\")
+ template_mem_def, meta_config_mem_def = self.load_config_and_template(\"memDef\")
+ template_mem_tab, meta_config_mem_tab = self.load_config_and_template(\"memTab\")
data = {
- "node": node,
- "templateMemDef": templateMemDef,
- "configMemDef": metaConfigMemDef,
- "templateMemTab": templateMemTab,
- "configMemTab": metaConfigMemTab,
- "config": merge_two_dicts(config, metaConfig),
+ \"node\": node,
+ \"template_mem_def\": template_mem_def,
+ \"config_mem_def\": meta_config_mem_def,
+ \"template_mem_tab\": template_mem_tab,
+ \"config_mem_tab\": meta_config_mem_tab,
+ \"config\": merge_two_dicts(config, meta_config),
}
return self.render(template, data)
@@ -478,9 +479,9 @@ def index(
kind_filters: Kind,
kind_parents: [Kind],
title: str,
- config: dict = None,
+ config: Optional[dict] = None,
):
- """! Render an index page.
+ \"\"\"! Render an index page.
@details
@param nodes ([Node]): List of nodes to render.
@param kind_filters (Kind): Kind of nodes to render.
@@ -488,10 +489,10 @@ def index(
@param title (str): Title of the index page.
@param config (dict): Config for the template. (default: None)
@return (str): Rendered index page.
- """
+ \"\"\"
if config is None:
config = {}
- template, metaConfig = self.loadConfigAndTemplate("index")
+ template, meta_config = self.load_config_and_template(\"index\")
found_nodes = recursive_find_with_parent(nodes, kind_filters, kind_parents)
dictionary = {letter: [] for letter in LETTERS}
@@ -522,8 +523,8 @@ def index(
sorted_dictionary[letter] = d
data = {
- "title": title,
- "dictionary": sorted_dictionary,
- "config": merge_two_dicts(config, metaConfig),
+ \"title\": title,
+ \"dictionary\": sorted_dictionary,
+ \"config\": merge_two_dicts(config, meta_config),
}
- return self.render(template, data)
+ return self.render(template, data)
\ No newline at end of file
diff --git a/mkdoxy/generatorSnippets.py b/mkdoxy/generatorSnippets.py
index 4c8c18fb..c037de8e 100644
--- a/mkdoxy/generatorSnippets.py
+++ b/mkdoxy/generatorSnippets.py
@@ -1,6 +1,7 @@
import logging
import pathlib
import re
+from typing import Optional, Union
import yaml
from mkdocs.structure import pages
@@ -12,56 +13,56 @@
log: logging.Logger = logging.getLogger("mkdocs")
-regexIncorrect = r"(?s)(?[a-zA-Z0-9_]+))?[\.]?[\s]*\n(?P.*?)\s*\n(?:(?=\n)|(?=:::)|\Z)" # https://regex101.com/r/IYl25b/2 # noqa: E501
-regexLong = r"(?s)(?[a-zA-Z0-9_]+)\.(?P[a-zA-Z0-9_.]+))\s*\n(?P.*?)(?:(?:(?:\r*\n)(?=\n))|(?=:::)|`|\Z)" # https://regex101.com/r/lIgOij/4 # noqa: E501
-regexShort = r"(?s)(?[a-zA-Z0-9_]+)\.(?P[a-zA-Z0-9_.]+))\s*\n(?:(?=\n)|(?=:::)|\Z)" # https://regex101.com/r/QnqxRc/2 # noqa: E501
+regex_incorrect = r"(?s)(?[a-zA-Z0-9_]+))?[\.]?[\s]*\n(?P.*?)\s*\n(?:(?=\n)|(?=:::)|\Z)" # https://regex101.com/r/IYl25b/2 # noqa: E501
+regex_long = r"(?s)(?[a-zA-Z0-9_]+)\.(?P[a-zA-Z0-9_.]+))\s*\n(?P.*?)(?:(?:(?:\r*\n)(?=\n))|(?=:::)|`|\Z)" # https://regex101.com/r/lIgOij/4 # noqa: E501
+regex_short = r"(?s)(?[a-zA-Z0-9_]+)\.(?P[a-zA-Z0-9_.]+))\s*\n(?:(?=\n)|(?=:::)|\Z)" # https://regex101.com/r/QnqxRc/2 # noqa: E501
class GeneratorSnippets:
def __init__(
self,
markdown: str,
- generatorBase: dict[str, GeneratorBase],
+ generator_base: dict[str, GeneratorBase],
doxygen: dict[str, Doxygen],
projects: dict[str, dict[str, any]],
- useDirectoryUrls: bool,
+ use_directory_urls: bool,
page: pages.Page,
config: dict,
debug: bool = False,
- ):
+ ) -> None:
self.markdown = markdown
- self.generatorBase = generatorBase
+ self.generator_base = generator_base
self.doxygen = doxygen
self.projects = projects
- self.useDirectoryUrls = useDirectoryUrls
+ self.use_directory_urls = use_directory_urls
self.page = page
self.config = config
self.debug = debug
self.finder = Finder(doxygen, debug)
self.doxy_arguments = {
- "code": self.doxyCode,
- "function": self.doxyFunction,
- "namespace.function": self.doxyNamespaceFunction,
- "class": self.doxyClass,
- "class.method": self.doxyClassMethod,
- "class.list": self.doxyClassList,
- "class.index": self.doxyClassIndex,
- "class.hierarchy": self.doxyClassHierarchy,
- "namespace.list": self.doxyNamespaceList,
- "file.list": self.doxyFileList,
+ "code": self.doxy_code,
+ "function": self.doxy_function,
+ "namespace.function": self.doxy_namespace_function,
+ "class": self.doxy_class,
+ "class.method": self.doxy_class_method,
+ "class.list": self.doxy_class_list,
+ "class.index": self.doxy_class_index,
+ "class.hierarchy": self.doxy_class_hierarchy,
+ "namespace.list": self.doxy_namespace_list,
+ "file.list": self.doxy_file_list,
}
# fix absolute path
path = pathlib.PurePath(self.page.url).parts
- self.pageUrlPrefix = "".join("../" for _ in range(len(path) - 1))
+ self.page_url_prefix = "".join("../" for _ in range(len(path) - 1))
def generate(self):
if self.is_doxy_inactive(self.config):
return self.markdown # doxygen is inactive return unchanged markdown
try:
- matches = re.finditer(regexIncorrect, self.markdown, re.MULTILINE)
+ matches = re.finditer(regex_incorrect, self.markdown, re.MULTILINE)
for match in reversed(list(matches)):
snippet = match.group()
project_name = match.group("project") or ""
@@ -79,7 +80,7 @@ def generate(self):
)
self.replace_markdown(match.start(), match.end(), replacement)
- matches = re.finditer(regexShort, self.markdown, re.MULTILINE)
+ matches = re.finditer(regex_short, self.markdown, re.MULTILINE)
for match in reversed(list(matches)):
snippet = match.group()
argument = match.group("argument").lower()
@@ -91,10 +92,10 @@ def generate(self):
if self.is_doxy_inactive(snippet_config):
continue
- replaceStr = self.call_doxy_by_name(snippet, project_name, argument, snippet_config)
- self.replace_markdown(match.start(), match.end(), replaceStr)
+ replace_str = self.call_doxy_by_name(snippet, project_name, argument, snippet_config)
+ self.replace_markdown(match.start(), match.end(), replace_str)
- matches = re.finditer(regexLong, self.markdown, re.MULTILINE)
+ matches = re.finditer(regex_long, self.markdown, re.MULTILINE)
for match in reversed(list(matches)):
snippet = match.group()
argument = match.group("argument").lower()
@@ -105,8 +106,8 @@ def generate(self):
snippet_config = self.config.copy()
snippet_config.update(self.try_load_yaml(match.group("yaml"), project_name, snippet, self.config))
- replaceStr = self.call_doxy_by_name(snippet, project_name, argument, snippet_config)
- self.replace_markdown(match.start(), match.end(), replaceStr)
+ replace_str = self.call_doxy_by_name(snippet, project_name, argument, snippet_config)
+ self.replace_markdown(match.start(), match.end(), replace_str)
return self.markdown
except Exception as e:
basename = pathlib.Path(__file__).name
@@ -119,7 +120,7 @@ def try_load_yaml(self, yaml_raw: str, project: str, snippet: str, config: dict)
return yaml.safe_load(yaml_raw)
except yaml.YAMLError:
log.error(f"YAML error in {project} project on page {self.page.url}")
- self.doxyError(
+ self.doxy_error(
project,
config,
"YAML error",
@@ -137,7 +138,7 @@ def incorrect_project(
config: dict,
snippet: str,
) -> str:
- return self.doxyError(
+ return self.doxy_error(
project,
config,
f"Incorrect project name: {project}",
@@ -149,7 +150,7 @@ def incorrect_project(
)
def incorrect_argument(self, project: str, argument: str, config: dict, snippet: str) -> str:
- return self.doxyError(
+ return self.doxy_error(
project,
config,
f"Incorrect argument: {argument}" if argument else f"Add argument to snippet: {project}",
@@ -160,15 +161,15 @@ def incorrect_argument(self, project: str, argument: str, config: dict, snippet:
snippet,
)
- def replace_markdown(self, start: int, end: int, replacement: str):
+ def replace_markdown(self, start: int, end: int, replacement: str) -> None:
self.markdown = self.markdown[:start] + replacement + "\n" + self.markdown[end:]
- def _setLinkPrefixNode(self, node: Node, linkPrefix: str):
- node.project.linkPrefix = linkPrefix
+ def _set_link_prefix_node(self, node: Node, link_prefix: str) -> None:
+ node.project.link_prefix = link_prefix
- def _setLinkPrefixNodes(self, nodes: list[Node], linkPrefix: str):
+ def _set_link_prefix_nodes(self, nodes: list[Node], link_prefix: str) -> None:
if nodes:
- nodes[0].project.linkPrefix = linkPrefix
+ nodes[0].project.link_prefix = link_prefix
def is_project_exist(self, project: str):
return project in self.projects
@@ -182,14 +183,14 @@ def call_doxy_by_name(self, snippet, project: str, argument: str, config: dict)
callback = self.doxy_arguments[argument]
return callback(snippet, project, config)
- def checkConfig(self, snippet, project: str, config, required_params: [str]) -> bool:
+ def check_config(self, snippet, project: str, config, required_params: [str]) -> bool:
"""
returns false if config is correct
return error message if project not exist or find problem in config
"""
return next(
(
- self.doxyError(
+ self.doxy_error(
project,
config,
f"Missing parameter: {param}",
@@ -207,7 +208,7 @@ def checkConfig(self, snippet, project: str, config, required_params: [str]) ->
### Create documentation generator callbacks
- def doxyError(
+ def doxy_error(
self,
project,
config: dict,
@@ -220,37 +221,37 @@ def doxyError(
) -> str:
log.error(f" -> {title} -> page: {self.page.canonical_url}")
if project not in self.projects:
- project = list(self.projects)[0]
- return self.generatorBase[project].error(
+ project = next(iter(self.projects))
+ return self.generator_base[project].error(
config, title, description, code_header, code, code_language, snippet_code
)
- def doxyCode(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, ["file"])
- if errorMsg:
- return errorMsg
- node = self.finder.doxyCode(project, config.get("file"))
+ def doxy_code(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, ["file"])
+ if error_msg:
+ return error_msg
+ node = self.finder.doxy_code(project, config.get("file"))
if node is None:
- return self.doxyNodeIsNone(project, config, snippet)
+ return self.doxy_node_is_none(project, config, snippet)
if isinstance(node, Node):
- progCode = self.codeStrip(
+ prog_code = self.code_strip(
node.programlisting,
node.code_language,
config.get("start", 1),
config.get("end", 0),
)
- if progCode is False:
- return self.doxyError(
+ if prog_code is False:
+ return self.doxy_error(
project,
config,
f"Parameter start: {config.get('start')} is greater than end: {config.get('end')}",
f"{snippet}",
"yaml",
)
- self._setLinkPrefixNode(node, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].code(node, config, progCode)
- return self.doxyError(
+ self._set_link_prefix_node(node, self.page_url_prefix + project + "/")
+ return self.generator_base[project].code(node, config, prog_code)
+ return self.doxy_error(
project,
config,
f"Did not find File: `{config.get('file')}`",
@@ -261,28 +262,28 @@ def doxyCode(self, snippet, project: str, config):
snippet,
)
- def codeStrip(self, codeRaw, codeLanguage: str, start: int = 1, end: int = None):
- lines = codeRaw.split("\n")
+ def code_strip(self, code_raw, code_language: str, start: int = 1, end: Optional[int] = None) -> Union[str, bool]:
+ lines = code_raw.split("\n")
if end and start > end:
return False
out = "".join(line + "\n" for num, line in enumerate(lines) if num >= start and (num <= end or end == 0))
- return f"```{codeLanguage} linenums='{start}'\n{out}```"
+ return f"```{code_language} linenums='{start}'\n{out}```"
- def doxyFunction(self, snippet, project: str, config: dict):
- errorMsg = self.checkConfig(snippet, project, config, ["name"])
- if errorMsg:
- return errorMsg
+ def doxy_function(self, snippet, project: str, config: dict):
+ error_msg = self.check_config(snippet, project, config, ["name"])
+ if error_msg:
+ return error_msg
- node = self.finder.doxyFunction(project, config.get("name"))
+ node = self.finder.doxy_function(project, config.get("name"))
if node is None:
- return self.doxyNodeIsNone(project, config, snippet)
+ return self.doxy_node_is_none(project, config, snippet)
if isinstance(node, Node):
- self._setLinkPrefixNode(node, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].function(node, config)
- return self.doxyError(
+ self._set_link_prefix_node(node, self.page_url_prefix + project + "/")
+ return self.generator_base[project].function(node, config)
+ return self.doxy_error(
project,
config,
"Incorrect function configuration",
@@ -293,19 +294,19 @@ def doxyFunction(self, snippet, project: str, config: dict):
snippet,
)
- def doxyClass(self, snippet, project: str, config: dict):
- errorMsg = self.checkConfig(snippet, project, config, ["name"])
- if errorMsg:
- return errorMsg
+ def doxy_class(self, snippet, project: str, config: dict):
+ error_msg = self.check_config(snippet, project, config, ["name"])
+ if error_msg:
+ return error_msg
- node = self.finder.doxyClass(project, config.get("name"))
+ node = self.finder.doxy_class(project, config.get("name"))
if node is None:
- return self.doxyNodeIsNone(project, config, snippet)
+ return self.doxy_node_is_none(project, config, snippet)
if isinstance(node, Node):
- self._setLinkPrefixNode(node, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].member(node, config)
- return self.doxyError(
+ self._set_link_prefix_node(node, self.page_url_prefix + project + "/")
+ return self.generator_base[project].member(node, config)
+ return self.doxy_error(
project,
config,
"Incorrect class configuration",
@@ -316,19 +317,19 @@ def doxyClass(self, snippet, project: str, config: dict):
snippet,
)
- def doxyClassMethod(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, ["name", "method"])
- if errorMsg:
- return errorMsg
+ def doxy_class_method(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, ["name", "method"])
+ if error_msg:
+ return error_msg
- node = self.finder.doxyClassMethod(project, config.get("name"), config.get("method"))
+ node = self.finder.doxy_class_method(project, config.get("name"), config.get("method"))
if node is None:
- return self.doxyNodeIsNone(project, config, snippet)
+ return self.doxy_node_is_none(project, config, snippet)
if isinstance(node, Node):
- self._setLinkPrefixNode(node, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].function(node, config)
- return self.doxyError(
+ self._set_link_prefix_node(node, self.page_url_prefix + project + "/")
+ return self.generator_base[project].function(node, config)
+ return self.doxy_error(
project,
config,
"Incorrect class method configuration",
@@ -339,51 +340,51 @@ def doxyClassMethod(self, snippet, project: str, config):
snippet,
)
- def doxyClassList(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, [])
- if errorMsg:
- return errorMsg
+ def doxy_class_list(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, [])
+ if error_msg:
+ return error_msg
nodes = self.doxygen[project].root.children
- self._setLinkPrefixNodes(nodes, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].annotated(nodes, config)
+ self._set_link_prefix_nodes(nodes, self.page_url_prefix + project + "/")
+ return self.generator_base[project].annotated(nodes, config)
- def doxyClassIndex(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, [])
- if errorMsg:
- return errorMsg
+ def doxy_class_index(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, [])
+ if error_msg:
+ return error_msg
nodes = self.doxygen[project].root.children
- self._setLinkPrefixNodes(nodes, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].classes(nodes, config)
+ self._set_link_prefix_nodes(nodes, self.page_url_prefix + project + "/")
+ return self.generator_base[project].classes(nodes, config)
- def doxyClassHierarchy(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, [])
- if errorMsg:
- return errorMsg
+ def doxy_class_hierarchy(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, [])
+ if error_msg:
+ return error_msg
nodes = self.doxygen[project].root.children
- self._setLinkPrefixNodes(nodes, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].hierarchy(nodes, config)
+ self._set_link_prefix_nodes(nodes, self.page_url_prefix + project + "/")
+ return self.generator_base[project].hierarchy(nodes, config)
- def doxyNamespaceList(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, [])
- if errorMsg:
- return errorMsg
+ def doxy_namespace_list(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, [])
+ if error_msg:
+ return error_msg
nodes = self.doxygen[project].root.children
- self._setLinkPrefixNodes(nodes, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].namespaces(nodes, config)
+ self._set_link_prefix_nodes(nodes, self.page_url_prefix + project + "/")
+ return self.generator_base[project].namespaces(nodes, config)
- def doxyNamespaceFunction(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, ["namespace", "name"])
- if errorMsg:
- return errorMsg
+ def doxy_namespace_function(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, ["namespace", "name"])
+ if error_msg:
+ return error_msg
- node = self.finder.doxyNamespaceFunction(project, config.get("namespace"), config.get("name"))
+ node = self.finder.doxy_namespace_function(project, config.get("namespace"), config.get("name"))
if node is None:
- return self.doxyNodeIsNone(project, config, snippet)
+ return self.doxy_node_is_none(project, config, snippet)
if isinstance(node, Node):
- self._setLinkPrefixNode(node, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].function(node, config)
- return self.doxyError(
+ self._set_link_prefix_node(node, self.page_url_prefix + project + "/")
+ return self.generator_base[project].function(node, config)
+ return self.doxy_error(
project,
config,
"Incorrect namespace function configuration",
@@ -394,16 +395,16 @@ def doxyNamespaceFunction(self, snippet, project: str, config):
snippet,
)
- def doxyFileList(self, snippet, project: str, config):
- errorMsg = self.checkConfig(snippet, project, config, [])
- if errorMsg:
- return errorMsg
+ def doxy_file_list(self, snippet, project: str, config):
+ error_msg = self.check_config(snippet, project, config, [])
+ if error_msg:
+ return error_msg
nodes = self.doxygen[project].files.children
- self._setLinkPrefixNodes(nodes, self.pageUrlPrefix + project + "/")
- return self.generatorBase[project].fileindex(nodes, config)
+ self._set_link_prefix_nodes(nodes, self.page_url_prefix + project + "/")
+ return self.generator_base[project].fileindex(nodes, config)
- def doxyNodeIsNone(self, project: str, config: dict, snippet: str) -> str:
- return self.doxyError(
+ def doxy_node_is_none(self, project: str, config: dict, snippet: str) -> str:
+ return self.doxy_error(
project,
config,
f"Could not find coresponding snippet for project {project}",
@@ -417,8 +418,8 @@ def doxyNodeIsNone(self, project: str, config: dict, snippet: str) -> str:
class SnippetClass:
- def __init__(self, config):
+ def __init__(self, config) -> None:
self.config = config
- def default(self):
+ def default(self) -> str:
return ""
diff --git a/mkdoxy/markdown.py b/mkdoxy/markdown.py
index 2740f250..d5fdfe9a 100644
--- a/mkdoxy/markdown.py
+++ b/mkdoxy/markdown.py
@@ -1,64 +1,61 @@
-from typing import List
-
-
def escape(s: str) -> str:
ret = s.replace("*", "\\*")
ret = ret.replace("_", "\\_")
ret = ret.replace("<", "<")
ret = ret.replace(">", ">")
- return ret.replace("|", "\|")
+ return ret.replace("|", "\\|")
class MdRenderer:
- def __init__(self):
+ def __init__(self) -> None:
self.output = ""
self.eol_flag = True
- def write(self, s: str):
+ def write(self, s: str) -> None:
self.output += s
self.eol_flag = False
- def eol(self):
+ def eol(self) -> None:
if not self.eol_flag:
self.output += "\n"
self.eol_flag = True
class Md:
- def __init__(self, children: List["Md"]):
+ def __init__(self, children: list["Md"]) -> None:
self.children = children
- def append(self, child: "Md"):
+ def append(self, child: "Md") -> None:
self.children.append(child)
- def extend(self, child: List["Md"]):
+ def extend(self, child: list["Md"]) -> None:
self.children.extend(child)
class Text:
- def __init__(self, text: str):
+ def __init__(self, text: str) -> None:
self.text = text
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
if self.text:
f.write(escape(self.text))
class Br:
- def __init__(self):
+ def __init__(self) -> None:
pass
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("\n\n")
class MdHint(Md):
- def __init__(self, children: List[Md], typ: str, title: str):
+ def __init__(self, children: list[Md], typ: str, title: str) -> None:
Md.__init__(self, children)
self.title = title
self.typ = typ
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write(f"::: {self.typ} {self.title}" + "\n")
for child in self.children:
child.render(f, "")
@@ -66,10 +63,10 @@ def render(self, f: MdRenderer, indent: str):
class MdBold(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("**")
for child in self.children:
child.render(f, "")
@@ -77,29 +74,29 @@ def render(self, f: MdRenderer, indent: str):
class MdImage:
- def __init__(self, url: str):
+ def __init__(self, url: str) -> None:
self.url = url
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write(f"")
class Code:
- def __init__(self, text: str):
+ def __init__(self, text: str) -> None:
self.text = text
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write(f"`{self.text}`")
class MdCodeBlock:
- def __init__(self, lines: List[str]):
+ def __init__(self, lines: list[str]) -> None:
self.lines = lines
- def append(self, line: str):
+ def append(self, line: str) -> None:
self.lines.append(line)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("```\n")
for line in self.lines:
f.write(line)
@@ -108,10 +105,10 @@ def render(self, f: MdRenderer, indent: str):
class MdBlockQuote(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("\n")
for child in self.children:
f.write("> ")
@@ -120,10 +117,10 @@ def render(self, f: MdRenderer, indent: str):
class MdItalic(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("_")
for child in self.children:
child.render(f, "")
@@ -131,21 +128,21 @@ def render(self, f: MdRenderer, indent: str):
class MdParagraph(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
for child in self.children:
child.render(f, indent)
f.eol()
class MdLink(Md):
- def __init__(self, children: List[Md], url: str):
+ def __init__(self, children: list[Md], url: str) -> None:
Md.__init__(self, children)
self.url = url
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("[")
for child in self.children:
child.render(f, "")
@@ -153,10 +150,10 @@ def render(self, f: MdRenderer, indent: str):
class MdList(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.eol()
for child in self.children:
if not isinstance(child, MdList):
@@ -165,21 +162,21 @@ def render(self, f: MdRenderer, indent: str):
class MdLine:
- def __init__(self):
+ def __init__(self) -> None:
pass
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.eol()
f.write("----------------------------------------")
f.eol()
class MdHeader(Md):
- def __init__(self, level: int, children: List[Md]):
+ def __init__(self, level: int, children: list[Md]) -> None:
Md.__init__(self, children)
self.level = level
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.write("#" * self.level + " ")
for child in self.children:
child.render(f, f"{indent}")
@@ -188,19 +185,19 @@ def render(self, f: MdRenderer, indent: str):
class MdTableCell(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
for child in self.children:
child.render(f, indent)
class MdTableRow(Md):
- def __init__(self, children: List[Md]):
+ def __init__(self, children: list[Md]) -> None:
Md.__init__(self, children)
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
f.eol()
f.write("|")
for child in self.children:
@@ -210,10 +207,10 @@ def render(self, f: MdRenderer, indent: str):
class MdTable(Md):
- def __init__(self):
+ def __init__(self) -> None:
Md.__init__(self, [])
- def render(self, f: MdRenderer, indent: str):
+ def render(self, f: MdRenderer, indent: str) -> None:
is_first = True
f.eol()
for child in self.children:
@@ -224,3 +221,22 @@ def render(self, f: MdRenderer, indent: str):
f.write("|")
is_first = False
f.write("\n\n")
+
+
+class MdInlineEquation(Md):
+ def __init__(self, equation: str) -> None:
+ self.equation = equation
+
+ def render(self, f: MdRenderer, indent: str) -> None:
+ if self.equation:
+ f.write(rf"\({self.equation}\)")
+
+
+class MdBlockEquation(Md):
+ def __init__(self, equation: str) -> None:
+ self.equation = equation
+
+ def render(self, f: MdRenderer, indent: str) -> None:
+ f.write("\n")
+ f.write(rf"\[{self.equation}\]")
+ f.write("\n")
diff --git a/mkdoxy/migration.py b/mkdoxy/migration.py
new file mode 100644
index 00000000..d09f17a2
--- /dev/null
+++ b/mkdoxy/migration.py
@@ -0,0 +1,56 @@
+import logging
+import re
+import shutil
+from pathlib import Path
+
+log = logging.getLogger("mkdoxy.migration")
+
+
+def update_new_config(yaml_file: Path, backup: bool, backup_file_name: str) -> None:
+ """
+ Migrate MkDoxy configuration to the new version by replacing legacy keys
+ directly in the text file—preserving comments and structure.
+
+ Legacy keys are replaced only on non-comment lines.
+
+ :param yaml_file: Path to the mkdocs YAML configuration file.
+ :param backup: If True, a backup of the original file is created.
+ :param backup_file_name: The filename to use for the backup.
+ """
+ if backup:
+ backup_path = yaml_file.parent / backup_file_name
+ shutil.copy2(yaml_file, backup_path)
+ log.info(f"Backup created at {backup_path}")
+
+ text = yaml_file.read_text(encoding="utf-8")
+
+ # Merge global and project legacy mappings.
+ legacy_mapping = {}
+ legacy_mapping.update(
+ {
+ "full-doc": "full_doc",
+ "ignore-errors": "ignore_errors",
+ "save-api": "custom_api_folder",
+ "doxygen-bin-path": "doxygen_bin_path",
+ }
+ )
+ legacy_mapping.update(
+ {
+ "src-dirs": "src_dirs",
+ "full-doc": "full_doc",
+ "ignore-errors": "ignore_errors",
+ "doxy-cfg": "doxy_config_dict",
+ "doxy-cfg-file": "doxy_config_file",
+ "template-dir": "custom_template_dir",
+ }
+ )
+
+ # Replace each legacy key only on lines that are not comments.
+ for old_key, new_key in legacy_mapping.items():
+ # Pattern matches lines that do not start with a comment (after optional whitespace),
+ # then the legacy key followed by optional spaces and a colon.
+ pattern = re.compile(rf"(?m)^(?!\s*#)(\s*){re.escape(old_key)}(\s*:)", re.UNICODE)
+ text = pattern.sub(rf"\1{new_key}\2", text)
+
+ yaml_file.write_text(text, encoding="utf-8")
+ log.info("Migration completed successfully")
diff --git a/mkdoxy/node.py b/mkdoxy/node.py
index 41323070..e7103285 100644
--- a/mkdoxy/node.py
+++ b/mkdoxy/node.py
@@ -1,6 +1,8 @@
import logging
import os
-from xml.etree import ElementTree
+import re
+from typing import Optional
+from xml.etree import ElementTree as ET
from xml.etree.ElementTree import Element as Element
from mkdoxy.constants import OVERLOAD_OPERATORS, Kind, Visibility
@@ -21,10 +23,10 @@ def __init__(
project: ProjectContext,
parser: XmlParser,
parent: "Node",
- refid: str = None,
+ refid: Optional[str] = None,
debug: bool = False,
- ):
- self._children: ["Node"] = []
+ ) -> None:
+ self._children: [Node] = []
self._cache = project.cache
self._parser: XmlParser = parser
self._parent = parent
@@ -41,7 +43,7 @@ def __init__(
if self.debug:
log.info(f"Loading XML from: {xml_file}")
self._dirname = os.path.dirname(xml_file)
- self._xml = ElementTree.parse(xml_file).getroot().find("compounddef")
+ self._xml = ET.parse(xml_file).getroot().find("compounddef")
if self._xml is None:
raise Exception(f"File {xml_file} has no ")
self._kind = Kind.from_str(self._xml.get("kind"))
@@ -88,16 +90,16 @@ def __init__(
self._definition = Property.Definition(self._xml, parser, self._kind)
self._programlisting = Property.Programlisting(self._xml, parser, self._kind)
- def __repr__(self):
+ def __repr__(self) -> str:
return f"Node: {self.name} refid: {self._refid}"
- def add_child(self, child: "Node"):
+ def add_child(self, child: "Node") -> None:
self._children.append(child)
- def sort_children(self):
+ def sort_children(self) -> None:
self._children.sort(key=lambda x: x._name, reverse=False)
- def _check_for_children(self):
+ def _check_for_children(self) -> None:
for innergroup in self._xml.findall("innergroup"):
refid = innergroup.get("refid")
if self._kind in [Kind.GROUP, Kind.DIR, Kind.FILE]:
@@ -240,7 +242,7 @@ def _check_for_children(self):
# if para.find('programlisting') is not None:
# self._programlisting = Property.Programlisting(para, self._parser, self._kind)
- def _check_attrs(self):
+ def _check_attrs(self) -> None:
prot = self._xml.get("prot")
self._visibility = Visibility(prot) if prot is not None else Visibility.PUBLIC
@@ -256,8 +258,8 @@ def _check_attrs(self):
inline = self._xml.get("inline")
self._inline = inline == "yes"
- const = self._xml.get("inline")
- self._const = const == "yes"
+ const_val = self._xml.get("inline")
+ self._const = const_val == "yes"
name = self._xml.find("name")
if name is not None and name.text:
@@ -281,9 +283,9 @@ def _check_attrs(self):
def has(self, visibility: str, kinds: [str], static: bool) -> bool:
return len(self.query(visibility, kinds, static)) > 0
- def query(self, visibility: str, kinds: [str], static: bool) -> ["Node"]:
+ def query(self, visibility: str, kinds: [str], static: bool) -> [Node]:
visibility = Visibility(visibility)
- kinds = list(map(lambda kind: Kind.from_str(kind), kinds))
+ kinds = [Kind.from_str(kind) for kind in kinds]
return [
child
for child in self._children
@@ -323,11 +325,11 @@ def has_children(self) -> bool:
return len(self._children) > 0
@property
- def children(self) -> ["Node"]:
+ def children(self) -> [Node]:
return self._children
@property
- def parent(self) -> "Node":
+ def parent(self) -> Node:
return self._parent
@property
@@ -417,9 +419,9 @@ def name(self) -> str:
@property
def name_params(self) -> str:
name = self._name
- type = self._type.plain()
+ type_val = self._type.plain()
params = self._specifiers.plain()
- return f"{type} {name}{params}" if params else self.name_long
+ return f"{type_val} {name}{params}" if params else self.name_long
@property
def title(self) -> str:
@@ -443,9 +445,22 @@ def operators_total(self) -> int:
@property
def operator_num(self) -> int:
+ stem = "operator" + ("-" * self._name.count("-"))
total = 0
for child in self.parent.children:
- if child.is_function and child.name.replace(" ", "") in OVERLOAD_OPERATORS:
+ child_refid = child.name.replace(" ", "")
+ # Check if the child is a displayed operator by ensuring:
+ # 1. Its identifier is in the predefined OVERLOAD_OPERATORS list.
+ # 2. It starts with the expected 'stem' derived from the parent's naming.
+ # 3. It does not start with an extra hyphen (stem+'-') to avoid excessive matching.
+ # 4. It is not private.
+ if (
+ child.is_function
+ and child_refid in OVERLOAD_OPERATORS
+ and child_refid.startswith(stem)
+ and not child_refid.startswith(stem + "-")
+ and child._visibility != Visibility.PRIVATE
+ ):
total += 1
if child.refid == self._refid:
break
@@ -454,16 +469,21 @@ def operator_num(self) -> int:
@property
def name_url_safe(self) -> str:
name = self.name_tokens[-1]
- return name.replace(" ", "-").replace("=", "").replace("~", "").lower()
+ # Strip special characters that do not appear in anchors
+ name = re.sub("[=~.,<>]", "", name)
+ return name.strip(" ").replace(" ", "-").lower()
@property
def anchor(self) -> str:
name = ""
if self._name.replace(" ", "") in OVERLOAD_OPERATORS:
num = self.operator_num
- name = f"operator_{str(self.operator_num - 1)}" if num > 1 else "operator"
+ if self._name.startswith("operator-"):
+ name = f"operator-_{num - 1!s}" if num > 1 else "operator-"
+ else:
+ name = f"operator_{num - 1!s}" if num > 1 else "operator"
elif self.is_overloaded:
- name = f"{self.name_url_safe}-{str(self.overload_num)}{str(self.overload_total)}"
+ name = f"{self.name_url_safe}-{self.overload_num!s}{self.overload_total!s}"
else:
name = self.name_url_safe
@@ -515,7 +535,7 @@ def filename(self) -> str:
return self.project.linkPrefix + self._refid + ".md"
@property
- def root(self) -> "Node":
+ def root(self) -> Node:
return self if self._kind == Kind.ROOT else self._parent.root
@property
@@ -574,10 +594,10 @@ def overload_suffix(self) -> str:
return ""
total = self.overload_total
- return f"[{str(self.overload_num)}/{str(total)}]" if total > 1 else ""
+ return f"[{self.overload_num!s}/{total!s}]" if total > 1 else ""
@property
- def parents(self) -> ["Node"]:
+ def parents(self) -> [Node]:
ret = []
if self._parent is not None and (self._parent.is_language or self._parent.is_dir):
ret.extend(self.parent.parents)
@@ -695,7 +715,7 @@ def has_derived_classes(self) -> bool:
return len(self._xml.findall("derivedcompoundref")) > 0
@property
- def base_classes(self) -> ["Node"]:
+ def base_classes(self) -> [Node]:
ret = []
for basecompoundref in self._xml.findall("basecompoundref"):
refid = basecompoundref.get("refid")
@@ -706,7 +726,7 @@ def base_classes(self) -> ["Node"]:
return ret
@property
- def derived_classes(self) -> ["Node"]:
+ def derived_classes(self) -> [Node]:
ret = []
for derivedcompoundref in self._xml.findall("derivedcompoundref"):
refid = derivedcompoundref.get("refid")
@@ -830,7 +850,7 @@ def is_resolved(self) -> bool:
return True
@property
- def reimplements(self) -> "Node":
+ def reimplements(self) -> Node:
reimp = self._xml.find("reimplements")
return self._cache.get(reimp.get("refid")) if reimp is not None else None
@@ -842,7 +862,7 @@ def print_node_recursive(self) -> str:
def _print_node_recursive_md(self, node: Element, depth: int) -> str:
# print as Markdown code block
- indent = " " * depth
+ indent = "\t" * depth
ret = f"{indent} * {node.tag} {node.attrib} -> Text: {node.text}\n"
for child in node.findall("*"):
ret += self._print_node_recursive_md(child, depth + 1)
@@ -851,7 +871,7 @@ def _print_node_recursive_md(self, node: Element, depth: int) -> str:
class DummyNode:
- def __init__(self, name_long: str, derived_classes: [Node], kind: Kind):
+ def __init__(self, name_long: str, derived_classes: [Node], kind: Kind) -> None:
self.name_long = name_long
self.derived_classes = derived_classes
self.kind = kind
diff --git a/mkdoxy/plugin.py b/mkdoxy/plugin.py
index 20063227..030adb23 100644
--- a/mkdoxy/plugin.py
+++ b/mkdoxy/plugin.py
@@ -5,53 +5,35 @@
"""
import logging
-from pathlib import Path, PurePath
+from pathlib import Path
-from mkdocs import exceptions
-from mkdocs.config import Config, base, config_options
+from mkdocs.config import Config
from mkdocs.plugins import BasePlugin
-from mkdocs.structure import files, pages
+from mkdocs.structure.files import Files
+from mkdocs.structure.pages import Page
from mkdoxy.cache import Cache
+from mkdoxy.doxy_config import MkDoxyConfig
from mkdoxy.doxygen import Doxygen
-from mkdoxy.doxyrun import DoxygenRun
+from mkdoxy.doxygen_generator import DoxygenGenerator
from mkdoxy.generatorAuto import GeneratorAuto
from mkdoxy.generatorBase import GeneratorBase
from mkdoxy.generatorSnippets import GeneratorSnippets
from mkdoxy.xml_parser import XmlParser
log: logging.Logger = logging.getLogger("mkdocs")
-pluginName: str = "MkDoxy"
+plugin_name: str = "MkDoxy"
-class MkDoxy(BasePlugin):
+class MkDoxy(BasePlugin[MkDoxyConfig]):
"""! MkDocs plugin for generating documentation from Doxygen XML files."""
- # Config options for the plugin
- config_scheme = (
- ("projects", config_options.Type(dict, default={})),
- ("full-doc", config_options.Type(bool, default=True)),
- ("debug", config_options.Type(bool, default=False)),
- ("ignore-errors", config_options.Type(bool, default=False)),
- ("save-api", config_options.Type(str, default="")),
- ("enabled", config_options.Type(bool, default=True)),
- (
- "doxygen-bin-path",
- config_options.Type(str, default="doxygen", required=False),
- ),
- )
-
- # Config options for each project
- config_project = (
- ("src-dirs", config_options.Type(str)),
- ("full-doc", config_options.Type(bool, default=True)),
- ("debug", config_options.Type(bool, default=False)),
- # ('ignore-errors', config_options.Type(bool, default=False)),
- ("api-path", config_options.Type(str, default=".")),
- ("doxy-cfg", config_options.Type(dict, default={}, required=False)),
- ("doxy-cfg-file", config_options.Type(str, default="", required=False)),
- ("template-dir", config_options.Type(str, default="", required=False)),
- )
+ def __init__(self) -> None:
+ self.generator_base: dict[str, GeneratorBase] = {}
+ self.doxygen: dict[str, Doxygen] = {}
+ self.default_template_config = {
+ "indent_level": 0,
+ }
def is_enabled(self) -> bool:
"""! Checks if the plugin is enabled
@@ -60,145 +42,107 @@ def is_enabled(self) -> bool:
"""
return self.config.get("enabled")
- def on_files(self, files: files.Files, config: base.Config) -> files.Files:
+ def on_files(self, files: Files, config: Config) -> Files:
"""! Called after files have been gathered by MkDocs.
- @details
+ @details generate automatic documentation and append files in the list of files to be processed by mkdocs
@param files: (Files) The files gathered by MkDocs.
@param config: (Config) The global configuration object.
@return: (Files) The files gathered by MkDocs.
"""
- if not self.is_enabled():
- return files
-
- def checkConfig(config_project, proData, strict: bool):
- cfg = Config(config_project, "")
- cfg.load_dict(proData)
- errors, warnings = cfg.validate()
- for config_name, warning in warnings:
- log.warning(f" -> Config value: '{config_name}' in project '{project_name}'. Warning: {warning}")
- for config_name, error in errors:
- log.error(f" -> Config value: '{config_name}' in project '{project_name}'. Error: {error}")
-
- if len(errors) > 0:
- raise exceptions.Abort(f"Aborted with {len(errors)} Configuration Errors!")
- elif strict and len(warnings) > 0:
- raise exceptions.Abort(f"Aborted with {len(warnings)} Configuration Warnings in 'strict' mode!")
-
- def tempDir(siteDir: str, tempDir: str, projectName: str) -> str:
- tempDoxyDir = PurePath.joinpath(Path(siteDir), Path(tempDir), Path(projectName))
- tempDoxyDir.mkdir(parents=True, exist_ok=True)
- return str(tempDoxyDir)
-
- self.doxygen = {}
- self.generatorBase = {}
- self.projects_config: dict[str, dict[str, any]] = self.config["projects"]
- self.debug = self.config.get("debug", False)
-
- # generate automatic documentation and append files in the list of files to be processed by mkdocs
- self.defaultTemplateConfig: dict = {
- "indent_level": 0,
- }
-
- log.info(f"Start plugin {pluginName}")
-
- for project_name, project_data in self.projects_config.items():
- log.info(f"-> Start project '{project_name}'")
+ for project_name, project_config in self.config.projects.items():
+ log.info(f"-> Processing project '{project_name}'")
- # Check project config -> raise exceptions
- checkConfig(self.config_project, project_data, config["strict"])
-
- if self.config.get("save-api"):
- tempDirApi = tempDir("", self.config.get("save-api"), project_name)
+ # Generate Doxygen and MD files to user defined folder or default temp folder
+ if self.config.custom_api_folder:
+ temp_doxy_folder = Path.joinpath(Path(self.config.custom_api_folder), Path(project_name))
else:
- tempDirApi = tempDir(config["site_dir"], "assets/.doxy/", project_name)
+ temp_doxy_folder = Path.joinpath(Path(config["site_dir"]), Path("assets/.doxy"), Path(project_name))
+
+ # Create temp dir for Doxygen if not exists
+ temp_doxy_folder.mkdir(parents=True, exist_ok=True)
# Check src changes -> run Doxygen
- doxygenRun = DoxygenRun(
- self.config["doxygen-bin-path"],
- project_data.get("src-dirs"),
- tempDirApi,
- project_data.get("doxy-cfg", {}),
- project_data.get("doxy-cfg-file", ""),
+ doxygen = DoxygenGenerator(
+ self.config,
+ project_config,
+ temp_doxy_folder,
)
- if doxygenRun.checkAndRun():
- log.info(" -> generating Doxygen files")
+ if doxygen.has_changes():
+ log.info(" -> Generating Doxygen files started")
+ doxygen.run()
+ log.info(" -> Doxygen files generated")
else:
- log.info(" -> skip generating Doxygen files (nothing changes)")
+ log.info(" -> skip generating Doxygen files (nothing seems to have changed)")
# Parse XML to basic structure
cache = Cache()
- parser = XmlParser(cache=cache, debug=self.debug)
+ parser = XmlParser(cache=cache, debug=self.config.debug)
# Parse basic structure to recursive Nodes
- self.doxygen[project_name] = Doxygen(doxygenRun.getOutputFolder(), parser=parser, cache=cache)
+ # TODO: Doxygen index_path should be Path object
+ self.doxygen[project_name] = Doxygen(str(doxygen.get_output_xml_folder()), parser=parser, cache=cache)
# Print parsed files
- if self.debug:
+ if self.config.debug:
self.doxygen[project_name].printStructure()
# Prepare generator for future use (GeneratorAuto, SnippetGenerator)
- self.generatorBase[project_name] = GeneratorBase(
- project_data.get("template-dir", ""),
- ignore_errors=self.config["ignore-errors"],
- debug=self.debug,
+ self.generator_base[project_name] = GeneratorBase(
+ project_config.custom_template_dir,
+ False, # ignore_errors=self.config.ignore_errors,
+ debug=self.config.debug,
)
- if self.config["full-doc"] and project_data.get("full-doc", True):
+ if self.config.full_doc and project_config.full_doc:
generatorAuto = GeneratorAuto(
- generatorBase=self.generatorBase[project_name],
- tempDoxyDir=tempDirApi,
+ generatorBase=self.generator_base[project_name],
+ tempDoxyDir=str(temp_doxy_folder),
siteDir=config["site_dir"],
- apiPath=project_data.get("api-path", project_name),
+ apiPath=project_name,
doxygen=self.doxygen[project_name],
useDirectoryUrls=config["use_directory_urls"],
)
- project_config = self.defaultTemplateConfig.copy()
- project_config.update(project_data)
- generatorAuto.fullDoc(project_config)
+ template_config = self.default_template_config.copy()
+
+ # Generate full documentation
+ generatorAuto.fullDoc(template_config)
- generatorAuto.summary(project_config)
+ # Generate summary pages
+ generatorAuto.summary(template_config)
+ # Append files to be processed by MkDocs
for file in generatorAuto.fullDocFiles:
files.append(file)
return files
- def on_page_markdown(
- self,
- markdown: str,
- page: pages.Page,
- config: base.Config,
- files: files.Files,
- ) -> str:
+ def on_page_markdown(self, markdown: str, page: Page, config: Config, files: Files) -> str:
"""! Generate snippets and append them to the markdown.
@details
-
- @param markdown (str): The markdown.
- @param page (Page): The MkDocs page.
- @param config (Config): The MkDocs config.
- @param files (Files): The MkDocs files.
- @return: (str) The markdown.
+ @param markdown: (str) The markdown content of the page.
+ @param page: (Page) The page object.
+ @param config: (Config) The global configuration object.
+ @param files: (Files) The files gathered by MkDocs.
+ @return: (str) The markdown content of the page.
"""
- if not self.is_enabled():
- return markdown
- # update default template config with page meta
- page_config = self.defaultTemplateConfig.copy()
+ # update default template config with page meta tags
+ page_config = self.default_template_config.copy()
page_config.update(page.meta)
- generatorSnippets = GeneratorSnippets(
+ generator_snippets = GeneratorSnippets(
markdown=markdown,
- generatorBase=self.generatorBase,
+ generatorBase=self.generator_base,
doxygen=self.doxygen,
- projects=self.projects_config,
+ projects=self.config.projects,
useDirectoryUrls=config["use_directory_urls"],
page=page,
config=page_config,
- debug=self.debug,
+ debug=self.config.debug,
)
- return generatorSnippets.generate()
+ return generator_snippets.generate()
# def on_serve(self, server):
diff --git a/mkdoxy/property.py b/mkdoxy/property.py
index bc37882b..a5d11ef3 100644
--- a/mkdoxy/property.py
+++ b/mkdoxy/property.py
@@ -8,7 +8,7 @@
class Property:
class Details:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -28,7 +28,7 @@ def has(self) -> bool:
return len(list(detaileddescription)) > 0
class Brief:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -53,7 +53,7 @@ def has(self) -> bool:
return len(list(detaileddescription)) > 0
class Includes:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -80,7 +80,7 @@ def has(self) -> bool:
return len(self.xml.findall("includes")) > 0
class Type:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -96,7 +96,7 @@ def has(self) -> bool:
return self.xml.find("type") is not None
class Location:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -128,7 +128,7 @@ def has(self) -> bool:
return self.xml.find("location") is not None
class Params:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -143,8 +143,8 @@ def array(self, plain: bool = False) -> [str]:
ret = []
for param in self.xml.findall("param"):
p = ""
- type = param.find("type")
- p = self.parser.paras_as_str(type, plain=plain)
+ type_elem = param.find("type")
+ p = self.parser.paras_as_str(type_elem, plain=plain)
declname = param.find("declname")
if declname is not None:
@@ -165,7 +165,7 @@ def has(self) -> bool:
return len(self.xml.findall("param")) > 0
class TemplateParams:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -187,8 +187,8 @@ def array(self, plain: bool = False, notype: bool = False) -> [str]:
declname = param.find("type")
ret.append(self.parser.paras_as_str(declname, plain=plain))
else:
- type = param.find("type")
- declaration = self.parser.paras_as_str(type, plain=plain)
+ type_elem = param.find("type")
+ declaration = self.parser.paras_as_str(type_elem, plain=plain)
declname = param.find("declname")
if declname is not None:
declaration += f" {self.parser.paras_as_str(declname, plain=plain)}"
@@ -199,7 +199,7 @@ def has(self) -> bool:
return self.xml.find("templateparamlist") is not None
class CodeBlock:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -214,7 +214,7 @@ def has(self) -> bool:
return True
class Specifiers:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -265,7 +265,7 @@ def has(self) -> bool:
return self.xml.find("argsstring") is not None
class Values:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -291,7 +291,7 @@ def has(self) -> bool:
return self.xml.find("enumvalue") is not None if self.kind.is_enum() else False
class Initializer:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -313,7 +313,7 @@ def has(self) -> bool:
return self.xml.find("initializer") is not None
class Definition:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
@@ -332,7 +332,7 @@ def has(self) -> bool:
return self.xml.find("definition") is not None
class Programlisting:
- def __init__(self, xml: Element, parser: XmlParser, kind: Kind):
+ def __init__(self, xml: Element, parser: XmlParser, kind: Kind) -> None:
self.xml = xml
self.parser = parser
self.kind = kind
diff --git a/mkdoxy/utils.py b/mkdoxy/utils.py
index b6617cc6..693784b3 100644
--- a/mkdoxy/utils.py
+++ b/mkdoxy/utils.py
@@ -28,7 +28,7 @@ def lookahead(iterable):
yield last, False
-def contains(a, pos, b):
+def contains(a, pos, b) -> bool:
ai = pos
bi = 0
if len(b) > len(a) - ai:
diff --git a/mkdoxy/xml_parser.py b/mkdoxy/xml_parser.py
index a58de2a4..ef4db21d 100644
--- a/mkdoxy/xml_parser.py
+++ b/mkdoxy/xml_parser.py
@@ -5,11 +5,13 @@
Br,
Code,
Md,
+ MdBlockEquation,
MdBlockQuote,
MdBold,
MdCodeBlock,
MdHeader,
MdImage,
+ MdInlineEquation,
MdItalic,
MdLink,
MdList,
@@ -51,7 +53,7 @@
class XmlParser:
- def __init__(self, cache: Cache, debug: bool = False):
+ def __init__(self, cache: Cache, debug: bool = False) -> None:
self.cache = cache
self.debug = debug
@@ -277,6 +279,12 @@ def paras(self, p: Element, italic: bool = False) -> [Md]:
elif item.tag == "emphasis":
ret.append(MdItalic(self.paras(item)))
+ elif item.tag == "formula":
+ equation = item.text.strip("$ ")
+ if len(p) == 1 and item.tail is None:
+ ret.append(MdBlockEquation(equation))
+ else:
+ ret.append(MdInlineEquation(equation))
if item.tail:
if italic:
diff --git a/pyproject.toml b/pyproject.toml
index 8f0b403f..28e90423 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,113 @@
+[build-system]
+requires = ["hatchling>=1.21.0"]
+build-backend = "hatchling.build"
+
+[project]
+name = "mkdoxy"
+version = "2.0.0"
+description = "MkDoxy → MkDocs + Doxygen = easy documentation generator with code snippets"
+readme = "README.md"
+keywords = ["mkdoxy", "python", "open-source", "documentation", "mkdocs", "doxygen", "multilanguage", "code-snippets", "code", "snippets", "documentation-generator"]
+license = "MIT"
+authors = [
+ {name = "Jakub Andrýsek", email = "email@kubaandrysek.cz"}
+]
+maintainers = [
+ {name = "Jakub Andrýsek", email = "email@kubaandrysek.cz"}
+]
+requires-python = ">=3.9"
+classifiers = [
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Operating System :: OS Independent",
+]
+dependencies = [
+ "mkdocs>=1.4.0",
+]
+
+[project.optional-dependencies]
+dev = [
+ "mkdocs-material~=9.6.18",
+ "mkdocs-open-in-new-tab~=1.0.8",
+ "pathlib~=1.0.1",
+ "isort~=6.0.1",
+ "pytest~=8.4.1",
+ "pre-commit~=4.3.0",
+ "setuptools~=80.9.0",
+ "build~=1.3.0",
+ "twine~=6.1.0",
+ "sourcery~=1.37.0",
+ "click~=8.2.1",
+ "ruff~=0.9.6",
+ "hatch~=1.14.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/JakubAndrysek/MkDoxy"
+Documentation = "https://mkdoxy.kubaandrysek.cz/"
+Source = "https://github.com/JakubAndrysek/MkDoxy"
+Tracker = "https://github.com/JakubAndrysek/MkDoxy/issues"
+Funding = "https://github.com/sponsors/jakubandrysek"
+
+[project.scripts]
+mkdoxy = "mkdoxy.__main__:main"
+
+[project.entry-points]
+"mkdocs.plugins" = {mkdoxy = "mkdoxy.plugin:MkDoxy"}
+
+[tool.setuptools.packages.find]
+include = ["mkdoxy*"]
+
+[tool.setuptools.package-data]
+mkdoxy = ["templates/*.jinja2"]
+
[tool.ruff]
line-length = 120
-[tool.black]
-line-length = 120
+[tool.ruff.lint]
+# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
+select = ["E", "F"]
+extend-select = [
+ "I", # isort
+ "C9", # mccabe
+ "N", # pep8-naming
+ "UP", # pyupgrade
+ "ANN", # flake8-annotations
+ "S", # bandit
+ "BLE", # flake8-blind-except
+ "B", # flake8-bugbear
+ "A", # flake8-builtins
+ "C4", # flake8-comprehensions
+ "DTZ", # flake8-datetimez
+ "EXE", # flake8-executable
+ "ISC", # flake8-implicit-str-concat
+ "ICN", # flake8-import-conventions
+ "G", # flake8-logging-format
+ "PIE", # flake8-pie
+ "PYI", # flake8-pyi
+ "RUF", # Ruff-specific rules
+]
+extend-ignore = [
+ "ANN101", # Missing type annotation for self
+ "ANN102", # Missing type annotation for cls
+]
+
+[tool.ruff.format]
+# Configure formatting options
+quote-style = "double"
+indent-style = "space"
+skip-magic-trailing-comma = false
+line-ending = "auto"
+
+[tool.hatch.build.targets.wheel]
+packages = ["mkdoxy"]
+
+[tool.hatch.build.targets.sdist]
+include = [
+ "/mkdoxy",
+ "/README.md",
+ "/LICENSE",
+]
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 016bb16d..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-mkdocs
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 0372c566..00000000
--- a/setup.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from setuptools import setup, find_packages
-
-
-def readme():
- with open("README.md") as f:
- return f.read()
-
-
-def requirements():
- with open("requirements.txt") as f:
- return f.read().splitlines()
-
-
-def import_requirements():
- """Imports requirements from requirements.txt file."""
- with open("requirements.txt") as f:
- return f.read().splitlines()
-
-
-def import_dev_requirements():
- """Imports requirements from devdeps.txt file."""
- with open("devdeps.txt") as f:
- return f.read().splitlines()
-
-
-# https://pypi.org/project/mkdoxy/
-setup(
- name="mkdoxy",
- version="1.2.7",
- description="MkDoxy → MkDocs + Doxygen = easy documentation generator with code snippets",
- long_description=readme(),
- long_description_content_type="text/markdown",
- keywords="mkdoxy, python, open-source, documentation, mkdocs, doxygen, "
- "multilanguage, code-snippets, code, snippets, documentation-generator",
- # noqa: E501
- url="https://github.com/JakubAndrysek/MkDoxy",
- author="Jakub Andrýsek",
- author_email="email@kubaandrysek.cz",
- license="MIT",
- python_requires=">=3.9",
- project_urls={
- "Source": "https://github.com/JakubAndrysek/MkDoxy",
- "Documentation": "https://mkdoxy.kubaandrysek.cz/",
- "Tracker": "https://github.com/JakubAndrysek/MkDoxy/issues",
- "Funding": "https://github.com/sponsors/jakubandrysek",
- },
- install_requires=import_requirements(),
- extras_require={
- "dev": import_dev_requirements(),
- },
- classifiers=[
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Operating System :: OS Independent",
- ],
- packages=find_packages(),
- package_data={"mkdoxy": ["templates/*.jinja2"]},
- entry_points={"mkdocs.plugins": ["mkdoxy = mkdoxy.plugin:MkDoxy"]},
-)
diff --git a/tests-old/callDoxyByName.py b/tests-old/callDoxyByName.py
index 8b15e131..e9e945f0 100644
--- a/tests-old/callDoxyByName.py
+++ b/tests-old/callDoxyByName.py
@@ -1,19 +1,19 @@
class DoxyCall:
- def __init__(self):
+ def __init__(self) -> None:
self.DOXY_CALL = {
"class": self.doxyClass,
"class.list": self.doxyClassList,
}
- def callDoxyByName(self, name, config):
+ def callDoxyByName(self, name, config) -> None:
if name in self.DOXY_CALL:
funcName = self.DOXY_CALL[name]
funcName(config)
- def doxyClass(self, config):
+ def doxyClass(self, config) -> None:
print("doxyClassasd", config)
- def doxyClassList(self, config):
+ def doxyClassList(self, config) -> None:
print("doxyClassList", config)
diff --git a/tests-old/doxygenSubprocess.py b/tests-old/doxygenSubprocess.py
index f38b716f..6c0a0180 100644
--- a/tests-old/doxygenSubprocess.py
+++ b/tests-old/doxygenSubprocess.py
@@ -1,4 +1,4 @@
-from subprocess import Popen, PIPE
+from subprocess import PIPE, Popen
p = Popen(["doxygen", "-"], stdout=PIPE, stdin=PIPE, stderr=PIPE)
diff --git a/tests-old/metaDataParse.py b/tests-old/metaDataParse.py
index 2c8fbc6b..ae422710 100644
--- a/tests-old/metaDataParse.py
+++ b/tests-old/metaDataParse.py
@@ -1,7 +1,7 @@
-import sys
import re
-import yaml
+import sys
+import yaml
text = """\
---
diff --git a/tests-old/mkdocsValidateCfg.py b/tests-old/mkdocsValidateCfg.py
index f0eaecf2..57a4c9ac 100644
--- a/tests-old/mkdocsValidateCfg.py
+++ b/tests-old/mkdocsValidateCfg.py
@@ -1,5 +1,4 @@
-from mkdocs.config import config_options, Config
-
+from mkdocs.config import Config, config_options
if __name__ == "__main__":
config_scheme = (
diff --git a/tests-old/parseMdTags.py b/tests-old/parseMdTags.py
index 44f72476..b60814c6 100644
--- a/tests-old/parseMdTags.py
+++ b/tests-old/parseMdTags.py
@@ -1,10 +1,11 @@
import re
import sys
+
import yaml
def readFile(filename: str) -> str:
- file = open(filename, "r")
+ file = open(filename)
return file.read()
diff --git a/tests-old/snippetsTest.py b/tests-old/snippetsTest.py
index b03e63dc..65da8ae4 100644
--- a/tests-old/snippetsTest.py
+++ b/tests-old/snippetsTest.py
@@ -1,11 +1,12 @@
+from pprint import pprint
+
+from doxygen_snippets.cache import Cache
from doxygen_snippets.doxygen import Doxygen
-from doxygen_snippets.generatorBase import GeneratorBase
+from doxygen_snippets.doxyrun import DoxygenRun
from doxygen_snippets.generatorAuto import GeneratorAuto
+from doxygen_snippets.generatorBase import GeneratorBase
from doxygen_snippets.generatorSnippets import GeneratorSnippets
from doxygen_snippets.xml_parser import XmlParser
-from doxygen_snippets.cache import Cache
-from doxygen_snippets.doxyrun import DoxygenRun
-from pprint import pprint
if __name__ == "__main__":
doxygenSource = "files/src-stm32"
diff --git a/tests-old/testTemplate.py b/tests-old/testTemplate.py
index 143fdcb7..cbe0f411 100644
--- a/tests-old/testTemplate.py
+++ b/tests-old/testTemplate.py
@@ -1,11 +1,12 @@
import os
+from pprint import pprint
+
+from doxygen_snippets.cache import Cache
from doxygen_snippets.doxygen import Doxygen
-from doxygen_snippets.generatorBase import GeneratorBase
+from doxygen_snippets.doxyrun import DoxygenRun
from doxygen_snippets.generatorAuto import GeneratorAuto
+from doxygen_snippets.generatorBase import GeneratorBase
from doxygen_snippets.xml_parser import XmlParser
-from doxygen_snippets.cache import Cache
-from doxygen_snippets.doxyrun import DoxygenRun
-from pprint import pprint
if __name__ == "__main__":
doxygenPath = "files/"
diff --git a/tests/data/Doxyfile b/tests/config/data/Doxyfile
similarity index 100%
rename from tests/data/Doxyfile
rename to tests/config/data/Doxyfile
diff --git a/tests/test_doxyrun.py b/tests/config/test_doxyrun.py
similarity index 57%
rename from tests/test_doxyrun.py
rename to tests/config/test_doxyrun.py
index 6a1dd74e..de97c540 100644
--- a/tests/test_doxyrun.py
+++ b/tests/config/test_doxyrun.py
@@ -1,81 +1,71 @@
+from pathlib import Path
+
import pytest
-from mkdoxy.doxyrun import DoxygenCustomConfigNotValid, DoxygenRun
+
+from mkdoxy.doxy_config import MkDoxyConfig, MkDoxyConfigProject
+from mkdoxy.doxygen_generator import DoxygenCustomConfigNotValid, DoxygenGenerator
-def test_dox_dict2str():
+def test_dox_dict2str() -> None:
dox_dict = {
"DOXYFILE_ENCODING": "UTF-8",
- "GENERATE_XML": True,
- "RECURSIVE": True,
"EXAMPLE_PATH": "examples",
- "SHOW_NAMESPACES": True,
"GENERATE_HTML": False,
"GENERATE_LATEX": False,
+ "GENERATE_XML": True,
+ "RECURSIVE": True,
+ "SHOW_NAMESPACES": True,
}
- doxygen_run = DoxygenRun(
- doxygenBinPath="doxygen",
- doxygenSource="/path/to/source/files",
- tempDoxyFolder="/path/to/temp/folder",
- doxyCfgNew=dox_dict,
- )
-
- result = doxygen_run.dox_dict2str(dox_dict)
-
expected_result = (
- "DOXYFILE_ENCODING = UTF-8\nGENERATE_XML = YES"
- "\nRECURSIVE = YES\nEXAMPLE_PATH = examples"
- "\nSHOW_NAMESPACES = YES\nGENERATE_HTML = NO"
- "\nGENERATE_LATEX = NO"
+ "DOXYFILE_ENCODING = UTF-8\n"
+ "EXAMPLE_PATH = examples\n"
+ "GENERATE_HTML = NO\n"
+ "GENERATE_LATEX = NO\n"
+ "GENERATE_XML = YES\n"
+ "RECURSIVE = YES\n"
+ "SHOW_NAMESPACES = YES"
)
- assert result == expected_result
+ assert DoxygenGenerator.dox_dict2str(dox_dict) == expected_result
# Sets the Doxygen configuration using a custom config file
-def test_set_doxy_cfg_custom_file():
- dox_dict = {}
-
- doxygen_run = DoxygenRun(
- doxygenBinPath="doxygen",
- doxygenSource="/path/to/source/files",
- tempDoxyFolder="/path/to/temp/folder",
- doxyConfigFile="./tests/data/Doxyfile",
- doxyCfgNew=dox_dict,
+def test_set_doxy_cfg_custom_file() -> None:
+ project = MkDoxyConfigProject()
+ project.src_dirs = "/path/to/source/files"
+
+ doxygen_run = DoxygenGenerator(
+ doxy_config=MkDoxyConfig(),
+ project_config=project,
+ temp_doxy_folder=Path("/path/to/temp/folder"),
)
- result = doxygen_run.setDoxyCfg(dox_dict)
+ dox_dict = doxygen_run.get_merged_doxy_dict()
expected_result = {
"DOXYFILE_ENCODING": "UTF-8",
- "GENERATE_XML": True,
- "RECURSIVE": True,
"EXAMPLE_PATH": "examples",
- "SHOW_NAMESPACES": True,
"GENERATE_HTML": False,
"GENERATE_LATEX": False,
+ "GENERATE_XML": True,
"INPUT": "/path/to/source/files",
"OUTPUT_DIRECTORY": "/path/to/temp/folder",
+ "RECURSIVE": True,
+ "SHOW_NAMESPACES": True,
}
- assert result == expected_result
+ assert expected_result == dox_dict
-def test_str2dox_dict():
+def test_str2dox_dict() -> None:
dox_str = (
"DOXYFILE_ENCODING = UTF-8\nGENERATE_XML = YES\n"
"RECURSIVE = YES\nEXAMPLE_PATH = examples\n"
"SHOW_NAMESPACES = YES\nGENERATE_HTML = NO\nGENERATE_LATEX = NO"
)
- doxygen_run = DoxygenRun(
- doxygenBinPath="doxygen",
- doxygenSource="/path/to/source/files",
- tempDoxyFolder="/path/to/temp/folder",
- doxyCfgNew={},
- )
-
- result = doxygen_run.str2dox_dict(dox_str)
+ result = DoxygenGenerator.str2dox_dict(dox_str)
expected_result = {
"DOXYFILE_ENCODING": "UTF-8",
@@ -90,7 +80,7 @@ def test_str2dox_dict():
assert result == expected_result
-def test_str2dox_dict_expanded_config():
+def test_str2dox_dict_expanded_config() -> None:
dox_str = (
"# This is a comment \n"
"PROJECT_LOGO =\n"
@@ -101,15 +91,6 @@ def test_str2dox_dict_expanded_config():
"PREDEFINED = BUILD_DATE DOXYGEN=1\n"
)
- doxygen_run = DoxygenRun(
- doxygenBinPath="doxygen",
- doxygenSource="/path/to/source/files",
- tempDoxyFolder="/path/to/temp/folder",
- doxyCfgNew={},
- )
-
- result = doxygen_run.str2dox_dict(dox_str)
-
expected_result = {
"PROJECT_LOGO": "",
"ABBREVIATE_BRIEF": '"The $name class" is',
@@ -117,68 +98,61 @@ def test_str2dox_dict_expanded_config():
"PREDEFINED": "BUILD_DATE DOXYGEN=1",
}
- assert result == expected_result
-
+ assert expected_result == DoxygenGenerator.str2dox_dict(dox_str)
-def test_str2dox_dict_expanded_config_errors():
- doxygen_run = DoxygenRun(
- doxygenBinPath="doxygen",
- doxygenSource="/path/to/source/files",
- tempDoxyFolder="/path/to/temp/folder",
- doxyCfgNew={},
- )
+def test_str2dox_dict_expanded_config_errors() -> None:
dox_str = "ONLY_KEY\n"
- error_message = str(
+ error_message = (
"Invalid line: 'ONLY_KEY'"
- "In custom Doxygen config file: None\n"
+ "In custom Doxygen config file: QuestionMark\n"
"Make sure the file is in standard Doxygen format."
"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
)
with pytest.raises(DoxygenCustomConfigNotValid, match=error_message):
- doxygen_run.str2dox_dict(dox_str)
+ DoxygenGenerator.str2dox_dict(dox_str, "QuestionMark")
dox_str = "= ONLY_VALUE\n"
- error_message = str(
+ error_message = (
"Invalid line: '= ONLY_VALUE'"
- "In custom Doxygen config file: None\n"
+ "In custom Doxygen config file: QuestionMark\n"
"Make sure the file is in standard Doxygen format."
"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
)
with pytest.raises(DoxygenCustomConfigNotValid, match=error_message):
- doxygen_run.str2dox_dict(dox_str)
+ DoxygenGenerator.str2dox_dict(dox_str, "QuestionMark")
dox_str = "KEY WITH SPACES = VALUE\n"
- error_message = str(
+ error_message = (
"Invalid line: 'KEY WITH SPACES = VALUE'"
- "In custom Doxygen config file: None\n"
+ "In custom Doxygen config file: QuestionMark\n"
"Make sure the file is in standard Doxygen format."
"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
)
with pytest.raises(DoxygenCustomConfigNotValid, match=error_message):
- doxygen_run.str2dox_dict(dox_str)
+ DoxygenGenerator.str2dox_dict(dox_str, "QuestionMark")
dox_str = "BAD_OPERATOR := VALUE\n"
- error_message = str(
+ error_message = (
"Invalid line: 'BAD_OPERATOR := VALUE'"
- "In custom Doxygen config file: None\n"
+ "In custom Doxygen config file: QuestionMark\n"
"Make sure the file is in standard Doxygen format."
"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
)
with pytest.raises(DoxygenCustomConfigNotValid, match=error_message):
- doxygen_run.str2dox_dict(dox_str)
+ DoxygenGenerator.str2dox_dict(dox_str, "QuestionMark")
- dox_str = "BAD_MULTILINE = BAD\n" " VALUE\n"
- error_message = str(
+ dox_str = "BAD_MULTILINE = BAD\n VALUE\n"
+ error_message = (
"Invalid line: ' VALUE'"
- "In custom Doxygen config file: None\n"
+ "In custom Doxygen config file: QuestionMark\n"
"Make sure the file is in standard Doxygen format."
"Look at https://mkdoxy.kubaandrysek.cz/usage/advanced/."
)
with pytest.raises(DoxygenCustomConfigNotValid, match=error_message):
- doxygen_run.str2dox_dict(dox_str)
+ DoxygenGenerator.str2dox_dict(dox_str, "QuestionMark")
diff --git a/tests/config/test_doxyrun_helpers.py b/tests/config/test_doxyrun_helpers.py
new file mode 100644
index 00000000..097e01ae
--- /dev/null
+++ b/tests/config/test_doxyrun_helpers.py
@@ -0,0 +1,51 @@
+from mkdoxy.doxygen_generator import DoxygenGenerator
+
+
+def test_merge_doxygen_input_empty_src_dirs(tmp_path) -> None:
+ # When src_dirs is empty, the function should return doxy_input unchanged.
+ src_dirs = ""
+ doxy_input = "dir1 dir2"
+ run_folder = tmp_path / "run"
+ run_folder.mkdir()
+
+ result = DoxygenGenerator.merge_doxygen_input(src_dirs, doxy_input, run_folder)
+ assert result == doxy_input
+
+
+def test_merge_doxygen_input_empty_doxy_input(tmp_path) -> None:
+ # When doxy_input is empty, the function should return src_dirs unchanged.
+ src_dirs = "dir1 dir2"
+ doxy_input = ""
+ run_folder = tmp_path / "run"
+ run_folder.mkdir()
+
+ result = DoxygenGenerator.merge_doxygen_input(src_dirs, doxy_input, run_folder)
+ assert result == src_dirs
+
+
+def test_merge_doxygen_input_both_non_empty(tmp_path) -> None:
+ # When both src_dirs and doxy_input are provided, the result should contain all unique paths.
+ src_dirs = "a b"
+ doxy_input = "b c"
+ run_folder = tmp_path / "run"
+ run_folder.mkdir()
+
+ result = DoxygenGenerator.merge_doxygen_input(src_dirs, doxy_input, run_folder)
+ # The returned string is a space-separated list of paths relative to run_folder.
+ # We compare as sets because the order may not be predictable.
+ result_set = set(result.split())
+ expected_set = {"a", "b", "c"}
+ assert result_set == expected_set
+
+
+def test_merge_doxygen_input_duplicates(tmp_path) -> None:
+ # Duplicate paths in the inputs should be deduplicated.
+ src_dirs = "a a"
+ doxy_input = "a"
+ run_folder = tmp_path / "run"
+ run_folder.mkdir()
+
+ result = DoxygenGenerator.merge_doxygen_input(src_dirs, doxy_input, run_folder)
+ result_list = result.split()
+ # Expect only one occurrence of "a"
+ assert result_list == ["a"]
diff --git a/tests/migration/data/1_expect.yaml b/tests/migration/data/1_expect.yaml
new file mode 100644
index 00000000..7015437d
--- /dev/null
+++ b/tests/migration/data/1_expect.yaml
@@ -0,0 +1,215 @@
+site_name: MkDoxy Demo
+site_url: https://mkdoxy-demo.kubaandrysek.cz/
+site_author: Jakub Andrýsek
+site_description: >-
+ This is a demo of MkDoxy, a tool for generating Doxygen documentation from Markdown files.
+
+# Repository
+repo_name: JakubAndrysek/MkDoxy-demo
+repo_url: https://github.com/JakubAndrysek/MkDoxy-demo
+
+# Copyright
+copyright: Copyright © 2023 Jakub Andrýsek
+
+theme:
+ name: material
+ language: en
+ logo: assets/logo.png
+ favicon: assets/logo.png
+ features:
+ - navigation.tabs
+ - navigation.indexes
+ - navigation.top
+ - navigation.instant
+ - navigation.tracking
+
+ icon:
+ repo: fontawesome/brands/github
+
+ palette:
+ # Palette toggle for dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ primary: amber
+ accent: amber
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+ # Palette toggle for light mode
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: amber
+ accent: amber
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/JakubAndrysek
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/KubaAndrysek
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/jakub-andrysek/
+ analytics:
+ provider: google
+ property: G-6VB0GPP3MT
+ feedback:
+ title: Was this page helpful?
+ ratings:
+ - icon: material/emoticon-happy-outline
+ name: This page was helpful
+ data: 1
+ note: >-
+ Thanks for your feedback!
+ - icon: material/emoticon-sad-outline
+ name: This page could be improved
+ data: 0
+ note: >-
+ Thanks for your feedback!
+
+use_directory_urls: True
+#use_directory_urls: False
+
+plugins:
+ - search
+ - glightbox
+ - open-in-new-tab
+ - mkdoxy:
+ projects:
+ esp:
+ src_dirs: demo-projets/esp
+ full_doc: True
+ custom_template_dir: templates-custom
+ # Example of custom template: https://mkdoxy-demo.kubaandrysek.cz/esp/annotated/
+ doxy_config_dict:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+ animal:
+ src_dirs: demo-projets/animal
+ full_doc: True
+ doxy_config_dict:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+ stm:
+ src_dirs: demo-projets/stm32
+ full_doc: True
+
+ jaculus:
+ src_dirs: demo-projets/jaculus/main demo-projets/jaculus/managed_components/jac-link demo-projets/jaculus/managed_components/jac-machine
+ full_doc: True
+ doxy_config_dict:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+
+ custom_api_folder: .mkdoxy
+ full_doc: True
+ debug: False
+ ignore_errors: False
+
+markdown_extensions:
+ - pymdownx.highlight
+ - pymdownx.superfences
+
+nav:
+ - "Home": "README.md"
+ - useage.md
+ - API Demo:
+ - api/index.md
+ - ESP-32:
+ - esp/index.md
+ - "Links": "esp/links.md"
+ - "Classes":
+ - "Class List": "esp/annotated.md"
+ - "Class Index": "esp/classes.md"
+ - "Class Hierarchy": "esp/hierarchy.md"
+ - "Class Members": "esp/class_members.md"
+ - "Class Member Functions": "esp/class_member_functions.md"
+ - "Class Member Variables": "esp/class_member_variables.md"
+ - "Class Member Typedefs": "esp/class_member_typedefs.md"
+ - "Class Member Enumerations": "esp/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "esp/namespaces.md"
+ - "Namespace Members": "esp/namespace_members.md"
+ - "Namespace Member Functions": "esp/namespace_member_functions.md"
+ - "Namespace Member Variables": "esp/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "esp/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "esp/namespace_member_enums.md"
+ - "Functions": "esp/functions.md"
+ - "Variables": "esp/variables.md"
+ - "Macros": "esp/macros.md"
+ - "Files": "esp/files.md"
+ - STM-32:
+ - stm32/index.md
+ - "Links": "stm/links.md"
+ - "Classes":
+ - "Class List": "stm/annotated.md"
+ - "Class Index": "stm/classes.md"
+ - "Class Hierarchy": "stm/hierarchy.md"
+ - "Class Members": "stm/class_members.md"
+ - "Class Member Functions": "stm/class_member_functions.md"
+ - "Class Member Variables": "stm/class_member_variables.md"
+ - "Class Member Typedefs": "stm/class_member_typedefs.md"
+ - "Class Member Enumerations": "stm/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "stm/namespaces.md"
+ - "Namespace Members": "stm/namespace_members.md"
+ - "Namespace Member Functions": "stm/namespace_member_functions.md"
+ - "Namespace Member Variables": "stm/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "stm/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "stm/namespace_member_enums.md"
+ - "Functions": "stm/functions.md"
+ - "Variables": "stm/variables.md"
+ - "Macros": "stm/macros.md"
+ - "Files": "stm/files.md"
+ - Animal:
+ - animal/index.md
+ - "Links": "animal/links.md"
+ - "Classes":
+ - "Class List": "animal/annotated.md"
+ - "Class Index": "animal/classes.md"
+ - "Class Hierarchy": "animal/hierarchy.md"
+ - "Class Members": "animal/class_members.md"
+ - "Class Member Functions": "animal/class_member_functions.md"
+ - "Class Member Variables": "animal/class_member_variables.md"
+ - "Class Member Typedefs": "animal/class_member_typedefs.md"
+ - "Class Member Enumerations": "animal/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "animal/namespaces.md"
+ - "Namespace Members": "animal/namespace_members.md"
+ - "Namespace Member Functions": "animal/namespace_member_functions.md"
+ - "Namespace Member Variables": "animal/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "animal/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "animal/namespace_member_enums.md"
+ - "Functions": "animal/functions.md"
+ - "Variables": "animal/variables.md"
+ - "Macros": "animal/macros.md"
+ - "Files": "animal/files.md"
+ - Jaculus:
+ - jaculus/index.md
+ - "Links": "jaculus/links.md"
+ - "Classes":
+ - "Class List": "jaculus/annotated.md"
+ - "Class Index": "jaculus/classes.md"
+ - "Class Hierarchy": "jaculus/hierarchy.md"
+ - "Class Members": "jaculus/class_members.md"
+ - "Class Member Functions": "jaculus/class_member_functions.md"
+ - "Class Member Variables": "jaculus/class_member_variables.md"
+ - "Class Member Typedefs": "jaculus/class_member_typedefs.md"
+ - "Class Member Enumerations": "jaculus/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "jaculus/namespaces.md"
+ - "Namespace Members": "jaculus/namespace_members.md"
+ - "Namespace Member Functions": "jaculus/namespace_member_functions.md"
+ - "Namespace Member Variables": "jaculus/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "jaculus/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "jaculus/namespace_member_enums.md"
+ - "Functions": "jaculus/functions.md"
+ - "Variables": "jaculus/variables.md"
+ - "Macros": "jaculus/macros.md"
+ - "Files": "jaculus/files.md"
diff --git a/tests/migration/data/1_old.yaml b/tests/migration/data/1_old.yaml
new file mode 100644
index 00000000..84e0dc23
--- /dev/null
+++ b/tests/migration/data/1_old.yaml
@@ -0,0 +1,215 @@
+site_name: MkDoxy Demo
+site_url: https://mkdoxy-demo.kubaandrysek.cz/
+site_author: Jakub Andrýsek
+site_description: >-
+ This is a demo of MkDoxy, a tool for generating Doxygen documentation from Markdown files.
+
+# Repository
+repo_name: JakubAndrysek/MkDoxy-demo
+repo_url: https://github.com/JakubAndrysek/MkDoxy-demo
+
+# Copyright
+copyright: Copyright © 2023 Jakub Andrýsek
+
+theme:
+ name: material
+ language: en
+ logo: assets/logo.png
+ favicon: assets/logo.png
+ features:
+ - navigation.tabs
+ - navigation.indexes
+ - navigation.top
+ - navigation.instant
+ - navigation.tracking
+
+ icon:
+ repo: fontawesome/brands/github
+
+ palette:
+ # Palette toggle for dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ primary: amber
+ accent: amber
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+ # Palette toggle for light mode
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: amber
+ accent: amber
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/JakubAndrysek
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/KubaAndrysek
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/jakub-andrysek/
+ analytics:
+ provider: google
+ property: G-6VB0GPP3MT
+ feedback:
+ title: Was this page helpful?
+ ratings:
+ - icon: material/emoticon-happy-outline
+ name: This page was helpful
+ data: 1
+ note: >-
+ Thanks for your feedback!
+ - icon: material/emoticon-sad-outline
+ name: This page could be improved
+ data: 0
+ note: >-
+ Thanks for your feedback!
+
+use_directory_urls: True
+#use_directory_urls: False
+
+plugins:
+ - search
+ - glightbox
+ - open-in-new-tab
+ - mkdoxy:
+ projects:
+ esp:
+ src-dirs: demo-projets/esp
+ full-doc: True
+ template-dir: templates-custom
+ # Example of custom template: https://mkdoxy-demo.kubaandrysek.cz/esp/annotated/
+ doxy-cfg:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+ animal:
+ src-dirs: demo-projets/animal
+ full-doc: True
+ doxy-cfg:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+ stm:
+ src-dirs: demo-projets/stm32
+ full-doc: True
+
+ jaculus:
+ src-dirs: demo-projets/jaculus/main demo-projets/jaculus/managed_components/jac-link demo-projets/jaculus/managed_components/jac-machine
+ full-doc: True
+ doxy-cfg:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+
+ save-api: .mkdoxy
+ full-doc: True
+ debug: False
+ ignore-errors: False
+
+markdown_extensions:
+ - pymdownx.highlight
+ - pymdownx.superfences
+
+nav:
+ - "Home": "README.md"
+ - useage.md
+ - API Demo:
+ - api/index.md
+ - ESP-32:
+ - esp/index.md
+ - "Links": "esp/links.md"
+ - "Classes":
+ - "Class List": "esp/annotated.md"
+ - "Class Index": "esp/classes.md"
+ - "Class Hierarchy": "esp/hierarchy.md"
+ - "Class Members": "esp/class_members.md"
+ - "Class Member Functions": "esp/class_member_functions.md"
+ - "Class Member Variables": "esp/class_member_variables.md"
+ - "Class Member Typedefs": "esp/class_member_typedefs.md"
+ - "Class Member Enumerations": "esp/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "esp/namespaces.md"
+ - "Namespace Members": "esp/namespace_members.md"
+ - "Namespace Member Functions": "esp/namespace_member_functions.md"
+ - "Namespace Member Variables": "esp/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "esp/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "esp/namespace_member_enums.md"
+ - "Functions": "esp/functions.md"
+ - "Variables": "esp/variables.md"
+ - "Macros": "esp/macros.md"
+ - "Files": "esp/files.md"
+ - STM-32:
+ - stm32/index.md
+ - "Links": "stm/links.md"
+ - "Classes":
+ - "Class List": "stm/annotated.md"
+ - "Class Index": "stm/classes.md"
+ - "Class Hierarchy": "stm/hierarchy.md"
+ - "Class Members": "stm/class_members.md"
+ - "Class Member Functions": "stm/class_member_functions.md"
+ - "Class Member Variables": "stm/class_member_variables.md"
+ - "Class Member Typedefs": "stm/class_member_typedefs.md"
+ - "Class Member Enumerations": "stm/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "stm/namespaces.md"
+ - "Namespace Members": "stm/namespace_members.md"
+ - "Namespace Member Functions": "stm/namespace_member_functions.md"
+ - "Namespace Member Variables": "stm/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "stm/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "stm/namespace_member_enums.md"
+ - "Functions": "stm/functions.md"
+ - "Variables": "stm/variables.md"
+ - "Macros": "stm/macros.md"
+ - "Files": "stm/files.md"
+ - Animal:
+ - animal/index.md
+ - "Links": "animal/links.md"
+ - "Classes":
+ - "Class List": "animal/annotated.md"
+ - "Class Index": "animal/classes.md"
+ - "Class Hierarchy": "animal/hierarchy.md"
+ - "Class Members": "animal/class_members.md"
+ - "Class Member Functions": "animal/class_member_functions.md"
+ - "Class Member Variables": "animal/class_member_variables.md"
+ - "Class Member Typedefs": "animal/class_member_typedefs.md"
+ - "Class Member Enumerations": "animal/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "animal/namespaces.md"
+ - "Namespace Members": "animal/namespace_members.md"
+ - "Namespace Member Functions": "animal/namespace_member_functions.md"
+ - "Namespace Member Variables": "animal/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "animal/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "animal/namespace_member_enums.md"
+ - "Functions": "animal/functions.md"
+ - "Variables": "animal/variables.md"
+ - "Macros": "animal/macros.md"
+ - "Files": "animal/files.md"
+ - Jaculus:
+ - jaculus/index.md
+ - "Links": "jaculus/links.md"
+ - "Classes":
+ - "Class List": "jaculus/annotated.md"
+ - "Class Index": "jaculus/classes.md"
+ - "Class Hierarchy": "jaculus/hierarchy.md"
+ - "Class Members": "jaculus/class_members.md"
+ - "Class Member Functions": "jaculus/class_member_functions.md"
+ - "Class Member Variables": "jaculus/class_member_variables.md"
+ - "Class Member Typedefs": "jaculus/class_member_typedefs.md"
+ - "Class Member Enumerations": "jaculus/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "jaculus/namespaces.md"
+ - "Namespace Members": "jaculus/namespace_members.md"
+ - "Namespace Member Functions": "jaculus/namespace_member_functions.md"
+ - "Namespace Member Variables": "jaculus/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "jaculus/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "jaculus/namespace_member_enums.md"
+ - "Functions": "jaculus/functions.md"
+ - "Variables": "jaculus/variables.md"
+ - "Macros": "jaculus/macros.md"
+ - "Files": "jaculus/files.md"
diff --git a/tests/migration/data/2_expect.yaml b/tests/migration/data/2_expect.yaml
new file mode 100644
index 00000000..f2ac4049
--- /dev/null
+++ b/tests/migration/data/2_expect.yaml
@@ -0,0 +1,208 @@
+site_name: MkDoxy
+site_url: https://mkdoxy.kubaandrysek.cz/
+site_author: Jakub Andrýsek
+site_description: >-
+ MkDoxy -> MkDocs + Doxygen. Easy documentation generator with code snippets.
+
+# Repository
+repo_name: JakubAndrysek/MkDoxy/
+repo_url: https://github.com/JakubAndrysek/MkDoxy/
+
+# Copyright
+copyright: Copyright © 2023 Jakub Andrýsek
+
+theme:
+ name: material
+ language: en
+ logo: assets/logo.png
+ favicon: assets/logo.png
+ features:
+ - navigation.tabs
+ - navigation.indexes
+ - navigation.top
+
+ icon:
+ repo: fontawesome/brands/github
+
+ palette:
+ # Palette toggle for dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ primary: orange
+ accent: orange
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+ # Palette toggle for light mode
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: orange
+ accent: orange
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/JakubAndrysek
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/KubaAndrysek
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/jakub-andrysek/
+ analytics:
+ provider: google
+ property: G-8WHJ2N4SHC
+ feedback:
+ title: Was this page helpful?
+ ratings:
+ - icon: material/emoticon-happy-outline
+ name: This page was helpful
+ data: 1
+ note: >-
+ Thanks for your feedback!
+ - icon: material/emoticon-sad-outline
+ name: This page could be improved
+ data: 0
+ note: >-
+ Thanks for your feedback!
+
+use_directory_urls: True
+#use_directory_urls: False
+
+plugins:
+ - search
+ - open-in-new-tab
+ - mkdoxy:
+ enabled: !ENV [ENABLE_MKDOXY, True]
+ projects:
+ mkdoxyApi:
+ src_dirs: mkdoxy
+ full_doc: True
+ custom_template_dir: templates-custom
+ doxy_config_file: demo-projects/animal/Doxyfile
+ doxy_config_dict:
+ FILE_PATTERNS: "*.py"
+ EXAMPLE_PATH: ""
+ RECURSIVE: True
+ OPTIMIZE_OUTPUT_JAVA: True
+ JAVADOC_AUTOBRIEF: True
+ EXTRACT_ALL: True
+ animal:
+ src_dirs: demo-projects/animal
+ full_doc: True
+ doxy_config_dict:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+ custom_api_folder: .mkdoxy
+ full_doc: True
+ debug: False
+ ignore_errors: False
+
+
+markdown_extensions:
+ - pymdownx.highlight
+ - pymdownx.superfences
+ - def_list
+ - toc:
+ permalink: True
+ - pymdownx.superfences
+ - admonition
+ - pymdownx.details
+ - pymdownx.superfences
+ - markdown.extensions.md_in_html
+ - pymdownx.snippets:
+ check_paths: true
+ - pymdownx.blocks.admonition:
+ types:
+ - new
+ - settings
+ - note
+ - abstract
+ - info
+ - tip
+ - success
+ - question
+ - warning
+ - failure
+ - danger
+ - bug
+ - example
+ - quote
+ - pymdownx.blocks.details:
+ - pymdownx.blocks.html:
+ - pymdownx.blocks.definition:
+ - pymdownx.blocks.tab:
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.emoji:
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
+
+nav:
+ - Home:
+ - "README.md"
+ - "Changelog": "changelog.md"
+ - "License": "license.md"
+ - "Usage":
+ - "usage/index.md"
+ - "Advanced Usage": "usage/advanced.md"
+
+ - Snippets:
+ - "snippets/index.md"
+ - "Intelli sense and errors": "snippets/intelli_sense_and_errors.md"
+ - "Classes": "snippets/classes.md"
+ - "Source code": "snippets/source_code.md"
+ - "Links": "snippets/links.md"
+ - "Functions": "snippets/functions.md"
+ - "Namespaces": "snippets/namespaces.md"
+ - "Files": "snippets/files.md"
+ - MkDoxy API:
+ - "mkdoxyApi/index.md"
+ - "Links": "mkdoxyApi/links.md"
+ - "Classes":
+ - "Class List": "mkdoxyApi/annotated.md"
+ - "Class Index": "mkdoxyApi/classes.md"
+ - "Class Hierarchy": "mkdoxyApi/hierarchy.md"
+ - "Class Members": "mkdoxyApi/class_members.md"
+ - "Class Member Functions": "mkdoxyApi/class_member_functions.md"
+ - "Class Member Variables": "mkdoxyApi/class_member_variables.md"
+ - "Class Member Typedefs": "mkdoxyApi/class_member_typedefs.md"
+ - "Class Member Enumerations": "mkdoxyApi/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "mkdoxyApi/namespaces.md"
+ - "Namespace Members": "mkdoxyApi/namespace_members.md"
+ - "Namespace Member Functions": "mkdoxyApi/namespace_member_functions.md"
+ - "Namespace Member Variables": "mkdoxyApi/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "mkdoxyApi/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "mkdoxyApi/namespace_member_enums.md"
+ - "Functions": "mkdoxyApi/functions.md"
+ - "Variables": "mkdoxyApi/variables.md"
+ - "Macros": "mkdoxyApi/macros.md"
+ - "Files": "mkdoxyApi/files.md"
+ - Demo API:
+ - "animal/index.md"
+ - "Links": "animal/links.md"
+ - "Classes":
+ - "Class List": "animal/annotated.md"
+ - "Class Index": "animal/classes.md"
+ - "Class Hierarchy": "animal/hierarchy.md"
+ - "Class Members": "animal/class_members.md"
+ - "Class Member Functions": "animal/class_member_functions.md"
+ - "Class Member Variables": "animal/class_member_variables.md"
+ - "Class Member Typedefs": "animal/class_member_typedefs.md"
+ - "Class Member Enumerations": "animal/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "animal/namespaces.md"
+ - "Namespace Members": "animal/namespace_members.md"
+ - "Namespace Member Functions": "animal/namespace_member_functions.md"
+ - "Namespace Member Variables": "animal/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "animal/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "animal/namespace_member_enums.md"
+ - "Functions": "animal/functions.md"
+ - "Variables": "animal/variables.md"
+ - "Macros": "animal/macros.md"
+ - "Files": "animal/files.md"
+ - Advanced Demo: "https://mkdoxy-demo.kubaandrysek.cz/"
diff --git a/tests/migration/data/2_old.yaml b/tests/migration/data/2_old.yaml
new file mode 100644
index 00000000..596b3541
--- /dev/null
+++ b/tests/migration/data/2_old.yaml
@@ -0,0 +1,208 @@
+site_name: MkDoxy
+site_url: https://mkdoxy.kubaandrysek.cz/
+site_author: Jakub Andrýsek
+site_description: >-
+ MkDoxy -> MkDocs + Doxygen. Easy documentation generator with code snippets.
+
+# Repository
+repo_name: JakubAndrysek/MkDoxy/
+repo_url: https://github.com/JakubAndrysek/MkDoxy/
+
+# Copyright
+copyright: Copyright © 2023 Jakub Andrýsek
+
+theme:
+ name: material
+ language: en
+ logo: assets/logo.png
+ favicon: assets/logo.png
+ features:
+ - navigation.tabs
+ - navigation.indexes
+ - navigation.top
+
+ icon:
+ repo: fontawesome/brands/github
+
+ palette:
+ # Palette toggle for dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ primary: orange
+ accent: orange
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+ # Palette toggle for light mode
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: orange
+ accent: orange
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/JakubAndrysek
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/KubaAndrysek
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/jakub-andrysek/
+ analytics:
+ provider: google
+ property: G-8WHJ2N4SHC
+ feedback:
+ title: Was this page helpful?
+ ratings:
+ - icon: material/emoticon-happy-outline
+ name: This page was helpful
+ data: 1
+ note: >-
+ Thanks for your feedback!
+ - icon: material/emoticon-sad-outline
+ name: This page could be improved
+ data: 0
+ note: >-
+ Thanks for your feedback!
+
+use_directory_urls: True
+#use_directory_urls: False
+
+plugins:
+ - search
+ - open-in-new-tab
+ - mkdoxy:
+ enabled: !ENV [ENABLE_MKDOXY, True]
+ projects:
+ mkdoxyApi:
+ src-dirs: mkdoxy
+ full-doc: True
+ template-dir: templates-custom
+ doxy-cfg-file: demo-projects/animal/Doxyfile
+ doxy-cfg:
+ FILE_PATTERNS: "*.py"
+ EXAMPLE_PATH: ""
+ RECURSIVE: True
+ OPTIMIZE_OUTPUT_JAVA: True
+ JAVADOC_AUTOBRIEF: True
+ EXTRACT_ALL: True
+ animal:
+ src-dirs: demo-projects/animal
+ full-doc: True
+ doxy-cfg:
+ FILE_PATTERNS: "*.cpp *.h*"
+ EXAMPLE_PATH: examples
+ RECURSIVE: True
+ save-api: .mkdoxy
+ full-doc: True
+ debug: False
+ ignore-errors: False
+
+
+markdown_extensions:
+ - pymdownx.highlight
+ - pymdownx.superfences
+ - def_list
+ - toc:
+ permalink: True
+ - pymdownx.superfences
+ - admonition
+ - pymdownx.details
+ - pymdownx.superfences
+ - markdown.extensions.md_in_html
+ - pymdownx.snippets:
+ check_paths: true
+ - pymdownx.blocks.admonition:
+ types:
+ - new
+ - settings
+ - note
+ - abstract
+ - info
+ - tip
+ - success
+ - question
+ - warning
+ - failure
+ - danger
+ - bug
+ - example
+ - quote
+ - pymdownx.blocks.details:
+ - pymdownx.blocks.html:
+ - pymdownx.blocks.definition:
+ - pymdownx.blocks.tab:
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.emoji:
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
+
+nav:
+ - Home:
+ - "README.md"
+ - "Changelog": "changelog.md"
+ - "License": "license.md"
+ - "Usage":
+ - "usage/index.md"
+ - "Advanced Usage": "usage/advanced.md"
+
+ - Snippets:
+ - "snippets/index.md"
+ - "Intelli sense and errors": "snippets/intelli_sense_and_errors.md"
+ - "Classes": "snippets/classes.md"
+ - "Source code": "snippets/source_code.md"
+ - "Links": "snippets/links.md"
+ - "Functions": "snippets/functions.md"
+ - "Namespaces": "snippets/namespaces.md"
+ - "Files": "snippets/files.md"
+ - MkDoxy API:
+ - "mkdoxyApi/index.md"
+ - "Links": "mkdoxyApi/links.md"
+ - "Classes":
+ - "Class List": "mkdoxyApi/annotated.md"
+ - "Class Index": "mkdoxyApi/classes.md"
+ - "Class Hierarchy": "mkdoxyApi/hierarchy.md"
+ - "Class Members": "mkdoxyApi/class_members.md"
+ - "Class Member Functions": "mkdoxyApi/class_member_functions.md"
+ - "Class Member Variables": "mkdoxyApi/class_member_variables.md"
+ - "Class Member Typedefs": "mkdoxyApi/class_member_typedefs.md"
+ - "Class Member Enumerations": "mkdoxyApi/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "mkdoxyApi/namespaces.md"
+ - "Namespace Members": "mkdoxyApi/namespace_members.md"
+ - "Namespace Member Functions": "mkdoxyApi/namespace_member_functions.md"
+ - "Namespace Member Variables": "mkdoxyApi/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "mkdoxyApi/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "mkdoxyApi/namespace_member_enums.md"
+ - "Functions": "mkdoxyApi/functions.md"
+ - "Variables": "mkdoxyApi/variables.md"
+ - "Macros": "mkdoxyApi/macros.md"
+ - "Files": "mkdoxyApi/files.md"
+ - Demo API:
+ - "animal/index.md"
+ - "Links": "animal/links.md"
+ - "Classes":
+ - "Class List": "animal/annotated.md"
+ - "Class Index": "animal/classes.md"
+ - "Class Hierarchy": "animal/hierarchy.md"
+ - "Class Members": "animal/class_members.md"
+ - "Class Member Functions": "animal/class_member_functions.md"
+ - "Class Member Variables": "animal/class_member_variables.md"
+ - "Class Member Typedefs": "animal/class_member_typedefs.md"
+ - "Class Member Enumerations": "animal/class_member_enums.md"
+ - "Namespaces":
+ - "Namespace List": "animal/namespaces.md"
+ - "Namespace Members": "animal/namespace_members.md"
+ - "Namespace Member Functions": "animal/namespace_member_functions.md"
+ - "Namespace Member Variables": "animal/namespace_member_variables.md"
+ - "Namespace Member Typedefs": "animal/namespace_member_typedefs.md"
+ - "Namespace Member Enumerations": "animal/namespace_member_enums.md"
+ - "Functions": "animal/functions.md"
+ - "Variables": "animal/variables.md"
+ - "Macros": "animal/macros.md"
+ - "Files": "animal/files.md"
+ - Advanced Demo: "https://mkdoxy-demo.kubaandrysek.cz/"
diff --git a/tests/migration/test_migration.py b/tests/migration/test_migration.py
new file mode 100644
index 00000000..45362192
--- /dev/null
+++ b/tests/migration/test_migration.py
@@ -0,0 +1,58 @@
+import shutil
+from pathlib import Path
+
+import pytest
+
+from mkdoxy.migration import update_new_config
+
+# Directory containing test data files.
+DATA_DIR = Path(__file__).parent / "data"
+
+
+@pytest.fixture(params=["1", "2"])
+def migration_files(request, tmp_path: Path) -> tuple:
+ """
+ Parameterized fixture that copies the legacy YAML file (_old.yaml)
+ to a temporary file and loads the expected file text from _expect.yaml.
+
+ :returns: A tuple (old_yaml_path, expected_text, prefix)
+ """
+ prefix = request.param
+ # Copy legacy file to a temporary file.
+ src = DATA_DIR / f"{prefix}_old.yaml"
+ dst = tmp_path / f"test_{prefix}.yaml"
+ shutil.copy(src, dst)
+
+ # Load expected configuration text (without parsing YAML).
+ expected_text = (DATA_DIR / f"{prefix}_expect.yaml").read_text(encoding="utf-8")
+ return dst, expected_text, prefix
+
+
+def test_migration_without_backup(migration_files) -> None:
+ """
+ Test that migration updates the legacy configuration correctly without creating a backup.
+ """
+ old_yaml, expected_text, prefix = migration_files
+ # Run migration with backup turned off.
+ update_new_config(old_yaml, backup=False, backup_file_name="backup.yaml")
+
+ updated_text = old_yaml.read_text(encoding="utf-8")
+ assert updated_text == expected_text, f"Test case {prefix} failed: output text does not match expected."
+
+
+def test_migration_with_backup(migration_files) -> None:
+ """
+ Test that migration creates a backup file and updates the configuration correctly.
+ """
+ old_yaml, expected_text, prefix = migration_files
+ backup_file_name = "backup.yaml"
+
+ # Run migration with backup enabled.
+ update_new_config(old_yaml, backup=True, backup_file_name=backup_file_name)
+
+ # Verify that the backup file was created.
+ backup_file = old_yaml.parent / backup_file_name
+ assert backup_file.exists(), f"Test case {prefix}: Backup file was not created."
+
+ updated_text = old_yaml.read_text(encoding="utf-8")
+ assert updated_text == expected_text, f"Test case {prefix} failed: output text does not match expected."