feat: zcatconfig v0.1.0 - Sistema de configuracion declarativo
Libreria para gestion de configuracion con: - Definicion declarativa de variables (ConfigVariable) - Engine generico con comptime (inline for + @field) - Persistencia a archivo texto legible - Validacion de valores (rangos, tipos) - Soporte: boolean, integer, float, string, color Extraido y generalizado de zsimifactu/src/config/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
15c7f7357e
5 changed files with 626 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.zig-cache/
|
||||
zig-out/
|
||||
200
CLAUDE.md
Normal file
200
CLAUDE.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# 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.1.0 |
|
||||
| **Fecha inicio** | 2025-12-17 |
|
||||
| **Estado** | EN DESARROLLO - Estructura inicial |
|
||||
| **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)
|
||||
- Generacion automatica de struct Config via comptime
|
||||
- Persistencia a archivo de texto legible
|
||||
- Validacion de valores
|
||||
- Sistema Get/Set generico con inline for + @field
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
- [ ] Extraer types.zig de zsimifactu
|
||||
- [ ] Adaptar engine.zig (parametrizar variables y Config)
|
||||
- [ ] Adaptar persistence.zig
|
||||
|
||||
### Fase 2: Generalizacion
|
||||
- [ ] Engine generico con comptime
|
||||
- [ ] Tests unitarios
|
||||
- [ ] Documentacion API
|
||||
|
||||
### Fase 3: Integracion
|
||||
- [ ] Integrar en zsimifactu como dependencia
|
||||
- [ ] Verificar que zsimifactu funciona igual
|
||||
|
||||
---
|
||||
|
||||
## EQUIPO
|
||||
|
||||
- **Usuario (R.Eugenio)**: Desarrollador principal
|
||||
- **Claude**: Asistente de programacion (Claude Code / Opus 4.5)
|
||||
|
||||
---
|
||||
|
||||
## HISTORIAL
|
||||
|
||||
| Fecha | Version | Cambios |
|
||||
|-------|---------|---------|
|
||||
| 2025-12-17 | v0.1.0 | Proyecto creado, estructura inicial |
|
||||
29
build.zig
Normal file
29
build.zig
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Library module
|
||||
const lib_mod = b.addModule("zcatconfig", .{
|
||||
.root_source_file = b.path("src/zcatconfig.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// Tests
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/zcatconfig.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_lib_unit_tests.step);
|
||||
|
||||
// Prevent unused variable warning
|
||||
_ = lib_mod;
|
||||
}
|
||||
14
build.zig.zon
Normal file
14
build.zig.zon
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.{
|
||||
.name = .zcatconfig,
|
||||
.version = "0.1.0",
|
||||
.minimum_zig_version = "0.15.0",
|
||||
.fingerprint = 0x6211a84c80e77eb,
|
||||
|
||||
.dependencies = .{},
|
||||
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
||||
381
src/zcatconfig.zig
Normal file
381
src/zcatconfig.zig
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
//! zcatconfig - Sistema de Configuracion Declarativo
|
||||
//!
|
||||
//! Libreria para gestion de configuracion con:
|
||||
//! - Definicion declarativa de variables
|
||||
//! - Generacion automatica de struct via comptime
|
||||
//! - Persistencia a archivo de texto legible
|
||||
//! - Validacion de valores
|
||||
//!
|
||||
//! ## Uso basico
|
||||
//!
|
||||
//! ```zig
|
||||
//! const zcatconfig = @import("zcatconfig");
|
||||
//! const variables = @import("config/variables.zig");
|
||||
//! const Config = @import("config/structures.zig").Config;
|
||||
//!
|
||||
//! const Engine = zcatconfig.Engine(variables.config_variables, Config);
|
||||
//!
|
||||
//! var config = Config{};
|
||||
//! Engine.load(&config, "config.txt") catch {};
|
||||
//! config.font_size = 16;
|
||||
//! try Engine.save(&config, "config.txt");
|
||||
//! ```
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
// =============================================================================
|
||||
// TIPOS
|
||||
// =============================================================================
|
||||
|
||||
/// Tipo de variable de configuracion
|
||||
pub const ConfigVarType = enum {
|
||||
boolean,
|
||||
integer,
|
||||
float,
|
||||
string,
|
||||
color,
|
||||
// Extensible para enums custom
|
||||
};
|
||||
|
||||
/// Categoria de variable (para UI de configuracion)
|
||||
pub const ConfigCategory = enum {
|
||||
general,
|
||||
apariencia,
|
||||
comportamiento,
|
||||
avanzado,
|
||||
};
|
||||
|
||||
/// Color RGB
|
||||
pub const Color = struct {
|
||||
r: u8 = 0,
|
||||
g: u8 = 0,
|
||||
b: u8 = 0,
|
||||
|
||||
pub fn rgb(r: u8, g: u8, b: u8) Color {
|
||||
return .{ .r = r, .g = g, .b = b };
|
||||
}
|
||||
|
||||
pub fn format(self: Color, allocator: std.mem.Allocator) ![]u8 {
|
||||
return std.fmt.allocPrint(allocator, "RGB({},{},{})", .{ self.r, self.g, self.b });
|
||||
}
|
||||
|
||||
pub fn parse(str: []const u8) ?Color {
|
||||
// Formato: RGB(r,g,b)
|
||||
if (!std.mem.startsWith(u8, str, "RGB(")) return null;
|
||||
if (!std.mem.endsWith(u8, str, ")")) return null;
|
||||
|
||||
const inner = str[4 .. str.len - 1];
|
||||
var it = std.mem.splitScalar(u8, inner, ',');
|
||||
|
||||
const r_str = it.next() orelse return null;
|
||||
const g_str = it.next() orelse return null;
|
||||
const b_str = it.next() orelse return null;
|
||||
|
||||
const r = std.fmt.parseInt(u8, std.mem.trim(u8, r_str, " "), 10) catch return null;
|
||||
const g = std.fmt.parseInt(u8, std.mem.trim(u8, g_str, " "), 10) catch return null;
|
||||
const b = std.fmt.parseInt(u8, std.mem.trim(u8, b_str, " "), 10) catch return null;
|
||||
|
||||
return Color.rgb(r, g, b);
|
||||
}
|
||||
};
|
||||
|
||||
/// Definicion de una variable de configuracion
|
||||
pub const ConfigVariable = struct {
|
||||
/// Nombre del campo en el struct Config
|
||||
name: []const u8,
|
||||
/// Clave en el archivo de configuracion (ej: "@auto_save")
|
||||
config_key: []const u8,
|
||||
/// Tipo de la variable
|
||||
var_type: ConfigVarType,
|
||||
/// Valor por defecto como string
|
||||
default: []const u8,
|
||||
/// Descripcion para documentacion/UI
|
||||
description: []const u8,
|
||||
/// Categoria para agrupar en UI
|
||||
category: ConfigCategory = .general,
|
||||
/// Valor minimo (para integer/float)
|
||||
min: ?i32 = null,
|
||||
/// Valor maximo (para integer/float)
|
||||
max: ?i32 = null,
|
||||
};
|
||||
|
||||
/// Resultado de una operacion get
|
||||
pub const ConfigResult = union(enum) {
|
||||
boolean: bool,
|
||||
integer: i32,
|
||||
float: f32,
|
||||
string: []const u8,
|
||||
color: Color,
|
||||
not_found,
|
||||
type_mismatch,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// ENGINE
|
||||
// =============================================================================
|
||||
|
||||
/// Crea un Engine de configuracion para un conjunto de variables y struct Config
|
||||
pub fn Engine(comptime variables: []const ConfigVariable, comptime ConfigType: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
/// Obtiene el valor de una variable por nombre
|
||||
pub fn get(config: *const ConfigType, name: []const u8) ConfigResult {
|
||||
inline for (variables) |v| {
|
||||
if (std.mem.eql(u8, v.name, name)) {
|
||||
return getField(config, v);
|
||||
}
|
||||
}
|
||||
return .not_found;
|
||||
}
|
||||
|
||||
/// Obtiene el valor de un campo segun su definicion
|
||||
fn getField(config: *const ConfigType, comptime v: ConfigVariable) ConfigResult {
|
||||
const value = @field(config.*, v.name);
|
||||
return switch (v.var_type) {
|
||||
.boolean => .{ .boolean = value },
|
||||
.integer => .{ .integer = value },
|
||||
.float => .{ .float = value },
|
||||
.string => .{ .string = value },
|
||||
.color => .{ .color = value },
|
||||
};
|
||||
}
|
||||
|
||||
/// Establece el valor de una variable por nombre (desde string)
|
||||
pub fn setFromString(config: *ConfigType, name: []const u8, str_value: []const u8) bool {
|
||||
inline for (variables) |v| {
|
||||
if (std.mem.eql(u8, v.name, name)) {
|
||||
return setFieldFromString(config, v, str_value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Establece un campo desde string
|
||||
fn setFieldFromString(config: *ConfigType, comptime v: ConfigVariable, str_value: []const u8) bool {
|
||||
switch (v.var_type) {
|
||||
.boolean => {
|
||||
if (std.mem.eql(u8, str_value, "Si") or
|
||||
std.mem.eql(u8, str_value, "si") or
|
||||
std.mem.eql(u8, str_value, "true") or
|
||||
std.mem.eql(u8, str_value, "1"))
|
||||
{
|
||||
@field(config.*, v.name) = true;
|
||||
return true;
|
||||
} else if (std.mem.eql(u8, str_value, "No") or
|
||||
std.mem.eql(u8, str_value, "no") or
|
||||
std.mem.eql(u8, str_value, "false") or
|
||||
std.mem.eql(u8, str_value, "0"))
|
||||
{
|
||||
@field(config.*, v.name) = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
.integer => {
|
||||
const val = std.fmt.parseInt(i32, str_value, 10) catch return false;
|
||||
// Validar rango si existe
|
||||
if (v.min) |min| {
|
||||
if (val < min) return false;
|
||||
}
|
||||
if (v.max) |max| {
|
||||
if (val > max) return false;
|
||||
}
|
||||
@field(config.*, v.name) = val;
|
||||
return true;
|
||||
},
|
||||
.float => {
|
||||
const val = std.fmt.parseFloat(f32, str_value) catch return false;
|
||||
@field(config.*, v.name) = val;
|
||||
return true;
|
||||
},
|
||||
.string => {
|
||||
@field(config.*, v.name) = str_value;
|
||||
return true;
|
||||
},
|
||||
.color => {
|
||||
if (Color.parse(str_value)) |c| {
|
||||
@field(config.*, v.name) = c;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Lista de variables disponibles
|
||||
pub fn getVariables() []const ConfigVariable {
|
||||
return variables;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PERSISTENCE
|
||||
// =============================================================================
|
||||
|
||||
/// Carga configuracion desde archivo
|
||||
pub fn load(
|
||||
comptime variables: []const ConfigVariable,
|
||||
comptime ConfigType: type,
|
||||
config: *ConfigType,
|
||||
path: []const u8,
|
||||
) !void {
|
||||
const EngineType = Engine(variables, ConfigType);
|
||||
|
||||
const file = try std.fs.cwd().openFile(path, .{});
|
||||
defer file.close();
|
||||
|
||||
var buf_reader = std.io.bufferedReader(file.reader());
|
||||
var reader = buf_reader.reader();
|
||||
|
||||
var line_buf: [1024]u8 = undefined;
|
||||
while (reader.readUntilDelimiterOrEof(&line_buf, '\n')) |line_opt| {
|
||||
const line = line_opt orelse break;
|
||||
|
||||
// Ignorar comentarios y lineas vacias
|
||||
const trimmed = std.mem.trim(u8, line, " \t\r");
|
||||
if (trimmed.len == 0) continue;
|
||||
if (trimmed[0] == '#') continue;
|
||||
|
||||
// Buscar separador ':'
|
||||
if (std.mem.indexOf(u8, trimmed, ":")) |sep_idx| {
|
||||
const key = std.mem.trim(u8, trimmed[0..sep_idx], " \t");
|
||||
var value = std.mem.trim(u8, trimmed[sep_idx + 1 ..], " \t");
|
||||
|
||||
// Quitar comentario inline
|
||||
if (std.mem.indexOf(u8, value, "#")) |hash_idx| {
|
||||
value = std.mem.trim(u8, value[0..hash_idx], " \t");
|
||||
}
|
||||
|
||||
// Buscar variable por config_key
|
||||
inline for (variables) |v| {
|
||||
if (std.mem.eql(u8, key, v.config_key)) {
|
||||
_ = EngineType.setFromString(config, v.name, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else |err| {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/// Guarda configuracion a archivo
|
||||
pub fn save(
|
||||
comptime variables: []const ConfigVariable,
|
||||
comptime ConfigType: type,
|
||||
config: *const ConfigType,
|
||||
allocator: std.mem.Allocator,
|
||||
path: []const u8,
|
||||
) !void {
|
||||
const EngineType = Engine(variables, ConfigType);
|
||||
|
||||
const file = try std.fs.cwd().createFile(path, .{});
|
||||
defer file.close();
|
||||
|
||||
var writer = file.writer();
|
||||
|
||||
// Header
|
||||
try writer.writeAll("# Archivo de configuracion (autogenerado)\n");
|
||||
try writer.writeAll("# Formato: @variable: valor # descripcion\n\n");
|
||||
|
||||
var current_category: ?ConfigCategory = null;
|
||||
|
||||
inline for (variables) |v| {
|
||||
// Separador de categoria
|
||||
if (current_category == null or current_category.? != v.category) {
|
||||
current_category = v.category;
|
||||
try writer.print("\n# === {} ===\n", .{@tagName(v.category)});
|
||||
}
|
||||
|
||||
// Obtener valor actual
|
||||
const result = EngineType.get(config, v.name);
|
||||
const value_str = switch (result) {
|
||||
.boolean => |b| if (b) "Si" else "No",
|
||||
.integer => |i| blk: {
|
||||
const buf = try allocator.alloc(u8, 16);
|
||||
const len = std.fmt.formatInt(i, 10, .lower, .{}, buf) catch 0;
|
||||
break :blk buf[0..len];
|
||||
},
|
||||
.float => |f| blk: {
|
||||
const buf = try allocator.alloc(u8, 32);
|
||||
const slice = std.fmt.bufPrint(buf, "{d:.2}", .{f}) catch "";
|
||||
break :blk slice;
|
||||
},
|
||||
.string => |s| s,
|
||||
.color => |c| blk: {
|
||||
break :blk try c.format(allocator);
|
||||
},
|
||||
else => "???",
|
||||
};
|
||||
|
||||
try writer.print("{s}: {s} # {s}\n", .{ v.config_key, value_str, v.description });
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TESTS
|
||||
// =============================================================================
|
||||
|
||||
test "Color parse and format" {
|
||||
const c = Color.parse("RGB(255,128,64)").?;
|
||||
try std.testing.expectEqual(@as(u8, 255), c.r);
|
||||
try std.testing.expectEqual(@as(u8, 128), c.g);
|
||||
try std.testing.expectEqual(@as(u8, 64), c.b);
|
||||
}
|
||||
|
||||
test "Color parse invalid" {
|
||||
try std.testing.expect(Color.parse("invalid") == null);
|
||||
try std.testing.expect(Color.parse("RGB(256,0,0)") == null); // 256 > u8
|
||||
}
|
||||
|
||||
test "Engine basic" {
|
||||
const test_vars = [_]ConfigVariable{
|
||||
.{
|
||||
.name = "enabled",
|
||||
.config_key = "@enabled",
|
||||
.var_type = .boolean,
|
||||
.default = "Si",
|
||||
.description = "Activar funcionalidad",
|
||||
},
|
||||
.{
|
||||
.name = "count",
|
||||
.config_key = "@count",
|
||||
.var_type = .integer,
|
||||
.default = "10",
|
||||
.description = "Contador",
|
||||
.min = 0,
|
||||
.max = 100,
|
||||
},
|
||||
};
|
||||
|
||||
const TestConfig = struct {
|
||||
enabled: bool = true,
|
||||
count: i32 = 10,
|
||||
};
|
||||
|
||||
const TestEngine = Engine(&test_vars, TestConfig);
|
||||
|
||||
var config = TestConfig{};
|
||||
|
||||
// Test get
|
||||
const enabled_result = TestEngine.get(&config, "enabled");
|
||||
try std.testing.expectEqual(true, enabled_result.boolean);
|
||||
|
||||
// Test setFromString
|
||||
try std.testing.expect(TestEngine.setFromString(&config, "enabled", "No"));
|
||||
try std.testing.expectEqual(false, config.enabled);
|
||||
|
||||
try std.testing.expect(TestEngine.setFromString(&config, "count", "42"));
|
||||
try std.testing.expectEqual(@as(i32, 42), config.count);
|
||||
|
||||
// Test range validation
|
||||
try std.testing.expect(!TestEngine.setFromString(&config, "count", "200")); // > max
|
||||
try std.testing.expectEqual(@as(i32, 42), config.count); // unchanged
|
||||
}
|
||||
|
||||
test "version" {
|
||||
// Placeholder test
|
||||
try std.testing.expect(true);
|
||||
}
|
||||
Loading…
Reference in a new issue