Snippet
Un exemple écrit à la main est une copie. Il cesse d'être vrai le jour où le
code change, et rien ne le signale : la page continue de montrer quelque chose
qui a marché une fois. Snippet désigne le fichier à la place, et le fichier
est lu à la génération de la page — il ne reste aucune copie pour dériver.
L'idée vient de Christophe Avonture, qui l'a construite pour son site.
Désigner un fichier
/**
* Un fichier que la page Snippet lit, au lieu de le recopier.
*
* Il est installé avec la documentation : les exemples de cette page
* fonctionnent donc dans votre projet comme ils fonctionnent sur ce site.
* Changez une ligne ici et la page change avec — c'est tout l'intérêt du
* composant.
*/
/** Mots qu'un slug laisse tomber, pour que les titres ne commencent pas tous pareil. */
const SKIPPED = new Set(['le', 'la', 'les', 'de', 'des', 'du']);
/**
* Transforme le titre d'une page en dernier segment de son adresse.
*
* @param {string} title Titre, tel que le frontmatter le donne.
* @returns {string} Un slug : minuscules, accents retirés, mots joints par un tiret.
*/
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" />
Le nom du fichier sert d'étiquette au bloc, et la coloration est celle de
n'importe quel bloc de code. Un chemin qui commence par ./ ou ../ est lu
depuis la page, comme un lien ; tout autre chemin est lu depuis la racine
de votre projet, si bien que src/index.js désigne le même fichier depuis
n'importe quelle page.
Une partie d'un fichier
Deux façons, et elles ne vieillissent pas pareil.
Un intervalle de lignes s'écrit vite et veut dire « la ligne 12 du fichier tel qu'il est aujourd'hui » :
/** Mots qu'un slug laisse tomber, pour que les titres ne commencent pas tous pareil. */<Snippet source="./snippet-example.js" lines="9-10" title="Les mots qu'il écarte" />
Écrivez lines="9" pour une ligne, "9-10" pour un intervalle, "9-" de là
jusqu'à la fin, "-10" depuis le début. Cela dit ce que le fichier contient
aujourd'hui, et rien de plus.
Une région est un repère laissé dans le fichier source lui-même, dans un commentaire. Elle suit le code quand il bouge — ce qui est tout l'intérêt de désigner un fichier plutôt que de le recopier :
if (typeof title !== 'string' || title.trim() === '') {
throw new TypeError('slugOf needs a title');
}<Snippet source="./snippet-example.js" region="guard" title="Le garde-fou" />
Dans le fichier, le repère est un commentaire ordinaire — celui-là même sur lequel les éditeurs savent déjà replier :
// #region guard
if (!name) {
throw new Error('greet needs a name');
}
// #endregion
L'indentation commune aux lignes gardées est retirée : une région repérée à l'intérieur d'une fonction ne ressort donc pas décalée.
Replier un long fichier
collapsed met le bloc dans un details. Sans script : un long fichier peut
ainsi tenir dans une page sans enterrer ce qui la suit.
Le fichier entier
/**
* Un fichier que la page Snippet lit, au lieu de le recopier.
*
* Il est installé avec la documentation : les exemples de cette page
* fonctionnent donc dans votre projet comme ils fonctionnent sur ce site.
* Changez une ligne ici et la page change avec — c'est tout l'intérêt du
* composant.
*/
/** Mots qu'un slug laisse tomber, pour que les titres ne commencent pas tous pareil. */
const SKIPPED = new Set(['le', 'la', 'les', 'de', 'des', 'du']);
/**
* Transforme le titre d'une page en dernier segment de son adresse.
*
* @param {string} title Titre, tel que le frontmatter le donne.
* @returns {string} Un slug : minuscules, accents retirés, mots joints par un tiret.
*/
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="Le fichier entier" />
Du code que vous écrivez
Sans source, le composant encadre le code écrit dans la page. Un bloc
clôturé à l'intérieur de la balise est la façon sûre de l'écrire : il porte
n'importe quoi, accolades et < compris, et aucun formateur ne touche à ce
qu'il contient.
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>
L'extension du titre nomme le langage ; lang le dit là où le titre ne le dit
pas, pour un shell par exemple :
npm install
npx docpensieve build<Snippet title="Générer le site" lang="bash">
```bash
npm install
npx docpensieve build
```
</Snippet>
Du code tapé à nu dans la balise
Le code écrit à nu dans la balise est lu lui aussi, dans la page telle que vous l'avez tapée — indentation gardée, rien de pris pour du Markdown :
<Snippet title="who_are_you.py">name = input("What's your name? ")</Snippet>
Deux choses qu'il ne peut pas porter, et toutes deux arrivent avant qu'aucun
composant ne voie le code. Une page est du MDX : {, } et < y sont lus
comme du MDX et arrêtent la génération sur une erreur de syntaxe. Et un
formateur passé sur vos pages reflue ce qu'il prend pour de la prose : deux
lignes de shell reviennent collées en une. Dès que l'un des deux s'applique —
c'est-à-dire pour la plupart du vrai code — employez la forme clôturée
ci-dessus.
La marque du langage
Un bloc porte la couleur de son langage — un filet au-dessus, et une courte marque à côté du nom de fichier. La couleur est posée en variable sur le cadre : c'est donc le thème qui décide de ce qu'elle peint, et un projet peut en mettre une autre.
La même fonction, dans l'autre fichier que lit cette page. Rien n'a été dit du langage : l'extension le portait, et avec lui la marque et la couleur.
if not isinstance(title, str) or not title.strip():
raise TypeError("slug_of needs a title")<Snippet source="./snippet-example.py" region="guard" title="Le même garde-fou, en Python" />
Un bloc que la page écrit dit son langage dans sa clôture, et se marque de la même façon :
steps:
- run: npx docpensieve build
- run: npx docpensieve checknpx docpensieve init mon-site
npx docpensieve devLà où un projet installe un jeu d'icônes et le nomme, le dessin remplace la marque :
snippetIcons: 'simple-icons',
Ce n'est jamais une raison d'échouer. L'icône est l'idée de l'outil, pas une
demande de votre page : un jeu que vous n'avez pas, ou un langage qu'il ne
dessine pas, ramène la marque. icon="simple-icons:docker" sur un bloc
l'emporte sur les deux.
Un langage que personne n'a marqué
Vingt et un langages ont une marque et une couleur. Tout autre s'affiche sans — le cadre, le nom, et le code.
La coloration est une autre affaire, et plus large : le coloriseur connaît plusieurs centaines de langages, et un bloc écrit dans un langage qu'il ignore ressort en texte brut plutôt que d'arrêter la génération. Vous pouvez donc écrire un snippet dans tout ce dans quoi votre projet est écrit.
Ce qu'il refuse
Rien n'échoue en silence : un snippet qui ne trouverait rien laisserait un cadre vide — et un cadre vide a l'air voulu.
| Écrit | Ce qui se passe |
|---|---|
| un fichier absent | La génération s'arrête en disant quel chemin a été cherché. |
| un chemin qui sort du projet | Refusé : une page est du contenu, et le contenu ne lit pas le disque. |
| une région qu'aucun repère ouvre ou ferme | La génération s'arrête en nommant la région et le fichier. |
| un intervalle qui se lit à l'envers | La génération s'arrête. |
Propriétés
| Propriété | Type | Effet |
|---|---|---|
source | string | Fichier à lire. Depuis la page avec ./ ou ../, depuis le projet sinon. |
title | string | Étiquette au-dessus du bloc. Par défaut, le nom du fichier. |
lines | string | 8, 8-14, 8- ou -14. |
region | string | Nom repéré par #region et #endregion dans le fichier. |
lang | string | Langage de la coloration, quand l'extension dit autre chose que la vérité. |
collapsed | boolean | Replie le bloc dans un details. |
icon | string | Une icône pour ce bloc, à la place de la marque de son langage. |
className | string | Classes ajoutées au cadre. |
style | object | Styles en ligne sur le cadre. |
Le fichier que lit cette page est installé avec elle : chaque exemple ci-dessus fonctionne donc dans votre projet comme il fonctionne ici.
DocPensieve