diff --git a/ly-core/build.zig b/ly-core/build.zig index 0fff11c..0671528 100644 --- a/ly-core/build.zig +++ b/ly-core/build.zig @@ -9,6 +9,9 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize }); + mod.addImport("zigini", zigini.module("zigini")); + const mod_tests = b.addTest(.{ .root_module = mod, }); diff --git a/ly-core/build.zig.zon b/ly-core/build.zig.zon index 13a7faf..98233c5 100644 --- a/ly-core/build.zig.zon +++ b/ly-core/build.zig.zon @@ -3,7 +3,12 @@ .version = "1.0.0", .fingerprint = 0xddda7afda795472, .minimum_zig_version = "0.15.0", - .dependencies = .{}, + .dependencies = .{ + .zigini = .{ + .url = "git+https://github.com/AnErrupTion/zigini?ref=zig-0.15.0#9281f47702b57779e831d7618e158abb8eb4d4a2", + .hash = "zigini-0.3.3-36M0FRJJAADZVq5HPm-hYKMpFFTr0OgjbEYcK2ijKZ5n", + }, + }, .paths = .{ "build.zig", "build.zig.zon", diff --git a/ly-core/src/root.zig b/ly-core/src/root.zig index afc7ea0..a7929f4 100644 --- a/ly-core/src/root.zig +++ b/ly-core/src/root.zig @@ -1,4 +1,76 @@ +const std = @import("std"); +const ini = @import("zigini"); + pub const interop = @import("interop.zig"); pub const UidRange = @import("UidRange.zig"); pub const LogFile = @import("LogFile.zig"); pub const SharedError = @import("SharedError.zig"); + +pub fn IniParser(comptime Struct: type) type { + return struct { + const Self = @This(); + const temporary_allocator = std.heap.page_allocator; + + pub const Error = struct { + type_name: []const u8, + key: []const u8, + value: []const u8, + error_name: []const u8, + }; + pub var global_errors: std.ArrayList(Error) = .empty; + + ini_struct: ini.Ini(Struct), + structure: Struct, + maybe_load_error: ?anyerror, + errors: std.ArrayList(Error), + + pub fn init( + allocator: std.mem.Allocator, + path: []const u8, + field_handler: ?fn (allocator: std.mem.Allocator, field: ini.IniField) ?ini.IniField, + ) !Self { + var ini_struct = ini.Ini(Struct).init(allocator); + errdefer ini_struct.deinit(); + + var maybe_load_error: ?anyerror = null; + + const structure = ini_struct.readFileToStruct(path, .{ + .fieldHandler = field_handler, + .errorHandler = errorHandler, + .comment_characters = "#", + }) catch |err| load_error: { + maybe_load_error = err; + break :load_error Struct{}; + }; + + return .{ + .ini_struct = ini_struct, + .structure = structure, + .maybe_load_error = maybe_load_error, + .errors = global_errors, + }; + } + + pub fn deinit(self: *Self) void { + self.ini_struct.deinit(); + + for (0..global_errors.items.len) |i| { + const err = global_errors.items[i]; + temporary_allocator.free(err.type_name); + temporary_allocator.free(err.key); + temporary_allocator.free(err.value); + } + + global_errors.deinit(temporary_allocator); + } + + fn errorHandler(type_name: []const u8, key: []const u8, value: []const u8, err: anyerror) void { + global_errors.append(temporary_allocator, .{ + .type_name = temporary_allocator.dupe(u8, type_name) catch return, + .key = temporary_allocator.dupe(u8, key) catch return, + .value = temporary_allocator.dupe(u8, value) catch return, + .error_name = @errorName(err), + }) catch return; + } + }; +} diff --git a/src/config/migrator.zig b/src/config/migrator.zig index 124ec89..cfd69cc 100644 --- a/src/config/migrator.zig +++ b/src/config/migrator.zig @@ -4,11 +4,14 @@ const std = @import("std"); const ini = @import("zigini"); +const ly_core = @import("ly-core"); const Config = @import("Config.zig"); const OldSave = @import("OldSave.zig"); const SavedUsers = @import("SavedUsers.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); +const IniParser = ly_core.IniParser; + const Color = TerminalBuffer.Color; const Styling = TerminalBuffer.Styling; @@ -187,13 +190,40 @@ pub fn lateConfigFieldHandler(config: *Config) void { } } -pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave { - var save = OldSave{}; +pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !?IniParser(OldSave) { + var save_parser = try IniParser(OldSave).init(allocator, path, null); + errdefer save_parser.deinit(); + var user_buf: [32]u8 = undefined; + const maybe_save = if (save_parser.maybe_load_error == null) save_parser.structure else tryMigrateFirstSaveFile(&user_buf); + + if (maybe_save) |save| { + // Add all other users to the list + for (usernames, 0..) |username, i| { + if (save.user) |user| { + if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i; + } + + try saved_users.user_list.append(allocator, .{ + .username = username, + .session_index = save.session_index orelse 0, + .first_run = false, + .allocated_username = false, + }); + } + + return save_parser; + } + + return null; +} + +fn tryMigrateFirstSaveFile(user_buf: *[32]u8) ?OldSave { if (maybe_save_file) |path| { defer temporary_allocator.free(path); - var file = std.fs.openFileAbsolute(path, .{}) catch return save; + var save = OldSave{}; + var file = std.fs.openFileAbsolute(path, .{}) catch return null; defer file.close(); var file_buffer: [64]u8 = undefined; @@ -201,50 +231,21 @@ pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave { var reader = &file_reader.interface; var user_writer = std.Io.Writer.fixed(user_buf); - var written = reader.streamDelimiter(&user_writer, '\n') catch return save; + var written = reader.streamDelimiter(&user_writer, '\n') catch return null; if (written > 0) save.user = user_buf[0..written]; var session_buf: [20]u8 = undefined; var session_writer = std.Io.Writer.fixed(&session_buf); - written = reader.streamDelimiter(&session_writer, '\n') catch return save; + written = reader.streamDelimiter(&session_writer, '\n') catch return null; var session_index: ?usize = null; if (written > 0) { - session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return save; + session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return null; } save.session_index = session_index; + + return save; } - return save; -} - -pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, save_ini: *ini.Ini(OldSave), path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !bool { - var old_save_file_exists = true; - - var user_buf: [32]u8 = undefined; - const save = save_ini.readFileToStruct(path, .{ - .fieldHandler = null, - .comment_characters = "#", - }) catch no_save_file: { - old_save_file_exists = false; - break :no_save_file tryMigrateFirstSaveFile(&user_buf); - }; - - if (!old_save_file_exists) return false; - - // Add all other users to the list - for (usernames, 0..) |username, i| { - if (save.user) |user| { - if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i; - } - - try saved_users.user_list.append(allocator, .{ - .username = username, - .session_index = save.session_index orelse 0, - .first_run = false, - .allocated_username = false, - }); - } - - return true; + return null; } diff --git a/src/main.zig b/src/main.zig index a164b79..a8c4bda 100644 --- a/src/main.zig +++ b/src/main.zig @@ -33,6 +33,7 @@ const interop = ly_core.interop; const UidRange = ly_core.UidRange; const LogFile = ly_core.LogFile; const SharedError = ly_core.SharedError; +const IniParser = ly_core.IniParser; const termbox = TerminalBuffer.termbox; const temporary_allocator = std.heap.page_allocator; const ly_version_str = "Ly version " ++ build_options.version; @@ -56,14 +57,6 @@ fn ttyControlTransferSignalHandler(_: c_int) callconv(.c) void { TerminalBuffer.shutdownStatic(); } -const ConfigError = struct { - type_name: []const u8, - key: []const u8, - value: []const u8, - error_name: []const u8, -}; -var config_errors: std.ArrayList(ConfigError) = .empty; - const UiState = struct { auth_fails: u64, update: bool, @@ -145,15 +138,15 @@ pub fn main() !void { }; defer if (maybe_res) |*res| res.deinit(); - var config: Config = undefined; - var lang: Lang = undefined; - var old_save_file_exists = false; - var maybe_config_load_error: ?anyerror = null; + var old_save_parser: ?IniParser(OldSave) = null; + defer if (old_save_parser) |*str| str.deinit(); + var start_cmd_exit_code: u8 = 0; var saved_users = SavedUsers.init(); defer saved_users.deinit(allocator); + var config_parent_path: []const u8 = build_options.config_directory ++ "/ly"; if (maybe_res) |*res| { if (res.args.help != 0) { try clap.help(stderr, clap.Help, ¶ms, .{}); @@ -167,79 +160,44 @@ pub fn main() !void { try stderr.flush(); std.process.exit(0); } + if (res.args.config) |path| config_parent_path = path; } // Load configuration file - var config_ini = Ini(Config).init(allocator); - defer config_ini.deinit(); - - var lang_ini = Ini(Lang).init(allocator); - defer lang_ini.deinit(); - - var old_save_ini = ini.Ini(OldSave).init(allocator); - defer old_save_ini.deinit(); - var save_path: []const u8 = build_options.config_directory ++ "/ly/save.txt"; var old_save_path: []const u8 = build_options.config_directory ++ "/ly/save.ini"; var save_path_alloc = false; - defer { - if (save_path_alloc) allocator.free(save_path); - if (save_path_alloc) allocator.free(old_save_path); + defer if (save_path_alloc) { + allocator.free(save_path); + allocator.free(old_save_path); + }; + + const config_path = try std.fs.path.join(allocator, &[_][]const u8{ config_parent_path, "config.ini" }); + defer allocator.free(config_path); + + var config_parser = try IniParser(Config).init(allocator, config_path, migrator.configFieldHandler); + defer config_parser.deinit(); + + var config = config_parser.structure; + + var lang_buffer: [16]u8 = undefined; + const lang_file = try std.fmt.bufPrint(&lang_buffer, "{s}.ini", .{config.lang}); + + const lang_path = try std.fs.path.join(allocator, &[_][]const u8{ config_parent_path, "lang", lang_file }); + defer allocator.free(lang_path); + + var lang_parser = try IniParser(Lang).init(allocator, lang_path, null); + defer lang_parser.deinit(); + + const lang = lang_parser.structure; + + if (config.save) { + save_path = try std.fs.path.join(allocator, &[_][]const u8{ config_parent_path, "save.txt" }); + old_save_path = try std.fs.path.join(allocator, &[_][]const u8{ config_parent_path, "save.ini" }); + save_path_alloc = true; } - const comment_characters = "#"; - - if (maybe_res != null and maybe_res.?.args.config != null) { - const s = maybe_res.?.args.config.?; - const trailing_slash = if (s[s.len - 1] != '/') "/" else ""; - - const config_path = try std.fmt.allocPrint(allocator, "{s}{s}config.ini", .{ s, trailing_slash }); - defer allocator.free(config_path); - - config = config_ini.readFileToStruct(config_path, .{ - .fieldHandler = migrator.configFieldHandler, - .errorHandler = configErrorHandler, - .comment_characters = comment_characters, - }) catch |err| load_error: { - maybe_config_load_error = err; - break :load_error Config{}; - }; - - const lang_path = try std.fmt.allocPrint(allocator, "{s}{s}lang/{s}.ini", .{ s, trailing_slash, config.lang }); - defer allocator.free(lang_path); - - lang = lang_ini.readFileToStruct(lang_path, .{ - .fieldHandler = null, - .comment_characters = comment_characters, - }) catch Lang{}; - - if (config.save) { - save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.txt", .{ s, trailing_slash }); - old_save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.ini", .{ s, trailing_slash }); - save_path_alloc = true; - } - } else { - const config_path = build_options.config_directory ++ "/ly/config.ini"; - - config = config_ini.readFileToStruct(config_path, .{ - .fieldHandler = migrator.configFieldHandler, - .errorHandler = configErrorHandler, - .comment_characters = comment_characters, - }) catch |err| load_error: { - maybe_config_load_error = err; - break :load_error Config{}; - }; - - const lang_path = try std.fmt.allocPrint(allocator, "{s}/ly/lang/{s}.ini", .{ build_options.config_directory, config.lang }); - defer allocator.free(lang_path); - - lang = lang_ini.readFileToStruct(lang_path, .{ - .fieldHandler = null, - .comment_characters = comment_characters, - }) catch Lang{}; - } - - if (maybe_config_load_error == null) { + if (config_parser.maybe_load_error == null) { migrator.lateConfigFieldHandler(&config); } @@ -251,10 +209,10 @@ pub fn main() !void { } if (config.save) read_save_file: { - old_save_file_exists = migrator.tryMigrateIniSaveFile(allocator, &old_save_ini, old_save_path, &saved_users, usernames.items) catch break :read_save_file; + old_save_parser = migrator.tryMigrateIniSaveFile(allocator, old_save_path, &saved_users, usernames.items) catch break :read_save_file; // Don't read the new save file if the old one still exists - if (old_save_file_exists) break :read_save_file; + if (old_save_parser != null) break :read_save_file; var save_file = std.fs.cwd().openFile(save_path, .{}) catch break :read_save_file; defer save_file.close(); @@ -378,22 +336,13 @@ pub fn main() !void { try log_file.err("sys", "failed to execute start command: exit code {d}", .{start_cmd_exit_code}); } - if (maybe_config_load_error) |err| { + if (config_parser.maybe_load_error) |load_error| { // We can't localize this since the config failed to load so we'd fallback to the default language anyway try info_line.addMessage("unable to parse config file", config.error_bg, config.error_fg); - try log_file.err("conf", "unable to parse config file: {s}", .{@errorName(err)}); + try log_file.err("conf", "unable to parse config file: {s}", .{@errorName(load_error)}); - defer config_errors.deinit(temporary_allocator); - - for (0..config_errors.items.len) |i| { - const config_error = config_errors.items[i]; - defer { - temporary_allocator.free(config_error.type_name); - temporary_allocator.free(config_error.key); - temporary_allocator.free(config_error.value); - } - - try log_file.err("conf", "failed to convert value '{s}' of option '{s}' to type '{s}': {s}", .{ config_error.value, config_error.key, config_error.type_name, config_error.error_name }); + for (config_parser.errors.items) |err| { + try log_file.err("conf", "failed to convert value '{s}' of option '{s}' to type '{s}': {s}", .{ err.value, err.key, err.type_name, err.error_name }); } } @@ -866,7 +815,7 @@ pub fn main() !void { // Delete previous save file if it exists if (migrator.maybe_save_file) |path| { std.fs.cwd().deleteFile(path) catch {}; - } else if (old_save_file_exists) { + } else if (old_save_parser != null) { std.fs.cwd().deleteFile(old_save_path) catch {}; } } @@ -1201,15 +1150,6 @@ fn drawUi(config: Config, lang: Lang, log_file: *LogFile, state: *UiState) !bool return true; } -fn configErrorHandler(type_name: []const u8, key: []const u8, value: []const u8, err: anyerror) void { - config_errors.append(temporary_allocator, .{ - .type_name = temporary_allocator.dupe(u8, type_name) catch return, - .key = temporary_allocator.dupe(u8, key) catch return, - .value = temporary_allocator.dupe(u8, value) catch return, - .error_name = @errorName(err), - }) catch return; -} - fn addOtherEnvironment(session: *Session, lang: Lang, display_server: DisplayServer, exec: ?[]const u8) !void { const name = switch (display_server) { .shell => lang.shell,