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,
.version = "0.1.0",
.minimum_zig_version = "0.15.0",
.minimum_zig_version = "0.16.0",
.fingerprint = 0x6211a84c80e77eb,
.dependencies = .{},

View file

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