/
format_idf_target.py
211 lines (162 loc) · 9.14 KB
/
format_idf_target.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
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)
# 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:
""" Allows for string substitution of target related strings
before any markup is parsed
Supports the following replacements (examples shown is for target=esp32s2):
{IDF_TARGET_NAME}, replaced with the current target name, e.g. ESP32-S2 Beta
{IDF_TARGET_PATH_NAME}, replaced with the path name, e.g. esp32s2
{IDF_TARGET_TOOLCHAIN_NAME}, replaced with the toolchain name, e.g. esp32s2
{IDF_TARGET_CFG_PREFIX}, replaced with the prefix used for config parameters, e.g. ESP32S2
{IDF_TARGET_TRM_EN_URL}, replaced with the url to the English technical reference manual
{IDF_TARGET_TRM_CH_URL}, replaced with the url to the Chinese technical reference manual
Also supports defines of local (single rst file) with the format:
{IDF_TARGET_TX_PIN:default="IO3",esp32="IO4",esp32s2="IO5"}
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': 'esp32', '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):
self.substitute_strings = {}
self.local_sub_strings = {}
def add_pair(self, tag, replace_value):
self.substitute_strings[tag] = replace_value
def init_sub_strings(self, config):
self.target_name = 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}", 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):
for sub_def in matches:
if len(sub_def) != 2:
raise ValueError("IDF_TARGET_X substitution define invalid, val={}".format(sub_def))
tag = "{" + "IDF_TARGET_{}".format(sub_def[0]) + "}"
match_default = re.match(r'^\s*default(\s*)=(\s*)\"(.*?)\"', sub_def[1])
if match_default is None:
# There should always be a default value
raise ValueError("No default value in IDF_TARGET_X substitution define, val={}".format(sub_def))
match_target = re.match(r'^.*{}(\s*)=(\s*)\"(.*?)\"'.format(self.target_name), sub_def[1])
if match_target is None:
sub_value = match_default.groups()[2]
else:
sub_value = match_target.groups()[2]
self.local_sub_strings[tag] = sub_value
def substitute(self, content):
# Add any new local tags that matches the reg.ex.
sub_defs = re.findall(self.RE_PATTERN, content)
if len(sub_defs) != 0:
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 self.substitute_strings:
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])
class FormatedInclude(BaseInclude):
"""
Include and format content read from a separate source file.
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 []