diff --git a/build.zig.zon b/build.zig.zon index 2ad442d..520e04a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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 = .{}, diff --git a/src/zcatconfig.zig b/src/zcatconfig.zig index c537b49..605014e 100644 --- a/src/zcatconfig.zig +++ b/src/zcatconfig.zig @@ -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" {