diff --git a/AddressSpace/templates/designToAddressSpaceDocMd.jinja b/AddressSpace/templates/designToAddressSpaceDocMd.jinja new file mode 100755 index 00000000..1b17dfc4 --- /dev/null +++ b/AddressSpace/templates/designToAddressSpaceDocMd.jinja @@ -0,0 +1,127 @@ +{# © Copyright CERN, 2015. #} +{# All rights not expressly granted are reserved. #} +{# This file is part of Quasar. #} +{# #} +{# Quasar is free software: you can redistribute it and/or modify #} +{# it under the terms of the GNU Lesser General Public Licence as published by #} +{# the Free Software Foundation, either version 3 of the Licence. #} +{# Quasar is distributed in the hope that it will be useful, #} +{# but WITHOUT ANY WARRANTY; without even the implied warranty of #} +{# #} +{# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #} +{# GNU Lesser General Public Licence for more details. #} +{# #} +{# You should have received a copy of the GNU Lesser General Public License #} +{# along with Quasar. If not, see #} +{# #} +{# Created: Sep 2018 (the original XSLT version) #} +{# 11 May 2020 (translated to Jinja2) #} +{# 05 February 2024 (converted to Markdown) #} +{# Authors: #} +{# Piotr Nikiel #} + +{% macro estimateFunctionReadWrite(variable) -%} + {%- if 'cachevariable' in variable.tag -%} + {%- if variable.get('addressSpaceWrite') == 'forbidden' -%} + read-only + {%- else -%} + read+write + {%- endif -%} + {% else %} + {%- if variable.get('addressSpaceWrite') == 'forbidden' and variable.get('addressSpaceRead') == 'forbidden' -%} + none + {%- elif variable.get('addressSpaceWrite') == 'forbidden' and variable.get('addressSpaceRead') != 'forbidden' -%} + read-only + {%- elif variable.get('addressSpaceWrite') != 'forbidden' and variable.get('addressSpaceRead') == 'forbidden' -%} + write-only + {%- elif variable.get('addressSpaceWrite') != 'forbidden' and variable.get('addressSpaceRead') != 'forbidden' -%} + read+write + {%- endif -%} + {%- endif -%} +{% endmacro %} + +# OPC-UA address space documentation for {{designInspector.getProjectName()}} +{# +Jump to: +{% for class_name in designInspector.get_names_of_all_classes()|sort %} +{% set cls = designInspector.objectify_class(class_name) %} + {{cls.get('name')}} +{% endfor %} +#} +{% for class_name in designInspector.get_names_of_all_classes()|sort %} + +--- +{% set cls = designInspector.objectify_class(class_name) %} + +## {{cls.get('name')}} +{% if (cls.documentation|length)>0 %} +DOC:{{cls.documentation|node_text_contents_to_string|trim}} +{% else %} +DOC: +{% endif %} + +{% if (cls.cachevariable|length + cls.sourcevariable|length + cls.method|length)>0 %} +{%- if (cls.cachevariable|length + cls.sourcevariable|length)>0 -%} +Variables: +{# we combine cache-vars and source-vars because the end-user should see both alphabetically + without paying attention whether these are CV or SV. #} +{% set variables = designInspector.to_list_if_exists(cls.cachevariable) + designInspector.to_list_if_exists(cls.sourcevariable) %} +{% for variable in variables|sorted_by_objectified_attr('name') %} +??? info "{{-variable.get('name')}} (dataType={{variable.get('dataType')}})" + Type: +{%- if 'cachevariable' in variable.tag %} + cache-variable
+{% else %} + source-variable
+{%- endif %} + Access= +{{-estimateFunctionReadWrite(variable)-}}
+{% if variable.documentation|length>0 %} + + DOC:{{- variable.documentation|node_text_contents_to_string|trim }} + +{% endif %} +{% endfor %} +{% endif %} + +{% if cls.method|length>0 %} +Methods: +{%- for m in cls.method|sorted_by_objectified_attr('name') %} + +??? info "{{m.get('name')}}" +{% if m.documentation|length>0 %} + DOC:{{m.documentation|node_text_contents_to_string|trim}}
+{% endif %} +{% if m.argument|length>0 %} + Arguments:
+{%- for arg in m.argument %} + + - {{arg.get('name')}} ({{arg.get('dataType')}} {% if arg.array|length>0 %}[]{% endif %}) + {%- if arg.documentation|length>0 -%} + :{{arg.documentation|node_text_contents_to_string|trim}}
+ {%- endif -%}
+{% endfor %} +{% endif -%} +{%- if m.returnvalue|length>0 %} + + Return values:
+{% for rv in m.returnvalue %} + - {{rv.get('name')}} ({{rv.get('dataType')}} {%- if rv.array|length>0 %}[]{% endif %}) + {%- if rv.documentation|length>0 -%} + :{{rv.documentation|node_text_contents_to_string|trim}}
+ {% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} + {% else %} + This quasar class has no address-space components. + {% endif %} +{% if cls.hasobjects|length>0 %} + +Classes possible to be children objects:
+{% for ho in cls.hasobjects %} +- {{ho.get('class')}}
+{% endfor %} +{% endif %} +{% endfor %} diff --git a/Configuration/templates/designToConfigDocumentationMd.jinja b/Configuration/templates/designToConfigDocumentationMd.jinja new file mode 100755 index 00000000..eea8ef38 --- /dev/null +++ b/Configuration/templates/designToConfigDocumentationMd.jinja @@ -0,0 +1,164 @@ +{# Copyright CERN, 2015. #} +{# All rights not expressly granted are reserved. #} +{# This file is part of Quasar. #} +{# #} +{# Quasar is free software: you can redistribute it and/or modify #} +{# it under the terms of the GNU Lesser General Public Licence as published by #} +{# the Free Software Foundation, either version 3 of the Licence. #} +{# Quasar is distributed in the hope that it will be useful, #} +{# but WITHOUT ANY WARRANTY; without even the implied warranty of #} +{# #} +{# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #} +{# GNU Lesser General Public Licence for more details. #} +{# #} +{# You should have received a copy of the GNU Lesser General Public License #} +{# along with Quasar. If not, see #} +{# #} +{# Created: Jul 2014 #} +{# Mar 2020 (translated to Jinja2) #} +{# Deb 2024 (converted to Md) #} +{# Authors: #} +{# Piotr Nikiel #} +{# Ben Farnham #} +{# Etienne Fortin #} +{% import 'headers.jinja' as headers %} +{% set this = designInspector.objectify_root() %} + +{% macro writeRestrictions(containingClassName, objectName, what) %} + {%- set restrictions=designInspector.get_restrictions(containingClassName, objectName, what) %} + + {%- if restrictions|length>0 -%} + Value restrictions:
+ {% set restrictionType = oracle.classify_xsd_restrictions(restrictions) %} + {%- if restrictionType == 'enumeration' -%} + Enumeration: + + {% for restriction in restrictions -%} + - {{restriction[1]|trim}} + {% endfor %} + {%- elif restrictionType == 'pattern' -%} + Pattern: + + {% for restriction in restrictions -%} + - {{restriction[1]|trim}} + {% endfor %} + {%- elif restrictionType == 'bounds' -%} + Bounds: + + {% for restriction in restrictions|sort(attribute=0, reverse=True) -%} + {%- if restriction[0] == 'minExclusive' -%} + - {{boundsRestrictionPrefix}}{{objectName}}>{{restriction[1]}} + {% endif %} + {%- if restriction[0] == 'maxExclusive' -%} + - {{boundsRestrictionPrefix}}{{objectName}}<{{restriction[1]}} + {% endif %} + {%- if restriction[0] == 'minInclusive' -%} + - {{boundsRestrictionPrefix}}{{objectName}}>={{restriction[1]}} + {% endif %} + {%- if restriction[0] == 'maxInclusive' -%} + - {{boundsRestrictionPrefix}}{{objectName}}<={{restriction[1]}} + {% endif %} + {%- endfor %} + {% endif %} + {% endif %} +{% endmacro %} + +{% macro writeCacheVarOrConfigEntry(containingClassName, elementObject, what) %} + +{% set objectName = elementObject.get('name') %} +{% set objectType = elementObject.get('dataType') %} +{% set optionalityMsg = 'optional, the default value shown below will be used if not present' if elementObject.get('defaultValue') else 'mandatory' %} +??? info "{{objectName}} ({{objectType}}) {{optionalityMsg}}" +{% set docElements = designInspector.objectifyDocumentation(containingClassName, objectName) %} +{% if docElements|length > 0 %} + DOC: + {%- for docElement in docElements %} + {{- designInspector.strip_documentation_tag(docElement)|trim }} + {% endfor %} + {# Add to the docs the enumeration documentation, if any. #} + {% if what == 'configentry' %} + {% set configEntryObjectified = designInspector.objectify_config_entries(containingClassName, "[@name='" + objectName + "']")[0] %} + {% set enumerationValuesWithDocs = designInspector.xpath_relative_to_object(configEntryObjectified, 'd:configRestriction/d:restrictionByEnumeration/d:enumerationValue[d:documentation]') %} + {% if enumerationValuesWithDocs|length > 0 %} +
    + {% for enumerationValue in designInspector.xpath_relative_to_object(configEntryObjectified, 'd:configRestriction/d:restrictionByEnumeration/d:enumerationValue') %} +
  • {{enumerationValue.get('value')}}: + {% if enumerationValue.getchildren()|length > 0 %} + {{ designInspector.strip_documentation_tag(enumerationValue.getchildren()[0]) }} + {% else %} + To the developer: complete this documentation entry in your Design. + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + {% endif %} + {% endif %} + + {% if elementObject.get('defaultValue') -%} + Default value: {{elementObject.get('defaultValue')|trim}}
+ {% endif %} + {{- writeRestrictions(containingClassName, objectName, what) }} +{% endmacro %} + +#Configuration documentation for {{designInspector.getProjectName()}} + +{# + Jump to: + {% for className in designInspector.get_names_of_all_classes()|sort() %} + {% set parentObjects = designInspector.objectifyAllParents(className, restrict_to_by_configuration=True) %} + {% if parentObjects|length > 0 %} + {{className}} + {% endif %} + {% endfor %} +#} + +{% for className in designInspector.get_names_of_all_classes()|sort() %} +{% set parentObjects = designInspector.objectifyAllParents(className, restrict_to_by_configuration=True) %} +{% if parentObjects|length > 0 %} +--- +##{{className}} +{% set documentation = designInspector.objectifyDocumentation(className) %} +{% if documentation %} +{% for docElement in documentation %} +DOC:{{ designInspector.strip_documentation_tag(docElement)|trim }}
+{% endfor %} +{% endif %} +{% if designInspector.is_class_always_singleton(className) %} +!!! warning This class is a singleton, do not declare it more than once +{% if designInspector.get_class_default_instance_name(className) != None %} +!!! info The name attribute in this class is defaulted to {{designInspector.get_class_default_instance_name(className)}}, don't waste time specifying it in the config file. +{% endif %} +{% endif %} + +Configuration attributes: +{% set configEntries= designInspector.objectify_config_entries(className) %} +{% if configEntries|length > 0 %} + {% for configEntry in configEntries %} + {{- writeCacheVarOrConfigEntry(className, configEntry, "configentry") }} + {% endfor %} +{% else %} + Class {{className}} has no configuration entries. +{% endif %} + +{% set configuredCacheVars = designInspector.objectify_cache_variables(className, restrict_by="[@initializeWith='configuration']") %} +{% if configuredCacheVars |length > 0 %} + {% for cacheVar in configuredCacheVars %} + {{- writeCacheVarOrConfigEntry(className, cacheVar, "cachevariable") }} + {% endfor %} +{%- else %} +Class {{className}} has no cache variables initialized from the configuration. +{% endif %} + +Possible children: + + - CalculatedVariable + - FreeVariable + {% for hasObject in designInspector.get_class_has_objects(className) -%} + {%- if hasObject.get('instantiateUsing')=='configuration' -%} + - {{hasObject.get('class')|trim}} + {% endif %} + {%- endfor %} + +{% endif %} +{% endfor %} diff --git a/FrameworkInternals/quasarCommands.py b/FrameworkInternals/quasarCommands.py index 4298dfdf..d0fce277 100644 --- a/FrameworkInternals/quasarCommands.py +++ b/FrameworkInternals/quasarCommands.py @@ -60,7 +60,9 @@ [['generate','config_cpp'], lambda context: transformByKey(TransformKeys.CONFIGURATOR, {'context':context}), False], [['generate','config_validator'], lambda context: transformByKey(TransformKeys.CONFIG_VALIDATOR, {'context':context}), False], [['generate','config_doc'], lambda context: transformByKey(TransformKeys.CONFIG_DOCUMENTATION, {'context':context}), True], - [['generate','as_doc'], lambda context: transformByKey(TransformKeys.AS_DOCUMENTATION, {'context':context}), True], + [['generate','as_doc'], lambda context: transformByKey(TransformKeys.AS_DOCUMENTATION, {'context':context}), True], + [['generate','config_doc_md'], lambda context: transformByKey(TransformKeys.CONFIG_DOCUMENTATION_MD, {'context':context}), True], + [['generate','as_doc_md'], lambda context: transformByKey(TransformKeys.AS_DOCUMENTATION_MD, {'context':context}), True], [['generate','diagram'], createDiagram, True], [['check_consistency'], mfCheckConsistency, True], [['setup_svn_ignore'], mfSetupSvnIgnore, True], diff --git a/FrameworkInternals/transformDesign.py b/FrameworkInternals/transformDesign.py index 8a88c678..355c05cc 100644 --- a/FrameworkInternals/transformDesign.py +++ b/FrameworkInternals/transformDesign.py @@ -59,6 +59,8 @@ class TransformKeys(enum.Enum): D_CMAKE = 22 CONFIG_DOCUMENTATION = 23 AS_DOCUMENTATION = 24 + CONFIG_DOCUMENTATION_MD = 25 + AS_DOCUMENTATION_MD = 26 # column IDs @enum.unique @@ -95,7 +97,9 @@ class FieldIds(enum.Enum): [TransformKeys.D_BASE_CMAKE, ['Device','designToGeneratedCmakeDeviceBase.jinja'], True, 'Device/generated/cmake_device_base_header.cmake', 'B', False, False, None], [TransformKeys.D_CMAKE, ['Device','designToGeneratedCmakeDevice.jinja'], False, 'Device/generated/cmake_device_header.cmake', 'B', False, False, None], [TransformKeys.CONFIG_DOCUMENTATION, ['Configuration','designToConfigDocumentationHtml.jinja'], True, 'Documentation/ConfigDocumentation.html', 'S', False, False, None], - [TransformKeys.AS_DOCUMENTATION, ['AddressSpace','designToAddressSpaceDocHtml.jinja'], True, 'Documentation/AddressSpaceDoc.html', 'S', False, False, None] + [TransformKeys.AS_DOCUMENTATION, ['AddressSpace','designToAddressSpaceDocHtml.jinja'], True, 'Documentation/AddressSpaceDoc.html', 'S', False, False, None], + [TransformKeys.CONFIG_DOCUMENTATION_MD, ['Configuration','designToConfigDocumentationMd.jinja'], False, 'Documentation/ConfigDocumentation.md', 'S', False, False, None], + [TransformKeys.AS_DOCUMENTATION_MD, ['AddressSpace','designToAddressSpaceDocMd.jinja'], False, 'Documentation/AddressSpaceDoc.md', 'S', False, False, None] ] def transformDesignVerbose(transformPath, designXmlPath, outputFile, requiresMerge, astyleRun=False, additionalParam=None):