Sashmark / Blog

Markdown code block filename: three ways pandoc read it

Bobby Huang ·

The section Attribute block with a filename holds the pandoc output for a file name set in braces. Under Words written after the language, a fence that adds a title after the word python goes through three readers. Look in Name in backticks above the fence for the run where the name sits on its own line over the block. Four more fence forms, plus one extra pass with an extension turned off, are quoted in Five more fences tried. Grep commands and their printed counts sit in Counting attributes with grep. Check Three fences in one test file for the input file plus the three pandoc commands.

Key takeaways

  • The section Three fences in one test file lists the input lines and a pandoc command per reader.
  • Under Attribute block with a filename, the HTML from the three readers is quoted.
  • Turn to Words written after the language for the runs on a fence with a title after python.
  • For the bold name and the colon label, see Five more fences tried.
  • Grep counts, with a pre tag count as a control, appear in Counting attributes with grep.

The phrase markdown code block filename means a name such as app.py that you want tied to a fenced block of code. During this test, pandoc wrote that name into its HTML as data-filename="app.py" when the markdown or the commonmark_x reader met an attribute block in braces. With the commonmark reader, the same fence came out as a pre element whose class was {.python, and the file name was missing from that line.

The runs took place on one Mac, and the first line of pandoc --version read pandoc 3.8.3. Below, I quote the HTML that pandoc gave, line breaks as it set them, and say plainly what that HTML contains. How any program draws that HTML on screen was never checked. For language tags as a topic, read the Markdown code blocks guide; this post leaves them alone.

Three fences in one test file

The test input lives in codetitle.md. An awk line count printed 19 for it, and cat codetitle.md printed:

Attribute block:

```{.python filename="app.py"}
print("Jane Doe")
```

Words after the language:

```python title="app.py"
print("Jane Doe")
```

Name on the line above:

`app.py`

```python
print("Jane Doe")
```

Lines 1, 7 and 13 hold the three labels. Lines 3 to 5 hold the attribute block, and lines 9 to 11 hold the fence with words after python. Line 15 is the code span, and its plain fence sits on lines 17 to 19.

Each reader then got one command:

pandoc -f markdown -t html codetitle.md
pandoc -f commonmark -t html codetitle.md
pandoc -f commonmark_x -t html codetitle.md

All three returned rc 0. Pandoc's --list-extensions output for these readers has two extension names that matter for these results. For markdown, line 18 reads +fenced_code_attributes. Third in the commonmark list comes -attributes, while third in the list for commonmark_x comes +attributes. Both names are pandoc extensions.

Attribute block with a filename

The first example puts a class and a key with a value inside braces:

```{.python filename="app.py"}
print("Jane Doe")
```

For how the braces work, see the post on code block line numbers; here I report just the HTML. This came back from markdown:

<div class="sourceCode" id="cb1" data-filename="app.py"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Jane Doe&quot;</span>)</span></code></pre></div>

Those two lines came out byte for byte alike from commonmark_x. So for markdown as for commonmark_x, the file name rides on the outer div as a data-filename attribute. For those two readers, pandoc put the word python in the class list of both the pre and the code tags, and two highlighting spans, bu and st, wrap parts of the code line.

Under commonmark, a single line came back instead:

<pre class="{.python"><code>print(&quot;Jane Doe&quot;)</code></pre>

In that commonmark line there's no div, no data-filename and no span inside code. That commonmark result gives the pre a class that reads {.python, brace and all, and app.py does not appear anywhere on the line.

Spec lines 1840-1843 (version 0.31.2) let a fence's first info-string word name the language. Here, with commonmark, that first word was {.python, and pandoc put it on the pre element.

Words written after the language

The second example drops the braces and adds a title after the language word:

```python title="app.py"
print("Jane Doe")
```

One paragraph was all markdown gave back:

<p><code>python title="app.py" print("Jane Doe")</code></p>

So in this markdown run the example produced no pre element. Under markdown, its lines became one inline code element that holds the words after the backticks and the code line, with one space joining the two. Inside that markdown span the double quotes stayed plain, while the highlighted blocks in the same output wrote them as &quot;.

For that fence, commonmark gave this block, and commonmark_x gave it too:

<div class="sourceCode" id="cb2"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Jane Doe&quot;</span>)</span></code></pre></div>

A highlighted python block came out of commonmark and of commonmark_x here. Neither commonmark nor commonmark_x kept the word title or the name app.py in that block, as an attribute or as text.

Name in backticks above the fence

The third example sets the name in backticks on its own line, then a plain fence:

`app.py`

```python
print("Jane Doe")
```

From markdown, pandoc gave:

