134 lines
3.9 KiB
JavaScript
134 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Markdown front-matter utilities for tag management
|
|
* Handles YAML front-matter parsing and serialization with strict business rules
|
|
*/
|
|
|
|
/**
|
|
* Normalise un tag : trim, normalise les espaces
|
|
*/
|
|
function normalizeTag(tag) {
|
|
return String(tag || '').trim().replace(/\s+/g, ' ');
|
|
}
|
|
|
|
/**
|
|
* Déduplique les tags (case-insensitive) en préservant la première occurrence
|
|
*/
|
|
function deduplicateTags(tags) {
|
|
const seen = new Set();
|
|
const result = [];
|
|
|
|
for (const tag of tags) {
|
|
const normalized = normalizeTag(tag);
|
|
if (!normalized) continue;
|
|
|
|
const lower = normalized.toLowerCase();
|
|
if (!seen.has(lower)) {
|
|
seen.add(lower);
|
|
result.push(normalized);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Réécrit le front-matter YAML d'un fichier Markdown avec les tags fournis.
|
|
*
|
|
* Règles métier :
|
|
* - Crée la section --- ... --- si absente
|
|
* - Normalise et déduplique les tags (case-insensitive)
|
|
* - Si liste vide, supprime la clé tags: (ne laisse pas tags: [])
|
|
* - Aucune ligne vide dans le front-matter résultant
|
|
* - Préserve l'ordre et le format des autres propriétés
|
|
*
|
|
* @param {string} rawMarkdown - Contenu Markdown brut
|
|
* @param {string[]} tags - Liste des tags à appliquer
|
|
* @returns {string} - Contenu Markdown mis à jour
|
|
*/
|
|
export function rewriteTagsFrontmatter(rawMarkdown, tags) {
|
|
// Normaliser le contenu (BOM, line endings)
|
|
const content = rawMarkdown.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
|
|
|
// Détecter le front-matter existant
|
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)/);
|
|
|
|
// Normaliser et dédupliquer les tags
|
|
const cleanTags = deduplicateTags(tags || []);
|
|
|
|
// Construire le bloc tags (format YAML liste)
|
|
const buildTagsBlock = (tagList) => {
|
|
if (tagList.length === 0) return ''; // Ne pas créer de clé tags si vide
|
|
return 'tags:\n' + tagList.map(t => ` - ${t}`).join('\n');
|
|
};
|
|
|
|
const newTagsBlock = buildTagsBlock(cleanTags);
|
|
|
|
// Cas 1 : Pas de front-matter existant
|
|
if (!fmMatch) {
|
|
if (cleanTags.length === 0) {
|
|
// Pas de tags, pas de front-matter à créer
|
|
return content;
|
|
}
|
|
// Créer un nouveau front-matter avec les tags
|
|
return `---\n${newTagsBlock}\n---\n${content}`;
|
|
}
|
|
|
|
// Cas 2 : Front-matter existant
|
|
const fmText = fmMatch[1];
|
|
const body = fmMatch[2] || '';
|
|
|
|
// Supprimer l'ancien bloc tags (format liste ou inline)
|
|
const tagsRe = /(^|\n)tags\s*:[^\n]*(?:\n\s+-\s+[^\n]*)*(?=\n|$)/i;
|
|
let updatedFm = fmText.replace(tagsRe, '');
|
|
|
|
// Nettoyer les lignes vides multiples
|
|
updatedFm = updatedFm.replace(/\n{2,}/g, '\n').trim();
|
|
|
|
// Ajouter le nouveau bloc tags si non vide
|
|
if (newTagsBlock) {
|
|
updatedFm = updatedFm ? `${updatedFm}\n${newTagsBlock}` : newTagsBlock;
|
|
}
|
|
|
|
// Si le front-matter est vide après suppression, ne pas créer de section vide
|
|
if (!updatedFm.trim()) {
|
|
return body;
|
|
}
|
|
|
|
// Reconstruire le document
|
|
return `---\n${updatedFm}\n---\n${body}`;
|
|
}
|
|
|
|
/**
|
|
* Extrait les tags du front-matter YAML
|
|
* @param {string} rawMarkdown - Contenu Markdown brut
|
|
* @returns {string[]} - Liste des tags trouvés
|
|
*/
|
|
export function extractTagsFromFrontmatter(rawMarkdown) {
|
|
const content = rawMarkdown.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
|
|
if (!fmMatch) return [];
|
|
|
|
const fmText = fmMatch[1];
|
|
|
|
// Chercher le bloc tags (format liste)
|
|
const tagsListMatch = fmText.match(/^tags:\s*\n((?:\s+-\s+.+\n?)+)/m);
|
|
if (tagsListMatch) {
|
|
const lines = tagsListMatch[1].split('\n').filter(Boolean);
|
|
return lines.map(line => line.replace(/^\s*-\s*/, '').trim()).filter(Boolean);
|
|
}
|
|
|
|
// Chercher format inline (tags: [tag1, tag2])
|
|
const tagsInlineMatch = fmText.match(/^tags:\s*\[(.*?)\]/m);
|
|
if (tagsInlineMatch) {
|
|
return tagsInlineMatch[1]
|
|
.split(',')
|
|
.map(t => t.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
return [];
|
|
}
|