- setSilent(): carga masiva sin disparar observers - Formato extendido auto_validate: opcion=descripcion - Genera comentarios multilínea en archivo config - 100% compatible hacia atrás 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
437 lines
14 KiB
Markdown
437 lines
14 KiB
Markdown
# ZCATCONFIG - Sistema de Configuracion Declarativo
|
|
|
|
> **IMPORTANTE PARA CLAUDE**: Lee la seccion "PROTOCOLO DE INICIO" antes de hacer cualquier cosa.
|
|
|
|
---
|
|
|
|
## PROTOCOLO DE INICIO (LEER PRIMERO)
|
|
|
|
### Paso 1: Leer normas del equipo
|
|
```
|
|
/mnt/cello2/arno/re/recode/teamdocs/LAST_UPDATE.md
|
|
```
|
|
|
|
### Paso 2: Verificar estado del proyecto
|
|
```bash
|
|
cd /mnt/cello2/arno/re/recode/zig/zcatconfig
|
|
git status
|
|
git log --oneline -5
|
|
PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig build test
|
|
```
|
|
|
|
---
|
|
|
|
## INFORMACION DEL PROYECTO
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **Nombre** | zcatconfig |
|
|
| **Version** | v0.2.4 |
|
|
| **Fecha inicio** | 2025-12-17 |
|
|
| **Estado** | FUNCIONAL - Integrado en zsimifactu (arquitectura 3 fuentes) |
|
|
| **Lenguaje** | Zig 0.15.2 |
|
|
| **Dependencias** | Ninguna (Zig puro) |
|
|
|
|
### Descripcion
|
|
|
|
**zcatconfig** es una libreria para gestion de configuracion declarativa:
|
|
- Definicion de variables con metadatos (tipo, default, descripcion, categoria)
|
|
- Persistencia a archivo de texto legible con comentarios
|
|
- Validacion de valores (rangos, enums, tipos)
|
|
- Sistema Get/Set generico con inline for + @field
|
|
- **ConfigManager**: Gestor autonomo con observers y auto-save
|
|
- **FileWatcher**: Detecta cambios en archivo via mtime (hot-reload)
|
|
- **loadFromString**: Carga config desde string embebido
|
|
|
|
---
|
|
|
|
## ORIGEN: zsimifactu config/
|
|
|
|
Esta libreria extrae y generaliza el sistema de configuracion implementado en zsimifactu.
|
|
|
|
### Archivos originales en zsimifactu:
|
|
```
|
|
src/config/
|
|
├── config.zig # Re-exports publicos
|
|
├── types.zig # ConfigVariable, ConfigVarType, Color
|
|
├── variables.zig # Definiciones declarativas (proyecto-especifico)
|
|
├── structures.zig # Config struct (proyecto-especifico)
|
|
├── engine.zig # Meta-engine Get/Set con validacion
|
|
└── persistence.zig # Load/Save archivo texto
|
|
```
|
|
|
|
### Que se extrae a zcatconfig:
|
|
- types.zig (completo)
|
|
- engine.zig (generalizado)
|
|
- persistence.zig (generalizado)
|
|
|
|
### Que queda en el proyecto consumidor:
|
|
- variables.zig (definiciones especificas del proyecto)
|
|
- structures.zig (struct Config especifico)
|
|
|
|
---
|
|
|
|
## ARQUITECTURA
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ PROYECTO CONSUMIDOR │
|
|
│ │
|
|
│ variables.zig: │
|
|
│ pub const config_variables = [_]ConfigVariable{ │
|
|
│ .{ .name = "auto_save", .var_type = .boolean, ... }, │
|
|
│ .{ .name = "font_size", .var_type = .integer, ... }, │
|
|
│ }; │
|
|
│ │
|
|
│ structures.zig: │
|
|
│ pub const Config = struct { │
|
|
│ auto_save: bool = true, │
|
|
│ font_size: i32 = 14, │
|
|
│ }; │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ ZCATCONFIG │
|
|
│ │
|
|
│ types.zig: │
|
|
│ ConfigVariable, ConfigVarType, ConfigResult, Color │
|
|
│ │
|
|
│ engine.zig: │
|
|
│ Engine(comptime variables, comptime ConfigStruct) │
|
|
│ - get(), set(), getByName(), setByName() │
|
|
│ - Validacion automatica │
|
|
│ │
|
|
│ persistence.zig: │
|
|
│ - load(config, path, variables) │
|
|
│ - save(config, path, variables) │
|
|
│ - Formato: @variable_name: valor # comentario │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## API PROPUESTA
|
|
|
|
### Uso basico
|
|
|
|
```zig
|
|
const zcatconfig = @import("zcatconfig");
|
|
|
|
// En el proyecto consumidor:
|
|
const variables = @import("config/variables.zig");
|
|
const Config = @import("config/structures.zig").Config;
|
|
|
|
// Crear engine tipado
|
|
const Engine = zcatconfig.Engine(variables.config_variables, Config);
|
|
|
|
// Uso
|
|
var config = Config{};
|
|
Engine.load(&config, "app_config.txt") catch {};
|
|
config.font_size = 16;
|
|
try Engine.save(&config, "app_config.txt");
|
|
```
|
|
|
|
### Tipos de variables soportados
|
|
|
|
| Tipo | Zig Type | Formato archivo |
|
|
|------|----------|-----------------|
|
|
| boolean | bool | Si/No |
|
|
| integer | i32 | 123 |
|
|
| float | f32 | 1.5 |
|
|
| string | []const u8 | texto libre |
|
|
| color | Color | RGB(r,g,b) |
|
|
| enum_tipo | enum | NombreVariante |
|
|
|
|
---
|
|
|
|
## COMANDOS
|
|
|
|
```bash
|
|
# Compilar
|
|
PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig build
|
|
|
|
# Tests
|
|
PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig build test
|
|
```
|
|
|
|
---
|
|
|
|
## RUTAS
|
|
|
|
```bash
|
|
# Este proyecto
|
|
/mnt/cello2/arno/re/recode/zig/zcatconfig/
|
|
|
|
# Proyecto origen (referencia)
|
|
/mnt/cello2/arno/re/recode/zig/zsimifactu/src/config/
|
|
|
|
# Documentacion equipo
|
|
/mnt/cello2/arno/re/recode/teamdocs/
|
|
```
|
|
|
|
---
|
|
|
|
## PLAN DE TRABAJO
|
|
|
|
### Fase 1: Estructura base ✅
|
|
- [x] Crear proyecto (build.zig, CLAUDE.md)
|
|
- [x] Extraer types.zig de zsimifactu
|
|
- [x] Adaptar engine.zig (parametrizar variables y Config)
|
|
- [x] Adaptar persistence.zig
|
|
|
|
### Fase 2: Generalizacion ✅
|
|
- [x] Engine generico con comptime
|
|
- [x] Tests unitarios
|
|
- [x] Documentacion API
|
|
|
|
### Fase 3: Integracion ✅
|
|
- [x] Integrar en zsimifactu como dependencia
|
|
- [x] Verificar que zsimifactu funciona igual
|
|
|
|
### Fase 4: ConfigManager ✅
|
|
- [x] ConfigManager con loadOrCreate, auto-save, observers
|
|
- [x] Sistema de observers con contexto
|
|
- [x] isDirty tracking
|
|
|
|
### Fase 5: FileWatcher + Utils ✅
|
|
- [x] FileWatcher para hot-reload (mtime polling)
|
|
- [x] loadFromString para defaults embebidos
|
|
- [x] updateMtime para evitar auto-detectar cambios propios
|
|
|
|
---
|
|
|
|
## API PRINCIPAL
|
|
|
|
### ConfigManager (gestor autonomo)
|
|
|
|
```zig
|
|
const zcatconfig = @import("zcatconfig");
|
|
|
|
// Crear ConfigManager tipado
|
|
const MyConfigManager = zcatconfig.ConfigManager(&variables, MyConfig, "mi_app");
|
|
|
|
// Opcion 1: init() - Carga o crea archivo automaticamente
|
|
var manager = try MyConfigManager.init(allocator, "config.txt");
|
|
defer manager.deinit(); // Auto-save si dirty
|
|
|
|
// Opcion 2: initDeferred() - NO carga/crea archivo (control manual)
|
|
var manager = MyConfigManager.initDeferred(allocator, "config.txt");
|
|
// Luego cargar manualmente cuando sea necesario:
|
|
try manager.load(); // o manager.save() para crear
|
|
|
|
// Acceso
|
|
const cfg = manager.getConfig();
|
|
try manager.set("@mi_variable", "nuevo_valor");
|
|
|
|
// Observers
|
|
manager.addObserver(myCallback, my_context);
|
|
```
|
|
|
|
### initDeferred() - Control manual de archivo (v0.2.3)
|
|
|
|
Cuando necesitas controlar **cuándo** se crea/carga el archivo (ej: arquitectura 3 fuentes):
|
|
|
|
```zig
|
|
// Caso de uso: File > BD > Defaults
|
|
// 1. Crear manager SIN cargar archivo
|
|
var manager = MyConfigManager.initDeferred(allocator, "config.txt");
|
|
|
|
// 2. Verificar si existe archivo
|
|
const archivo_existe = std.fs.cwd().access("config.txt", .{}) catch false;
|
|
|
|
// 3. Verificar si BD tiene config
|
|
const bd_tiene_config = checkDatabase();
|
|
|
|
// 4. Aplicar prioridad
|
|
if (archivo_existe) {
|
|
try manager.load(); // Cargar de archivo
|
|
syncToBd(&manager); // Sync a BD
|
|
} else if (bd_tiene_config) {
|
|
loadFromBd(&manager); // Cargar de BD
|
|
try manager.save(); // Crear archivo
|
|
} else {
|
|
try manager.save(); // Crear con defaults
|
|
syncToBd(&manager); // Sync defaults a BD
|
|
}
|
|
```
|
|
|
|
**Por qué initDeferred()?**
|
|
- `init()` llama `loadOrCreate()` que crea el archivo con defaults
|
|
- Si BD tiene valores, el archivo ya existe antes de poder verificar
|
|
- `initDeferred()` permite verificar BD primero, luego decidir
|
|
|
|
### FileWatcher (hot-reload)
|
|
|
|
```zig
|
|
var watcher = zcatconfig.FileWatcher.init("config.txt", 1000); // check cada 1s
|
|
|
|
// En main loop:
|
|
if (watcher.checkForChanges()) {
|
|
// Recargar configuracion
|
|
}
|
|
|
|
// Despues de guardar:
|
|
watcher.updateMtime(); // Evitar detectar nuestro propio cambio
|
|
```
|
|
|
|
### loadFromString (defaults embebidos)
|
|
|
|
```zig
|
|
const defaults = @embedFile("defaults.txt");
|
|
zcatconfig.loadFromString(&variables, Config, &config, defaults);
|
|
```
|
|
|
|
---
|
|
|
|
## VALIDACION DE RANGOS (v0.2.3)
|
|
|
|
### Rangos negativos soportados
|
|
|
|
La validación de rangos ahora soporta valores mínimos negativos:
|
|
|
|
```zig
|
|
// Definición de variable con rango negativo
|
|
.{ .name = "offset_x", .var_type = .integer, .range = "-100-100", ... }
|
|
.{ .name = "offset_y", .var_type = .integer, .range = "-50-50", ... }
|
|
.{ .name = "temperature", .var_type = .float, .range = "-40.0-85.0", ... }
|
|
```
|
|
|
|
### Bug corregido (commit `957767d`)
|
|
|
|
**Problema:** `splitScalar('-')` dividía `"-100-100"` como `["", "100", "100"]`
|
|
|
|
**Solución:** Buscar el separador '-' excluyendo el primer carácter si es negativo:
|
|
|
|
```zig
|
|
const separator_pos = blk: {
|
|
const start: usize = if (range.len > 0 and range[0] == '-') 1 else 0;
|
|
for (range[start..], start..) |c, i| {
|
|
if (c == '-') break :blk i;
|
|
}
|
|
return false;
|
|
};
|
|
```
|
|
|
|
### Formatos de rango válidos
|
|
|
|
| Rango | Mínimo | Máximo | Notas |
|
|
|-------|--------|--------|-------|
|
|
| `"0-100"` | 0 | 100 | Positivo simple |
|
|
| `"-100-100"` | -100 | 100 | Negativo a positivo |
|
|
| `"-50-50"` | -50 | 50 | Centrado en cero |
|
|
| `"-100-0"` | -100 | 0 | Solo negativos |
|
|
|
|
---
|
|
|
|
## setSilent() - Carga masiva sin observers (v0.2.4)
|
|
|
|
### Problema
|
|
|
|
Al cargar config desde BD, cada `set()` dispara observers que escriben de vuelta a BD → loop infinito de logs.
|
|
|
|
### Solución
|
|
|
|
`setSilent()` establece valores SIN notificar observers:
|
|
|
|
```zig
|
|
// Cargar desde BD sin disparar observers
|
|
var stmt = try db.prepare("SELECT config_key, config_value FROM config_backup");
|
|
while (try stmt.step()) {
|
|
const key = stmt.columnText(0) orelse continue;
|
|
const value = stmt.columnText(1) orelse continue;
|
|
// Usar setSilent en vez de set
|
|
manager.setSilent(key, value) catch continue;
|
|
}
|
|
```
|
|
|
|
### API
|
|
|
|
```zig
|
|
/// Establece valor SIN notificar observers
|
|
/// Útil para carga masiva desde BD
|
|
pub fn setSilent(self: *Self, key: []const u8, value: []const u8) ConfigError!void
|
|
```
|
|
|
|
---
|
|
|
|
## DESCRIPCIONES EXTENDIDAS EN ARCHIVO CONFIG (v0.2.4)
|
|
|
|
### Problema
|
|
|
|
El archivo de configuración mostraba opciones pero no explicaba qué hacía cada una:
|
|
|
|
```
|
|
@validacion_nif_modo: Estricto # Modo validación NIF [Estricto,Permisivo,Desactivado]
|
|
```
|
|
|
|
El usuario ve las opciones pero no sabe qué significa cada una.
|
|
|
|
### Solución: Formato extendido en auto_validate
|
|
|
|
En `config_variables`, usar formato `opcion=descripcion`:
|
|
|
|
```zig
|
|
// Formato simple (sin descripciones):
|
|
.{ "mi_var", .string, .cat, "Op1,Op2,Op3", "Descripción", ... }
|
|
|
|
// Formato extendido (con descripciones):
|
|
.{ "mi_var", .string, .cat, "Op1=Hace X,Op2=Hace Y,Op3=Hace Z", "Descripción", ... }
|
|
```
|
|
|
|
### Resultado en archivo config
|
|
|
|
**Formato simple** (sin `=`):
|
|
```
|
|
@mi_var: Op1 # Descripción [Op1,Op2,Op3]
|
|
```
|
|
|
|
**Formato extendido** (con `=`):
|
|
```
|
|
# Descripción
|
|
# Op1: Hace X
|
|
# Op2: Hace Y
|
|
# Op3: Hace Z
|
|
@mi_var: Op1
|
|
```
|
|
|
|
### Ejemplo real
|
|
|
|
```zig
|
|
// En config_unified.zig:
|
|
.{ "validacion_nif_modo", .string, Cat.validaciones,
|
|
"Estricto=Bloquea guardar si NIF inválido,Permisivo=Avisa pero permite guardar,Desactivado=Sin validación",
|
|
"Modo validación NIF", false, "# VALIDACIONES" },
|
|
```
|
|
|
|
Genera:
|
|
```
|
|
# Modo validación NIF
|
|
# Estricto: Bloquea guardar si NIF inválido
|
|
# Permisivo: Avisa pero permite guardar
|
|
# Desactivado: Sin validación
|
|
@validacion_nif_modo: Estricto
|
|
```
|
|
|
|
### Compatibilidad
|
|
|
|
- Si `auto_validate` NO contiene `=` → formato simple (actual)
|
|
- Si `auto_validate` contiene `=` → formato extendido (nuevo)
|
|
- 100% compatible con archivos existentes
|
|
|
|
---
|
|
|
|
## EQUIPO
|
|
|
|
- **Usuario (R.Eugenio)**: Desarrollador principal
|
|
- **Claude**: Asistente de programacion (Claude Code / Opus 4.5)
|
|
|
|
---
|
|
|
|
## HISTORIAL
|
|
|
|
| Fecha | Version | Commit | Cambios |
|
|
|-------|---------|--------|---------|
|
|
| 2025-12-25 | v0.2.4 | `3dd835e` | setSilent() + descripciones extendidas en archivo config |
|
|
| 2025-12-18 | v0.2.3 | `957767d` | validateIntRange/Float fix: rangos negativos `"-100-100"` |
|
|
| 2025-12-18 | v0.2.2 | `0ef5efd` | initDeferred(): control manual sin auto-crear archivo |
|
|
| 2025-12-18 | v0.2.1 | - | FileWatcher, loadFromString, updateMtime |
|
|
| 2025-12-17 | v0.2.0 | - | ConfigManager con observers, auto-save, isDirty |
|
|
| 2025-12-17 | v0.1.0 | - | Proyecto creado, Engine + persistence |
|