mkdocs-include-markdown-plugin 7.1.4__tar.gz → 7.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/PKG-INFO +1 -1
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/pyproject.toml +1 -1
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/directive.py +91 -31
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/event.py +121 -108
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/process.py +65 -35
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/.gitignore +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/LICENSE +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/README.md +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/__init__.py +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/cache.py +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/config.py +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/files_watcher.py +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/logger.py +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/plugin.py +0 -0
- {mkdocs_include_markdown_plugin-7.1.4 → mkdocs_include_markdown_plugin-7.1.5}/src/mkdocs_include_markdown_plugin/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-include-markdown-plugin
|
|
3
|
-
Version: 7.1.
|
|
3
|
+
Version: 7.1.5
|
|
4
4
|
Summary: Mkdocs Markdown includer plugin.
|
|
5
5
|
Project-URL: Source, https://github.com/mondeja/mkdocs-include-markdown-plugin
|
|
6
6
|
Project-URL: Documentation, https://github.com/mondeja/mkdocs-include-markdown-plugin#documentation
|
|
@@ -20,10 +20,11 @@ from mkdocs_include_markdown_plugin.logger import logger
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class DirectiveBoolArgument: # noqa: D101
|
|
22
22
|
value: bool
|
|
23
|
-
regex: re.Pattern[str]
|
|
23
|
+
regex: Callable[[], re.Pattern[str]]
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING: # pragma: no cover
|
|
27
|
+
from collections.abc import Iterable
|
|
27
28
|
from typing import Callable, Literal, TypedDict
|
|
28
29
|
|
|
29
30
|
DirectiveBoolArgumentsDict = dict[str, DirectiveBoolArgument]
|
|
@@ -50,18 +51,18 @@ RE_ESCAPED_PUNCTUATION = re.escape(string.punctuation)
|
|
|
50
51
|
DOUBLE_QUOTED_STR_RE = r'([^"]|(?<=\\)")+'
|
|
51
52
|
SINGLE_QUOTED_STR_RE = r"([^']|(?<=\\)')+"
|
|
52
53
|
|
|
53
|
-
# In the following regular expression, the substrings "
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
INCLUDE_TAG_RE =
|
|
57
|
-
(?P<_includer_indent>[ \t\w\\.]*?)
|
|
54
|
+
# In the following regular expression, the substrings "\{%", "%\}"
|
|
55
|
+
# will be replaced by custom opening and closing tags in the `on_config`
|
|
56
|
+
# plugin event if required.
|
|
57
|
+
INCLUDE_TAG_RE = r'''
|
|
58
|
+
(?P<_includer_indent>[ \t\w\\.]*?)\{%
|
|
58
59
|
\s*
|
|
59
60
|
include
|
|
60
61
|
\s+
|
|
61
|
-
(?:"(?P<double_quoted_filename>
|
|
62
|
+
(?:"(?P<double_quoted_filename>''' + DOUBLE_QUOTED_STR_RE + r''')")?(?:'(?P<single_quoted_filename>''' + SINGLE_QUOTED_STR_RE + r''')')?
|
|
62
63
|
(?P<arguments>.*?)
|
|
63
64
|
\s*
|
|
64
|
-
|
|
65
|
+
%\}
|
|
65
66
|
''' # noqa: E501
|
|
66
67
|
|
|
67
68
|
TRUE_FALSE_STR_BOOL = {
|
|
@@ -110,6 +111,7 @@ ARGUMENT_REGEXES = {
|
|
|
110
111
|
'heading-offset': functools.partial(arg, 'heading-offset'),
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
INCLUDE_MARKDOWN_DIRECTIVE_ARGS = set(ARGUMENT_REGEXES)
|
|
113
115
|
INCLUDE_DIRECTIVE_ARGS = {
|
|
114
116
|
key for key in ARGUMENT_REGEXES if key not in (
|
|
115
117
|
'rewrite-relative-urls', 'heading-offset', 'comments',
|
|
@@ -121,29 +123,71 @@ WARN_INVALID_DIRECTIVE_ARGS_REGEX = re.compile(
|
|
|
121
123
|
)
|
|
122
124
|
|
|
123
125
|
|
|
126
|
+
def _maybe_arguments_iter(arguments_string: str) -> Iterable[str]:
|
|
127
|
+
"""Iterate over parts of the string that look like arguments."""
|
|
128
|
+
current_string_opening = '' # can be either `'` or `"`
|
|
129
|
+
inside_string = False
|
|
130
|
+
escaping = False
|
|
131
|
+
opening_argument = False # whether we are at the beginning of an argument
|
|
132
|
+
current_value = ''
|
|
133
|
+
|
|
134
|
+
for c in arguments_string:
|
|
135
|
+
if inside_string:
|
|
136
|
+
if c == '\\':
|
|
137
|
+
escaping = not escaping
|
|
138
|
+
continue
|
|
139
|
+
elif c == current_string_opening and not escaping:
|
|
140
|
+
inside_string = False
|
|
141
|
+
current_string_opening = ''
|
|
142
|
+
else:
|
|
143
|
+
escaping = False
|
|
144
|
+
elif c == '=':
|
|
145
|
+
new_current_value = ''
|
|
146
|
+
for ch in reversed(current_value):
|
|
147
|
+
if ch in string.whitespace:
|
|
148
|
+
current_value = new_current_value[::-1]
|
|
149
|
+
break
|
|
150
|
+
new_current_value += ch
|
|
151
|
+
yield current_value
|
|
152
|
+
current_value = ''
|
|
153
|
+
opening_argument = True
|
|
154
|
+
elif opening_argument:
|
|
155
|
+
opening_argument = False
|
|
156
|
+
if c in ('"', "'"):
|
|
157
|
+
current_string_opening = c
|
|
158
|
+
inside_string = True
|
|
159
|
+
current_value += c
|
|
160
|
+
current_value += c
|
|
161
|
+
else:
|
|
162
|
+
current_value += c
|
|
163
|
+
|
|
164
|
+
|
|
124
165
|
def warn_invalid_directive_arguments(
|
|
125
166
|
arguments_string: str,
|
|
126
167
|
directive_lineno: Callable[[], int],
|
|
127
168
|
directive: Literal['include', 'include-markdown'],
|
|
128
169
|
page_src_path: str | None,
|
|
129
170
|
docs_dir: str,
|
|
130
|
-
) ->
|
|
171
|
+
) -> list[str]:
|
|
131
172
|
"""Warns about the invalid arguments passed to a directive."""
|
|
173
|
+
used_arguments = []
|
|
132
174
|
valid_args = (
|
|
133
|
-
INCLUDE_DIRECTIVE_ARGS
|
|
134
|
-
|
|
175
|
+
INCLUDE_DIRECTIVE_ARGS
|
|
176
|
+
if directive == 'include'
|
|
177
|
+
else INCLUDE_MARKDOWN_DIRECTIVE_ARGS
|
|
135
178
|
)
|
|
136
|
-
for
|
|
137
|
-
|
|
138
|
-
):
|
|
139
|
-
if arg_value.split('=', 1)[0] not in valid_args:
|
|
179
|
+
for maybe_arg in _maybe_arguments_iter(arguments_string):
|
|
180
|
+
if maybe_arg not in valid_args:
|
|
140
181
|
location = process.file_lineno_message(
|
|
141
182
|
page_src_path, docs_dir, directive_lineno(),
|
|
142
183
|
)
|
|
143
184
|
logger.warning(
|
|
144
|
-
f"Invalid argument '{
|
|
185
|
+
f"Invalid argument '{maybe_arg}' in"
|
|
145
186
|
f" '{directive}' directive at {location}. Ignoring...",
|
|
146
187
|
)
|
|
188
|
+
else:
|
|
189
|
+
used_arguments.append(maybe_arg)
|
|
190
|
+
return used_arguments
|
|
147
191
|
|
|
148
192
|
|
|
149
193
|
def parse_filename_argument(
|
|
@@ -156,21 +200,23 @@ def parse_filename_argument(
|
|
|
156
200
|
if raw_filename is None:
|
|
157
201
|
filename = None
|
|
158
202
|
else:
|
|
159
|
-
filename = raw_filename.replace("
|
|
203
|
+
filename = raw_filename.replace(r"\'", "'")
|
|
160
204
|
else:
|
|
161
|
-
filename = raw_filename.replace('
|
|
205
|
+
filename = raw_filename.replace(r'\"', '"')
|
|
162
206
|
return filename, raw_filename
|
|
163
207
|
|
|
164
208
|
|
|
165
|
-
def parse_string_argument(match: re.Match[str]) -> str | None:
|
|
209
|
+
def parse_string_argument(match: re.Match[str] | None) -> str | None:
|
|
166
210
|
"""Return the string argument matched by ``match``."""
|
|
211
|
+
if match is None:
|
|
212
|
+
return None
|
|
167
213
|
value = match[1]
|
|
168
214
|
if value is None:
|
|
169
215
|
value = match[3]
|
|
170
216
|
if value is not None:
|
|
171
|
-
value = value.replace("
|
|
217
|
+
value = value.replace(r"\'", "'")
|
|
172
218
|
else:
|
|
173
|
-
value = value.replace('
|
|
219
|
+
value = value.replace(r'\"', '"')
|
|
174
220
|
return value
|
|
175
221
|
|
|
176
222
|
|
|
@@ -182,18 +228,31 @@ def create_include_tag(
|
|
|
182
228
|
Replaces the substrings '$OPENING_TAG' and '$CLOSING_TAG' from
|
|
183
229
|
INCLUDE_TAG_RE by the effective tag.
|
|
184
230
|
"""
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
231
|
+
pattern = INCLUDE_TAG_RE
|
|
232
|
+
if tag != 'include':
|
|
233
|
+
pattern = pattern.replace(
|
|
234
|
+
' include',
|
|
235
|
+
(
|
|
236
|
+
' include-markdown' if tag == 'include-markdown'
|
|
237
|
+
else f' {re.escape(tag)}'
|
|
238
|
+
),
|
|
239
|
+
1,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if opening_tag != '{%':
|
|
243
|
+
pattern = pattern.replace(r'\{%', re.escape(opening_tag), 1)
|
|
244
|
+
|
|
245
|
+
if closing_tag != '%}':
|
|
246
|
+
pattern = pattern.replace(r'%\}', re.escape(closing_tag), 1)
|
|
247
|
+
|
|
248
|
+
return re.compile(pattern, flags=re.VERBOSE | re.DOTALL)
|
|
191
249
|
|
|
192
250
|
|
|
193
251
|
def parse_bool_options(
|
|
194
252
|
option_names: list[str],
|
|
195
253
|
defaults: DefaultValues,
|
|
196
254
|
arguments_string: str,
|
|
255
|
+
used_arguments: list[str],
|
|
197
256
|
) -> tuple[DirectiveBoolArgumentsDict, list[str]]:
|
|
198
257
|
"""Parse boolean options from arguments string."""
|
|
199
258
|
invalid_args: list[str] = []
|
|
@@ -202,16 +261,17 @@ def parse_bool_options(
|
|
|
202
261
|
for option_name in option_names:
|
|
203
262
|
bool_options[option_name] = DirectiveBoolArgument(
|
|
204
263
|
value=defaults[option_name], # type: ignore
|
|
205
|
-
regex=ARGUMENT_REGEXES[option_name]
|
|
264
|
+
regex=ARGUMENT_REGEXES[option_name],
|
|
206
265
|
)
|
|
207
266
|
|
|
208
267
|
for arg_name, arg in bool_options.items():
|
|
209
|
-
|
|
210
|
-
if bool_arg_match is None:
|
|
268
|
+
if arg_name not in used_arguments:
|
|
211
269
|
continue
|
|
270
|
+
bool_arg_match = arg.regex().search(arguments_string)
|
|
212
271
|
try:
|
|
213
272
|
bool_options[arg_name].value = TRUE_FALSE_STR_BOOL[
|
|
214
|
-
bool_arg_match
|
|
273
|
+
(bool_arg_match and bool_arg_match[1])
|
|
274
|
+
or TRUE_FALSE_BOOL_STR[arg.value]
|
|
215
275
|
]
|
|
216
276
|
except KeyError:
|
|
217
277
|
invalid_args.append(arg_name)
|
|
@@ -125,7 +125,7 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
125
125
|
|
|
126
126
|
arguments_string = match['arguments']
|
|
127
127
|
|
|
128
|
-
warn_invalid_directive_arguments(
|
|
128
|
+
used_arguments = warn_invalid_directive_arguments(
|
|
129
129
|
arguments_string,
|
|
130
130
|
directive_lineno,
|
|
131
131
|
'include',
|
|
@@ -133,9 +133,11 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
133
133
|
docs_dir,
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
-
exclude_match = ARGUMENT_REGEXES['exclude']().search(arguments_string)
|
|
137
136
|
ignore_paths = [*settings_ignore_paths]
|
|
138
|
-
if
|
|
137
|
+
if 'exclude' in used_arguments:
|
|
138
|
+
exclude_match = ARGUMENT_REGEXES['exclude']().search(
|
|
139
|
+
arguments_string,
|
|
140
|
+
)
|
|
139
141
|
exclude_string = parse_string_argument(exclude_match)
|
|
140
142
|
if exclude_string is None:
|
|
141
143
|
location = process.file_lineno_message(
|
|
@@ -169,38 +171,22 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
169
171
|
if files_watcher is not None and not is_url:
|
|
170
172
|
files_watcher.included_files.extend(file_paths_to_include)
|
|
171
173
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
defaults,
|
|
176
|
-
arguments_string,
|
|
177
|
-
)
|
|
178
|
-
if invalid_bool_args:
|
|
179
|
-
location = process.file_lineno_message(
|
|
180
|
-
page_src_path, docs_dir, directive_lineno(),
|
|
181
|
-
)
|
|
182
|
-
raise PluginError(
|
|
183
|
-
f"Invalid value for '{invalid_bool_args[0]}' argument of"
|
|
184
|
-
f" 'include' directive at {location}."
|
|
185
|
-
f' Possible values are true or false.',
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
start_match = ARGUMENT_REGEXES['start']().search(arguments_string)
|
|
189
|
-
if start_match:
|
|
174
|
+
start = defaults['start']
|
|
175
|
+
if 'start' in used_arguments:
|
|
176
|
+
start_match = ARGUMENT_REGEXES['start']().search(arguments_string)
|
|
190
177
|
start = parse_string_argument(start_match)
|
|
191
178
|
if start is None:
|
|
192
179
|
location = process.file_lineno_message(
|
|
193
180
|
page_src_path, docs_dir, directive_lineno(),
|
|
194
181
|
)
|
|
195
182
|
raise PluginError(
|
|
196
|
-
"Invalid empty 'start' argument in 'include' directive
|
|
197
|
-
f' {location}',
|
|
183
|
+
"Invalid empty 'start' argument in 'include' directive"
|
|
184
|
+
f' at {location}',
|
|
198
185
|
)
|
|
199
|
-
else:
|
|
200
|
-
start = defaults['start']
|
|
201
186
|
|
|
202
|
-
|
|
203
|
-
if
|
|
187
|
+
end = defaults['end']
|
|
188
|
+
if 'end' in used_arguments:
|
|
189
|
+
end_match = ARGUMENT_REGEXES['end']().search(arguments_string)
|
|
204
190
|
end = parse_string_argument(end_match)
|
|
205
191
|
if end is None:
|
|
206
192
|
location = process.file_lineno_message(
|
|
@@ -210,14 +196,14 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
210
196
|
"Invalid empty 'end' argument in 'include' directive at"
|
|
211
197
|
f' {location}',
|
|
212
198
|
)
|
|
213
|
-
else:
|
|
214
|
-
end = defaults['end']
|
|
215
199
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
200
|
+
encoding = defaults['encoding']
|
|
201
|
+
if 'encoding' in used_arguments:
|
|
202
|
+
encoding_match = ARGUMENT_REGEXES['encoding']().search(
|
|
203
|
+
arguments_string,
|
|
204
|
+
)
|
|
205
|
+
encoding_ = parse_string_argument(encoding_match)
|
|
206
|
+
if encoding_ is None:
|
|
221
207
|
location = process.file_lineno_message(
|
|
222
208
|
page_src_path, docs_dir, directive_lineno(),
|
|
223
209
|
)
|
|
@@ -225,8 +211,28 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
225
211
|
"Invalid empty 'encoding' argument in 'include'"
|
|
226
212
|
f' directive at {location}',
|
|
227
213
|
)
|
|
228
|
-
|
|
229
|
-
|
|
214
|
+
encoding = encoding_
|
|
215
|
+
|
|
216
|
+
bool_options, invalid_bool_args = parse_bool_options(
|
|
217
|
+
[
|
|
218
|
+
'preserve-includer-indent',
|
|
219
|
+
'dedent',
|
|
220
|
+
'trailing-newlines',
|
|
221
|
+
'recursive',
|
|
222
|
+
],
|
|
223
|
+
defaults,
|
|
224
|
+
arguments_string,
|
|
225
|
+
used_arguments,
|
|
226
|
+
)
|
|
227
|
+
if invalid_bool_args:
|
|
228
|
+
location = process.file_lineno_message(
|
|
229
|
+
page_src_path, docs_dir, directive_lineno(),
|
|
230
|
+
)
|
|
231
|
+
raise PluginError(
|
|
232
|
+
f"Invalid value for '{invalid_bool_args[0]}' argument of"
|
|
233
|
+
f" 'include' directive at {location}."
|
|
234
|
+
f' Possible values are true or false.',
|
|
235
|
+
)
|
|
230
236
|
|
|
231
237
|
text_to_include = ''
|
|
232
238
|
expected_but_any_found = [start is not None, end is not None]
|
|
@@ -322,7 +328,7 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
322
328
|
)
|
|
323
329
|
|
|
324
330
|
includer_indent = match['_includer_indent']
|
|
325
|
-
|
|
331
|
+
filled_includer_indent = ' ' * len(includer_indent)
|
|
326
332
|
|
|
327
333
|
filename, raw_filename = parse_filename_argument(match)
|
|
328
334
|
if filename is None:
|
|
@@ -336,7 +342,7 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
336
342
|
|
|
337
343
|
arguments_string = match['arguments']
|
|
338
344
|
|
|
339
|
-
warn_invalid_directive_arguments(
|
|
345
|
+
used_arguments = warn_invalid_directive_arguments(
|
|
340
346
|
arguments_string,
|
|
341
347
|
directive_lineno,
|
|
342
348
|
'include-markdown',
|
|
@@ -344,9 +350,11 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
344
350
|
docs_dir,
|
|
345
351
|
)
|
|
346
352
|
|
|
347
|
-
exclude_match = ARGUMENT_REGEXES['exclude']().search(arguments_string)
|
|
348
353
|
ignore_paths = [*settings_ignore_paths]
|
|
349
|
-
if
|
|
354
|
+
if 'exclude' in used_arguments:
|
|
355
|
+
exclude_match = ARGUMENT_REGEXES['exclude']().search(
|
|
356
|
+
arguments_string,
|
|
357
|
+
)
|
|
350
358
|
exclude_string = parse_string_argument(exclude_match)
|
|
351
359
|
if exclude_string is None:
|
|
352
360
|
location = process.file_lineno_message(
|
|
@@ -379,42 +387,23 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
379
387
|
if files_watcher is not None and not is_url:
|
|
380
388
|
files_watcher.included_files.extend(file_paths_to_include)
|
|
381
389
|
|
|
382
|
-
bool_options, invalid_bool_args = parse_bool_options(
|
|
383
|
-
[
|
|
384
|
-
'rewrite-relative-urls', 'comments',
|
|
385
|
-
'preserve-includer-indent', 'dedent',
|
|
386
|
-
'trailing-newlines', 'recursive',
|
|
387
|
-
],
|
|
388
|
-
defaults,
|
|
389
|
-
arguments_string,
|
|
390
|
-
)
|
|
391
|
-
if invalid_bool_args:
|
|
392
|
-
location = process.file_lineno_message(
|
|
393
|
-
page_src_path, docs_dir, directive_lineno(),
|
|
394
|
-
)
|
|
395
|
-
raise PluginError(
|
|
396
|
-
f"Invalid value for '{invalid_bool_args[0]}' argument of"
|
|
397
|
-
" 'include-markdown' directive at"
|
|
398
|
-
f' {location}. Possible values are true or false.',
|
|
399
|
-
)
|
|
400
|
-
|
|
401
390
|
# start and end arguments
|
|
402
|
-
|
|
403
|
-
if
|
|
391
|
+
start = defaults['start']
|
|
392
|
+
if 'start' in used_arguments:
|
|
393
|
+
start_match = ARGUMENT_REGEXES['start']().search(arguments_string)
|
|
404
394
|
start = parse_string_argument(start_match)
|
|
405
395
|
if start is None:
|
|
406
396
|
location = process.file_lineno_message(
|
|
407
397
|
page_src_path, docs_dir, directive_lineno(),
|
|
408
398
|
)
|
|
409
399
|
raise PluginError(
|
|
410
|
-
"Invalid empty 'start' argument in
|
|
411
|
-
f' directive at {location}
|
|
400
|
+
"Invalid empty 'start' argument in"
|
|
401
|
+
f" 'include-markdown' directive at {location}",
|
|
412
402
|
)
|
|
413
|
-
else:
|
|
414
|
-
start = defaults['start']
|
|
415
403
|
|
|
416
|
-
|
|
417
|
-
if
|
|
404
|
+
end = defaults['end']
|
|
405
|
+
if 'end' in used_arguments:
|
|
406
|
+
end_match = ARGUMENT_REGEXES['end']().search(arguments_string)
|
|
418
407
|
end = parse_string_argument(end_match)
|
|
419
408
|
if end is None:
|
|
420
409
|
location = process.file_lineno_message(
|
|
@@ -424,50 +413,73 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
424
413
|
"Invalid empty 'end' argument in 'include-markdown'"
|
|
425
414
|
f' directive at {location}',
|
|
426
415
|
)
|
|
427
|
-
else:
|
|
428
|
-
end = defaults['end']
|
|
429
416
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
417
|
+
encoding = defaults['encoding']
|
|
418
|
+
if 'encoding' in used_arguments:
|
|
419
|
+
encoding_match = ARGUMENT_REGEXES['encoding']().search(
|
|
420
|
+
arguments_string,
|
|
421
|
+
)
|
|
422
|
+
encoding_ = parse_string_argument(encoding_match)
|
|
423
|
+
if encoding_ is None:
|
|
435
424
|
location = process.file_lineno_message(
|
|
436
425
|
page_src_path, docs_dir, directive_lineno(),
|
|
437
426
|
)
|
|
438
427
|
raise PluginError(
|
|
439
|
-
"Invalid empty 'encoding' argument in
|
|
440
|
-
f' directive at {location}
|
|
428
|
+
"Invalid empty 'encoding' argument in"
|
|
429
|
+
f" 'include-markdown' directive at {location}",
|
|
441
430
|
)
|
|
442
|
-
|
|
443
|
-
encoding = defaults['encoding']
|
|
431
|
+
encoding = encoding_
|
|
444
432
|
|
|
445
433
|
# heading offset
|
|
446
|
-
|
|
434
|
+
offset = defaults['heading-offset']
|
|
435
|
+
if 'heading-offset' in used_arguments:
|
|
436
|
+
offset_match = ARGUMENT_REGEXES['heading-offset']().search(
|
|
437
|
+
arguments_string,
|
|
438
|
+
)
|
|
439
|
+
if offset_match:
|
|
440
|
+
offset_raw_value = offset_match[1]
|
|
441
|
+
if offset_raw_value == '':
|
|
442
|
+
location = process.file_lineno_message(
|
|
443
|
+
page_src_path, docs_dir, directive_lineno(),
|
|
444
|
+
)
|
|
445
|
+
raise PluginError(
|
|
446
|
+
"Invalid empty 'heading-offset' argument in"
|
|
447
|
+
f" 'include-markdown' directive at {location}",
|
|
448
|
+
)
|
|
449
|
+
try:
|
|
450
|
+
offset = int(offset_raw_value)
|
|
451
|
+
except ValueError:
|
|
452
|
+
location = process.file_lineno_message(
|
|
453
|
+
page_src_path, docs_dir, directive_lineno(),
|
|
454
|
+
)
|
|
455
|
+
raise PluginError(
|
|
456
|
+
f"Invalid 'heading-offset' argument"
|
|
457
|
+
f" '{offset_raw_value}' in 'include-markdown'"
|
|
458
|
+
f" directive at {location}",
|
|
459
|
+
) from None
|
|
460
|
+
|
|
461
|
+
bool_options, invalid_bool_args = parse_bool_options(
|
|
462
|
+
[
|
|
463
|
+
'rewrite-relative-urls',
|
|
464
|
+
'comments',
|
|
465
|
+
'preserve-includer-indent',
|
|
466
|
+
'dedent',
|
|
467
|
+
'trailing-newlines',
|
|
468
|
+
'recursive',
|
|
469
|
+
],
|
|
470
|
+
defaults,
|
|
447
471
|
arguments_string,
|
|
472
|
+
used_arguments,
|
|
448
473
|
)
|
|
449
|
-
if
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
)
|
|
459
|
-
try:
|
|
460
|
-
offset = int(offset_raw_value)
|
|
461
|
-
except ValueError:
|
|
462
|
-
location = process.file_lineno_message(
|
|
463
|
-
page_src_path, docs_dir, directive_lineno(),
|
|
464
|
-
)
|
|
465
|
-
raise PluginError(
|
|
466
|
-
f"Invalid 'heading-offset' argument \"{offset_raw_value}\""
|
|
467
|
-
f" in 'include-markdown' directive at {location}",
|
|
468
|
-
) from None
|
|
469
|
-
else:
|
|
470
|
-
offset = defaults['heading-offset']
|
|
474
|
+
if invalid_bool_args:
|
|
475
|
+
location = process.file_lineno_message(
|
|
476
|
+
page_src_path, docs_dir, directive_lineno(),
|
|
477
|
+
)
|
|
478
|
+
raise PluginError(
|
|
479
|
+
f"Invalid value for '{invalid_bool_args[0]}' argument of"
|
|
480
|
+
" 'include-markdown' directive at"
|
|
481
|
+
f' {location}. Possible values are true or false.',
|
|
482
|
+
)
|
|
471
483
|
|
|
472
484
|
separator = '\n' if bool_options['trailing-newlines'].value else ''
|
|
473
485
|
if not start and not end:
|
|
@@ -555,14 +567,15 @@ def get_file_content( # noqa: PLR0913, PLR0915
|
|
|
555
567
|
new_text_to_include = textwrap.dedent(new_text_to_include)
|
|
556
568
|
|
|
557
569
|
# includer indentation preservation
|
|
558
|
-
if bool_options['preserve-includer-indent'].value
|
|
559
|
-
new_text_to_include
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
570
|
+
if bool_options['preserve-includer-indent'].value and (
|
|
571
|
+
new_text_to_include
|
|
572
|
+
):
|
|
573
|
+
lines = new_text_to_include.splitlines(keepends=True)
|
|
574
|
+
new_text_to_include = lines[0]
|
|
575
|
+
for i in range(1, len(lines)):
|
|
576
|
+
new_text_to_include += (
|
|
577
|
+
filled_includer_indent + lines[i]
|
|
564
578
|
)
|
|
565
|
-
)
|
|
566
579
|
|
|
567
580
|
if offset:
|
|
568
581
|
new_text_to_include = process.increase_headings_offset(
|
|
@@ -249,9 +249,10 @@ def transform_line_by_line_skipping_codeblocks(
|
|
|
249
249
|
markdown: str,
|
|
250
250
|
func: Callable[[str], str],
|
|
251
251
|
) -> str:
|
|
252
|
-
"""Apply a transformation line by line in a Markdown text using a function
|
|
252
|
+
"""Apply a transformation line by line in a Markdown text using a function,.
|
|
253
253
|
|
|
254
|
-
Skip fenced codeblock lines, where the transformation
|
|
254
|
+
Skip fenced codeblock lines and empty lines, where the transformation
|
|
255
|
+
is never applied.
|
|
255
256
|
|
|
256
257
|
Indented codeblocks are not taken into account because in the practice
|
|
257
258
|
this function is only used for transformations of heading prefixes. See
|
|
@@ -263,13 +264,15 @@ def transform_line_by_line_skipping_codeblocks(
|
|
|
263
264
|
|
|
264
265
|
lines = []
|
|
265
266
|
for line in io.StringIO(markdown):
|
|
267
|
+
lstripped_line = line.lstrip()
|
|
266
268
|
if not _current_fcodeblock_delimiter:
|
|
267
|
-
lstripped_line
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
if lstripped_line.startswith('```'):
|
|
270
|
+
_current_fcodeblock_delimiter = '```'
|
|
271
|
+
elif lstripped_line.startswith('~~~'):
|
|
272
|
+
_current_fcodeblock_delimiter = '~~~'
|
|
270
273
|
else:
|
|
271
274
|
line = func(line) # noqa: PLW2901
|
|
272
|
-
elif
|
|
275
|
+
elif lstripped_line.startswith(_current_fcodeblock_delimiter):
|
|
273
276
|
_current_fcodeblock_delimiter = ''
|
|
274
277
|
lines.append(line)
|
|
275
278
|
|
|
@@ -287,20 +290,12 @@ def rewrite_relative_urls(
|
|
|
287
290
|
``source_path`` will still work when inserted into a file at
|
|
288
291
|
``destination_path``.
|
|
289
292
|
"""
|
|
290
|
-
from urllib.parse import urlparse, urlunparse
|
|
291
|
-
|
|
292
293
|
def rewrite_url(url: str) -> str:
|
|
293
|
-
if is_url(url):
|
|
294
|
-
return url
|
|
295
|
-
|
|
296
|
-
scheme, netloc, path, params, query, fragment = urlparse(url)
|
|
297
|
-
|
|
298
|
-
# absolute or mail
|
|
299
|
-
if path.startswith('/') or scheme == 'mailto':
|
|
294
|
+
if is_url(url) or is_absolute_path(url):
|
|
300
295
|
return url
|
|
301
296
|
|
|
302
297
|
new_path = os.path.relpath(
|
|
303
|
-
os.path.join(os.path.dirname(source_path),
|
|
298
|
+
os.path.join(os.path.dirname(source_path), url),
|
|
304
299
|
os.path.dirname(destination_path),
|
|
305
300
|
)
|
|
306
301
|
|
|
@@ -308,18 +303,14 @@ def rewrite_relative_urls(
|
|
|
308
303
|
new_path = new_path.replace('\\', '/').replace('//', '/')
|
|
309
304
|
|
|
310
305
|
try:
|
|
311
|
-
if
|
|
306
|
+
if url[-1] == '/':
|
|
312
307
|
# the above operation removes a trailing slash,
|
|
313
308
|
# so add it back if it was present in the input
|
|
314
309
|
new_path += '/'
|
|
315
310
|
except IndexError: # pragma: no cover
|
|
316
311
|
pass
|
|
317
312
|
|
|
318
|
-
|
|
319
|
-
if new_path == '.':
|
|
320
|
-
new_path = ''
|
|
321
|
-
|
|
322
|
-
return urlunparse((scheme, netloc, new_path, params, query, fragment))
|
|
313
|
+
return new_path
|
|
323
314
|
|
|
324
315
|
def found_href(m: re.Match[str], url_group_index: int = -1) -> str:
|
|
325
316
|
match_start, match_end = m.span(0)
|
|
@@ -528,27 +519,68 @@ def filter_paths(
|
|
|
528
519
|
return response
|
|
529
520
|
|
|
530
521
|
|
|
522
|
+
def _is_valid_url_scheme_char(c: str) -> bool:
|
|
523
|
+
"""Determine is a character is a valid URL scheme character.
|
|
524
|
+
|
|
525
|
+
Valid characters are:
|
|
526
|
+
|
|
527
|
+
```
|
|
528
|
+
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.
|
|
529
|
+
```
|
|
530
|
+
"""
|
|
531
|
+
codepoint = ord(c)
|
|
532
|
+
A = 65
|
|
533
|
+
Z = 90
|
|
534
|
+
a = 97
|
|
535
|
+
z = 122
|
|
536
|
+
zero = 48
|
|
537
|
+
nine = 57
|
|
538
|
+
dot = 46
|
|
539
|
+
plus = 43
|
|
540
|
+
minus = 45
|
|
541
|
+
return (
|
|
542
|
+
A <= codepoint <= Z
|
|
543
|
+
or a <= codepoint <= z
|
|
544
|
+
or zero <= codepoint <= nine
|
|
545
|
+
or codepoint in (plus, minus, dot)
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
|
|
531
549
|
def is_url(string: str) -> bool:
|
|
532
|
-
"""Determine if a string is an URL.
|
|
533
|
-
|
|
550
|
+
"""Determine if a string is an URL.
|
|
551
|
+
|
|
552
|
+
The implementation has been adapted from `urllib.urlparse`.
|
|
553
|
+
"""
|
|
554
|
+
i = string.find(':')
|
|
555
|
+
if i <= 1: # noqa: PLR2004 -> exclude C: or D: on Windows
|
|
534
556
|
return False
|
|
535
|
-
from urllib.parse import urlparse
|
|
536
557
|
|
|
537
558
|
try:
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
except ValueError: # pragma: no cover
|
|
559
|
+
return all(_is_valid_url_scheme_char(string[j]) for j in range(i))
|
|
560
|
+
except (IndexError, ValueError): # pragma: no cover
|
|
541
561
|
return False
|
|
542
562
|
|
|
543
563
|
|
|
544
564
|
def is_relative_path(string: str) -> bool:
|
|
545
565
|
"""Check if a string looks like a relative path."""
|
|
546
|
-
|
|
566
|
+
try:
|
|
567
|
+
return (
|
|
568
|
+
string[0] == '.'
|
|
569
|
+
and (
|
|
570
|
+
string[1] == '/'
|
|
571
|
+
or (string[1] == '.' and string[2] == '/')
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
except IndexError: # pragma: no cover
|
|
575
|
+
return False
|
|
547
576
|
|
|
548
577
|
|
|
549
578
|
def is_absolute_path(string: str) -> bool:
|
|
550
579
|
"""Check if a string looks like an absolute path."""
|
|
551
|
-
|
|
580
|
+
try:
|
|
581
|
+
return string[0] == '/' or string[0] == os.sep
|
|
582
|
+
except IndexError: # pragma: no cover
|
|
583
|
+
return False
|
|
552
584
|
|
|
553
585
|
|
|
554
586
|
def read_file(file_path: str, encoding: str) -> str:
|
|
@@ -581,14 +613,12 @@ def read_url(
|
|
|
581
613
|
def safe_os_path_relpath(path: str, start: str) -> str:
|
|
582
614
|
"""Return the relative path of a file from a start directory.
|
|
583
615
|
|
|
584
|
-
Safe version of `os.path.relpath` that catches `ValueError`
|
|
585
|
-
|
|
616
|
+
Safe version of `os.path.relpath` that catches possible `ValueError`
|
|
617
|
+
exceptions and returns the original path in case of error.
|
|
586
618
|
On Windows, `ValueError` is raised when `path` and `start` are on
|
|
587
619
|
different drives.
|
|
588
620
|
"""
|
|
589
|
-
|
|
590
|
-
return os.path.relpath(path, start)
|
|
591
|
-
try: # pragma: nt cover
|
|
621
|
+
try:
|
|
592
622
|
return os.path.relpath(path, start)
|
|
593
623
|
except ValueError: # pragma: no cover
|
|
594
624
|
return path
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|