Files
osint_obsidian_plugin/parse.ts
T
agent 011ccfb8cd feat: scaffold inicial del plugin de Obsidian osint_obsidian_plugin
Plugin fino (id osint-db) que habla HTTP con el service local osint_db
(FastAPI + DuckDB) y renderiza tablas de datos en las notas del vault
osint mediante el code block osintdb. Incluye parser puro de directivas
con tests (node --test), settings tab, comando de paleta, enlaces
internos para columnas note_path, build con esbuild + tsc y deploy.sh
al vault.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 23:58:04 +02:00

81 lines
2.7 KiB
TypeScript

// Parseo del contenido de un bloque ```osintdb```. Módulo puro, sin dependencias
// de Obsidian, para poder testearlo con node --test sin levantar la app.
//
// Dos formas de bloque:
// 1. SQL crudo: el bloque entero es una sentencia SELECT que se manda a /api/query.
// 2. Con directivas: líneas "clave: valor" al inicio del bloque. Si hay directiva
// "query" se ejecuta una query nombrada via /api/query/named. Las directivas
// "max_rows" y "title" aplican a ambas formas.
export interface ParsedBlock {
/** Nombre de la query guardada en el service (forma named). */
query?: string;
/** SQL crudo a ejecutar (forma raw). */
sql?: string;
/** Límite de filas pedido al service. */
maxRows?: number;
/** Título a mostrar encima de la tabla. */
title?: string;
/** Mensaje de error de parseo; si está presente, el resto se ignora. */
error?: string;
}
const DIRECTIVE_RE = /^(query|max_rows|title)\s*:\s*(.*)$/;
/**
* Parsea el texto de un bloque osintdb. Consume las líneas de directivas del
* inicio (clave: valor) y trata el resto como SQL crudo. Reglas:
* - "query" presente -> query nombrada (no se admite SQL adicional en el bloque).
* - sin "query" pero con SQL -> query cruda.
* - bloque vacío o directivas inválidas -> error.
*/
export function parseBlock(source: string): ParsedBlock {
const lines = source.split(/\r?\n/);
const result: ParsedBlock = {};
let i = 0;
for (; i < lines.length; i++) {
const line = lines[i].trim();
if (line === "" && result.query === undefined && result.maxRows === undefined && result.title === undefined) {
// Líneas en blanco iniciales antes de cualquier directiva: se saltan.
continue;
}
const m = line.match(DIRECTIVE_RE);
if (!m) {
break; // Fin de las directivas; lo que queda es SQL.
}
const key = m[1];
const value = m[2].trim();
if (key === "query") {
if (value === "") {
return { error: "La directiva 'query' necesita un nombre de query guardada." };
}
result.query = value;
} else if (key === "max_rows") {
const n = Number(value);
if (!Number.isInteger(n) || n <= 0) {
return { error: `Valor inválido para max_rows: "${value}" (debe ser un entero positivo).` };
}
result.maxRows = n;
} else if (key === "title") {
result.title = value;
}
}
const rest = lines.slice(i).join("\n").trim();
if (result.query !== undefined) {
if (rest !== "") {
return { error: "El bloque mezcla la directiva 'query' con SQL. Usa una de las dos formas." };
}
return result;
}
if (rest === "") {
return { error: "Bloque vacío: escribe un SELECT o una directiva 'query: <nombre>'." };
}
result.sql = rest;
return result;
}