diff --git a/README.md b/README.md
index 517a320..d1db7df 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ viewmd examples/sample.md --head examples/sample.css --head examples/sample.js
viewmd examples/nbsample.ipynb
```
-`fillmd` instantiates a template from the command line: it executes the template's `{python}` blocks, fills tokens from frontmatter `formdata:` plus an optional YAML values file (scalars stay strings), and writes the filled Markdown to stdout or `--out`. `--lenient` defers unresolved tokens with warnings instead of raising, for staged fills.
+`fillmd` instantiates a template from the command line: it executes the template's `{python}` blocks, fills tokens from frontmatter `formdata:` plus an optional YAML values file (scalars stay strings), and writes the filled Markdown to stdout or `--out`. `--lenient` defers unresolved tokens with warnings instead of raising, for staged fills. Executing code needs `execnb`, which the `fill` extra installs (`pip install 'mdhtml[fill]'`); everything else works without it.
A dialog or notebook `.ipynb` works as the template too. Its code cells are the executable blocks (`eval: false` cells are skipped), each cell's rendered outputs weave in as prose in place of the source, messages participate per aidialog's `export_filter` rule (every exported message when any exist, otherwise every non-pinned one), and a leading frontmatter message is consumed for `formdata:` rather than emitted.
diff --git a/pyproject.toml b/pyproject.toml
index 282c7b0..ffd59b9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,7 +8,7 @@ dynamic = ["version"]
description = "A bounded-time, Pandoc-leaning Markdown parser with GFM, Extra/kramdown, math, fenced divs, and MDHTML output."
license = {text = "Apache-2.0"}
requires-python = ">=3.11"
-dependencies = ["fast5ever>=0.1.1", "fastcore>=2.2.7", "aidialog>=0.0.22", "pyyaml>=6.0.3", "execnb>=0.3.2"]
+dependencies = ["fast5ever>=0.1.1", "fastcore>=2.2.7", "aidialog>=0.0.22", "pyyaml>=6.0.3"]
readme = "README.md"
authors = [{name = "Jeremy Howard", email = "j@fast.ai"}]
classifiers = [
@@ -28,7 +28,8 @@ Repository = "https://github.com/AnswerDotAI/mdhtml"
Issues = "https://github.com/AnswerDotAI/mdhtml/issues"
[project.optional-dependencies]
-dev = ["fastship>=0.0.14", "maturin~=1.0", "pytest", "fastpylight>=0.1.6", "math-core~=0.7.0"]
+fill = ["execnb>=0.3.2"]
+dev = ["fastship>=0.0.14", "maturin~=1.0", "pytest", "execnb>=0.3.2", "fastpylight>=0.1.6", "math-core~=0.7.0"]
[project.entry-points.fastaudit_safe_native]
mdhtml = "mdhtml"
diff --git a/python/mdhtml/fill.py b/python/mdhtml/fill.py
index b7c7f86..9452331 100644
--- a/python/mdhtml/fill.py
+++ b/python/mdhtml/fill.py
@@ -11,7 +11,7 @@
text; `instantiate` adds data gathering (frontmatter `formdata:` via `fastcore.xtras.frontmatter`
with `strvals=True`: structure kept, every scalar a `str` except `true`/`True`/`false`/`False`,
which are `bool`) and the one execution point for
-`{python}` blocks (an `execnb` shell: IPython last-expression semantics, `_repr_markdown_`
+`{python}` blocks (an `execnb` shell, from the `fill` extra: IPython last-expression semantics, `_repr_markdown_`
preferred over `str()`, stdout discarded). Dialog templates (`instantiate_nb`) run code opt-in:
only cells marked `#| eval: true` participate (all but `eval: false` cells when the dialog's own
frontmatter says `eval: true`), and a cell that doesn't participate contributes nothing to the
@@ -29,7 +29,6 @@
from fastcore.script import call_parse
from fastcore.xtras import frontmatter, strloader
from fastcore.nbio import nb_frontmatter, cell_frontmatter
-from execnb.shell import CaptureShell
from aidialog.dialog import dlg2md
from aidialog.ipynb import read_ipynb
from fast5ever import parse_fragment
@@ -271,11 +270,18 @@ def frontmatter_data(src):
+def _capture_shell():
+ "A `CaptureShell`, imported lazily: a bare install carries no execnb or IPython (the `fill` extra provides them)."
+ try: from execnb.shell import CaptureShell
+ except ImportError as e: raise ImportError("executing code needs execnb: pip install 'mdhtml[fill]'") from e
+ return CaptureShell()
+
+
def _weave(norm, data, tmpls):
"Execute `{python}` blocks once each, in document order, in one shared `execnb` shell, and splice each block's rendered output: what a notebook's output area shows (`CaptureShell.run_text`). Blocks may read and mutate `__data__` (the live values dict); rebinding it is ignored."
spans = [b for b in _blocks(norm, templates=tmpls) if b["type"] == "code_block" and b.get("info") == "{python}"]
if not spans: return norm
- shell = CaptureShell()
+ shell = _capture_shell()
shell.user_ns["__data__"] = data
starts = _line_starts(norm)
out, cur = [], 0
@@ -323,7 +329,7 @@ def instantiate_nb(
d = read_ipynb(fname)
fd = nb_frontmatter(d, strvals=True).get("formdata")
merged = {**(fd if isinstance(fd, dict) else {}), **(data or {})}
- shell = CaptureShell()
+ shell = _capture_shell()
shell.user_ns["__data__"] = merged
ran = d.execute(default_eval=False, shell=shell)
if shell.exc:
diff --git a/tests/source/cmark-gfm/spec.txt b/tests/source/cmark-gfm/spec.txt
index 4dda0b7..922010e 100644
--- a/tests/source/cmark-gfm/spec.txt
+++ b/tests/source/cmark-gfm/spec.txt
@@ -482,7 +482,7 @@ bar
Foo
````````````````````````````````
-```````````````````````````````` example
+```````````````````````````````` example disabled
*→*→*→
.
@@ -548,7 +548,7 @@ of three or more matching `-`, `_`, or `*` characters, each followed
optionally by any number of spaces or tabs, forms a
[thematic break](@).
-```````````````````````````````` example
+```````````````````````````````` example disabled
***
---
___
@@ -590,7 +590,7 @@ __
One to three spaces indent are allowed:
-```````````````````````````````` example
+```````````````````````````````` example disabled
***
***
***
@@ -622,7 +622,7 @@ Foo
More than three characters may be used:
-```````````````````````````````` example
+```````````````````````````````` example disabled
_____________________________________
.
@@ -631,21 +631,21 @@ _____________________________________
Spaces are allowed between the characters:
-```````````````````````````````` example
+```````````````````````````````` example disabled
- - -
.
````````````````````````````````
-```````````````````````````````` example
+```````````````````````````````` example disabled
** * ** * ** * **
.
````````````````````````````````
-```````````````````````````````` example
+```````````````````````````````` example disabled
- - - -
.
@@ -654,7 +654,7 @@ Spaces are allowed between the characters:
Spaces are allowed at the end:
-```````````````````````````````` example
+```````````````````````````````` example disabled
- - - -
.
@@ -688,7 +688,7 @@ So, this is not a thematic break:
Thematic breaks do not need blank lines before or after:
-```````````````````````````````` example
+```````````````````````````````` example disabled
- foo
***
- bar
@@ -705,7 +705,7 @@ Thematic breaks do not need blank lines before or after:
Thematic breaks can interrupt a paragraph:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
***
bar
@@ -738,7 +738,7 @@ bar
When both a thematic break and a list item are possible
interpretations of a line, the thematic break takes precedence:
-```````````````````````````````` example
+```````````````````````````````` example disabled
* Foo
* * *
* Bar
@@ -755,7 +755,7 @@ interpretations of a line, the thematic break takes precedence:
If you want a thematic break in a list item, use a different bullet:
-```````````````````````````````` example
+```````````````````````````````` example disabled
- Foo
- * * *
.
@@ -955,7 +955,7 @@ of the closing sequence:
ATX headings need not be separated from surrounding content by blank
lines, and they can interrupt paragraphs:
-```````````````````````````````` example
+```````````````````````````````` example disabled
****
## foo
****
@@ -1021,7 +1021,7 @@ Simple examples:
Dialect deviation: no setext headings - a `---` underline is a thematic break and an `===` underline is paragraph text.
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo *bar*
=========
@@ -1065,7 +1065,7 @@ baz→
The underlining can be any length:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
-------------------------
@@ -1082,7 +1082,7 @@ Foo
The heading content can be indented up to three spaces, and need
not line up with the underlining:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
---
@@ -1122,7 +1122,7 @@ Foo
The setext heading underline can be indented up to three spaces, and
may have trailing spaces:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
----
.
@@ -1144,7 +1144,7 @@ Foo
The setext heading underline cannot contain internal spaces:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
= =
@@ -1160,7 +1160,7 @@ Foo
Trailing spaces in the content line do not cause a line break:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
-----
.
@@ -1171,7 +1171,7 @@ Foo
Nor does a backslash at the end:
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo\
----
.
@@ -1183,7 +1183,7 @@ Foo\
Since indicators of block structure take precedence over
indicators of inline structure, the following are setext headings:
-```````````````````````````````` example
+```````````````````````````````` example disabled
`Foo
----
`
@@ -1297,7 +1297,7 @@ in these examples gets interpreted as a thematic break:
````````````````````````````````
-```````````````````````````````` example
+```````````````````````````````` example disabled
- foo
-----
.
@@ -1318,7 +1318,7 @@ in these examples gets interpreted as a thematic break:
````````````````````````````````
-```````````````````````````````` example
+```````````````````````````````` example disabled
> foo
-----
.
@@ -1332,7 +1332,7 @@ in these examples gets interpreted as a thematic break:
If you want a heading with `> foo` as its literal text, you can
use backslash escapes:
-```````````````````````````````` example
+```````````````````````````````` example disabled
\> foo
------
.
@@ -1399,7 +1399,7 @@ bar
or use a thematic break that cannot count as a [setext heading
underline], such as
-```````````````````````````````` example
+```````````````````````````````` example disabled
Foo
bar
* * *
@@ -1571,7 +1571,7 @@ blocks:
Dialect deviation: no setext headings - a `---` underline is a thematic break and an `===` underline is paragraph text.
-```````````````````````````````` example
+```````````````````````````````` example disabled
# Heading
foo
Heading
@@ -3881,7 +3881,7 @@ foo
In general, blank lines are not needed before or after block
quotes:
-```````````````````````````````` example
+```````````````````````````````` example disabled
> aaa
***
> bbb
diff --git a/tests/source/php-markdown-extra.mdtest/Abbr.text b/tests/source/php-markdown-extra.mdtest/Abbr.text
index ae72f4e..3644199 100644
--- a/tests/source/php-markdown-extra.mdtest/Abbr.text
+++ b/tests/source/php-markdown-extra.mdtest/Abbr.text
@@ -18,14 +18,14 @@ Let's transfert documents through TCP/IP, using TCP packets.
*[IP]: Internet Protocol
*[TCP]: Transmission Control Protocol
- ---
+---
Bienvenue sur [CMS](http://www.bidulecms.com "Bidule CMS").
*[CMS]: Content Management System
- ---
+---
ATCCE
-*[ATCCE]: Abbreviation "Testing" Correct 'Character' < Escapes >
\ No newline at end of file
+*[ATCCE]: Abbreviation "Testing" Correct 'Character' < Escapes >
diff --git a/tests/source/php-markdown-extra.mdtest/Definition Lists.text b/tests/source/php-markdown-extra.mdtest/Definition Lists.text
index 5b3bdb6..aa2cafb 100644
--- a/tests/source/php-markdown-extra.mdtest/Definition Lists.text
+++ b/tests/source/php-markdown-extra.mdtest/Definition Lists.text
@@ -68,7 +68,7 @@ Definition 1 paragraph 1 line 2 (lazy)
Definition 1 paragraph 2 line 1 ...
Definition 1 paragraph 2 line 2 (lazy)
-* * *
+---
A mix:
@@ -105,11 +105,11 @@ Term 4
Definition 9 paragraph 2 line 1
: Definition 10 (no paragraph)
-* * *
+---
Special cases:
Term
: code block
- as first element of a definition
\ No newline at end of file
+ as first element of a definition
diff --git a/tests/source/php-markdown-extra.mdtest/Footnotes.text b/tests/source/php-markdown-extra.mdtest/Footnotes.text
index 95b5ea8..6b66a62 100644
--- a/tests/source/php-markdown-extra.mdtest/Footnotes.text
+++ b/tests/source/php-markdown-extra.mdtest/Footnotes.text
@@ -53,13 +53,13 @@ footnote test[^reference].
from another footnote. But [^reference] should be litteral
since the footnote with that name has already been used.
- - - -
+---
Testing unusual footnote name[^1$^!"'].
[^1$^!"']: Haha!
- - - -
+---
Footnotes mixed with images[^image-mixed]
![1800 Travel][img6]
diff --git a/tests/source/php-markdown-extra.mdtest/Headers with attributes.text b/tests/source/php-markdown-extra.mdtest/Headers with attributes.text
index 4242136..0a45d98 100644
--- a/tests/source/php-markdown-extra.mdtest/Headers with attributes.text
+++ b/tests/source/php-markdown-extra.mdtest/Headers with attributes.text
@@ -7,7 +7,7 @@ Header { #id2}
### Header {#id3 }
#### Header ## { #id4 }
- - - -
+---
Header {.cl}
======
@@ -18,7 +18,7 @@ Header { .cl}
### Header {.cl }
#### Header ## { .cl }
- - - -
+---
Header {.cl.class}
======
@@ -29,7 +29,7 @@ Header { .cl .class}
### Header {.cl .class }
#### Header ## { .cl.class }
- - - -
+---
Header {#id5.cl.class}
======
diff --git a/tests/source/php-markdown-extra.mdtest/Headers with attributes.xhtml b/tests/source/php-markdown-extra.mdtest/Headers with attributes.xhtml
index 9b51e65..b90d1d8 100644
--- a/tests/source/php-markdown-extra.mdtest/Headers with attributes.xhtml
+++ b/tests/source/php-markdown-extra.mdtest/Headers with attributes.xhtml
@@ -1,27 +1,27 @@
Header {#id1}
======
-Header { #id2}
-
+Header { #id2}
+------
Header
Header
Header {.cl}
======
-Header { .cl}
-
+Header { .cl}
+------
Header
Header
Header {.cl.class}
======
-Header { .cl .class}
-
+Header { .cl .class}
+------
Header
Header
Header {#id5.cl.class}
======
-Header { #id6 .cl .class}
-
+Header { #id6 .cl .class}
+------
Header
Header
diff --git a/tests/source/php-markdown-extra.mdtest/Tables.text b/tests/source/php-markdown-extra.mdtest/Tables.text
index 720fb84..713cd89 100644
--- a/tests/source/php-markdown-extra.mdtest/Tables.text
+++ b/tests/source/php-markdown-extra.mdtest/Tables.text
@@ -26,7 +26,7 @@ With leading and tailing pipes:
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
-* * *
+---
# One-column one-row table
@@ -48,7 +48,7 @@ With leading and tailing pipes:
| ------- |
| Cell |
-* * *
+---
Table alignement:
@@ -64,7 +64,7 @@ Table alignement (alternate spacing):
| Long Cell | Long Cell | Long Cell | Long Cell |
| Cell | Cell | Cell | Cell |
-* * *
+---
# Empty cells
@@ -78,7 +78,7 @@ Header 1 | Header 2
A | B
| D
-* * *
+---
# Missing tailing pipe
diff --git a/tests/test_fill.py b/tests/test_fill.py
index 221744a..d4356b9 100644
--- a/tests/test_fill.py
+++ b/tests/test_fill.py
@@ -214,3 +214,9 @@ def test_instantiate_nb_error(tmp_path):
p = mk_dlg(tmp_path, [bad])
with pytest.raises(ZeroDivisionError) as ei: instantiate_nb(p)
assert any(bad.id in n for n in ei.value.__notes__)
+
+
+def test_bare_import_skips_execnb():
+ import subprocess, sys
+ code = "import sys, mdhtml; assert 'execnb' not in sys.modules; assert 'IPython' not in sys.modules"
+ subprocess.run([sys.executable, '-c', code], check=True)