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>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user