You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
4.6 KiB
146 lines
4.6 KiB
/**
|
|
* Compara el catálogo de la base con el de los archivos, campo a campo.
|
|
*
|
|
* npm run db:comparar
|
|
*
|
|
* Es la red de la fase 4. Cambiar de dónde salen los datos de veinte rutas sin
|
|
* poder comprobar que salen iguales sería confiar en que las pruebas de
|
|
* interfaz noten una diferencia de un año o de un porcentaje de autoría, y no
|
|
* la notarían: pintan lo que les den.
|
|
*
|
|
* Lo que se compara es el objeto entero de cada canción, álbum y estilo, con
|
|
* las mismas claves y ordenado igual. Las diferencias se listan una a una con
|
|
* su ruta —`dorina.registro.reparto[0].porcentaje`— porque un «no coinciden»
|
|
* sin más no sirve para arreglar nada.
|
|
*/
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { load as leerYaml } from 'js-yaml';
|
|
|
|
import { parsearAlbum, parsearCancion, parsearEstilo } from '$lib/content/schema';
|
|
import { extraerLineas } from '$lib/content/extracto';
|
|
import { leerCatalogo } from '$lib/server/catalogo';
|
|
|
|
/*
|
|
* Los archivos se leen a mano y no por `$lib/content`, que usa
|
|
* `import.meta.glob`: eso lo resuelve Vite al construir y en Node no existe.
|
|
* Los analizadores sí son los mismos, que es lo que hace que la comparación
|
|
* signifique algo.
|
|
*/
|
|
const RAIZ = 'src/content';
|
|
|
|
const SALTO = String.fromCharCode(10);
|
|
|
|
function partir(texto) {
|
|
const lineas = texto.split(new RegExp(String.raw`\r?\n`));
|
|
if (lineas[0]?.trim() !== '---') return { datos: {}, cuerpo: texto.trim() };
|
|
const cierre = lineas.indexOf('---', 1);
|
|
if (cierre === -1) return { datos: {}, cuerpo: texto.trim() };
|
|
return {
|
|
datos: leerYaml(lineas.slice(1, cierre).join(SALTO)) ?? {},
|
|
cuerpo: lineas
|
|
.slice(cierre + 1)
|
|
.join(SALTO)
|
|
.trim()
|
|
};
|
|
}
|
|
|
|
function leerCarpeta(carpeta, parser) {
|
|
const dir = path.join(RAIZ, carpeta);
|
|
if (!fs.existsSync(dir)) return [];
|
|
return fs
|
|
.readdirSync(dir)
|
|
.filter((n) => n.endsWith('.md'))
|
|
.sort()
|
|
.map((nombre) => {
|
|
const ruta = path.join(dir, nombre);
|
|
const { datos, cuerpo } = partir(fs.readFileSync(ruta, 'utf8'));
|
|
const slug = nombre.replace(/\.md$/, '');
|
|
const parseado = parser(datos, slug, ruta);
|
|
// El extracto lo inyecta un preprocesador al construir, no el parser.
|
|
if (carpeta === 'canciones') parseado.extracto = extraerLineas(cuerpo);
|
|
return parseado;
|
|
});
|
|
}
|
|
|
|
const deArchivo = leerCarpeta('canciones', parsearCancion);
|
|
const albumesArchivo = leerCarpeta('albumes', parsearAlbum);
|
|
const estilosArchivo = leerCarpeta('estilos', parsearEstilo);
|
|
|
|
/** Recorre dos objetos a la vez y devuelve dónde difieren. */
|
|
function diferencias(a, b, ruta = '') {
|
|
const fuera = [];
|
|
|
|
if (a === b) return fuera;
|
|
|
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
if (a.length !== b.length) {
|
|
fuera.push(`${ruta}: archivo ${a.length} elementos, base ${b.length}`);
|
|
}
|
|
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
fuera.push(...diferencias(a[i], b[i], `${ruta}[${i}]`));
|
|
}
|
|
return fuera;
|
|
}
|
|
|
|
if (a && b && typeof a === 'object' && typeof b === 'object') {
|
|
for (const clave of new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
fuera.push(...diferencias(a[clave], b[clave], ruta ? `${ruta}.${clave}` : clave));
|
|
}
|
|
return fuera;
|
|
}
|
|
|
|
// `undefined` y ausente son lo mismo para este sitio: el tipo los declara
|
|
// opcionales y la plantilla los trata igual.
|
|
if ((a ?? undefined) === (b ?? undefined)) return fuera;
|
|
|
|
fuera.push(`${ruta}: archivo ${JSON.stringify(a)} · base ${JSON.stringify(b)}`);
|
|
return fuera;
|
|
}
|
|
|
|
const base = await leerCatalogo();
|
|
|
|
const grupos = [
|
|
['canciones', deArchivo, base.canciones],
|
|
['álbumes', albumesArchivo, base.albumes],
|
|
['estilos', estilosArchivo, base.estilos]
|
|
];
|
|
|
|
let problemas = 0;
|
|
|
|
for (const [nombre, archivo, deBase] of grupos) {
|
|
console.log(`\n${nombre.toUpperCase()} · archivo ${archivo.length} · base ${deBase.length}`);
|
|
|
|
const porSlug = new Map(deBase.map((x) => [x.slug, x]));
|
|
|
|
for (const uno of archivo) {
|
|
const otro = porSlug.get(uno.slug);
|
|
if (!otro) {
|
|
console.log(` ✗ ${uno.slug}: no está en la base`);
|
|
problemas++;
|
|
continue;
|
|
}
|
|
porSlug.delete(uno.slug);
|
|
|
|
const fuera = diferencias(uno, otro, uno.slug);
|
|
if (fuera.length === 0) continue;
|
|
|
|
problemas += fuera.length;
|
|
console.log(` ✗ ${uno.slug}`);
|
|
for (const d of fuera) console.log(` ${d}`);
|
|
}
|
|
|
|
for (const sobrante of porSlug.keys()) {
|
|
console.log(` · ${sobrante}: solo en la base (no es un fallo si se añadió después)`);
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
problemas === 0
|
|
? '\nIdénticos. La base puede sustituir a los archivos sin que cambie nada.'
|
|
: `\n${problemas} diferencias. Cada una es algo que la web enseñaría distinto.`
|
|
);
|
|
|
|
process.exit(problemas === 0 ? 0 : 1);
|