importpython-markdown 0.1.0__tar.gz → 0.1.1__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.
- {importpython_markdown-0.1.0/src/importpython_markdown.egg-info → importpython_markdown-0.1.1}/PKG-INFO +6 -4
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/README.md +5 -3
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/pyproject.toml +1 -1
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown/__init__.py +49 -6
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1/src/importpython_markdown.egg-info}/PKG-INFO +6 -4
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/tests/test_extension.py +73 -2
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/LICENSE +0 -0
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/setup.cfg +0 -0
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown.egg-info/SOURCES.txt +0 -0
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown.egg-info/dependency_links.txt +0 -0
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown.egg-info/entry_points.txt +0 -0
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown.egg-info/requires.txt +0 -0
- {importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: importpython-markdown
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Runnable Python examples in Markdown, via importpython.org.
|
|
5
5
|
Author-email: "Nicholas H.Tollervey" <ntoll@ntoll.org>
|
|
6
6
|
License: MIT
|
|
@@ -43,9 +43,11 @@ reader presses Run. Plain fences are the program; a fence with
|
|
|
43
43
|
`title="name.py"` is a file delivered beside it; `config` and
|
|
44
44
|
`body` fences carry BFP settings and page markup (escaped for
|
|
45
45
|
you). The `py` and `mpy` types choose Pyodide or MicroPython,
|
|
46
|
-
and `terminal`, `debug`, `
|
|
47
|
-
`stop-label="..."`, `theme="..."`,
|
|
48
|
-
|
|
46
|
+
and `terminal`, `debug`, `send-embedder-url`, `title="..."`,
|
|
47
|
+
`run-label="..."`, `stop-label="..."`, `theme="..."`,
|
|
48
|
+
`terminal-rows="..."`, `terminal-cols="..."`, `class` and `id`
|
|
49
|
+
pass through to the element - each documented at
|
|
50
|
+
[importpython.org](https://importpython.org/docs/writing-markdown/).
|
|
49
51
|
|
|
50
52
|
Install and enable:
|
|
51
53
|
|
|
@@ -22,9 +22,11 @@ reader presses Run. Plain fences are the program; a fence with
|
|
|
22
22
|
`title="name.py"` is a file delivered beside it; `config` and
|
|
23
23
|
`body` fences carry BFP settings and page markup (escaped for
|
|
24
24
|
you). The `py` and `mpy` types choose Pyodide or MicroPython,
|
|
25
|
-
and `terminal`, `debug`, `
|
|
26
|
-
`stop-label="..."`, `theme="..."`,
|
|
27
|
-
|
|
25
|
+
and `terminal`, `debug`, `send-embedder-url`, `title="..."`,
|
|
26
|
+
`run-label="..."`, `stop-label="..."`, `theme="..."`,
|
|
27
|
+
`terminal-rows="..."`, `terminal-cols="..."`, `class` and `id`
|
|
28
|
+
pass through to the element - each documented at
|
|
29
|
+
[importpython.org](https://importpython.org/docs/writing-markdown/).
|
|
28
30
|
|
|
29
31
|
Install and enable:
|
|
30
32
|
|
{importpython_markdown-0.1.0 → importpython_markdown-0.1.1}/src/importpython_markdown/__init__.py
RENAMED
|
@@ -49,8 +49,9 @@ ATTRIBUTE = re.compile(r'([\w-]+)(?:="([^"]*)")?')
|
|
|
49
49
|
# fail the build, not be silently ignored in every reader's
|
|
50
50
|
# browser.
|
|
51
51
|
TYPES = ("py", "mpy")
|
|
52
|
-
FLAGS = ("terminal", "debug")
|
|
53
|
-
VALUED = ("title", "run-label", "stop-label", "theme", "class",
|
|
52
|
+
FLAGS = ("terminal", "debug", "send-embedder-url")
|
|
53
|
+
VALUED = ("title", "run-label", "stop-label", "theme", "class",
|
|
54
|
+
"id", "terminal-rows", "terminal-cols")
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class ExampleError(ValueError):
|
|
@@ -106,6 +107,23 @@ def parse_gate(header, line):
|
|
|
106
107
|
raise ExampleError(
|
|
107
108
|
line, f'cannot parse attributes at "{rest[span:]}"'
|
|
108
109
|
)
|
|
110
|
+
# The terminal's explicit size: each dimension needs the
|
|
111
|
+
# terminal it sizes, and only a positive whole number will do
|
|
112
|
+
# - the same rules the element applies, failing the build
|
|
113
|
+
# here instead of a reader's browser there.
|
|
114
|
+
for name in ("terminal-rows", "terminal-cols"):
|
|
115
|
+
if name in attributes:
|
|
116
|
+
if "terminal" not in attributes:
|
|
117
|
+
raise ExampleError(
|
|
118
|
+
line, f"{name} requires the terminal flag"
|
|
119
|
+
)
|
|
120
|
+
value = attributes[name]
|
|
121
|
+
if not (value.isdigit() and int(value) >= 1):
|
|
122
|
+
raise ExampleError(
|
|
123
|
+
line,
|
|
124
|
+
f'{name} is "{value}"; use a positive whole '
|
|
125
|
+
"number",
|
|
126
|
+
)
|
|
109
127
|
return attributes
|
|
110
128
|
|
|
111
129
|
|
|
@@ -173,7 +191,7 @@ def split_example(lines, first_line):
|
|
|
173
191
|
return pieces
|
|
174
192
|
|
|
175
193
|
|
|
176
|
-
def validate(pieces, line):
|
|
194
|
+
def validate(pieces, attributes, line):
|
|
177
195
|
"""
|
|
178
196
|
The element's own rules, applied at build time: exactly one
|
|
179
197
|
program, no duplicate filenames, at most one config (as valid
|
|
@@ -201,12 +219,37 @@ def validate(pieces, line):
|
|
|
201
219
|
)
|
|
202
220
|
for piece in [p for p in pieces if p["part"] == "config"]:
|
|
203
221
|
try:
|
|
204
|
-
json.loads("\n".join(piece["text"]))
|
|
222
|
+
settings = json.loads("\n".join(piece["text"]))
|
|
205
223
|
except json.JSONDecodeError as error:
|
|
206
224
|
raise ExampleError(
|
|
207
225
|
piece["line"],
|
|
208
226
|
f"the config block is not valid JSON: {error}",
|
|
209
227
|
) from None
|
|
228
|
+
# send-embedder-url writes importpython.py, so while the
|
|
229
|
+
# flag is on the name is reserved - here in the author's
|
|
230
|
+
# own config files too, matching the element's rule.
|
|
231
|
+
if "send-embedder-url" in attributes:
|
|
232
|
+
names = (settings.get("files") or {}).values()
|
|
233
|
+
if any(
|
|
234
|
+
str(name).removeprefix("./") == "importpython.py"
|
|
235
|
+
for name in names
|
|
236
|
+
):
|
|
237
|
+
raise ExampleError(
|
|
238
|
+
piece["line"],
|
|
239
|
+
"the send-embedder-url flag reserves the "
|
|
240
|
+
'file "importpython.py"',
|
|
241
|
+
)
|
|
242
|
+
if "send-embedder-url" in attributes:
|
|
243
|
+
for piece in pieces:
|
|
244
|
+
if (
|
|
245
|
+
piece["part"] == "file"
|
|
246
|
+
and piece["title"] == "importpython.py"
|
|
247
|
+
):
|
|
248
|
+
raise ExampleError(
|
|
249
|
+
piece["line"],
|
|
250
|
+
"the send-embedder-url flag reserves the "
|
|
251
|
+
'file "importpython.py"',
|
|
252
|
+
)
|
|
210
253
|
|
|
211
254
|
|
|
212
255
|
class ImportPythonPreprocessor(Preprocessor):
|
|
@@ -301,7 +344,7 @@ class ImportPythonPreprocessor(Preprocessor):
|
|
|
301
344
|
"""
|
|
302
345
|
attributes = parse_gate(header, first_line)
|
|
303
346
|
pieces = split_example(lines, first_line + 1)
|
|
304
|
-
validate(pieces, first_line)
|
|
347
|
+
validate(pieces, attributes, first_line)
|
|
305
348
|
parts = ["<import-python"]
|
|
306
349
|
for name, value in attributes.items():
|
|
307
350
|
if value is None:
|
|
@@ -385,7 +428,7 @@ class ImportPythonExtension(Extension):
|
|
|
385
428
|
def __init__(self, **kwargs):
|
|
386
429
|
self.config = {
|
|
387
430
|
"script_url": [
|
|
388
|
-
"https://importpython.org/
|
|
431
|
+
"https://importpython.org/v/test/embed.js",
|
|
389
432
|
"Where embed.js is served from; point this at "
|
|
390
433
|
"your own copy to use one.",
|
|
391
434
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: importpython-markdown
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Runnable Python examples in Markdown, via importpython.org.
|
|
5
5
|
Author-email: "Nicholas H.Tollervey" <ntoll@ntoll.org>
|
|
6
6
|
License: MIT
|
|
@@ -43,9 +43,11 @@ reader presses Run. Plain fences are the program; a fence with
|
|
|
43
43
|
`title="name.py"` is a file delivered beside it; `config` and
|
|
44
44
|
`body` fences carry BFP settings and page markup (escaped for
|
|
45
45
|
you). The `py` and `mpy` types choose Pyodide or MicroPython,
|
|
46
|
-
and `terminal`, `debug`, `
|
|
47
|
-
`stop-label="..."`, `theme="..."`,
|
|
48
|
-
|
|
46
|
+
and `terminal`, `debug`, `send-embedder-url`, `title="..."`,
|
|
47
|
+
`run-label="..."`, `stop-label="..."`, `theme="..."`,
|
|
48
|
+
`terminal-rows="..."`, `terminal-cols="..."`, `class` and `id`
|
|
49
|
+
pass through to the element - each documented at
|
|
50
|
+
[importpython.org](https://importpython.org/docs/writing-markdown/).
|
|
49
51
|
|
|
50
52
|
Install and enable:
|
|
51
53
|
|
|
@@ -198,9 +198,9 @@ def test_script_tag_once_for_many_examples():
|
|
|
198
198
|
configured one.
|
|
199
199
|
"""
|
|
200
200
|
two = EXAMPLE + "\n" + EXAMPLE
|
|
201
|
-
out = render(two, script_url="/
|
|
201
|
+
out = render(two, script_url="/v/test/embed.js")
|
|
202
202
|
assert out.count("<import-python") == 2
|
|
203
|
-
assert out.count('src="/
|
|
203
|
+
assert out.count('src="/v/test/embed.js"') == 1
|
|
204
204
|
assert 'type="module"' in out
|
|
205
205
|
|
|
206
206
|
|
|
@@ -272,3 +272,74 @@ def test_the_element_is_never_paragraph_wrapped():
|
|
|
272
272
|
out = render(EXAMPLE)
|
|
273
273
|
assert "<p><import-python" not in out
|
|
274
274
|
assert "<p><script" not in out
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_send_embedder_url_flag_passes_through():
|
|
278
|
+
"""
|
|
279
|
+
Intent: the flag is allow-listed and reaches the element as
|
|
280
|
+
its attribute, so the page's embed.js can act on it.
|
|
281
|
+
"""
|
|
282
|
+
out = render(
|
|
283
|
+
"#!mpy send-embedder-url\n\n```\nimport importpython\n```\n!#\n"
|
|
284
|
+
)
|
|
285
|
+
assert "send-embedder-url" in out.split(">")[0]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_send_embedder_url_reserves_the_filename():
|
|
289
|
+
"""
|
|
290
|
+
Intent: while the flag is on, importpython.py belongs to the
|
|
291
|
+
service - a titled block or a configured files entry by that
|
|
292
|
+
name is a named build failure, never a silent overwrite.
|
|
293
|
+
"""
|
|
294
|
+
with pytest.raises(ExampleError, match="reserves the file"):
|
|
295
|
+
render(
|
|
296
|
+
"#!mpy send-embedder-url\n\n"
|
|
297
|
+
'```title="importpython.py"\nx\n```\n\n'
|
|
298
|
+
"```\nimport importpython\n```\n!#\n"
|
|
299
|
+
)
|
|
300
|
+
with pytest.raises(ExampleError, match="reserves the file"):
|
|
301
|
+
render(
|
|
302
|
+
"#!mpy send-embedder-url\n\n"
|
|
303
|
+
"```config\n"
|
|
304
|
+
'{ "files": { "data:x": "./importpython.py" } }\n'
|
|
305
|
+
"```\n\n```\nimport importpython\n```\n!#\n"
|
|
306
|
+
)
|
|
307
|
+
# Without the flag, the name is the author's to use.
|
|
308
|
+
out = render(
|
|
309
|
+
'#!mpy\n\n```title="importpython.py"\nx = 1\n```\n\n'
|
|
310
|
+
"```\nimport importpython\n```\n!#\n"
|
|
311
|
+
)
|
|
312
|
+
assert 'title="importpython.py"' in out
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_terminal_size_passes_through():
|
|
316
|
+
"""
|
|
317
|
+
Intent: terminal-rows and terminal-cols are allow-listed valued
|
|
318
|
+
attributes that reach the element, sized and named as BFP
|
|
319
|
+
has them.
|
|
320
|
+
"""
|
|
321
|
+
out = render(
|
|
322
|
+
'#!mpy terminal terminal-rows="4" terminal-cols="60"\n\n'
|
|
323
|
+
"```\nprint(1)\n```\n!#\n"
|
|
324
|
+
)
|
|
325
|
+
assert 'terminal-rows="4"' in out
|
|
326
|
+
assert 'terminal-cols="60"' in out
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def test_terminal_size_is_validated_at_build_time():
|
|
330
|
+
"""
|
|
331
|
+
Intent: a dimension without the terminal flag, or a value
|
|
332
|
+
that is not a positive whole number, fails the build with
|
|
333
|
+
the element's own words - never a runtime error in a
|
|
334
|
+
reader's browser.
|
|
335
|
+
"""
|
|
336
|
+
with pytest.raises(ExampleError, match="requires the terminal"):
|
|
337
|
+
render('#!mpy terminal-rows="4"\n\n```\nx\n```\n!#\n')
|
|
338
|
+
with pytest.raises(ExampleError, match="positive whole"):
|
|
339
|
+
render(
|
|
340
|
+
'#!mpy terminal terminal-rows="0"\n\n```\nx\n```\n!#\n'
|
|
341
|
+
)
|
|
342
|
+
with pytest.raises(ExampleError, match="positive whole"):
|
|
343
|
+
render(
|
|
344
|
+
'#!mpy terminal terminal-cols="wide"\n\n```\nx\n```\n!#\n'
|
|
345
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|