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