Repository: jupyter/nbconvert
Pull request #2162 (2024-07-06 12:11:31)

Don't die if template path cannot be read
Ordinary users may not have read access to conf in system directories,

In those cases .exists() will trigger a PermissionError when enumerating the conf directories.

stuaxo: This may be https://github.com/jupyter/jupyter/issues/634
takluyver: Is this sufficient to avoid the problem you encountered? It looks like there are other `exists()` checks in `get_template_names()` which could also run into permission errors.

(It also seems weird to me that standard system directories under `/usr` wouldn't be readable, but I agree Jupyter should ideally cope with that, even if it's unusual)
stuaxo: It was sufficient for the bug I was seeing, it's certainly possible there could be other places with the same issue.
stuaxo: (I also found the fact .exists() can throw a permission error suprising, but it seems to be a thing.

Patch:
diff --git a/nbconvert/exporters/templateexporter.py b/nbconvert/exporters/templateexporter.py
index c09e6583..c6a337d4 100644
--- a/nbconvert/exporters/templateexporter.py
+++ b/nbconvert/exporters/templateexporter.py
@@ -565,13 +565,19 @@ class TemplateExporter(Exporter):
         for template_name in template_names:
             for base_dir in self.extra_template_basedirs:
                 path = os.path.join(base_dir, template_name)
-                if not prune or os.path.exists(path):
-                    paths.append(path)
+                try:
+                    if not prune or os.path.exists(path):
+                        paths.append(path)
+                except PermissionError:
+                    pass
             for root_dir in root_dirs:
                 base_dir = os.path.join(root_dir, "nbconvert", "templates")
                 path = os.path.join(base_dir, template_name)
-                if not prune or os.path.exists(path):
-                    paths.append(path)
+                try:
+                    if not prune or os.path.exists(path):
+                        paths.append(path)
+                except PermissionError:
+                    pass
 
         for root_dir in root_dirs:
             # we include root_dir for when we want to be very explicit, e.g.
diff --git a/nbconvert/filters/markdown.py b/nbconvert/filters/markdown.py
index 7e4bee9f..fe0cb10e 100644
--- a/nbconvert/filters/markdown.py
+++ b/nbconvert/filters/markdown.py
@@ -8,6 +8,10 @@ markdown within Jinja templates.
 
 import re
 
+from packaging.version import Version
+
+from nbconvert.utils.pandoc import get_pandoc_version
+
 try:
     from .markdown_mistune import markdown2html_mistune
 
@@ -66,7 +70,16 @@ def markdown2html_pandoc(source, extra_args=None):
 
 def markdown2asciidoc(source, extra_args=None):
     """Convert a markdown string to asciidoc via pandoc"""
-    extra_args = extra_args or ["--atx-headers"]
+
+    # Prior to version 3.0, pandoc supported the --atx-headers flag.
+    # For later versions, we must instead pass --markdown-headings=atx.
+    # See https://pandoc.org/releases.html#pandoc-3.0-2023-01-18
+    atx_args = ["--atx-headers"]
+    pandoc_version = get_pandoc_version()
+    if pandoc_version and Version(pandoc_version) >= Version("3.0"):
+        atx_args = ["--markdown-headings=atx"]
+
+    extra_args = extra_args or atx_args
     asciidoc = convert_pandoc(source, "markdown", "asciidoc", extra_args=extra_args)
     # workaround for https://github.com/jgm/pandoc/issues/3068
     if "__" in asciidoc:


Test patch:
diff --git a/tests/exporters/test_asciidoc.py b/tests/exporters/test_asciidoc.py
index e2a2ae35..0298bf90 100644
--- a/tests/exporters/test_asciidoc.py
+++ b/tests/exporters/test_asciidoc.py
@@ -50,6 +50,10 @@ class TestASCIIDocExporter(ExportersTestsBase):
         assert re.findall(in_regex, output)
         assert re.findall(out_regex, output)
 
+        # Assert that the Markdown header from our test notebook made it into the output.
+        # This can fail when nbconvert invokes pandoc incorrectly, as in issue #2017.
+        assert "== NumPy and Matplotlib examples" in output
+
     @onlyif_cmds_exist("pandoc")
     def test_export_no_prompt(self):
         """