File generatorSnippets.py¶
File List > mkdoxy > generatorSnippets.py
Go to the documentation of this file
import logging
import pathlib
import re
import yaml
from mkdocs.structure import pages
from mkdoxy.doxygen import Doxygen
from mkdoxy.finder import Finder
from mkdoxy.generatorBase import GeneratorBase
from mkdoxy.node import Node
log: logging.Logger = logging.getLogger("mkdocs")
regexIncorrect = r"(?s)(?<!```yaml\n)(^::: doxy)(\.(?P<project>[a-zA-Z0-9_]+))?[\.]?[\s]*\n(?P<yaml>.*?)\s*\n(?:(?=\n)|(?=:::)|\Z)" # https://regex101.com/r/IYl25b/2 # noqa: E501
regexLong = r"(?s)(?<!```yaml\n)(^::: doxy\.(?P<project>[a-zA-Z0-9_]+)\.(?P<argument>[a-zA-Z0-9_.]+))\s*\n(?P<yaml>.*?)(?:(?:(?:\r*\n)(?=\n))|(?=:::)|`|\Z)" # https://regex101.com/r/lIgOij/4 # noqa: E501
regexShort = r"(?s)(?<!```yaml\n)(^::: doxy\.(?P<project>[a-zA-Z0-9_]+)\.(?P<argument>[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],
doxygen: dict[str, Doxygen],
projects: dict[str, dict[str, any]],
useDirectoryUrls: bool,
page: pages.Page,
config: dict,
debug: bool = False,
):
self.markdownmarkdown = markdown
self.generatorBasegeneratorBase = generatorBase
self.doxygendoxygen = doxygen
self.projectsprojects = projects
self.useDirectoryUrlsuseDirectoryUrls = useDirectoryUrls
self.pagepage = page
self.configconfig = config
self.debugdebug = debug
self.finderfinder = Finder(doxygen, debug)
self.doxy_argumentsdoxy_arguments = {
"code": self.doxyCodedoxyCode,
"function": self.doxyFunctiondoxyFunction,
"namespace.function": self.doxyNamespaceFunctiondoxyNamespaceFunction,
"class": self.doxyClassdoxyClass,
"class.method": self.doxyClassMethoddoxyClassMethod,
"class.list": self.doxyClassListdoxyClassList,
"class.index": self.doxyClassIndexdoxyClassIndex,
"class.hierarchy": self.doxyClassHierarchydoxyClassHierarchy,
"namespace.list": self.doxyNamespaceListdoxyNamespaceList,
"file.list": self.doxyFileListdoxyFileList,
}
# fix absolute path
path = pathlib.PurePath(self.pagepage.url).parts
self.pageUrlPrefixpageUrlPrefix = "".join("../" for _ in range(len(path) - 1))
def generate(self):
if self.is_doxy_inactiveis_doxy_inactive(self.configconfig):
return self.markdownmarkdown # doxygen is inactive return unchanged markdown
try:
matches = re.finditer(regexIncorrect, self.markdownmarkdown, re.MULTILINE)
for match in reversed(list(matches)):
snippet = match.group()
project_name = match.group("project") or "<project_name>"
snippet_config = self.configconfig.copy()
snippet_config.update(self.try_load_yamltry_load_yaml(match.group("yaml"), project_name, snippet, self.configconfig))
if self.is_doxy_inactiveis_doxy_inactive(snippet_config):
continue
replacement = (
self.incorrect_argumentincorrect_argument(project_name, "", snippet_config, snippet)
if self.is_project_existis_project_exist(project_name)
else self.incorrect_projectincorrect_project(project_name, snippet_config, snippet)
)
self.replace_markdownreplace_markdown(match.start(), match.end(), replacement)
matches = re.finditer(regexShort, self.markdownmarkdown, re.MULTILINE)
for match in reversed(list(matches)):
snippet = match.group()
argument = match.group("argument").lower()
project_name = match.group("project")
snippet_config = self.configconfig.copy()
snippet_config.update(self.try_load_yamltry_load_yaml(match.group("yaml"), project_name, snippet, self.configconfig))
if self.is_doxy_inactiveis_doxy_inactive(snippet_config):
continue
replaceStr = self.call_doxy_by_namecall_doxy_by_name(snippet, project_name, argument, snippet_config)
self.replace_markdownreplace_markdown(match.start(), match.end(), replaceStr)
matches = re.finditer(regexLong, self.markdownmarkdown, re.MULTILINE)
for match in reversed(list(matches)):
snippet = match.group()
argument = match.group("argument").lower()
project_name = match.group("project")
# log.debug(f"\nArgument: {argument}")
# config has been updated by yaml
snippet_config = self.configconfig.copy()
snippet_config.update(self.try_load_yamltry_load_yaml(match.group("yaml"), project_name, snippet, self.configconfig))
replaceStr = self.call_doxy_by_namecall_doxy_by_name(snippet, project_name, argument, snippet_config)
self.replace_markdownreplace_markdown(match.start(), match.end(), replaceStr)
return self.markdownmarkdown
except Exception as e:
basename = pathlib.Path(__file__).name
log.error(f"Error in {self.page.url} page. Incorrect doxy snippet or error in file {basename}")
log.error(f"Error: {e}")
return self.markdownmarkdown
def try_load_yaml(self, yaml_raw: str, project: str, snippet: str, config: dict) -> dict:
try:
return yaml.safe_load(yaml_raw)
except yaml.YAMLError:
log.error(f"YAML error in {project} project on page {self.page.url}")
self.doxyErrordoxyError(
project,
config,
"YAML error",
"Check your YAML syntax",
"YAML snippet:",
yaml_raw,
"yaml",
snippet,
)
return {}
def incorrect_project(
self,
project: str,
config: dict,
snippet: str,
) -> str:
return self.doxyErrordoxyError(
project,
config,
f"Incorrect project name: {project}",
"Project name have to contain [a-zA-Z0-9_]",
"A list of available projects:",
"\n".join(self.projectsprojects.keys()),
"yaml",
snippet,
)
def incorrect_argument(self, project: str, argument: str, config: dict, snippet: str) -> str:
return self.doxyErrordoxyError(
project,
config,
f"Incorrect argument: {argument}" if argument else f"Add argument to snippet: {project}",
f"Argument have to be based on this diagram → **:::doxy.{project}.<argument\\>**",
"A list of available arguments:",
"\n".join(self.doxy_argumentsdoxy_arguments.keys()),
"yaml",
snippet,
)
def replace_markdown(self, start: int, end: int, replacement: str):
self.markdownmarkdown = self.markdownmarkdown[:start] + replacement + "\n" + self.markdownmarkdown[end:]
def _setLinkPrefixNode(self, node: Node, linkPrefix: str):
node.project.linkPrefix = linkPrefix
def _setLinkPrefixNodes(self, nodes: list[Node], linkPrefix: str):
if nodes:
nodes[0].project.linkPrefix = linkPrefix
def is_project_exist(self, project: str):
return project in self.projectsprojects
def is_doxy_inactive(self, config: dict):
return config.get("disable_doxy_snippets", False)
def call_doxy_by_name(self, snippet, project: str, argument: str, config: dict) -> str:
if argument not in self.doxy_argumentsdoxy_arguments:
return self.incorrect_argumentincorrect_argument(project, argument, config, snippet)
callback = self.doxy_argumentsdoxy_arguments[argument]
return callback(snippet, project, config)
def checkConfig(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.doxyErrordoxyError(
project,
config,
f"Missing parameter: {param}",
"This parameter is required",
"Required parameters:",
"\n".join(required_params),
"yaml",
snippet,
)
for param in required_params
if not config.get(param)
),
False,
)
def doxyError(
self,
project,
config: dict,
title: str,
description: str,
code_header: str = "",
code: str = "",
code_language: str = "",
snippet_code: str = "",
) -> str:
log.error(f" -> {title} -> page: {self.page.canonical_url}")
if project not in self.projectsprojects:
project = list(self.projectsprojects)[0]
return self.generatorBasegeneratorBase[project].error(
config, title, description, code_header, code, code_language, snippet_code
)
def doxyCode(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, ["file"])
if errorMsg:
return errorMsg
node = self.finderfinder.doxyCode(project, config.get("file"))
if node is None:
return self.doxyNodeIsNonedoxyNodeIsNone(project, config, snippet)
if isinstance(node, Node):
progCode = self.codeStripcodeStrip(
node.programlisting,
node.code_language,
config.get("start", 1),
config.get("end", 0),
)
if progCode is False:
return self.doxyErrordoxyError(
project,
config,
f"Parameter start: {config.get('start')} is greater than end: {config.get('end')}",
f"{snippet}",
"yaml",
)
self._setLinkPrefixNode_setLinkPrefixNode(node, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].code(node, config, progCode)
return self.doxyErrordoxyError(
project,
config,
f"Did not find File: `{config.get('file')}`",
"Check your file name",
f"Available files in {project} project:",
"\n".join(node),
"yaml",
snippet,
)
def codeStrip(self, codeRaw, codeLanguage: str, start: int = 1, end: int = None):
lines = codeRaw.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}```"
def doxyFunction(self, snippet, project: str, config: dict):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, ["name"])
if errorMsg:
return errorMsg
node = self.finderfinder.doxyFunction(project, config.get("name"))
if node is None:
return self.doxyNodeIsNonedoxyNodeIsNone(project, config, snippet)
if isinstance(node, Node):
self._setLinkPrefixNode_setLinkPrefixNode(node, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].function(node, config)
return self.doxyErrordoxyError(
project,
config,
"Incorrect function configuration",
f"Did not find Function with name: `{config.get('name')}`",
"Available functions:",
"\n".join(node),
"yaml",
snippet,
)
def doxyClass(self, snippet, project: str, config: dict):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, ["name"])
if errorMsg:
return errorMsg
node = self.finderfinder.doxyClass(project, config.get("name"))
if node is None:
return self.doxyNodeIsNonedoxyNodeIsNone(project, config, snippet)
if isinstance(node, Node):
self._setLinkPrefixNode_setLinkPrefixNode(node, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].member(node, config)
return self.doxyErrordoxyError(
project,
config,
"Incorrect class configuration",
f"Did not find Class with name: `{config.get('name')}`",
"Available classes:",
"\n".join(node),
"yaml",
snippet,
)
def doxyClassMethod(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, ["name", "method"])
if errorMsg:
return errorMsg
node = self.finderfinder.doxyClassMethod(project, config.get("name"), config.get("method"))
if node is None:
return self.doxyNodeIsNonedoxyNodeIsNone(project, config, snippet)
if isinstance(node, Node):
self._setLinkPrefixNode_setLinkPrefixNode(node, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].function(node, config)
return self.doxyErrordoxyError(
project,
config,
"Incorrect class method configuration",
f"Did not find Class with name: `{config.get('name')}` and method: `{config.get('method')}`",
"Available classes and methods:",
"\n".join(node),
"yaml",
snippet,
)
def doxyClassList(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, [])
if errorMsg:
return errorMsg
nodes = self.doxygendoxygen[project].root.children
self._setLinkPrefixNodes_setLinkPrefixNodes(nodes, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].annotated(nodes, config)
def doxyClassIndex(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, [])
if errorMsg:
return errorMsg
nodes = self.doxygendoxygen[project].root.children
self._setLinkPrefixNodes_setLinkPrefixNodes(nodes, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].classes(nodes, config)
def doxyClassHierarchy(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, [])
if errorMsg:
return errorMsg
nodes = self.doxygendoxygen[project].root.children
self._setLinkPrefixNodes_setLinkPrefixNodes(nodes, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].hierarchy(nodes, config)
def doxyNamespaceList(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, [])
if errorMsg:
return errorMsg
nodes = self.doxygendoxygen[project].root.children
self._setLinkPrefixNodes_setLinkPrefixNodes(nodes, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].namespaces(nodes, config)
def doxyNamespaceFunction(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, ["namespace", "name"])
if errorMsg:
return errorMsg
node = self.finderfinder.doxyNamespaceFunction(project, config.get("namespace"), config.get("name"))
if node is None:
return self.doxyNodeIsNonedoxyNodeIsNone(project, config, snippet)
if isinstance(node, Node):
self._setLinkPrefixNode_setLinkPrefixNode(node, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].function(node, config)
return self.doxyErrordoxyError(
project,
config,
"Incorrect namespace function configuration",
f"Did not find Namespace with name: `{config.get('namespace')}` and function: `{config.get('name')}`",
"Available classes and methods:",
"\n".join(node),
"yaml",
snippet,
)
def doxyFileList(self, snippet, project: str, config):
errorMsg = self.checkConfigcheckConfig(snippet, project, config, [])
if errorMsg:
return errorMsg
nodes = self.doxygendoxygen[project].files.children
self._setLinkPrefixNodes_setLinkPrefixNodes(nodes, self.pageUrlPrefixpageUrlPrefix + project + "/")
return self.generatorBasegeneratorBase[project].fileindex(nodes, config)
def doxyNodeIsNone(self, project: str, config: dict, snippet: str) -> str:
return self.doxyErrordoxyError(
project,
config,
f"Could not find coresponding snippet for project {project}",
f"Config: {config}",
"yaml",
snippet,
)
class SnippetClass:
def __init__(self, config):
self.configconfig = config
def default(self):
return ""