build: Migrar a Zig 0.16

- Propagar io: std.Io en metodos de ConfigManager, load y save.
- Actualizar FileWatcher para usar std.Io.Dir.statFile y manejar nuevo Io.Timestamp (i96).
- Implementar helpers currentTimestamp y currentMilliTimestamp usando std.posix.clock_gettime.
- Actualizar build.zig.zon a 0.16.0.

Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
R.Eugenio 2026-01-18 00:54:32 +01:00
parent 85d50fd920
commit c761cb838d
2 changed files with 62 additions and 47 deletions

View file

@ -1,7 +1,7 @@
.{ .{
.name = .zcatconfig, .name = .zcatconfig,
.version = "0.1.0", .version = "0.1.0",
.minimum_zig_version = "0.15.0", .minimum_zig_version = "0.16.0",
.fingerprint = 0x6211a84c80e77eb, .fingerprint = 0x6211a84c80e77eb,
.dependencies = .{}, .dependencies = .{},

View file

@ -460,9 +460,15 @@ fn parseBool(value: []const u8) ?bool {
// PERSISTENCE // PERSISTENCE
// ============================================================================= // =============================================================================
fn currentMilliTimestamp() i64 {
const ts = std.posix.clock_gettime(std.posix.CLOCK.REALTIME) catch return 0;
return ts.sec * 1000 + @divTrunc(ts.nsec, 1_000_000);
}
/// Carga configuracion desde archivo. /// Carga configuracion desde archivo.
/// Requiere que ConfigType tenga un campo `allocator`. /// Requiere que ConfigType tenga un campo `allocator`.
pub fn load( pub fn load(
io: std.Io,
comptime variables: []const ConfigVariable, comptime variables: []const ConfigVariable,
comptime ConfigType: type, comptime ConfigType: type,
config: *ConfigType, config: *ConfigType,
@ -470,9 +476,13 @@ pub fn load(
) !void { ) !void {
const EngineType = Engine(variables, ConfigType); const EngineType = Engine(variables, ConfigType);
// Leer archivo completo (propaga FileNotFound para que loadOrCreate pueda crear) // Leer archivo completo
const content = try std.fs.cwd().readFileAlloc(config.allocator, path, 1024 * 1024); const file = try std.Io.Dir.cwd().openFile(io, path, .{});
defer file.close(io);
const stat = try file.stat(io);
const content = try config.allocator.alloc(u8, @intCast(stat.size));
defer config.allocator.free(content); defer config.allocator.free(content);
_ = try file.readPositional(io, &.{content}, 0);
// Parsear linea por linea // Parsear linea por linea
var lines = std.mem.splitScalar(u8, content, '\n'); var lines = std.mem.splitScalar(u8, content, '\n');
@ -522,6 +532,7 @@ fn findCommentStart(text: []const u8) ?usize {
/// Guarda configuracion a archivo /// Guarda configuracion a archivo
pub fn save( pub fn save(
io: std.Io,
comptime variables: []const ConfigVariable, comptime variables: []const ConfigVariable,
comptime ConfigType: type, comptime ConfigType: type,
config: *const ConfigType, config: *const ConfigType,
@ -530,20 +541,17 @@ pub fn save(
) !void { ) !void {
const EngineType = Engine(variables, ConfigType); const EngineType = Engine(variables, ConfigType);
const file = try std.fs.cwd().createFile(path, .{}); const file = try std.Io.Dir.cwd().createFile(io, path, .{});
defer file.close(); defer file.close(io);
// Header del archivo // Header del archivo
try file.writeAll("# ============================================================================\n"); try file.writeStreamingAll(io, "# ============================================================================\n");
var header_buf: [128]u8 = undefined; var header_buf: [128]u8 = undefined;
const header_line = std.fmt.bufPrint(&header_buf, "# {s} - Archivo de Configuracion\n", .{app_name}) catch ""; const header_line = std.fmt.bufPrint(&header_buf, "# {s} - Archivo de Configuracion\n", .{app_name}) catch "";
try file.writeAll(header_line); try file.writeStreamingAll(io, header_line);
try file.writeAll("# ============================================================================\n"); try file.writeStreamingAll(io, "# ============================================================================\n");
try file.writeAll("# \n"); try file.writeStreamingAll(io, "# \n");
try file.writeAll("# Este archivo se genera automaticamente. Puedes editarlo manualmente.\n"); try file.writeAll(io, &.{ "# Este archivo se genera automaticamente. Puedes editarlo manualmente.\n", "# Formato: @variable: valor # descripcion\n", "# \n", "\n" });
try file.writeAll("# Formato: @variable: valor # descripcion\n");
try file.writeAll("# \n");
try file.writeAll("\n");
var current_category: ?usize = null; var current_category: ?usize = null;
var value_buf: [256]u8 = undefined; var value_buf: [256]u8 = undefined;
@ -556,13 +564,13 @@ pub fn save(
// Header de categoria si cambio // Header de categoria si cambio
if (current_category == null or current_category.? != v.category) { if (current_category == null or current_category.? != v.category) {
if (current_category != null) { if (current_category != null) {
try file.writeAll("\n"); try file.writeStreamingAll(io, "\n");
} }
// Si la variable tiene header personalizado, usarlo // Si la variable tiene header personalizado, usarlo
if (v.category_header) |header| { if (v.category_header) |header| {
try file.writeAll(header); try file.writeStreamingAll(io, header);
try file.writeAll("\n"); try file.writeStreamingAll(io, "\n");
} }
current_category = v.category; current_category = v.category;
@ -591,7 +599,7 @@ pub fn save(
// @variable: valor // @variable: valor
var desc_line: [256]u8 = undefined; var desc_line: [256]u8 = undefined;
const desc_text = std.fmt.bufPrint(&desc_line, "# {s}\n", .{v.description}) catch ""; const desc_text = std.fmt.bufPrint(&desc_line, "# {s}\n", .{v.description}) catch "";
try file.writeAll(desc_text); try file.writeStreamingAll(io, desc_text);
// Parsear opciones con descripciones // Parsear opciones con descripciones
var options_iter = std.mem.splitScalar(u8, v.auto_validate.?, ','); var options_iter = std.mem.splitScalar(u8, v.auto_validate.?, ',');
@ -600,7 +608,7 @@ pub fn save(
const opt_name = option_desc[0..eq_pos]; const opt_name = option_desc[0..eq_pos];
const opt_desc = option_desc[eq_pos + 1 ..]; const opt_desc = option_desc[eq_pos + 1 ..];
const opt_line = std.fmt.bufPrint(&desc_line, "# {s}: {s}\n", .{ opt_name, opt_desc }) catch ""; const opt_line = std.fmt.bufPrint(&desc_line, "# {s}: {s}\n", .{ opt_name, opt_desc }) catch "";
try file.writeAll(opt_line); try file.writeStreamingAll(io, opt_line);
} }
} }
@ -609,7 +617,7 @@ pub fn save(
v.config_key, v.config_key,
value, value,
}) catch unreachable; }) catch unreachable;
try file.writeAll(line); try file.writeStreamingAll(io, line);
} else { } else {
// Formato simple: comentario inline con opciones entre corchetes // Formato simple: comentario inline con opciones entre corchetes
var comment_buf: [256]u8 = undefined; var comment_buf: [256]u8 = undefined;
@ -626,21 +634,21 @@ pub fn save(
spaces(padding), spaces(padding),
comment, comment,
}) catch unreachable; }) catch unreachable;
try file.writeAll(line); try file.writeStreamingAll(io, line);
} else { } else {
const line = std.fmt.bufPrint(&line_buf, "{s}: {s}\n", .{ const line = std.fmt.bufPrint(&line_buf, "{s}: {s}\n", .{
v.config_key, v.config_key,
value, value,
}) catch unreachable; }) catch unreachable;
try file.writeAll(line); try file.writeStreamingAll(io, line);
} }
} }
} }
try file.writeAll("\n"); try file.writeStreamingAll(io, "\n");
try file.writeAll("# ============================================================================\n"); try file.writeStreamingAll(io, "# ============================================================================\n");
try file.writeAll("# Fin del archivo de configuracion\n"); try file.writeStreamingAll(io, "# Fin del archivo de configuracion\n");
try file.writeAll("# ============================================================================\n"); try file.writeStreamingAll(io, "# ============================================================================\n");
} }
/// Genera string de N espacios (maximo 64) /// Genera string de N espacios (maximo 64)
@ -649,13 +657,19 @@ fn spaces(n: usize) []const u8 {
return space_buf[0..@min(n, 64)]; return space_buf[0..@min(n, 64)];
} }
fn currentTimestamp() i64 {
const ts = std.posix.clock_gettime(std.posix.CLOCK.REALTIME) catch return 0;
return ts.sec;
}
/// Crea backup del archivo de configuracion /// Crea backup del archivo de configuracion
pub fn createBackup(path: []const u8) !void { pub fn createBackup(io: std.Io, path: []const u8) !void {
const now = std.time.timestamp(); const now = currentTimestamp();
var backup_path_buf: [512]u8 = undefined; var backup_path_buf: [512]u8 = undefined;
const backup_path = std.fmt.bufPrint(&backup_path_buf, "{s}.{d}.bak", .{ path, now }) catch return; const backup_path = std.fmt.bufPrint(&backup_path_buf, "{s}.{d}.bak", .{ path, now }) catch return;
std.fs.cwd().copyFile(path, std.fs.cwd(), backup_path, .{}) catch |err| { const my_cwd = std.Io.Dir.cwd();
my_cwd.copyFile(my_cwd, path, my_cwd, backup_path, io, .{}) catch |err| {
if (err == error.FileNotFound) return; if (err == error.FileNotFound) return;
return err; return err;
}; };
@ -720,6 +734,7 @@ pub fn ConfigManager(
/// - Crea Config con valores por defecto /// - Crea Config con valores por defecto
/// - Llama loadOrCreate() automaticamente /// - Llama loadOrCreate() automaticamente
pub fn init( pub fn init(
io: std.Io,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
file_path: []const u8, file_path: []const u8,
) !Self { ) !Self {
@ -732,7 +747,7 @@ pub fn ConfigManager(
}; };
// Cargar o crear archivo automaticamente // Cargar o crear archivo automaticamente
try self.loadOrCreate(); try self.loadOrCreate(io);
return self; return self;
} }
@ -756,10 +771,10 @@ pub fn ConfigManager(
} }
/// Libera recursos y guarda si hay cambios pendientes /// Libera recursos y guarda si hay cambios pendientes
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self, io: std.Io) void {
// Auto-save si hay cambios // Auto-save si hay cambios
if (self.dirty) { if (self.dirty) {
self.save() catch {}; self.save(io) catch {};
} }
// Liberar config // Liberar config
@ -834,23 +849,23 @@ pub fn ConfigManager(
} }
/// Carga configuracion desde archivo /// Carga configuracion desde archivo
pub fn load(self: *Self) !void { pub fn load(self: *Self, io: std.Io) !void {
try loadFn(variables, ConfigType, &self.config, self.file_path); try loadFn(io, variables, ConfigType, &self.config, self.file_path);
self.dirty = false; self.dirty = false;
} }
/// Guarda configuracion a archivo /// Guarda configuracion a archivo
pub fn save(self: *Self) !void { pub fn save(self: *Self, io: std.Io) !void {
try saveFn(variables, ConfigType, &self.config, self.file_path, app_name); try saveFn(io, variables, ConfigType, &self.config, self.file_path, app_name);
self.dirty = false; self.dirty = false;
} }
/// Carga configuracion o crea archivo con defaults si no existe /// Carga configuracion o crea archivo con defaults si no existe
pub fn loadOrCreate(self: *Self) !void { pub fn loadOrCreate(self: *Self, io: std.Io) !void {
self.load() catch |err| { self.load(io) catch |err| {
if (err == error.FileNotFound) { if (err == error.FileNotFound) {
// Archivo no existe - guardar defaults // Archivo no existe - guardar defaults
try self.save(); try self.save(io);
return; return;
} }
return err; return err;
@ -918,7 +933,7 @@ const saveFn = save;
/// ``` /// ```
pub const FileWatcher = struct { pub const FileWatcher = struct {
path: []const u8, path: []const u8,
last_mtime: i128 = 0, last_mtime: i96 = 0,
check_interval_ms: i64 = 1000, check_interval_ms: i64 = 1000,
last_check: i64 = 0, last_check: i64 = 0,
@ -935,8 +950,8 @@ pub const FileWatcher = struct {
/// Verifica si el archivo ha cambiado desde la ultima verificacion /// Verifica si el archivo ha cambiado desde la ultima verificacion
/// Respeta el intervalo minimo entre verificaciones /// Respeta el intervalo minimo entre verificaciones
/// @return true si el archivo cambio, false si no o si hubo error /// @return true si el archivo cambio, false si no o si hubo error
pub fn checkForChanges(self: *FileWatcher) bool { pub fn checkForChanges(self: *FileWatcher, io: std.Io) bool {
const now = std.time.milliTimestamp(); const now = currentMilliTimestamp();
// Respetar intervalo minimo // Respetar intervalo minimo
if (now - self.last_check < self.check_interval_ms) { if (now - self.last_check < self.check_interval_ms) {
@ -945,11 +960,11 @@ pub const FileWatcher = struct {
self.last_check = now; self.last_check = now;
// Obtener mtime del archivo // Obtener mtime del archivo
const stat = std.fs.cwd().statFile(self.path) catch { const stat = std.Io.Dir.cwd().statFile(io, self.path, .{}) catch {
return false; return false;
}; };
const file_mtime = stat.mtime; const file_mtime = stat.mtime.nanoseconds;
// Primera vez - guardar mtime inicial // Primera vez - guardar mtime inicial
if (self.last_mtime == 0) { if (self.last_mtime == 0) {
@ -974,9 +989,9 @@ pub const FileWatcher = struct {
/// Fuerza actualizar el mtime sin detectar cambio /// Fuerza actualizar el mtime sin detectar cambio
/// Util despues de guardar el archivo /// Util despues de guardar el archivo
pub fn updateMtime(self: *FileWatcher) void { pub fn updateMtime(self: *FileWatcher, io: std.Io) void {
const stat = std.fs.cwd().statFile(self.path) catch return; const stat = std.Io.Dir.cwd().statFile(io, self.path, .{}) catch return;
self.last_mtime = stat.mtime; self.last_mtime = stat.mtime.nanoseconds;
} }
}; };
@ -1206,7 +1221,7 @@ test "FileWatcher init" {
const watcher = FileWatcher.init("test.txt", 500); const watcher = FileWatcher.init("test.txt", 500);
try std.testing.expectEqualStrings("test.txt", watcher.path); try std.testing.expectEqualStrings("test.txt", watcher.path);
try std.testing.expectEqual(@as(i64, 500), watcher.check_interval_ms); try std.testing.expectEqual(@as(i64, 500), watcher.check_interval_ms);
try std.testing.expectEqual(@as(i128, 0), watcher.last_mtime); try std.testing.expectEqual(@as(i96, 0), watcher.last_mtime);
} }
test "loadFromString basic" { test "loadFromString basic" {