550c98f275
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
142 lines
4.8 KiB
Rust
142 lines
4.8 KiB
Rust
//! Evaluador Nickel para inputs `.ncl`.
|
|
//!
|
|
//! El brazo de Cards lee Nickel como **fuente** y produce JSON como
|
|
//! **representación intermedia** que después dispatcha por los readers
|
|
//! estándar. Esto significa que un `.ncl` puede producir cualquier
|
|
//! variant del [`super::CardBody`] siempre que evalúe a una shape JSON
|
|
//! que alguno de los readers reconozca.
|
|
//!
|
|
//! # Templates
|
|
//!
|
|
//! Nickel soporta `import "..."` y el operador `&` de merge nativo. Un
|
|
//! Card "concreto" puede ser un template + override:
|
|
//!
|
|
//! ```nickel
|
|
//! let base = import "ente_basic.ncl" in
|
|
//! base & { id = "01ARZ...", label = "mi-ente" }
|
|
//! ```
|
|
//!
|
|
//! **Convención obligatoria del template**: las fields que el usuario
|
|
//! va a sobrescribir tienen que estar marcadas `| default` (o
|
|
//! `| optional`). Nickel rechaza el merge de dos strings/numbers
|
|
//! distintos con la misma prioridad — el `| default` baja la prioridad
|
|
//! del template y deja que el override del user gane:
|
|
//!
|
|
//! ```nickel
|
|
//! # template ui_module_basic.ncl
|
|
//! {
|
|
//! id | String | default = "TEMPLATE_ID",
|
|
//! label | String | default = "TEMPLATE_LABEL",
|
|
//! # ...
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Resolución de imports (en orden):
|
|
//! 1. Relativo al directorio del archivo input (default de Nickel).
|
|
//! 2. `BRAHMAN_CARDS_TEMPLATES_DIR` (env). Permite tener un
|
|
//! registry global de templates accesible por nombre desnudo:
|
|
//! `import "ui_module_basic.ncl"`.
|
|
//!
|
|
//! No agregamos magic resolución por kind — el autor decide qué
|
|
//! template importa explícitamente.
|
|
|
|
use std::ffi::OsString;
|
|
use std::path::Path;
|
|
|
|
use serde_json::Value;
|
|
use thiserror::Error;
|
|
|
|
/// Variable de entorno opcional. Si está set, su path se agrega al
|
|
/// search path de imports de Nickel después del parent dir del input,
|
|
/// permitiendo `import "<nombre>.ncl"` desde cualquier ubicación.
|
|
pub const BRAHMAN_CARDS_TEMPLATES_ENV: &str = "BRAHMAN_CARDS_TEMPLATES_DIR";
|
|
|
|
/// Errores específicos del pipeline Nickel. Wrap del error de Nickel
|
|
/// formateado como texto plano (sin ANSI) + el path del input para
|
|
/// contexto.
|
|
#[derive(Debug, Error)]
|
|
pub enum NickelEvalError {
|
|
#[error("io leyendo {path}: {source}")]
|
|
Io {
|
|
path: String,
|
|
#[source]
|
|
source: std::io::Error,
|
|
},
|
|
|
|
#[error("evaluación de '{path}' falló:\n{message}")]
|
|
Eval { path: String, message: String },
|
|
|
|
#[error("export a JSON de '{path}' falló:\n{message}")]
|
|
Export { path: String, message: String },
|
|
|
|
#[error("JSON exportado por Nickel no parsea de vuelta: {source}")]
|
|
JsonReparse {
|
|
#[source]
|
|
source: serde_json::Error,
|
|
},
|
|
}
|
|
|
|
/// Lee `path` (debe ser un `.ncl` válido), lo evalúa profundamente vía
|
|
/// `nickel-lang` y devuelve el resultado como `serde_json::Value`
|
|
/// listo para dispatch a un reader JSON.
|
|
///
|
|
/// El parent dir del input se agrega como import path para que
|
|
/// imports relativos tipo `import "./template.ncl"` funcionen sin
|
|
/// configuración extra. Si `BRAHMAN_CARDS_TEMPLATES_DIR` está set,
|
|
/// también se agrega.
|
|
pub fn eval_nickel_file(path: &Path) -> Result<Value, NickelEvalError> {
|
|
let path_display = path.display().to_string();
|
|
let source = std::fs::read_to_string(path).map_err(|e| NickelEvalError::Io {
|
|
path: path_display.clone(),
|
|
source: e,
|
|
})?;
|
|
|
|
let mut import_paths: Vec<OsString> = Vec::new();
|
|
if let Some(parent) = path.parent() {
|
|
if !parent.as_os_str().is_empty() {
|
|
import_paths.push(parent.into());
|
|
}
|
|
}
|
|
if let Ok(reg) = std::env::var(BRAHMAN_CARDS_TEMPLATES_ENV) {
|
|
if !reg.is_empty() {
|
|
import_paths.push(reg.into());
|
|
}
|
|
}
|
|
|
|
let mut ctx = nickel_lang::Context::new()
|
|
.with_added_import_paths(import_paths)
|
|
.with_source_name(path_display.clone());
|
|
|
|
let expr = ctx
|
|
.eval_deep_for_export(&source)
|
|
.map_err(|e| NickelEvalError::Eval {
|
|
path: path_display.clone(),
|
|
message: format_nickel_error(&e),
|
|
})?;
|
|
|
|
let json_str = ctx
|
|
.expr_to_json(&expr)
|
|
.map_err(|e| NickelEvalError::Export {
|
|
path: path_display.clone(),
|
|
message: format_nickel_error(&e),
|
|
})?;
|
|
|
|
serde_json::from_str(&json_str).map_err(|e| NickelEvalError::JsonReparse { source: e })
|
|
}
|
|
|
|
/// Formatea un error de Nickel como texto plano. Usa `ErrorFormat::Text`
|
|
/// (sin ANSI) para que sea legible en logs y mensajes de UI sin
|
|
/// escape sequences.
|
|
fn format_nickel_error(err: &nickel_lang::Error) -> String {
|
|
let mut buf: Vec<u8> = Vec::new();
|
|
if err
|
|
.format(&mut buf, nickel_lang::ErrorFormat::Text)
|
|
.is_err()
|
|
{
|
|
// Si la propia formateación falla, devolvemos el Debug —
|
|
// peor mensaje que el normal pero no perdemos info.
|
|
return format!("{err:?}");
|
|
}
|
|
String::from_utf8(buf).unwrap_or_else(|_| format!("{err:?}"))
|
|
}
|