Compare commits

...

4 commits

Author SHA1 Message Date
614ec063f0
docs: mention _cgfs/opts.json in README 2024-07-10 00:11:18 +02:00
bfee9382bd
chore: bump version 2024-07-10 00:04:25 +02:00
dbc84f2c99
fix: correctly handle & display post-processor errors 2024-07-10 00:03:50 +02:00
488a64809d
chore: refactor format serialization API
The inconsistently named `cg.toJSON` has been renamed to
`cg.fmt.json.serialize`. The code has also been restructured to fit more
formats in the future.
2024-07-09 23:22:12 +02:00
9 changed files with 91 additions and 42 deletions

View file

@ -79,8 +79,9 @@ confgenfs /path/to/confgen.lua ~/confgenfs
This mounts a FUSE3 filesystem containing all the config files. The advantage of this is that
the templates will be generated when the file is opened and not ahead of time.
Additionally, the filesystem will contain "meta-files" inside `_cgfs/`, currently only `_cgfs/eval`.
You can write some Lua code to this file, and it will be evaluated in the global Lua context.
Additionally, the filesystem will contain "meta-files" inside `_cgfs/`, currently only `_cgfs/eval`
and `_cgfs/opts.json`.
You can write some Lua code to the former file, and it will be evaluated in the global Lua context.
This allows for dynamic configurations, here's a practical example:
`.config/waybar/config.cgt`:

View file

