srctree

Gregory Mullen parent ea202096 b1d50d05
refactor the ini api

Trade off some more internal complexity for a much simpler public api

inlinesplit
src/endpoints/commit-flex.zig added: 162, removed: 167, total 0
@@ -422,7 +422,7 @@ const allocPrint = std.fmt.allocPrint;
const DateTime = @import("../datetime.zig");
const Git = @import("../git.zig");
 
const global_config = &@import("../main.zig").global_config;
const global_config = &@import("../main.zig").global_config.config;
 
const Verse = @import("verse");
const Template = Verse.template;
 
src/endpoints/settings.zig added: 162, removed: 167, total 0
@@ -11,19 +11,17 @@ pub fn index(vrs: *Frame) Router.Error!void {
 
var blocks: []S.ConfigBlocks = &[0]S.ConfigBlocks{};
 
if (global_ini.*) |cfg| {
blocks = try vrs.alloc.alloc(S.ConfigBlocks, cfg.ns.len);
for (cfg.ns, 0..) |ns, i| {
blocks[i] = .{
.config_name = ns.name,
.config_text = ns.block,
.count = try std.fmt.allocPrint(
vrs.alloc,
"{}",
.{std.mem.count(u8, ns.block, "\n") + 2},
),
};
}
blocks = try vrs.alloc.alloc(S.ConfigBlocks, global_config.ctx.ns.len);
for (global_config.ctx.ns, 0..) |ns, i| {
blocks[i] = .{
.config_name = ns.name,
.config_text = ns.block,
.count = try std.fmt.allocPrint(
vrs.alloc,
"{}",
.{std.mem.count(u8, ns.block, "\n") + 2},
),
};
}
 
var page = SettingsPage.init(.{
@@ -59,4 +57,4 @@ const template = verse.template;
const S = template.Structs;
const Router = verse.Router;
const RequestData = verse.RequestData.RequestData;
const global_ini = &@import("../main.zig").root_ini;
const global_config = &@import("../main.zig").global_config;
 
src/git.zig added: 162, removed: 167, total 0
@@ -180,10 +180,11 @@ pub const Repo = struct {
var list = std.ArrayList(Remote).init(a);
errdefer list.clearAndFree();
const config_data = try self.dir.readFileAlloc(a, "config", 0xffff);
const cfg = try Ini.Config(void).initOwned(a, config_data);
defer a.free(config_data);
const cfg = try Ini.Config(void).init(a, config_data);
defer cfg.raze(a);
for (0..cfg.ns.len) |i| {
const ns = cfg.filter("remote", i) orelse break;
for (0..cfg.ctx.ns.len) |i| {
const ns = cfg.ctx.filter("remote", i) orelse break;
try list.append(.{
.name = try a.dupe(u8, std.mem.trim(u8, ns.name[6..], "' \t\n\"")),
.url = if (ns.get("url")) |url| try a.dupe(u8, url) else null,
 
src/ini.zig added: 162, removed: 167, total 0
@@ -86,42 +86,70 @@ pub const Namespace = struct {
}
};
 
pub fn Config(Base: anytype) type {
pub fn Config(B: anytype) type {
return struct {
ns: []Namespace,
data: []const u8,
owned: ?[]const u8,
config: Base,
ctx: IniData,
 
pub const Base = B;
 
pub const IniData = struct {
ns: []Namespace,
data: []const u8,
owned: ?[]const u8,
 
pub fn filter(ctx: IniData, prefix: []const u8, index: usize) ?Namespace {
var remaining = index;
for (ctx.ns) |ns| {
if (startsWith(u8, ns.name, prefix)) {
if (remaining == 0) return ns;
remaining -= 1;
}
} else return null;
}
 
pub fn get(ctx: IniData, name: []const u8) ?Namespace {
for (ctx.ns) |ns| {
if (eql(u8, ns.name, name)) {
return ns;
}
}
return null;
}
 
fn buildStruct(ctx: IniData, T: type, name: []const u8) !?T {
if (T == void) return {};
var namespace: T = undefined;
const ns = ctx.get(name) orelse return null;
inline for (@typeInfo(T).@"struct".fields) |s| {
switch (s.type) {
bool => {
@field(namespace, s.name) = ns.getBool(s.name) orelse brk: {
if (s.defaultValue()) |dv| {
break :brk dv;
} else return error.SettingMissing;
};
},
?bool => {
@field(namespace, s.name) = ns.getBool(s.name);
},
[]const u8 => {
@field(namespace, s.name) = ns.get(s.name) orelse return error.SettingMissing;
},
?[]const u8 => {
@field(namespace, s.name) = ns.get(s.name);
},
else => @compileError("not implemented"),
}
}
return namespace;
}
};
 
pub const Self = @This();
 
fn buildStruct(self: Self, T: type, name: []const u8) !?T {
var namespace: T = undefined;
const ns = self.get(name) orelse return null;
inline for (@typeInfo(T).@"struct".fields) |s| {
switch (s.type) {
bool => {
@field(namespace, s.name) = ns.getBool(s.name) orelse brk: {
if (s.defaultValue()) |dv| {
break :brk dv;
} else return error.SettingMissing;
};
},
?bool => {
@field(namespace, s.name) = ns.getBool(s.name);
},
[]const u8 => {
@field(namespace, s.name) = ns.get(s.name) orelse return error.SettingMissing;
},
?[]const u8 => {
@field(namespace, s.name) = ns.get(s.name);
},
else => @compileError("not implemented"),
}
}
return namespace;
}
 
pub fn config(self: Self) !Base {
fn makeBase(self: IniData) !Base {
if (Base == void) return {};
var base: Base = undefined;
inline for (@typeInfo(Base).@"struct".fields) |f| {
if (f.type == []const u8) continue; // Root variable not yet supported
@@ -139,43 +167,17 @@ pub fn Config(Base: anytype) type {
return base;
}
 
pub fn filter(self: Self, prefix: []const u8, index: usize) ?Namespace {
var remaining = index;
for (self.ns) |ns| {
if (startsWith(u8, ns.name, prefix)) {
if (remaining == 0) return ns;
remaining -= 1;
}
} else return null;
}
 
pub fn get(self: Self, name: []const u8) ?Namespace {
for (self.ns) |ns| {
if (eql(u8, ns.name, name)) {
return ns;
}
}
return null;
}
 
pub fn raze(self: Self, a: Allocator) void {
for (self.ns) |ns| {
for (self.ctx.ns) |ns| {
ns.raze(a);
}
a.free(self.ns);
if (self.owned) |owned| {
a.free(self.ctx.ns);
if (self.ctx.owned) |owned| {
a.free(owned);
}
}
 
pub fn initDupe(a: Allocator, ini: []const u8) !Self {
const owned = try a.dupe(u8, ini);
var c = try init(a, owned);
c.owned = c.data;
return c;
}
 
/// `data` must outlive returned Config, use initDupe otherwise
/// `data` must outlive returned object
pub fn init(a: Allocator, data: []const u8) !Self {
var itr = splitScalar(u8, data, '\n');
 
@@ -194,62 +196,51 @@ pub fn Config(Base: anytype) type {
}
}
 
return .{
const ctx: IniData = .{
.ns = try list.toOwnedSlice(),
.data = data,
.owned = null,
};
}
 
/// I'm not happy with this API. I think I deleted it once already... deleted
/// twice incoming!
pub fn initOwned(a: Allocator, data: []u8) !Self {
var c = try init(a, data);
c.owned = data;
return c;
return .{
.config = try makeBase(ctx),
.ctx = ctx,
};
}
 
pub fn fromFile(a: Allocator, file: std.fs.File) !Self {
const data = try file.readToEndAlloc(a, 1 <<| 18);
return try initOwned(a, data);
var self = try init(a, data);
self.ctx.data = data;
return self;
}
};
 
//const RealBase = @Type(.{
// .Struct = .{
// .layout = .auto,
// .is_tuple = false,
// .fields = &[_]std.builtin.Type.StructField{} ++
// @typeInfo(Base).Struct.fields[0..] ++
// @typeInfo(Real).Struct.fields[0..],
// .decls = &[_]std.builtin.Type.Declaration{} ++
// @typeInfo(Base).Struct.decls ++
// @typeInfo(Real).Struct.decls,
// },
//});
}
 
test "default" {
const a = std.testing.allocator;
 
const expected = Config(void){
.ns = @constCast(&[1]Namespace{
Namespace{
.name = @as([]u8, @constCast("one")),
.settings = @constCast(&[1]Setting{
.{
.name = "left",
.val = "right",
},
}),
.block = @constCast("left = right"),
},
}),
.data = @constCast("[one]\nleft = right"),
.owned = @constCast("[one]\nleft = right"),
.config = {},
.ctx = .{
.ns = @constCast(&[1]Namespace{
Namespace{
.name = @as([]u8, @constCast("one")),
.settings = @constCast(&[1]Setting{
.{
.name = "left",
.val = "right",
},
}),
.block = @constCast("left = right"),
},
}),
.data = @constCast("[one]\nleft = right"),
.owned = null,
},
};
 
const vtest = try Config(void).initDupe(a, "[one]\nleft = right");
const vtest = try Config(void).init(a, "[one]\nleft = right");
defer vtest.raze(a);
 
try std.testing.expectEqualDeep(expected, vtest);
@@ -266,7 +257,8 @@ test "getBool" {
\\sixth = FALSE
\\seventh = f
\\ eight = 0
\\ ninth = F
\\ ninth = F
++ " \n" ++ // intentional trailing spaces
\\tenth = failure
\\
;
@@ -277,7 +269,7 @@ test "getBool" {
const a = std.testing.allocator;
const c = try Cfg.init(a, data);
defer c.raze(a);
const ns = c.get("test data").?;
const ns = c.ctx.get("test data").?;
 
try std.testing.expectEqual(true, ns.getBool("first").?);
try std.testing.expectEqual(true, ns.getBool("second").?);
@@ -304,23 +296,26 @@ test "commented" {
;
 
const expected = Config(void){
.ns = @constCast(&[1]Namespace{
Namespace{
.name = @as([]u8, @constCast("open")),
.settings = @constCast(
&[2]Setting{
.{ .name = "left", .val = "right" },
.{ .name = "this", .val = "works" },
},
),
.block = @constCast(vut[7..]),
},
}),
.data = @constCast(vut),
.owned = @constCast(vut),
.config = {},
.ctx = .{
.ns = @constCast(&[1]Namespace{
Namespace{
.name = @as([]u8, @constCast("open")),
.settings = @constCast(
&[2]Setting{
.{ .name = "left", .val = "right" },
.{ .name = "this", .val = "works" },
},
),
.block = @constCast(vut[7..]),
},
}),
.data = @constCast(vut),
.owned = null,
},
};
 
const vtest = try Config(void).initDupe(a, vut);
const vtest = try Config(void).init(a, vut);
defer vtest.raze(a);
 
try std.testing.expectEqualDeep(expected, vtest);
 
src/main.zig added: 162, removed: 167, total 0
@@ -62,15 +62,12 @@ const Options = struct {
source_path: []const u8,
};
 
pub const SrcConfig = struct {
pub const SrcConfig = Ini.Config(struct {
owner: ?struct {
email: ?[]const u8,
tz: ?[]const u8,
},
agent: ?struct {
enabled: bool = false,
push_upstream: bool = false,
},
agent: ?Agent,
server: ?struct {
sock: ?[]const u8,
remove_on_start: bool = false,
@@ -80,11 +77,17 @@ pub const SrcConfig = struct {
repos: ?[]const u8,
/// Directory of private repos
private_repos: ?[]const u8,
/// List of repos that should be hidden
@"hidden-repos": ?[]const u8,
},
};
 
pub const Agent = struct {
enabled: bool = false,
push_upstream: bool = false,
};
});
 
// No, I don't like this
pub var root_ini: ?Ini.Config(SrcConfig) = null;
pub var global_config: SrcConfig = undefined;
 
const Auth = struct {
@@ -112,6 +115,7 @@ const Auth = struct {
}
 
pub fn lookupUser(ptr: *anyopaque, user_id: []const u8) !verse.auth.User {
log.debug("lookup user {s}", .{user_id});
const auth: *Auth = @ptrCast(@alignCast(ptr));
const user = Types.User.findMTLSFingerprint(auth.alloc, user_id) catch |err| {
std.debug.print("mtls lookup error {}\n", .{err});
@@ -155,17 +159,13 @@ pub fn main() !void {
cfg_file = try cwd.openFile("./config.ini", .{});
}
 
var config = Ini.Config(SrcConfig).fromFile(a, cfg_file.?) catch |e| switch (e) {
global_config = SrcConfig.fromFile(a, cfg_file.?) catch |e| switch (e) {
//error.FileNotFound => Ini.Config.empty(),
else => return e,
};
defer config.raze(a);
root_ini = config;
defer global_config.raze(a);
 
const src_conf = try config.config();
global_config = src_conf;
 
if (config.get("owner")) |ns| {
if (global_config.ctx.get("owner")) |ns| {
if (ns.get("email")) |email| {
log.debug("{s}", .{email});
}
@@ -178,10 +178,10 @@ pub fn main() !void {
defer cache.raze();
 
var agent_config: Repos.AgentConfig = .{
.g_config = &src_conf,
.agent = &global_config.config.agent,
};
 
if (src_conf.server) |srv| {
if (global_config.config.server) |srv| {
if (srv.remove_on_start) {
cwd.deleteFile("./srctree.sock") catch |err| switch (err) {
error.FileNotFound => {},
@@ -190,7 +190,7 @@ pub fn main() !void {
}
}
 
if (src_conf.agent.?.enabled) {
if (global_config.config.agent.?.enabled) {
const thread = try Thread.spawn(.{}, Repos.updateThread, .{&agent_config});
defer thread.join();
}
@@ -204,7 +204,7 @@ pub fn main() !void {
.base = auth.provider(),
};
 
if (src_conf.server) |srvcfg| {
if (global_config.config.server) |srvcfg| {
if (srvcfg.sock) |sock| {
std.debug.print("sock: {s}\n", .{sock});
}
 
src/repos.zig added: 162, removed: 167, total 0
@@ -1,10 +1,3 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const sleep = std.time.sleep;
 
const Git = @import("git.zig");
const SrcConfig = @import("main.zig").SrcConfig;
 
const Repos = @This();
 
const DEBUG = false;
@@ -32,7 +25,7 @@ pub const AgentConfig = struct {
const SECONDS = 1000 * 1000 * 1000;
running: bool = true,
sleep_for: usize = 60 * 60 * SECONDS,
g_config: *const SrcConfig,
agent: *const ?SrcConfig.Base.Agent,
};
 
fn pushUpstream(a: Allocator, name: []const u8, repo: *Git.Repo) !void {
@@ -72,7 +65,7 @@ pub fn updateThread(cfg: *AgentConfig) void {
var name_buffer: [2048]u8 = undefined;
 
var push_upstream: bool = false;
if (cfg.g_config.agent) |agent| {
if (cfg.agent.*) |agent| {
if (agent.push_upstream) {
push_upstream = true;
}
@@ -145,3 +138,11 @@ pub fn updateThread(cfg: *AgentConfig) void {
}
std.debug.print("update thread done!\n", .{});
}
 
const std = @import("std");
const Allocator = std.mem.Allocator;
const sleep = std.time.sleep;
const eql = std.mem.eql;
 
const Git = @import("git.zig");
const SrcConfig = @import("main.zig").SrcConfig;