feat(nakui-ui): migrar consumer al brazo brahman_cards::load_cards_from_dir
Primera consumer migration del brazo. nakui-ui ya no llama a nakui_ui_schema::load_modules_from_dir directamente; pasa por brahman_cards::load_cards_from_dir y extrae UiModule del CardBody. Beneficios concretos: - Soporta .ncl además de .json (templates Nickel + merge funcionan en cualquier subdir de modules). - Cards de otros body kinds (Ente/Monad) se skipean limpio con toast informativo, no rompen la carga. Cambios en brahman-cards: - Nuevo load_cards_from_dir(dir) + variante con readers/filenames custom. DEFAULT_CARD_FILENAMES = [card.ncl, card.json, module.ncl, module.json] (orden de prioridad). - 4 tests nuevos del helper. Cambios en nakui-ui: - Nueva dep brahman-cards. - Helper load_ui_modules(dir) -> (Vec<Module>, Vec<String>) envuelve el brazo, filtra a UiModule, aplica Module::validate(), detecta duplicate ids. - MetaUi::new usa el helper, emite toast con cards skipped si las hay. - 3 tests e2e nuevos. 26/26 brahman-cards verdes (+4). 48/48 nakui-ui verdes (+3). Workspace build verde. nakui_ui_schema::load_modules_from_dir queda intacto (sus tests lo usan + otros consumers futuros pueden preferirlo). Migración opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -256,3 +256,61 @@ fn dispatch_to_reader(
|
||||
}
|
||||
Err(CardLoadError::NoMatchingReader)
|
||||
}
|
||||
|
||||
/// Filenames convencionales que [`load_cards_from_dir`] busca dentro
|
||||
/// de cada subdir, en orden de preferencia. Si `card.ncl` existe se
|
||||
/// usa ese; sino `card.json`; sino los aliases legacy `module.*`. Los
|
||||
/// últimos dos son por compat con el layout actual de
|
||||
/// `examples/nakui-modules/<id>/module.json`.
|
||||
pub const DEFAULT_CARD_FILENAMES: &[&str] =
|
||||
&["card.ncl", "card.json", "module.ncl", "module.json"];
|
||||
|
||||
/// Carga todas las Cards encontradas como subdirs inmediatos de
|
||||
/// `dir`. Por cada subdir, busca los filenames convencionales (ver
|
||||
/// [`DEFAULT_CARD_FILENAMES`]) y carga el primero que existe. Subdirs
|
||||
/// sin ningún filename matching se skipean silenciosamente — permite
|
||||
/// que un dir contenga subdirs auxiliares (assets, fixtures, etc.).
|
||||
///
|
||||
/// Devuelve las Cards en orden lexicográfico por subdir name (estable
|
||||
/// across runs). NO ordena por `Card.id` — el caller decide si quiere
|
||||
/// re-ordenar y/o dedupar.
|
||||
///
|
||||
/// Errores: cualquier I/O al leer el `dir` mismo, o cualquier
|
||||
/// `CardLoadError` de un archivo encontrado (NO continúa tras el
|
||||
/// primer fallo — fallo loud > corrupción silenciosa).
|
||||
pub fn load_cards_from_dir(dir: impl AsRef<Path>) -> Result<Vec<Card>, CardLoadError> {
|
||||
load_cards_from_dir_with(dir, DEFAULT_CARD_FILENAMES, &default_readers())
|
||||
}
|
||||
|
||||
/// Variante de [`load_cards_from_dir`] con filenames y readers
|
||||
/// custom. Útil para apps que quieren restringir formatos o usar un
|
||||
/// nombre canónico distinto.
|
||||
pub fn load_cards_from_dir_with(
|
||||
dir: impl AsRef<Path>,
|
||||
filenames: &[&str],
|
||||
readers: &[Box<dyn CardReader>],
|
||||
) -> Result<Vec<Card>, CardLoadError> {
|
||||
let dir = dir.as_ref();
|
||||
let mut subdir_paths: Vec<std::path::PathBuf> = std::fs::read_dir(dir)?
|
||||
.flatten()
|
||||
.filter_map(|e| {
|
||||
let p = e.path();
|
||||
if p.is_dir() { Some(p) } else { None }
|
||||
})
|
||||
.collect();
|
||||
// Orden estable por subdir name — el output del brazo no debería
|
||||
// depender del orden de read_dir (que varía por filesystem).
|
||||
subdir_paths.sort();
|
||||
|
||||
let mut out: Vec<Card> = Vec::new();
|
||||
for sub in subdir_paths {
|
||||
for fname in filenames {
|
||||
let candidate = sub.join(fname);
|
||||
if candidate.exists() {
|
||||
out.push(load_card_with(&candidate, readers)?);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user