feat(brahman-cards): Nickel reader + templates con merge nativo (V2)

El brazo ahora acepta `.ncl`: evalúa via nickel-lang 2.0, exporta a
JSON, dispatcha por los readers JSON estándar. Templates funcionan
con import + & merge nativos de Nickel — el brazo no inventa
mecánica paralela.

- Dep nickel-lang = "2.0.0" (interfaz estable).
- Nuevo módulo nickel_eval con eval_nickel_file(path) -> Value y
  errores tipados (Io/Eval/Export/JsonReparse). Mensaje de Nickel
  como texto plano sin ANSI.
- load_card_with añade arm "ncl" simétrico al "json".
- CardLoadError::Nickel propaga el error limpio.
- Imports resueltos: parent dir del input + env
  BRAHMAN_CARDS_TEMPLATES_DIR (registry global, opcional).
- Convención obligatoria documentada: fields override-ables del
  template usan `| default` (sin eso Nickel rechaza el merge).

9 tests nuevos: eval directo, dispatch a UiModule/Ente, template
merge con id+label override, registry via env, error wrapping,
contract violation en eval-time (`id | String = 42`), shape
desconocida.

22 tests totales en brahman-cards (13 JSON V1 + 9 Nickel V2).
Workspace build verde.

NO hace: migrar consumers, set canonical de templates, KCL→Nickel
— todos para commits siguientes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-05-09 23:25:57 +00:00
parent 09501911b7
commit 2a4443790c
6 changed files with 1185 additions and 27 deletions
+15 -1
View File
@@ -157,6 +157,9 @@ pub enum CardLoadError {
ext: String,
supported: Vec<&'static str>,
},
#[error("evaluación Nickel: {0}")]
Nickel(#[from] NickelEvalError),
}
/// Trait de reader. Cada formato implementa una instancia.
@@ -180,7 +183,9 @@ pub trait CardReader: Send + Sync {
fn read(&self, input: Value) -> Result<Card, CardLoadError>;
}
mod nickel_eval;
mod readers;
pub use nickel_eval::{eval_nickel_file, NickelEvalError, BRAHMAN_CARDS_TEMPLATES_ENV};
pub use readers::{EnteJsonReader, MonadJsonReader, UiModuleJsonReader};
/// Construye el set default de readers para inputs JSON. El orden
@@ -222,9 +227,18 @@ pub fn load_card_with(
let value: Value = serde_json::from_slice(&bytes)?;
dispatch_to_reader(value, readers)
}
"ncl" => {
// Nickel pipeline: leer archivo → evaluar deeply → exportar
// a JSON → parsear como Value → dispatch a los readers JSON
// estándar. Templates funcionan via los `import` nativos de
// Nickel; el evaluator resuelve relativo al input y al
// `BRAHMAN_CARDS_TEMPLATES_DIR` env (si está set).
let value = eval_nickel_file(path)?;
dispatch_to_reader(value, readers)
}
other => Err(CardLoadError::UnsupportedExtension {
ext: other.to_string(),
supported: vec!["json"],
supported: vec!["json", "ncl"],
}),
}
}