Skip to content

Commit

Permalink
Merge branch 'feature/custom_defines' into 'master'
Browse files Browse the repository at this point in the history
format_esp_target: add event for adding custom subs

See merge request espressif/esp-docs!50
  • Loading branch information
ESP-Marius committed Mar 14, 2023
2 parents 98ccff8 + 501f30b commit 67d9629
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 13 deletions.
11 changes: 11 additions & 0 deletions docs/en/writing-documentation/writing-for-multiple-targets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ The above line will define a substitution for the tag ``IDF_TARGET_TX_PIN``, whi
* These single-file definitions can be placed anywhere in the reStructuredText file on their own line, but the name of the directive must start with ``IDF_TARGET_``.
* Also note that these replacements cannot be used inside markup that rely on alignment of characters, e.g., tables.

ESP-Docs also allows other extensions to add additional substitutions through Sphinx events. For example, in ESP-IDF it is possible to use defines from ``soc_caps.h``::

The target has {IDF_TARGET_SOC_SPI_PERIPH_NUM} SPI peripherals.

The text will be rendered for ESP32-S2 as the following::

The target has 3 SPI peripherals.

For a full overview of available substitutions in your project, you can take a look at ``IDF_TARGET-substitutions.txt``, which is generated in the build folder when a project is built.



Target-Specific Paragraph
--------------------------
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = esp-docs
version = 1.3.0
version = 1.4.0
author = Espressif
author_email = [email protected]
description = Documentation building package used at Espressif
Expand Down
62 changes: 50 additions & 12 deletions src/esp_docs/esp_extensions/format_esp_target.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import os.path
import pprint
import re

from docutils import io, nodes, statemachine, utils
Expand All @@ -10,12 +11,15 @@


def setup(app):
sub = StringSubstituter()

# 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)

# Signal to inject any additional substitution
app.add_event('format-esp-target-add-sub')
app.connect('format-esp-target-add-sub', sub.add_sub)

# 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)
Expand All @@ -27,7 +31,7 @@ def check_content(content, docname):
# Log warnings for any {IDF_TARGET} expressions that haven't been replaced
logger = logging.getLogger(__name__)

errors = re.findall(r'{IDF_TARGET.*?}', content)
errors = re.findall(r'{IDF_TARGET_.*?}', content)

for err in errors:
logger.warning('Badly formated string substitution: {}'.format(err), location=docname)
Expand All @@ -52,6 +56,8 @@ 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
Provides, and listens to the `format-esp-target-add-sub`-event, which can be used to add custom substitutions
"""
TARGET_NAMES = {'esp8266': 'ESP8266', 'esp32': 'ESP32', 'esp32s2': 'ESP32-S2',
'esp32s3': 'ESP32-S3', 'esp32c3': 'ESP32-C3', 'esp32c2': 'ESP32-C2',
Expand Down Expand Up @@ -109,13 +115,19 @@ class StringSubstituter:

RE_PATTERN = re.compile(r'^\s*{IDF_TARGET_(\w+?):(.+?)}', re.MULTILINE)

SUB_LOG_FILE = "IDF_TARGET-substitutions.txt"

def __init__(self):
self.substitute_strings = {}
self.local_sub_strings = {}

def add_pair(self, tag, replace_value):
self.substitute_strings[tag] = replace_value

def log_subs_to_file(self, config):
with open(os.path.join(config.build_dir, self.SUB_LOG_FILE), 'w') as f:
pprint.pprint(self.substitute_strings, f)
print('Saved substitution list to %s' % f.name)

def init_sub_strings(self, config):

if not config.idf_target:
Expand All @@ -132,7 +144,10 @@ def init_sub_strings(self, config):
self.add_pair('{IDF_TARGET_DATASHEET_EN_URL}', self.DATASHEET_EN_URL[config.idf_target])
self.add_pair('{IDF_TARGET_DATASHEET_CN_URL}', self.DATASHEET_CN_URL[config.idf_target])

self.log_subs_to_file(config)

def add_local_subs(self, matches):
local_sub_strings = {}

for sub_def in matches:
if len(sub_def) != 2:
Expand All @@ -153,22 +168,24 @@ def add_local_subs(self, matches):
else:
sub_value = match_target.groups()[2]

self.local_sub_strings[tag] = sub_value
local_sub_strings[tag] = sub_value

return local_sub_strings

def substitute(self, content):
# Add any new local tags that matches the reg.ex.
sub_defs = re.findall(self.RE_PATTERN, content)

local_sub_strings = {}

if len(sub_defs) != 0:
self.add_local_subs(sub_defs)
local_sub_strings.update(self.add_local_subs(sub_defs))

# Remove the tag defines
content = re.sub(self.RE_PATTERN, '', content)

for key in self.local_sub_strings:
content = content.replace(key, self.local_sub_strings[key])

self.local_sub_strings = {}
for key in local_sub_strings:
content = content.replace(key, local_sub_strings[key])

for key in self.substitute_strings:
content = content.replace(key, self.substitute_strings[key])
Expand All @@ -180,6 +197,30 @@ def substitute_source_read_cb(self, app, docname, source):

check_content(source[0], docname)

def add_sub(self, app, subs):
for name, value in subs.items():
try:
sub_name = f'{{IDF_TARGET_{name}}}'

# We dont do do any complex interpretation of the value,
# but try to fix the most common formatting "issues"
sub_val = value.strip('()')
if sub_val.endswith('UL'):
sub_val = sub_val[:-2]
if sub_val.endswith('U'):
sub_val = sub_val[:-1]

self.add_pair(sub_name, sub_val)
except Exception:
# Dont fail build just because we got a value we cannot parse, silently drop it instead
continue

self.log_subs_to_file(app.config)


# Shared between read source callbacks and .. include:: directive, but read only
sub = StringSubstituter()


class FormatedInclude(BaseInclude):

Expand Down Expand Up @@ -247,9 +288,6 @@ def run(self):
(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,
Expand Down
1 change: 1 addition & 0 deletions src/esp_docs/idf_extensions/gen_defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def generate_defines(app, project_description):
add_tags(app, defines)

app.emit('defines-generated', defines)
app.emit('format-esp-target-add-sub', defines)


def get_defines(header_path, sdk_config_path, compiler):
Expand Down
2 changes: 2 additions & 0 deletions test/unit_tests/test_esp_extensions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3

import os.path as path
import unittest
from unittest.mock import MagicMock

Expand All @@ -14,6 +15,7 @@ def setUp(self):

config = MagicMock()
config.idf_target = 'esp32'
config.build_dir = path.dirname(path.realpath(__file__))
self.str_sub.init_sub_strings(config)

def test_add_subs(self):
Expand Down

0 comments on commit 67d9629

Please sign in to comment.