338 lines
10 KiB
Zig
338 lines
10 KiB
Zig
pub const ConfigRegistry = struct {
|
|
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.ConfigRegistry");
|
|
|
|
allocator: std.mem.Allocator = undefined,
|
|
configMap: ?ConfigMap = null,
|
|
|
|
pub fn create(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
|
|
if (!first)
|
|
return;
|
|
|
|
self.* = .{
|
|
.allocator = allocator,
|
|
};
|
|
}
|
|
|
|
// by convention this should be in the root
|
|
// content path
|
|
//
|
|
// with the name <programName>.toml
|
|
pub fn loadConfigFile(self: *@This(), configName: []const u8) !void {
|
|
const configFile = core.fs().loadFile(configName) catch {
|
|
core.engine_log("unable to load configuration file {s}", .{configName});
|
|
return;
|
|
};
|
|
defer core.fs().unmap(configFile);
|
|
|
|
const parser = try Parser.create(self.allocator);
|
|
parser.fileName = configName;
|
|
defer parser.destroy();
|
|
|
|
self.configMap = try parser.parseBytes(self.allocator, configFile.bytesNoEnd());
|
|
|
|
core.engine_log("config file loaded {d} keys set", .{self.configMap.?.mapValues.count()});
|
|
|
|
// parse and tokenize the config file
|
|
// self.tokenizeConfigFile();
|
|
|
|
}
|
|
|
|
pub fn destroy(self: *@This()) void {
|
|
if (self.configMap) |*map| {
|
|
map.deinit();
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn configVar(comptime T: type, configName: []const u8, default: T) T {
|
|
return getConfigVar(T, configName) orelse default;
|
|
}
|
|
|
|
pub const getConfigRegistry = core.EngineObject(ConfigRegistry).get;
|
|
|
|
pub fn getConfigVar(comptime T: type, configName: []const u8) ?T {
|
|
const ctx = getConfigRegistry();
|
|
|
|
if (ctx.configMap) |map| {
|
|
if (map.mapValues.get(configName)) |v| {
|
|
switch (v.value) {
|
|
.number => |number| {
|
|
switch (@typeInfo(T)) {
|
|
.int => {
|
|
return @intFromFloat(number);
|
|
},
|
|
.float => {
|
|
return @floatCast(number);
|
|
},
|
|
else => {
|
|
core.engine_err("tried to get config {s} but it isn't a number", .{configName});
|
|
return null;
|
|
},
|
|
}
|
|
},
|
|
.string => |string| {
|
|
switch (@typeInfo(T)) {
|
|
.pointer => |pointerInfo| {
|
|
if (pointerInfo.child == u8) {
|
|
return string;
|
|
}
|
|
},
|
|
else => {
|
|
core.engine_err("tried to get config {s} but it isn't a string", .{configName});
|
|
return null;
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const lastSetBy_referenceInit: []const u8 = "reference";
|
|
const lastSetBy_configFile: []const u8 = "config file";
|
|
|
|
pub const ConfigValueEntry = struct {
|
|
lastSetBy: []const u8 = lastSetBy_referenceInit,
|
|
value: union(enum(u8)) {
|
|
number: f64,
|
|
string: []const u8,
|
|
},
|
|
};
|
|
|
|
pub const ConfigMap = struct {
|
|
backingAllocator: std.mem.Allocator,
|
|
mapValues: std.StringHashMapUnmanaged(ConfigValueEntry) = .{},
|
|
arena: std.heap.ArenaAllocator,
|
|
//stringList: std.ArrayListUnmanaged([]const u8) = .{},
|
|
|
|
pub fn init(allocator: std.mem.Allocator) !@This() {
|
|
return .{
|
|
.backingAllocator = allocator,
|
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
};
|
|
}
|
|
|
|
pub fn pushMapValue(self: *@This(), key: []const u8, value: []const u8) !void {
|
|
// take ownership of both strings
|
|
const allocator = self.arena.allocator();
|
|
|
|
const k = try allocator.dupe(u8, key);
|
|
|
|
var mapValue: ConfigValueEntry = .{ .value = undefined };
|
|
mapValue.lastSetBy = lastSetBy_configFile;
|
|
|
|
if (value[0] == '"') {
|
|
if (value[value.len - 1] != '"') {
|
|
return error.MissingClosingQuote;
|
|
}
|
|
|
|
mapValue.value = .{
|
|
.string = try allocator.dupe(u8, value[1 .. value.len - 1]),
|
|
};
|
|
} else if (std.fmt.parseFloat(f64, value) catch null) |flt| {
|
|
mapValue.value = .{ .number = flt };
|
|
} else {
|
|
return error.UnableToParse;
|
|
}
|
|
|
|
try self.mapValues.put(allocator, k, mapValue);
|
|
}
|
|
|
|
pub fn deinit(self: *@This()) void {
|
|
self.arena.deinit();
|
|
}
|
|
};
|
|
|
|
// A Very nonstandard and very dumb ini parser
|
|
pub const Parser = struct {
|
|
backingAllocator: std.mem.Allocator,
|
|
arena: std.heap.ArenaAllocator,
|
|
allocator: std.mem.Allocator = undefined,
|
|
|
|
fileName: []const u8 = "unknown file",
|
|
lineNumber: usize = 0,
|
|
lineEnd: usize = 0,
|
|
lineStart: usize = 0,
|
|
bytes: []const u8 = undefined,
|
|
outputMap: ?ConfigMap = null,
|
|
|
|
configSection: ?[]const u8 = null,
|
|
|
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
|
const self = try allocator.create(@This());
|
|
|
|
self.* = .{
|
|
.backingAllocator = allocator,
|
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
};
|
|
self.allocator = self.arena.allocator();
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn nextLine(self: *@This()) ?[]const u8 {
|
|
while (std.ascii.isWhitespace(self.bytes[self.lineEnd])) : (self.lineEnd += 1) {
|
|
if (self.lineEnd + 1 >= self.bytes.len) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
self.lineStart = self.lineEnd;
|
|
|
|
while (self.bytes[self.lineEnd] != '\r' and self.bytes[self.lineEnd] != '\n') : (self.lineEnd += 1) {
|
|
if (self.lineEnd + 1 >= self.bytes.len) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
self.lineNumber += 1;
|
|
|
|
if (self.bytes[self.lineStart] == 0) {
|
|
return null;
|
|
}
|
|
|
|
return self.bytes[self.lineStart..self.lineEnd];
|
|
}
|
|
|
|
fn stripWhiteSpace(l: []const u8) []const u8 {
|
|
if (l.len == 0) {
|
|
return l;
|
|
}
|
|
|
|
var s: usize = 0;
|
|
var e: usize = l.len;
|
|
|
|
while (std.ascii.isWhitespace(l[s]) and s < l.len) : (s += 1) {}
|
|
|
|
while (std.ascii.isWhitespace(l[e - 1]) and e - 1 > 0) : (e -= 1) {}
|
|
|
|
return l[s..e];
|
|
}
|
|
|
|
fn printError(self: *@This(), comptime fmt: []const u8, args: anytype) !void {
|
|
if (core.getLogger()) |logger| {
|
|
try logger.print(core.colors.Color.Red, fmt, args);
|
|
try logger.print(core.colors.Color.Red, "[Config ]: Config File Parse Error> {s}:{d} \n", .{ self.fileName, self.lineNumber });
|
|
}
|
|
}
|
|
|
|
pub fn parseLabel(self: *@This(), l: []const u8) !?[]const u8 {
|
|
if (l[l.len - 1] != ']') {
|
|
core.engine_err("\n expected closing brace ] in label..\n fatal error\n{d}: {s} \n{s}:{d}", .{ self.lineNumber, l, self.fileName, self.lineNumber });
|
|
try self.printError("expected closing brace ] in label statement. fatal.", .{});
|
|
return error.UnableToParse;
|
|
}
|
|
|
|
var line = l[1 .. l.len - 1];
|
|
line = stripWhiteSpace(line);
|
|
// core.engine_log("label : '{s}'", .{line});
|
|
|
|
return line;
|
|
}
|
|
|
|
pub fn parseStatement(self: *@This(), line: []const u8) !?struct { left: []const u8, right: []const u8 } {
|
|
var s: usize = 0;
|
|
|
|
while (s < line.len and line[s] != '=') : (s += 1) {}
|
|
|
|
if (s == line.len) {
|
|
try self.printError("missing right hand side of assignment after '=' {s} skipping..", .{line});
|
|
return null;
|
|
}
|
|
|
|
const left = stripWhiteSpace(line[0..s]);
|
|
const right = stripWhiteSpace(line[s + 1 .. line.len]);
|
|
|
|
// core.engine_log("statement : left '{s}' '{s}'", .{ left, right });
|
|
|
|
return .{ .left = left, .right = right };
|
|
}
|
|
|
|
// line should already be whitespace stripped
|
|
pub fn parseLine(self: *@This(), l: []const u8) !void {
|
|
var i: usize = 0;
|
|
var line = l;
|
|
|
|
// strip comments
|
|
while (i < line.len) : (i += 1) {
|
|
if (line[i] == '#') {
|
|
line = line[0..i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
|
|
while (i < line.len) : (i += 1) {
|
|
if (line[i] == '[') {
|
|
const section = try self.parseLabel(line[i..line.len]);
|
|
if (section) |sect| {
|
|
self.configSection = sect;
|
|
}
|
|
break;
|
|
}
|
|
if (line[i] == '=') {
|
|
if (try self.parseStatement(line)) |statement| {
|
|
var fullVarName = statement.left;
|
|
defer self.allocator.free(fullVarName);
|
|
if (self.configSection) |section| {
|
|
// use temporary allocator
|
|
fullVarName = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ section, fullVarName });
|
|
}
|
|
|
|
// takes ownership of both strings;
|
|
try self.outputMap.?.pushMapValue(fullVarName, statement.right);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn parseBytes(self: *@This(), allocator: std.mem.Allocator, bytes: []const u8) !ConfigMap {
|
|
self.outputMap = try ConfigMap.init(allocator);
|
|
|
|
self.bytes = bytes;
|
|
if (self.bytes.len == 0) {
|
|
return self.outputMap.?;
|
|
}
|
|
|
|
self.lineStart = 0;
|
|
self.lineEnd = 0;
|
|
|
|
while (self.nextLine()) |line| {
|
|
// core.engine_log("ConfigParser > {s}", .{line});
|
|
self.parseLine(line) catch break;
|
|
}
|
|
|
|
return self.outputMap.?;
|
|
}
|
|
|
|
pub fn destroy(self: *@This()) void {
|
|
self.arena.deinit();
|
|
self.backingAllocator.destroy(self);
|
|
}
|
|
};
|
|
|
|
pub fn setupConfigs(configFilePath: []const u8) !void {
|
|
_ = try core.createObject(ConfigRegistry, .{});
|
|
core.engine_log("loading configs {s}", .{configFilePath});
|
|
try getConfigRegistry().loadConfigFile(configFilePath);
|
|
}
|
|
|
|
pub fn ConfigEntry(comptime T: type) type {
|
|
return struct {
|
|
value: ?T = null,
|
|
default: T,
|
|
configName: []const u8,
|
|
|
|
pub fn make(name: []const u8, default: T) void {
|
|
return .{ .default = default, .name = name };
|
|
}
|
|
};
|
|
}
|
|
|
|
const core = @import("core.zig");
|
|
const std = @import("std");
|