2********************************************************************************
3@file doxygen_creator.py
4@brief create doxygen documentation
5********************************************************************************
16from typing
import Any, Optional
17from threading
import Thread
18from difflib
import get_close_matches
20import packaging.version
24B_PLANTUML_SUPPORT =
True
25B_GITHUB_CORNER_SUPPORT =
True
26B_DOXY_CONFIG_DIFF_SUPPORT =
True
27B_DOXY_PY_CHECKER_SUPPORT =
True
28B_AUTO_VERSION_SUPPORT =
True
29B_FOOTER_SUPPORT =
False
31if B_DOXY_PY_CHECKER_SUPPORT:
34log = logging.getLogger(
"DoxygenCreator")
38WARNING_FAIL =
"FAIL_ON_WARNINGS"
40S_DOXYGEN_PATH =
"doxygen.exe"
41S_DEFAULT_OUTPUT_FOLDER =
"Output_Doxygen"
43S_MAIN_FOLDER_FOLDER =
"../../"
45DOXYGEN_VERSION =
"1.12.0"
46S_DOXYGEN_URL = f
"https://sourceforge.net/projects/doxygen/files/rel-{DOXYGEN_VERSION}/doxygen-{DOXYGEN_VERSION}.windows.x64.bin.zip/download"
47S_DOXYGEN_ZIP = f
"doxygen-{DOXYGEN_VERSION}.windows.x64.bin.zip"
48S_DOXYGEN_DLL =
"libclang.dll"
50S_WARNING_FILE_PREFIX =
"Doxygen_warnings_"
51S_WARNING_FILE_SUFFIX =
".log"
52S_INDEX_FILE =
"html/index.html"
55S_PYTHON_PATTERN =
"*.py"
56L_DEFAULT_FILE_PATTERN: list[str] = []
59 PLANT_UML_VERSION =
"1.2024.7"
60 S_PLANTUML_JAR_URL = f
"https://github.com/plantuml/plantuml/releases/download/v{PLANT_UML_VERSION}/plantuml-{PLANT_UML_VERSION}.jar"
61 S_PLANTUML_JAR_NAME =
"plantuml.jar"
62 S_PLANTUML_PATH =
"./"
66if B_DOXY_CONFIG_DIFF_SUPPORT:
67 S_DOXY_DIFF_HTML_NAME =
"DoxyfileDiff.html"
68 S_DOXY_FILE_DEFAULT_NAME =
"Default.Doxyfile"
71if B_GITHUB_CORNER_SUPPORT:
72 S_GITHUB_CORNER_FIRST =
"<a href="
73 S_GITHUB_CORNER_LAST =
""" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>"""
78 @brief Class to open Notepad in thread to prevent program stop until close file
79 @param s_file : file to open
87 def run(self) -> None:
89 @brief Open file with notepad
92 with subprocess.Popen([
"notepad.exe", self.
s_file]):
98 @brief Class to generate Doxygen documentation for any code documentation with uniform settings and styling.
99 @param s_webside : URL to website
101 d_settings: dict[str, str | list[str] | int] = {
102 "PROJECT_NAME":
"MyProject",
103 "OUTPUT_DIRECTORY": S_DEFAULT_OUTPUT_FOLDER,
104 "ABBREVIATE_BRIEF":
"",
105 "FULL_PATH_NAMES": NO,
106 "JAVADOC_AUTOBRIEF": YES,
107 "OPTIMIZE_OUTPUT_JAVA": YES,
110 "INTERNAL_DOCS": YES,
111 "HIDE_SCOPE_NAMES": YES,
112 "SORT_BRIEF_DOCS": YES,
113 "SORT_BY_SCOPE_NAME": YES,
114 "WARN_NO_PARAMDOC": YES,
115 "WARN_AS_ERROR": WARNING_FAIL,
117 "FILE_PATTERNS": L_DEFAULT_FILE_PATTERN,
119 "IMAGE_PATH": S_MAIN_FOLDER_FOLDER,
120 "USE_MDFILE_AS_MAINPAGE": f
"{S_MAIN_FOLDER_FOLDER}README.md",
121 "SOURCE_BROWSER": YES,
122 "INLINE_SOURCES": YES,
123 "HTML_COLORSTYLE":
"LIGHT",
124 "HTML_COLORSTYLE_HUE": 209,
125 "HTML_COLORSTYLE_SAT": 255,
126 "HTML_COLORSTYLE_GAMMA": 113,
127 "HTML_DYNAMIC_SECTIONS": YES,
128 "HTML_COPY_CLIPBOARD": NO,
130 "GENERATE_TREEVIEW": YES,
133 "SERVER_BASED_SEARCH": NO,
134 "EXTERNAL_SEARCH": NO,
135 "GENERATE_LATEX": NO,
136 "LATEX_CMD_NAME":
"latex",
138 "DOT_IMAGE_FORMAT":
"svg",
139 "INTERACTIVE_SVG": YES,
140 "PLANTUML_JAR_PATH": S_PLANTUML_PATH,
141 "PLANTUML_INCLUDE_PATH": S_PLANTUML_PATH,
142 "DOT_MULTI_TARGETS": YES,
146 def __init__(self, s_webside: Optional[str] =
None) ->
None:
155 @brief Create default doxyfile
156 @param s_file_name : doxygen file name
158 subprocess.call([S_DOXYGEN_PATH,
"-g", s_file_name])
162 @brief Set doxygen configuration.
163 @param s_type : type to set in configuration
164 @param value : value to set for s_type in configuration
165 @param b_override : info if selected default setting should override
167 if isinstance(value, list):
168 if b_override
or (s_type
not in self.
d_settings):
171 if isinstance(list_to_extend, list):
172 list_to_extend.extend(value)
174 log.warning(
"Not possible to append value to %s", s_type)
176 if b_override
or (s_type
not in self.
d_settings):
181 @brief Get type in doxygen configuration.
182 @param s_type : type in configuration get setting
183 @return configuration settings
193 @brief Prepare doxfile configuration with default settings that depend on other parameters.
194 Parameters that set before as fix parameter or by user will not override.
195 This allows the user to make settings that differ from the default configuration.
204 self.
set_configuration(
"PROJECT_BRIEF", f
"{s_project_name}-Documentation", b_override=
False)
208 if B_AUTO_VERSION_SUPPORT:
213 packaging.version.Version(s_version)
215 except packaging.version.InvalidVersion:
219 l_extra_files = [
"doxygen-awesome-darkmode-toggle.js",
220 "doxygen-awesome-fragment-copy-button.js",
221 "doxygen-awesome-paragraph-link.js",
222 "doxygen-awesome-interactive-toc.js",
223 "doxygen-awesome-tabs.js"]
225 l_extra_stylesheet = [
"doxygen-awesome.css",
226 "doxygen-awesome-sidebar-only.css",
227 "doxygen-awesome-sidebar-only-darkmode-toggle.css"]
228 self.
set_configuration(
"HTML_EXTRA_STYLESHEET", l_extra_stylesheet, b_override=
False)
235 @brief Override default settings in doxyfile with selected settings.
240 l_existing_keys = list(configuration.keys())
244 if key
in l_existing_keys:
245 if isinstance(value, list):
248 new_values.append(entry)
249 configuration[key] = new_values
251 if isinstance(value, int):
253 configuration[key] = value
255 text = f
"'{key}' is a invalid doxygen setting"
256 similar_key = get_close_matches(key, l_existing_keys, n=1)
257 if len(similar_key) > 0:
258 text += f
". Did you mean: '{similar_key[0]}'?"
266 @brief Add warnings to warning file
271 file.write(s_warning +
"\n")
273 if B_PLANTUML_SUPPORT:
276 @brief Download PlantUML Jar
278 if not os.path.exists(S_PLANTUML_JAR_NAME):
279 log.info(
"Download %s ...", S_PLANTUML_JAR_NAME)
281 with requests.get(S_PLANTUML_JAR_URL, timeout=I_TIMEOUT)
as response:
282 response.raise_for_status()
283 with open(S_PLANTUML_JAR_NAME, mode=
"wb")
as file:
284 file.write(response.content)
285 except requests.Timeout:
286 log.error(
"Timeout for download %s!", S_PLANTUML_JAR_NAME)
287 except requests.RequestException
as e:
288 log.error(
"Can not download %s! %s", S_PLANTUML_JAR_NAME, e)
290 log.info(
"%s already exist!", S_PLANTUML_JAR_NAME)
294 @brief Download Doxygen
296 if not os.path.exists(S_DOXYGEN_PATH)
or not os.path.exists(S_DOXYGEN_DLL):
297 if not os.path.exists(S_DOXYGEN_ZIP):
298 log.info(
"Download %s ...", S_DOXYGEN_ZIP)
300 with requests.get(S_DOXYGEN_URL, timeout=I_TIMEOUT)
as response:
301 response.raise_for_status()
302 with open(S_DOXYGEN_ZIP, mode=
"wb")
as file:
303 file.write(response.content)
304 except requests.Timeout:
305 log.error(
"Timeout for download %s!", S_DOXYGEN_ZIP)
306 except requests.RequestException
as e:
307 log.error(
"Can not download %s! %s", S_DOXYGEN_ZIP, e)
309 log.info(
"%s already exist!", S_DOXYGEN_ZIP)
310 with zipfile.ZipFile(S_DOXYGEN_ZIP, mode=
"r")
as zip_ref:
311 zip_ref.extract(S_DOXYGEN_PATH,
"./")
312 zip_ref.extract(S_DOXYGEN_DLL,
"./")
314 log.info(
"%s and %s already exist!", S_DOXYGEN_PATH, S_DOXYGEN_DLL)
318 @brief Check for doxygen Warnings
319 @param b_open_warning_file : [True] open warning file; [False] only check for warnings
320 @return status if doxygen warnings exist
326 lines = file.readlines()
329 log.warning(
"Doxygen Warnings found!!!")
331 log.warning(
" %s", s_line)
333 if b_open_warning_file
and b_warnings:
340 @brief Generate Doxygen output depend on existing doxyfile
341 @param b_open_doxygen_output : [True] open output in browser; [False] only generate output
345 if b_open_doxygen_output:
347 filename = f
"file:///{os.getcwd()}/{self.s_output_dir}/{S_INDEX_FILE}"
348 webbrowser.open_new_tab(filename)
350 if B_GITHUB_CORNER_SUPPORT:
353 @brief Add Github corner and tab icon
355 s_folder = f
"{self.s_output_dir}/html/"
357 s_corner_text = S_GITHUB_CORNER_FIRST + self.
s_webside + S_GITHUB_CORNER_LAST
360 for html_file
in os.listdir(s_folder):
361 if html_file.endswith(
".html"):
362 file_path = f
"{s_folder}{html_file}"
363 with open(file_path, mode=
"a", encoding=
"utf-8")
as file:
364 if s_corner_text
is not None:
365 file.write(s_corner_text +
"\n")
369 @brief Add .nojekyll file that files with underscores visible
371 s_file_name =
".nojekyll"
372 with open(f
"{self.s_output_dir}/html/{s_file_name}", mode=
"w", encoding=
"utf-8")
as file:
375 if B_DOXY_CONFIG_DIFF_SUPPORT:
378 @brief Generate doxyfile diff to view changes
385 configuration = config_parser.load_configuration(s_default_doxyfile)
386 config_parser.store_configuration(configuration, s_default_doxyfile)
387 with open(s_default_doxyfile, mode=
"r", encoding=
"utf-8")
as file:
388 s_default_config = file.read()
389 os.remove(s_default_doxyfile)
393 s_modified_config = file.read()
396 difference = difflib.HtmlDiff(wrapcolumn=I_WRAP_LENGTH).make_file(s_default_config.splitlines(), s_modified_config.splitlines(),
"Default",
"Modified")
397 with open(s_diff_file_name, mode=
"w", encoding=
"utf-8")
as file:
398 file.write(difference)
400 def run_doxygen(self, b_open_doxygen_output: bool =
True) -> bool:
402 @brief Generate Doxyfile and Doxygen output depend on doxyfile settings
403 @param b_open_doxygen_output : [True] open output in browser; [False] only generate output
404 @return status for found doxygen warning
407 if B_PLANTUML_SUPPORT:
415 if B_GITHUB_CORNER_SUPPORT:
418 if B_DOXY_CONFIG_DIFF_SUPPORT:
421 if B_DOXY_PY_CHECKER_SUPPORT:
423 file_patterns = self.
d_settings[
"FILE_PATTERNS"]
424 if isinstance(file_patterns, list):
425 l_file_patterns = [str(pattern)
for pattern
in file_patterns]
427 l_file_patterns = [str(file_patterns)]
428 if S_PYTHON_PATTERN
in l_file_patterns:
430 l_finding = doxy_checker.run_check()
441 @brief Function to define CMD arguments.
442 @return Function returns argument parser.
444 o_parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
445 o_parser.add_argument(
"-o",
"--open",
448 help=
"open output files after generation")
449 return o_parser.parse_args()
This class should be used to parse and store a doxygen configuration file.
Doxygen documentation checker class.
Class to generate Doxygen documentation for any code documentation with uniform settings and styling.
None prepare_doxyfile_configuration(self)
Prepare doxfile configuration with default settings that depend on other parameters.
None set_configuration(self, str s_type, Any value, bool b_override=True)
Set doxygen configuration.
None download_plantuml_jar(self)
Download PlantUML Jar.
None __init__(self, Optional[str] s_webside=None)
None generate_configuration_diff(self)
Generate doxyfile diff to view changes.
None generate_doxygen_output(self, bool b_open_doxygen_output=True)
Generate Doxygen output depend on existing doxyfile.
None download_doxygen(self)
Download Doxygen.
None create_default_doxyfile(self, str s_file_name)
Create default doxyfile.
bool run_doxygen(self, bool b_open_doxygen_output=True)
Generate Doxyfile and Doxygen output depend on doxyfile settings.
None add_nojekyll_file(self)
Add .nojekyll file that files with underscores visible.
bool check_doxygen_warnings(self, bool b_open_warning_file=True)
Check for doxygen Warnings.
Any get_configuration(self, str s_type)
Get type in doxygen configuration.
None edit_select_doxyfile_settings(self)
Override default settings in doxyfile with selected settings.
None add_github_corner(self)
Add Github corner and tab icon.
None add_warnings(self)
Add warnings to warning file.
Class to open Notepad in thread to prevent program stop until close file.
None run(self)
Open file with notepad.
__init__(self, str s_file)
argparse.Namespace get_cmd_args()
Function to define CMD arguments.