@ -1,6 +1,6 @@
.{
.name = "confgen",
.version = "0.5.0",
.version = "0.6.0",
.dependencies = .{
.zig_args = .{

View file

@ -66,6 +66,20 @@ pub fn main() u8 {
, .{usage});
},
error.Explained => {},
error.LuaError => {
// Once Zig is smart enough to remove LuaError from the error set here, we'll
// replace this branch with this compile-time check:
//comptime {
// const ret_errors = @typeInfo(@typeInfo(@typeInfo(@TypeOf(run)).Fn.return_type.?).ErrorUnion.error_set).ErrorSet.?;
// for (ret_errors) |err| {
// if (std.mem.eql(u8, err.name, "LuaError"))
// @compileError("Run function must never return a LuaError!");
// }
//}
// We can't get the error message here as the Lua state will alread have been destroyed.
std.log.err("UNKNOWN LUA ERROR! THIS IS A BUG!", .{});
},
else => {
std.log.err("UNEXPECTED: {s}", .{@errorName(e)});
if (@errorReturnTrace()) |ert| std.debug.dumpStackTrace(ert.*);
@ -144,14 +158,14 @@ pub fn run() !void {
libcg.c.lua_getfield(l, -1, "opt");
if (arg.positionals.len == 0) {
try libcg.json.luaToJSON(l, &wstream);
try libcg.format.formats.json.luaToJSON(l, &wstream);
libcg.c.lua_pop(l, 1);
} else {
try wstream.beginObject();
for (arg.positionals) |opt| {
try wstream.objectField(opt);
libcg.c.lua_getfield(l, -1, opt);
try libcg.json.luaToJSON(l, &wstream);
try libcg.format.formats.json.luaToJSON(l, &wstream);
}
libcg.c.lua_pop(l, 2);
try wstream.endObject();

View file

@ -575,7 +575,7 @@ fn generateOptsJSON(self: *FileSystem) ![]const u8 {
libcg.c.lua_getglobal(self.l, "cg");
libcg.c.lua_getfield(self.l, -1, "opt");
try libcg.json.luaToJSON(self.l, &wstream);
try libcg.format.formats.json.luaToJSON(self.l, &wstream);
return try buf.toOwnedSlice();
}

16
libcg/format.zig Normal file
View file

@ -0,0 +1,16 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const c = ffi.c;
pub const formats = struct {
pub const json = @import("format/json.zig");
};
pub fn pushFmtTable(l: *c.lua_State) void {
c.lua_createtable(l, 0, @typeInfo(formats).Struct.decls.len);
inline for (@typeInfo(formats).Struct.decls) |decl| {
@field(formats, decl.name).luaPush(l);
c.lua_setfield(l, -2, decl.name);
}
}

View file

@ -1,9 +1,17 @@
//! Tools for serialization of Lua values to JSON
//! Used by the --json-opt flag
const std = @import("std");
const ffi = @import("ffi.zig");
const ffi = @import("../ffi.zig");
const c = ffi.c;
const luaapi = @import("../luaapi.zig");
pub fn luaPush(l: *c.lua_State) void {
c.lua_createtable(l, 0, 1);
c.lua_pushcfunction(l, ffi.luaFunc(lSerialize));
c.lua_setfield(l, -2, "serialize");
}
/// Writes a lua object to the stream. stream must be a json.WriteStream
pub fn luaToJSON(l: *c.lua_State, stream: anytype) !void {
const ty = c.lua_type(l, -1);
@ -71,3 +79,29 @@ pub fn luaToJSON(l: *c.lua_State, stream: anytype) !void {
else => unreachable,
}
}
fn lSerialize(l: *c.lua_State) !c_int {
c.luaL_checkany(l, 1);
const pretty = if (c.lua_gettop(l) >= 2) c.lua_toboolean(l, 2) != 0 else false;
const state = luaapi.getState(l);
// If you're doing more than 16KiB of JSON, open an issue
// and bring a VERY good explanation with you :D
var buf: [1024 * 16]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
var wstream = std.json.WriteStream(@TypeOf(fbs.writer()), .assumed_correct).init(
state.files.allocator,
fbs.writer(),
.{ .whitespace = if (pretty) .indent_2 else .minified },
);
defer wstream.deinit();
c.lua_pushvalue(l, 1);
try @import("json.zig").luaToJSON(l, &wstream);
const written = fbs.getWritten();
c.lua_pushlstring(l, written.ptr, written.len);
return 1;
}

View file

@ -1,6 +1,8 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const c = ffi.c;
const format = @import("format.zig");
const luagen = @import("luagen.zig");
const TemplateCode = luagen.TemplateCode;
@ -92,12 +94,12 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
c.lua_pushcfunction(l, ffi.luaFunc(lOnDone));
c.lua_setfield(l, -2, "onDone");
c.lua_pushcfunction(l, ffi.luaFunc(lToJSON));
c.lua_setfield(l, -2, "toJSON");
c.lua_pushcfunction(l, ffi.luaFunc(lFileIter));
c.lua_setfield(l, -2, "fileIter");
format.pushFmtTable(l);
c.lua_setfield(l, -2, "fmt");
// add cg table to globals
c.lua_setglobal(l, "cg");
@ -187,12 +189,17 @@ pub fn generate(l: *c.lua_State, code: TemplateCode) !GeneratedFile {
if (c.lua_pcall(l, 0, 0, 0) != 0) {
std.log.err("failed to run template: {?s}", .{ffi.luaToString(l, -1)});
return error.RunTemplate;
}
return .{
.content = try tmpl.getOutput(l),
.content = tmpl.getOutput(l) catch |e| switch (e) {
error.LuaError => {
std.log.err("failed to run post-processor: {?s}", .{ffi.luaToString(l, -1)});
return error.RunPostProcessor;
},
else => return e,
},
.mode = tmpl.mode,
.assume_deterministic = tmpl.assume_deterministic,
};
@ -503,32 +510,6 @@ fn lOnDone(l: *c.lua_State) !c_int {
return 0;
}
fn lToJSON(l: *c.lua_State) !c_int {
c.luaL_checkany(l, 1);
const pretty = if (c.lua_gettop(l) >= 2) c.lua_toboolean(l, 2) != 0 else false;
const state = getState(l);
// If you're doing more than 16KiB of JSON, open an issue
// and bring a VERY good explanation with you :D
var buf: [1024 * 16]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
var wstream = std.json.WriteStream(@TypeOf(fbs.writer()), .assumed_correct).init(
state.files.allocator,
fbs.writer(),
.{ .whitespace = if (pretty) .indent_2 else .minified },
);
defer wstream.deinit();
c.lua_pushvalue(l, 1);
try @import("json.zig").luaToJSON(l, &wstream);
const written = fbs.getWritten();
c.lua_pushlstring(l, written.ptr, written.len);
return 1;
}
fn lFileIter(l: *c.lua_State) !c_int {
const state = getState(l);
@ -650,7 +631,6 @@ pub const LTemplate = struct {
/// caller owns return value.
fn getOutput(self: *LTemplate, l: *c.lua_State) ![]const u8 {
const top = c.lua_gettop(l);
defer c.lua_settop(l, top);
c.lua_pushlightuserdata(l, self);
c.lua_gettable(l, c.LUA_REGISTRYINDEX);
@ -659,6 +639,7 @@ pub const LTemplate = struct {
// check if there's no post processor
if (c.lua_isnil(l, -1)) {
c.lua_settop(l, top);
return try self.output.allocator.dupe(u8, self.output.items);
}
@ -667,11 +648,14 @@ pub const LTemplate = struct {
// call post processor
if (c.lua_pcall(l, 1, 1, 0) != 0) {
try ffi.luaFmtString(l, "running post processor: {?s}", .{ffi.luaToString(l, -1)});
c.lua_insert(l, top + 1);
c.lua_settop(l, top + 1);
return error.LuaError;
}
const out = ffi.luaConvertString(l, -1);
c.lua_settop(l, top);
return try self.output.allocator.dupe(u8, out);
}

View file

@ -2,7 +2,7 @@ const std = @import("std");
pub const c = ffi.c;
pub const ffi = @import("ffi.zig");
pub const json = @import("json.zig");
pub const format = @import("format.zig");
pub const luaapi = @import("luaapi.zig");
pub const luagen = @import("luagen.zig");

View file

@ -133,7 +133,7 @@ Example:
.B cg.onDone(function(errors) print(\(dqHad errors: \(dq .. tostring(errors)) end)
.TP
.B cg.toJSON(value[, pretty])
.B cg.fmt.json.serialize(value[, pretty])
Convert an arbitrary lua value to JSON and return that as a string. The JSON will be minified by
default unless
.IR pretty \ is \ true .
@ -144,7 +144,7 @@ will be serialized as
.IR null .
Example:
.B local json = cg.toJSON({ x = \(dqy\(dq }) -- '{\(dqx\(dq: \(dqy\(dq}'
.B local json = cg.fmt.json.serialize({ x = \(dqy\(dq }) -- '{\(dqx\(dq: \(dqy\(dq}'
.TP
.B cg.fileIter()