<p><code>app.py</code></p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Jane Doe&quot;</span>)</span></code></pre></div>

Output from commonmark matched it, and so did output from commonmark_x, except that both use cb3 where markdown used cb2 in the ids and links. Under all three readers, the name came out as a code element inside a p, and the highlighted block after it holds no attribute with the file name.

Five more fences tried

My extra script fed four more single-fence files to the three readers, then made one additional run with an extension turned off. Each file is 3 lines long by awk count, except codetitle-bold.md, which counts 5.

  1. codetitle-title-attr.md swaps the key to title:
```{.python title="app.py"}
print("Jane Doe")
```

These two lines came from markdown, and a run with commonmark_x repeated them unchanged:

<div class="sourceCode" id="cb1" title="app.py"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Jane Doe&quot;</span>)</span></code></pre></div>

Given title as the key, markdown wrote a plain title attribute with no data- in front, as did commonmark_x. The commonmark run gave <pre class="{.python"><code>print(&quot;Jane Doe&quot;)</code></pre> for this file.

  1. codetitle-noclass.md keeps the key and drops the class:
```{filename="app.py"}
print("Jane Doe")
```

Pandoc gave this under markdown as well as commonmark_x:

<pre data-filename="app.py"><code>print(&quot;Jane Doe&quot;)</code></pre>

This time, with commonmark, pandoc printed:

<pre class="{filename=&quot;app.py&quot;}"><code>print(&quot;Jane Doe&quot;)</code></pre>

Lacking a class, the fence got no div and no highlighting spans from markdown or commonmark_x, and the attribute sat on the pre itself. The commonmark run turned the whole brace text into the class.

  1. codetitle-bold.md puts the name in bold above a plain fence:
**app.py**

```python
print("Jane Doe")
```

Output was identical across the three readers:

<p><strong>app.py</strong></p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Jane Doe&quot;</span>)</span></code></pre></div>
  1. codetitle-colon.md joins the language and the name with a colon:
```python:app.py
print("Jane Doe")
```

Again the three readers matched:

<div class="sourceCode" id="cb1"><pre
class="sourceCode python:app.py"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Jane Doe&quot;</span>)</span></code></pre></div>

Here the pre class list holds python:app.py whole, while the code class list holds python with no :app.py after it, and the line still got its bu and st spans.

  1. Last, I ran codetitle-attr.md, which holds the attribute block by itself, with the extension removed:
pandoc -f markdown-fenced_code_attributes -t html codetitle-attr.md

Its rc was 0, and it gave:

<p><code>{.python filename="app.py"} print("Jane Doe")</code></p>

Once fenced_code_attributes was off, markdown gave the brace fence back as one inline code element, matching the shape of its output for the fence with words after python.

Counting attributes with grep

These are the commands my extra script ran from the test folder for the markdown reader. It then ran them again for the two other readers, swapping the reader name into both the -f value and the file names.

pandoc -f markdown -t html codetitle.md -o codetitle-out-markdown.html
grep -c 'data-filename' codetitle-out-markdown.html
grep -c 'title=' codetitle-out-markdown.html
grep -c '<pre' codetitle-out-markdown.html
  1. Save each reader's output to a file. All three pandoc runs returned rc 0.
  2. Count lines holding data-filename. The count was 1 for markdown and for commonmark_x, and 0 for commonmark.
  3. Count lines holding title=. Here markdown gave 1, while the two other readers gave 0. The one markdown match is the inline code text quoted under Words written after the language, since no title attribute was written for that file.
  4. Count lines holding <pre as a control. That gave 2 under markdown and 3 under each of the two other readers. So the zero in step 2 came from a commonmark file that did contain code blocks.

A companion post runs these three readers over a Markdown directory tree.

Where Sashmark fits

Sashmark, a work in progress, sets out two goals here. With Round-Trip, a fix you key into the formatted copy should land in the saved bytes as that fix and nothing more. With Changes View, returning to a doc an agent kept editing should mark the stretches that got rewritten, whether git tracks the folder or not.

It is in development and has not been released. The app itself has no AI features. See the planned features, the help pages and the roadmap for where each part stands.

More at sashmark.com.

Questions

Which fence syntax puts a file name on a Markdown code block?
Compare your fence with the runs shown in Attribute block with a filename.
What happens to words typed after the language name?
Each reader's output for that fence is quoted in Words written after the language.
Can I check pandoc's HTML for a file name attribute?
See the numbered steps in Counting attributes with grep.
What about a colon between the language and the name?
That run, with python:app.py as the label, is quoted in Five more fences tried.

What Sashmark plans for v1: Features · Roadmap

View as Markdown