diff --git a/docs/en/contribute/add-ons-reference.rst b/docs/en/contribute/add-ons-reference.rst index abbd7b0ff..3d93adcae 100644 --- a/docs/en/contribute/add-ons-reference.rst +++ b/docs/en/contribute/add-ons-reference.rst @@ -157,9 +157,10 @@ Other Extensions This will define a replacement of the tag {\IDF_TARGET_TX_PIN} in the current rst-file. + The extension also overrides the default ``.. include::`` directive in order to format any included content using the same rules. + These replacements cannot be used inside markup that rely on alignment of characters, e.g. tables. - These replacement can't be used in a file which is `::include`-ed from another file. *This includes any English document where the ``zh_CN`` translation includes then ``en`` translation*. Related Documents ----------------- diff --git a/docs/en/contribute/documenting-code.rst b/docs/en/contribute/documenting-code.rst index b3892222b..13b5c1974 100644 --- a/docs/en/contribute/documenting-code.rst +++ b/docs/en/contribute/documenting-code.rst @@ -303,8 +303,6 @@ This extension also supports markup for defining a local (for a single .rst-file {\IDF_TARGET_TX_PIN:default="IO3",esp32="IO4",esp32s2beta="IO5"} will define a substitution for the tag {\IDF_TARGET_TX_PIN}, which would be replaced by the text IO5 if sphinx was called with the tag esp32s2beta. -.. note:: Due to limitations in Sphinx processing, these substitutions are not applied to any document that is included via the ``.. include::` directive. In these cases it's necessary to use the ``only`` blocks and write per-target sections instead. Unfortunately this includes any document which is not yet translated, as the ``zh_CN`` version will include the ``en`` version. - Put it all together ------------------- diff --git a/docs/idf_extensions/format_idf_target.py b/docs/idf_extensions/format_idf_target.py index 15c0db68f..249c36bbd 100644 --- a/docs/idf_extensions/format_idf_target.py +++ b/docs/idf_extensions/format_idf_target.py @@ -1,14 +1,24 @@ import re +import os +import os.path +from docutils import io, nodes, statemachine, utils +from docutils.utils.error_reporting import SafeString, ErrorString +from docutils.parsers.rst import directives +from sphinx.directives.other import Include as BaseInclude -TARGET_NAMES = {'esp32': 'ESP32', 'esp32s2': 'ESP32-S2'} -TOOLCHAIN_NAMES = {'esp32': 'esp', 'esp32s2': 'esp32s2'} -CONFIG_PREFIX = {'esp32': 'ESP32', 'esp32s2': 'ESP32S2'} -TRM_EN_URL = {'esp32': 'https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf', - 'esp32s2': 'https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf'} +def setup(app): + sub = StringSubstituter() -TRM_CN_URL = {'esp32': 'https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_cn.pdf', - 'esp32s2': 'https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_cn.pdf'} + # Config values not available when setup is called + app.connect('config-inited', lambda _, config: sub.init_sub_strings(config)) + app.connect('source-read', sub.substitute_source_read_cb) + + # Override the default include directive to include formatting with idf_target + # This is needed since there are no source-read events for includes + app.add_directive('include', FormatedInclude, override=True) + + return {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': '0.2'} class StringSubstituter: @@ -29,6 +39,15 @@ class StringSubstituter: This will define a replacement of the tag {IDF_TARGET_TX_PIN} in the current rst-file, see e.g. uart.rst for example """ + TARGET_NAMES = {'esp32': 'ESP32', 'esp32s2': 'ESP32-S2'} + TOOLCHAIN_NAMES = {'esp32': 'esp', 'esp32s2': 'esp32s2'} + CONFIG_PREFIX = {'esp32': 'ESP32', 'esp32s2': 'ESP32S2'} + + TRM_EN_URL = {'esp32': 'https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf', + 'esp32s2': 'https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_en.pdf'} + + TRM_CN_URL = {'esp32': 'https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_cn.pdf', + 'esp32s2': 'https://www.espressif.com/sites/default/files/documentation/esp32-s2_technical_reference_manual_cn.pdf'} RE_PATTERN = re.compile(r'^\s*{IDF_TARGET_(\w+?):(.+?)}', re.MULTILINE) def __init__(self): @@ -38,15 +57,15 @@ class StringSubstituter: def add_pair(self, tag, replace_value): self.substitute_strings[tag] = replace_value - def init_sub_strings(self, app, config): + def init_sub_strings(self, config): self.target_name = config.idf_target - self.add_pair("{IDF_TARGET_NAME}", TARGET_NAMES[config.idf_target]) + self.add_pair("{IDF_TARGET_NAME}", self.TARGET_NAMES[config.idf_target]) self.add_pair("{IDF_TARGET_PATH_NAME}", config.idf_target) - self.add_pair("{IDF_TARGET_TOOLCHAIN_NAME}", TOOLCHAIN_NAMES[config.idf_target]) - self.add_pair("{IDF_TARGET_CFG_PREFIX}", CONFIG_PREFIX[config.idf_target]) - self.add_pair("{IDF_TARGET_TRM_EN_URL}", TRM_EN_URL[config.idf_target]) - self.add_pair("{IDF_TARGET_TRM_CN_URL}", TRM_CN_URL[config.idf_target]) + self.add_pair("{IDF_TARGET_TOOLCHAIN_NAME}", self.TOOLCHAIN_NAMES[config.idf_target]) + self.add_pair("{IDF_TARGET_CFG_PREFIX}", self.CONFIG_PREFIX[config.idf_target]) + self.add_pair("{IDF_TARGET_TRM_EN_URL}", self.TRM_EN_URL[config.idf_target]) + self.add_pair("{IDF_TARGET_TRM_CN_URL}", self.TRM_CN_URL[config.idf_target]) def add_local_subs(self, matches): @@ -71,30 +90,122 @@ class StringSubstituter: self.local_sub_strings[tag] = sub_value - def substitute(self, app, docname, source): + def substitute(self, content): # Add any new local tags that matches the reg.ex. - sub_defs = re.findall(self.RE_PATTERN, source[0]) + sub_defs = re.findall(self.RE_PATTERN, content) if len(sub_defs) != 0: self.add_local_subs(sub_defs) # Remove the tag defines - source[0] = re.sub(self.RE_PATTERN,'', source[0]) + content = re.sub(self.RE_PATTERN,'', content) for key in self.local_sub_strings: - source[0] = source[0].replace(key, self.local_sub_strings[key]) + content = content.replace(key, self.local_sub_strings[key]) self.local_sub_strings = {} for key in self.substitute_strings: - source[0] = source[0].replace(key, self.substitute_strings[key]) + content = content.replace(key, self.substitute_strings[key]) + + return content + + def substitute_source_read_cb(self, app, docname, source): + source[0] = self.substitute(source[0]) -def setup(app): - sub = StringSubstituter() +class FormatedInclude(BaseInclude): - # Config values not available when setup is called - app.connect('config-inited', sub.init_sub_strings) - app.connect('source-read', sub.substitute) + """ + Include and format content read from a separate source file. - return {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': '0.1'} + Code is based on the default include directive from docutils + but extended to also format the content according to IDF target. + + """ + def run(self): + + # For code or literal include blocks we run the normal include + if 'literal' in self.options or 'code' in self.options: + return super(FormatedInclude, self).run() + + """Include a file as part of the content of this reST file.""" + if not self.state.document.settings.file_insertion_enabled: + raise self.warning('"%s" directive disabled.' % self.name) + source = self.state_machine.input_lines.source( + self.lineno - self.state_machine.input_offset - 1) + + source_dir = os.path.dirname(os.path.abspath(source)) + + rel_filename, filename = self.env.relfn2path(self.arguments[0]) + self.arguments[0] = filename + self.env.note_included(filename) + path = directives.path(self.arguments[0]) + + if path.startswith('<') and path.endswith('>'): + path = os.path.join(self.standard_include_path, path[1:-1]) + path = os.path.normpath(os.path.join(source_dir, path)) + + path = utils.relative_path(None, path) + path = nodes.reprunicode(path) + + encoding = self.options.get( + 'encoding', self.state.document.settings.input_encoding) + e_handler = self.state.document.settings.input_encoding_error_handler + tab_width = self.options.get( + 'tab-width', self.state.document.settings.tab_width) + try: + self.state.document.settings.record_dependencies.add(path) + include_file = io.FileInput(source_path=path, + encoding=encoding, + error_handler=e_handler) + except UnicodeEncodeError: + raise self.severe(u'Problems with "%s" directive path:\n' + 'Cannot encode input file path "%s" ' + '(wrong locale?).' % + (self.name, SafeString(path))) + except IOError as error: + raise self.severe(u'Problems with "%s" directive path:\n%s.' % + (self.name, ErrorString(error))) + startline = self.options.get('start-line', None) + endline = self.options.get('end-line', None) + try: + if startline or (endline is not None): + lines = include_file.readlines() + rawtext = ''.join(lines[startline:endline]) + else: + rawtext = include_file.read() + except UnicodeError as error: + raise self.severe(u'Problem with "%s" directive:\n%s' % + (self.name, ErrorString(error))) + + # Format input + sub = StringSubstituter() + config = self.state.document.settings.env.config + sub.init_sub_strings(config) + rawtext = sub.substitute(rawtext) + + # start-after/end-before: no restrictions on newlines in match-text, + # and no restrictions on matching inside lines vs. line boundaries + after_text = self.options.get('start-after', None) + if after_text: + # skip content in rawtext before *and incl.* a matching text + after_index = rawtext.find(after_text) + if after_index < 0: + raise self.severe('Problem with "start-after" option of "%s" ' + 'directive:\nText not found.' % self.name) + rawtext = rawtext[after_index + len(after_text):] + before_text = self.options.get('end-before', None) + if before_text: + # skip content in rawtext after *and incl.* a matching text + before_index = rawtext.find(before_text) + if before_index < 0: + raise self.severe('Problem with "end-before" option of "%s" ' + 'directive:\nText not found.' % self.name) + rawtext = rawtext[:before_index] + + include_lines = statemachine.string2lines(rawtext, tab_width, + convert_whitespace=True) + + self.state_machine.insert_input(include_lines, path) + return []