diff --git a/.github/workflows/mkdoxy-pytest-repo.yaml b/.github/workflows/mkdoxy-pytest-repo.yaml
new file mode 100644
index 00000000..88a290f1
--- /dev/null
+++ b/.github/workflows/mkdoxy-pytest-repo.yaml
@@ -0,0 +1,28 @@
+name: MkDoxy Pytest MkDoxy with all supported Python versions
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest] # windows-latest, macos-latest requires doxygen to be installed manually
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Display Python version
+ run: python -c "import sys; print(sys.version)"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install -e ".[dev]"
+ sudo apt-get install doxygen
+ - name: Run Pytest
+ run: |
+ pytest tests/
diff --git a/.github/workflows/mkdoxy-test-demos.yaml b/.github/workflows/mkdoxy-test-demos.yaml
index e7df08ad..8d09ba83 100644
--- a/.github/workflows/mkdoxy-test-demos.yaml
+++ b/.github/workflows/mkdoxy-test-demos.yaml
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # windows-latest, macos-latest requires doxygen to be installed manually
- python-version: ["3.9", "3.10", "3.11"]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
diff --git a/.github/workflows/mkdoxy-test-repo.yaml b/.github/workflows/mkdoxy-test-repo.yaml
index 81a93d27..c008dce6 100644
--- a/.github/workflows/mkdoxy-test-repo.yaml
+++ b/.github/workflows/mkdoxy-test-repo.yaml
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest] # windows-latest, macos-latest requires doxygen to be installed manually
- python-version: ["3.9", "3.10", "3.11"]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
diff --git a/.idea/misc.xml b/.idea/misc.xml
index f88c0a0d..a0641c7e 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,7 @@
+
+
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index c29d74d2..a37e08bd 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,9 +5,9 @@
-
-
+
+
@@ -43,6 +43,9 @@
+ {
+ "associatedIndex": 3
+}
@@ -52,11 +55,15 @@
{
"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-new-templates/docs",
+ "git-widget-placeholder": "doxygen-config-file",
+ "last_opened_file_path": "/Users/kuba/Documents/git/kuba/mkdoxy/mkdoxy-base/mkdoxy-main/tests/data",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
@@ -68,19 +75,20 @@
}
+
-
+
-
+
@@ -95,6 +103,7 @@
+
@@ -117,6 +126,7 @@
+
@@ -139,6 +149,7 @@
+
@@ -184,6 +195,7 @@
+
@@ -199,6 +211,7 @@
+
@@ -212,8 +225,9 @@
-
+
+
@@ -227,26 +241,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
@@ -276,6 +317,7 @@
+
1674630349699
@@ -547,6 +589,21 @@
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/templates/relatedPages.jinja2
5
@@ -562,16 +619,18 @@
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
diff --git a/demo-projects/animal/Doxyfile b/demo-projects/animal/Doxyfile
new file mode 100644
index 00000000..f8cc2640
--- /dev/null
+++ b/demo-projects/animal/Doxyfile
@@ -0,0 +1,7 @@
+DOXYFILE_ENCODING = UTF-8
+GENERATE_XML = YES
+RECURSIVE = YES
+EXAMPLE_PATH = examples
+SHOW_NAMESPACES = YES
+GENERATE_HTML = NO
+GENERATE_LATEX = YES
diff --git a/docs/usage/advanced.md b/docs/usage/advanced.md
index 4a4d5838..d4ec5a71 100644
--- a/docs/usage/advanced.md
+++ b/docs/usage/advanced.md
@@ -7,7 +7,7 @@
## Disabling the plugin
You can use the `enabled` option to optionally disable this plugin. A possible use case is local development where you might want faster build times.
-```yaml
+```yaml hl_lines="3"
plugins:
- mkdoxy:
enabled: !ENV [ENABLE_MKDOXY, True]
@@ -29,9 +29,23 @@ If `doxygen-bin-path` is not found, the plugin will raise DoxygenBinPathNotValid
- addad by [thb-sb](https://github.com/thb-sb)
-```yaml
+```yaml hl_lines="3"
plugins:
- mkdoxy:
doxygen-bin-path: /path/to/doxygen
...
```
+
+## Configure custom Doxygen configuration file
+If you want to use a standard Doxygen configuration file, you can specify the path to the file using the `doxygen-config` option in the plugin configuration.
+
+```yaml hl_lines="6"
+plugins:
+ - mkdoxy:
+ projects:
+ myProjectCpp:
+ src-dirs: ...
+ doxy-cfg-file: path/to/Doxyfile # relative path to the Doxygen configuration file (relative to the mkdocs.yml file)
+ doxy-cfg: # standard doxygen configuration (key: value)
+ FILE_PATTERNS: ... # other configuration options - merge (this will override the configuration from the Doxyfile)
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index aa58b568..0c4a8e26 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -67,6 +67,7 @@ plugins:
src-dirs: mkdoxy
full-doc: True
# template-dir: templates-custom
+ doxy-cfg-file: demo-projects/animal/Doxyfile
doxy-cfg:
FILE_PATTERNS: "*.py"
EXAMPLE_PATH: ""
diff --git a/mkdoxy/doxyrun.py b/mkdoxy/doxyrun.py
index a9a631aa..b8e6f9ef 100644
--- a/mkdoxy/doxyrun.py
+++ b/mkdoxy/doxyrun.py
@@ -12,7 +12,7 @@
class DoxygenRun:
"""! Class for running Doxygen.
- @details This class is used to run Doxygen and parse the XML output.
+ @detailss This class is used to run Doxygen and parse the XML output.
"""
def __init__(
@@ -21,6 +21,7 @@ def __init__(
doxygenSource: str,
tempDoxyFolder: str,
doxyCfgNew,
+ doxyConfigFile: Optional[str] = None,
runPath: Optional[str] = None,
):
"""! Constructor.
@@ -36,10 +37,11 @@ def __init__(
- GENERATE_HTML: NO
- GENERATE_LATEX: NO
- @details
+ @detailss
@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
@@ -53,28 +55,68 @@ def __init__(
self.doxygenBinPath: str = doxygenBinPath
self.doxygenSource: str = doxygenSource
self.tempDoxyFolder: str = tempDoxyFolder
- self.doxyCfgNew: dict = doxyCfgNew
+ self.doxyConfigFile: Optional[str] = doxyConfigFile
self.hashFileName: str = "hashChanges.yaml"
self.hashFilePath: PurePath = PurePath.joinpath(Path(self.tempDoxyFolder), Path(self.hashFileName))
self.runPath: Optional[str] = runPath
+ self.doxyCfg: dict = self.setDoxyCfg(doxyCfgNew)
- self.doxyCfg: dict = {
- "INPUT": self.doxygenSource,
- "OUTPUT_DIRECTORY": self.tempDoxyFolder,
- "DOXYFILE_ENCODING": "UTF-8",
- "GENERATE_XML": "YES",
- "RECURSIVE": "YES",
- "SHOW_NAMESPACES": "YES",
- "GENERATE_HTML": "NO",
- "GENERATE_LATEX": "NO",
- }
+ 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.
- self.doxyCfg.update(self.doxyCfgNew)
- self.doxyCfgStr: str = self.dox_dict2str(self.doxyCfg)
+ @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.
+ @detailss 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.
"""
@@ -89,7 +131,7 @@ def is_doxygen_valid_path(self, doxygen_bin_path: str) -> bool:
# 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
+ @detailss
@param dox_dict: (dict) Dictionary to convert.
@return: (str) String that can be written to a doxygen config file.
"""
@@ -108,17 +150,43 @@ def dox_dict2str(self, dox_dict: dict) -> str:
# 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.
+ @detailss
+ @param dox_str: (str) String from a doxygen config file.
+ @return: (dict) Dictionary.
+ """
+ dox_dict = {}
+ try:
+ for line in dox_str.split("\n"):
+ if line.strip() == "":
+ continue
+ key, value = line.split(" = ")
+ if value == "YES":
+ dox_dict[key] = True
+ elif value == "NO":
+ dox_dict[key] = False
+ else:
+ 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):
"""! Check if the source files have changed since the last run.
- @details
+ @detailss
@return: (bool) True if the source files have changed since the last run.
"""
- def heshWrite(filename: str, hash: str):
+ def hashWrite(filename: PurePath, hash: str):
with open(filename, "w") as file:
file.write(hash)
- def hashRead(filename: str) -> str:
+ def hashRead(filename: PurePath) -> str:
with open(filename, "r") as file:
return str(file.read())
@@ -128,7 +196,7 @@ def hashRead(filename: str) -> str:
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 # lets read stuff in 64kb chunks!
+ BUF_SIZE = 65536 # let's read stuff in 64kb chunks!
if path.is_file():
with open(path, "rb") as f:
while True:
@@ -138,18 +206,18 @@ def hashRead(filename: str) -> str:
sha1.update(data)
# print(f"{path}: {sha1.hexdigest()}")
- hahsNew = sha1.hexdigest()
+ hashNew = sha1.hexdigest()
if Path(self.hashFilePath).is_file():
hashOld = hashRead(self.hashFilePath)
- if hahsNew == hashOld:
+ if hashNew == hashOld:
return False
- heshWrite(self.hashFilePath, hahsNew)
+ hashWrite(self.hashFilePath, hashNew)
return True
def run(self):
"""! Run Doxygen with the current configuration using the Popen class.
- @details
+ @detailss
"""
doxyBuilder = Popen(
[self.doxygenBinPath, "-"],
@@ -158,13 +226,13 @@ def run(self):
stderr=PIPE,
cwd=self.runPath,
)
- (doxyBuilder.communicate(self.doxyCfgStr.encode("utf-8"))[0].decode().strip())
+ (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
+ @detailss
@return: (bool) True if Doxygen was run.
"""
if self.hasChanged():
@@ -175,7 +243,7 @@ def checkAndRun(self):
def getOutputFolder(self) -> PurePath:
"""! Get the path to the XML output folder.
- @details
+ @detailss
@return: (PurePath) Path to the XML output folder.
"""
return Path.joinpath(Path(self.tempDoxyFolder), Path("xml"))
@@ -184,3 +252,11 @@ def getOutputFolder(self) -> PurePath:
# not valid path exception
class DoxygenBinPathNotValid(Exception):
pass
+
+
+class DoxygenCustomConfigNotFound(Exception):
+ pass
+
+
+class DoxygenCustomConfigNotValid(Exception):
+ pass
diff --git a/mkdoxy/generatorBase.py b/mkdoxy/generatorBase.py
index 07d4eded..4726a6f3 100644
--- a/mkdoxy/generatorBase.py
+++ b/mkdoxy/generatorBase.py
@@ -117,10 +117,13 @@ def error(
):
"""! Render an error page.
@details
- @param title (str): Title of the error page (default: "")
- @param message (str): Message of the error page (default: "")
- @param language (str): Programming language of the error page (default: "")
- @return (str): Rendered error page.
+ @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: "")
"""
if config is None:
config = {}
diff --git a/mkdoxy/plugin.py b/mkdoxy/plugin.py
index 64c8534f..97e58e9e 100644
--- a/mkdoxy/plugin.py
+++ b/mkdoxy/plugin.py
@@ -49,6 +49,7 @@ class MkDoxy(BasePlugin):
("debug", config_options.Type(bool, default=False)),
# ('ignore-errors', config_options.Type(bool, default=False)),
("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)),
)
@@ -119,6 +120,7 @@ def tempDir(siteDir: str, tempDir: str, projectName: str) -> str:
project_data.get("src-dirs"),
tempDirApi,
project_data.get("doxy-cfg", {}),
+ project_data.get("doxy-cfg-file", ""),
runPath,
)
if doxygenRun.checkAndRun():
diff --git a/setup.py b/setup.py
index 945e8a06..4e8dc004 100755
--- a/setup.py
+++ b/setup.py
@@ -33,12 +33,14 @@ def requirements():
install_requires=["mkdocs"],
extras_require={
"dev": [
- "mkdocs-material==9.1.18",
+ "mkdocs-material~=9.1.18",
"Jinja2~=3.1.2",
"mkdocs-open-in-new-tab~=1.0.2",
"pathlib~=1.0.1",
"path~=16.7.1",
"isort~=5.12.0",
+ "pytest~=6.2.5",
+ "pre-commit~=3.7.0",
],
},
classifiers=[
diff --git a/tests/data/Doxyfile b/tests/data/Doxyfile
new file mode 100644
index 00000000..ac135f69
--- /dev/null
+++ b/tests/data/Doxyfile
@@ -0,0 +1,7 @@
+DOXYFILE_ENCODING = UTF-8
+GENERATE_XML = YES
+RECURSIVE = YES
+EXAMPLE_PATH = examples
+SHOW_NAMESPACES = YES
+GENERATE_HTML = NO
+GENERATE_LATEX = NO
diff --git a/tests/test_doxyrun.py b/tests/test_doxyrun.py
new file mode 100644
index 00000000..2c8cbff9
--- /dev/null
+++ b/tests/test_doxyrun.py
@@ -0,0 +1,89 @@
+from mkdoxy.doxyrun import DoxygenRun
+
+
+def test_dox_dict2str():
+ dox_dict = {
+ "DOXYFILE_ENCODING": "UTF-8",
+ "GENERATE_XML": True,
+ "RECURSIVE": True,
+ "EXAMPLE_PATH": "examples",
+ "SHOW_NAMESPACES": True,
+ "GENERATE_HTML": False,
+ "GENERATE_LATEX": False,
+ }
+
+ 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"
+ )
+
+ assert result == 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,
+ )
+
+ result = doxygen_run.setDoxyCfg(dox_dict)
+
+ expected_result = {
+ "DOXYFILE_ENCODING": "UTF-8",
+ "GENERATE_XML": True,
+ "RECURSIVE": True,
+ "EXAMPLE_PATH": "examples",
+ "SHOW_NAMESPACES": True,
+ "GENERATE_HTML": False,
+ "GENERATE_LATEX": False,
+ "INPUT": "/path/to/source/files",
+ "OUTPUT_DIRECTORY": "/path/to/temp/folder",
+ }
+
+ assert result == expected_result
+
+
+def test_str2dox_dict():
+ 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)
+
+ expected_result = {
+ "DOXYFILE_ENCODING": "UTF-8",
+ "GENERATE_XML": True,
+ "RECURSIVE": True,
+ "EXAMPLE_PATH": "examples",
+ "SHOW_NAMESPACES": True,
+ "GENERATE_HTML": False,
+ "GENERATE_LATEX": False,
+ }
+
+ assert result == expected_result