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 [];
 | |
| }
 |