Snippet
An example written by hand is a copy. It stops being true the day the code
changes, and nothing reports it: the page goes on showing something that
worked once. Snippet names the file instead, and the file is read when the
page is generated — there is no copy left to drift.
The idea comes from Christophe Avonture, who built it for his own site.
Naming a file
/**
* A file the Snippet page reads, rather than quoting.
*
* It is installed along with the documentation, so the examples on that page
* work in your project exactly as they do on this site. Change a line here
* and the page changes with it — which is the whole point of the component.
*/
/** Words a slug drops, so that titles do not all begin alike. */
const SKIPPED = new Set(['a', 'an', 'the', 'of', 'to']);
/**
* Turns the title of a page into the last segment of its address.
*
* @param {string} title Title, as the frontmatter gives it.
* @returns {string} A slug: lower case, accents dropped, words joined by a dash.
*/
export function slugOf(title) {
// #region guard
if (typeof title !== 'string' || title.trim() === '') {
throw new TypeError('slugOf needs a title');
}
// #endregion
return title
.normalize('NFD')
.replace(/\p{M}/gu, '')
.toLowerCase()
.split(/[^a-z0-9]+/)
.filter((word) => word && !SKIPPED.has(word))
.join('-');
}<Snippet source="./snippet-example.js" />
The file name labels the block, and the highlighting is the one every code
block gets. A path beginning with ./ or ../ is read from the page, as
a link is; any other path is read from the root of your project, so that
src/index.js means the same file from every page.
Part of a file
Two ways, and they do not age the same.
A range of lines is quick to write and means line 12 of the file as it is today:
/** Words a slug drops, so that titles do not all begin alike. */
const SKIPPED = new Set(['a', 'an', 'the', 'of', 'to']);<Snippet source="./snippet-example.js" lines="9-10" title="The words it drops" />
Write lines="9" for one line, "9-10" for a range, "9-" from there to the
end, "-10" from the start. It says what the file holds today, and only
today.
A region is a marker left in the source file itself, in a comment. It follows the code when the code moves, which is the whole point of naming a file rather than copying it:
if (typeof title !== 'string' || title.trim() === '') {
throw new TypeError('slugOf needs a title');
}<Snippet source="./snippet-example.js" region="guard" title="The guard" />
In the file, the marker is an ordinary comment — the one editors already fold on:
// #region guard
if (!name) {
throw new Error('greet needs a name');
}
// #endregion
The indentation the kept lines share is removed, so a region marked inside a function does not come out shifted.
Folding a long file
collapsed puts the block inside a details. No script: a long file can sit
in a page without burying what follows it.
The whole file
/**
* A file the Snippet page reads, rather than quoting.
*
* It is installed along with the documentation, so the examples on that page
* work in your project exactly as they do on this site. Change a line here
* and the page changes with it — which is the whole point of the component.
*/
/** Words a slug drops, so that titles do not all begin alike. */
const SKIPPED = new Set(['a', 'an', 'the', 'of', 'to']);
/**
* Turns the title of a page into the last segment of its address.
*
* @param {string} title Title, as the frontmatter gives it.
* @returns {string} A slug: lower case, accents dropped, words joined by a dash.
*/
export function slugOf(title) {
// #region guard
if (typeof title !== 'string' || title.trim() === '') {
throw new TypeError('slugOf needs a title');
}
// #endregion
return title
.normalize('NFD')
.replace(/\p{M}/gu, '')
.toLowerCase()
.split(/[^a-z0-9]+/)
.filter((word) => word && !SKIPPED.has(word))
.join('-');
}<Snippet source="./snippet-example.js" collapsed title="The whole file" />
Code you write yourself
Without source, the component frames the code written in the page. A fenced
block inside the tag is the dependable way to write it: it carries anything,
braces and < included, and no formatter touches what is inside it.
def greet(name, greetings={'en': 'Hello'}):
return f"{greetings['en']}, {name}!"<Snippet title="greet.py">
```python
def greet(name, greetings={'en': 'Hello'}):
return f"{greetings['en']}, {name}!"
```
</Snippet>
The extension of the title names the language; lang says it where the title
does not, as for a shell:
npm install
npx docpensieve build<Snippet title="Build the site" lang="bash">
```bash
npm install
npx docpensieve build
```
</Snippet>
Code typed straight into the tag
Code written bare inside the tag is read too, from the page as you typed it — indentation kept, nothing taken for Markdown:
<Snippet title="who_are_you.py">name = input("What's your name? ")</Snippet>
Two things it cannot carry, both of which happen before any component sees the
code. A page is MDX, so {, } and < are read as MDX and stop the build on
a syntax error. And a formatter run over your pages reflows what it takes for
prose: two lines of shell come back as one. Where either applies — which is
most real code — use the fenced form above.
The mark of the language
A block carries the colour of its language — a rule above it, and a short mark beside the file name. The colour is set as a variable on the frame, so the theme decides what it paints and a project can set another.
The same function, in the other file this page reads. Nothing was said about the language: the extension carried it, and with it the mark and the colour.
if not isinstance(title, str) or not title.strip():
raise TypeError("slug_of needs a title")<Snippet source="./snippet-example.py" region="guard" title="The same guard, in Python" />
A block the page writes says its language in the fence, and is marked the same way:
steps:
- run: npx docpensieve build
- run: npx docpensieve checknpx docpensieve init my-site
npx docpensieve devWhere a project installs an icon collection and names it, the drawing replaces the mark:
snippetIcons: 'simple-icons',
It is never a reason to fail. The icon is the tool's idea, not something your
page asked for: a collection you do not have, or a language it does not draw,
brings back the mark. icon="simple-icons:docker" on a single block wins over
both.
A language nobody marked
Twenty-one languages have a mark and a colour. Any other is shown without one — the frame, the name, and the code.
Highlighting is a separate matter, and a wider one: the highlighter knows several hundred languages, and a block in one it does not know comes out as plain text rather than stopping the build. You can write a snippet in anything your project is written in.
What it refuses
Nothing fails quietly, because a snippet that resolved to nothing would leave an empty frame — and an empty frame looks deliberate.
| Written | What happens |
|---|---|
| a file that is not there | The build stops, saying which path was looked for. |
| a path leading outside the project | Refused: a page is content, and content does not read the disk. |
| a region no marker opens, or closes | The build stops, naming the region and the file. |
| a range that reads backwards | The build stops. |
Properties
| Property | Type | Effect |
|---|---|---|
source | string | File to read. From the page with ./ or ../, from the project otherwise. |
title | string | Label above the block. Defaults to the name of the file. |
lines | string | 8, 8-14, 8- or -14. |
region | string | Name marked by #region and #endregion in the file. |
lang | string | Language for the highlighting, when the extension says the wrong thing. |
collapsed | boolean | Folds the block into a details. |
icon | string | An icon for this block, instead of the mark of its language. |
className | string | Classes added to the frame. |
style | object | Inline styles on the frame. |
The file this page reads is installed along with it, so every example above works in your project exactly as it does here.
DocPensieve