forked from LordMZTE/confgen
Compare commits
13 commits
fd9d21256b
...
5a88ad31cf
Author | SHA1 | Date | |
---|---|---|---|
5a88ad31cf | |||
190dfa2695 | |||
d2d1b47b51 | |||
624485b360 | |||
f7abe9ee0a | |||
10ff95f6dd | |||
72b0725b59 | |||
332069af73 | |||
5b3f33518a | |||
51ee2de92a | |||
1f076bd833 | |||
b7d5c3eb58 | |||
c2ad954274 |
27 changed files with 624 additions and 111 deletions
35
.forgejo/workflows/push-nix.yml
Normal file
35
.forgejo/workflows/push-nix.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
push-nix:
|
||||
runs-on: docker-x86_64
|
||||
steps:
|
||||
- name: Install Packages
|
||||
run: |
|
||||
apt update
|
||||
apt install -y sudo
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: https://github.com/cachix/install-nix-action@v27
|
||||
with:
|
||||
extra_nix_config: |
|
||||
substituters = https://nix.mzte.de/mzte https://cache.nixos.org
|
||||
trusted-public-keys = mzte:nH2vGx119m6VyU6we113jgo+RVEBlj+1oYWvOXcwGFM= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
|
||||
|
||||
- name: Build Derivation
|
||||
run: |
|
||||
nix build
|
||||
|
||||
- name: Install & Push to Attic
|
||||
run: |
|
||||
# Cached in MZTE cache
|
||||
nix profile install github:zhaofengli/attic
|
||||
|
||||
attic login mzte https://nix.mzte.de ${{ secrets.ATTIC_TOKEN }}
|
||||
attic push mzte result
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
- name: Setup Zig
|
||||
uses: https://github.com/goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: 0.12.0
|
||||
version: 0.13.0
|
||||
|
||||
- name: Setup Packages
|
||||
run: |
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
@ -10,7 +10,7 @@ jobs:
|
|||
- name: Setup Zig
|
||||
uses: https://github.com/goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: 0.12.0
|
||||
version: 0.13.0
|
||||
|
||||
- name: Setup Packages
|
||||
run: |
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
zig-cache/
|
||||
.zig-cache/
|
||||
zig-out/
|
||||
deps.zig
|
||||
gyro.lock
|
||||
.gyro
|
||||
result*/
|
||||
|
|
15
README.md
15
README.md
|
@ -1,9 +1,20 @@
|
|||
# confgen
|
||||
# Confgen
|
||||
|
||||
confgen is a tool to generate config files using a custom template language.
|
||||
Confgen is a tool to generate config files using a custom template language.
|
||||
|
||||
The system is backed by a Lua runtime that is used to configure the tool.
|
||||
|
||||
## Installation
|
||||
|
||||
You can find binaries built on a Debian box in the releases tab, or build yourself like any other
|
||||
Zig project.
|
||||
|
||||
### Nix
|
||||
|
||||
This project includes a Nix flake you can depend on. Additionally, the Confgen derivation as well as
|
||||
derivations of other projects of mine are automatically built and pushed to an attic cache at
|
||||
`https://nix.mzte.de/mzte` on every commit.
|
||||
|
||||
## Usage
|
||||
|
||||
Start by creating `confgen.lua` in your dotfiles. It should look something like this:
|
||||
|
|
10
build.zig
10
build.zig
|
@ -16,7 +16,7 @@ pub fn build(b: *std.Build) void {
|
|||
}).module("args");
|
||||
|
||||
const libcg = b.createModule(.{
|
||||
.root_source_file = .{ .path = "libcg/main.zig" },
|
||||
.root_source_file = b.path("libcg/main.zig"),
|
||||
.link_libc = true,
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
|
@ -28,7 +28,7 @@ pub fn build(b: *std.Build) void {
|
|||
|
||||
const confgen_exe = b.addExecutable(.{
|
||||
.name = "confgen",
|
||||
.root_source_file = .{ .path = "confgen/main.zig" },
|
||||
.root_source_file = b.path("confgen/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void {
|
|||
b.installArtifact(confgen_exe);
|
||||
|
||||
b.installDirectory(.{
|
||||
.source_dir = .{ .path = "share" },
|
||||
.source_dir = b.path("share"),
|
||||
.install_dir = .{ .custom = "share" },
|
||||
.install_subdir = ".",
|
||||
});
|
||||
|
@ -54,7 +54,7 @@ pub fn build(b: *std.Build) void {
|
|||
run_confgen_step.dependOn(&run_confgen_cmd.step);
|
||||
|
||||
const exe_confgen_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "confgen/main.zig" },
|
||||
.root_source_file = b.path("confgen/main.zig"),
|
||||
.link_libc = true,
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
|
@ -65,7 +65,7 @@ pub fn build(b: *std.Build) void {
|
|||
if (confgenfs) {
|
||||
const confgenfs_exe = b.addExecutable(.{
|
||||
.name = "confgenfs",
|
||||
.root_source_file = .{ .path = "confgenfs/main.zig" },
|
||||
.root_source_file = b.path("confgenfs/main.zig"),
|
||||
.link_libc = true,
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.{
|
||||
.name = "confgen",
|
||||
.version = "0.3.1",
|
||||
.version = "0.5.0",
|
||||
|
||||
.dependencies = .{
|
||||
.zig_args = .{
|
||||
.url = "git+https://git.mzte.de/mirrors/zig-args.git#89f18a104d9c13763b90e97d6b4ce133da8a3e2b",
|
||||
.hash = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829",
|
||||
.url = "git+https://git.mzte.de/mirrors/zig-args.git#872272205d95bdba33798c94e72c5387a31bc806",
|
||||
.hash = "1220fe6ae56b668cc4a033282b5f227bfbb46a67ede6d84e9f9493fea9de339b5f37",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
122
confgen/Notifier.zig
Normal file
122
confgen/Notifier.zig
Normal file
|
@ -0,0 +1,122 @@
|
|||
const std = @import("std");
|
||||
|
||||
const sigset = sigs: {
|
||||
var set = std.posix.empty_sigset;
|
||||
std.os.linux.sigaddset(&set, std.posix.SIG.INT);
|
||||
std.os.linux.sigaddset(&set, std.posix.SIG.TERM);
|
||||
break :sigs set;
|
||||
};
|
||||
|
||||
inotifyfd: std.os.linux.fd_t,
|
||||
sigfd: std.os.linux.fd_t,
|
||||
watches: WatchesMap,
|
||||
inotifyrd: std.io.BufferedReader(1024 * 4, std.fs.File.Reader),
|
||||
|
||||
const WatchesMap = std.AutoHashMap(i32, []const u8);
|
||||
|
||||
const Notifier = @This();
|
||||
|
||||
pub const Event = union(enum) {
|
||||
quit,
|
||||
file_changed: []const u8,
|
||||
};
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) !Notifier {
|
||||
std.posix.sigprocmask(std.posix.SIG.BLOCK, &sigset, null);
|
||||
|
||||
const inotifyfd = try std.posix.inotify_init1(0);
|
||||
errdefer std.posix.close(inotifyfd);
|
||||
|
||||
const sigfd = try std.posix.signalfd(-1, &sigset, 0);
|
||||
errdefer std.posix.close(sigfd);
|
||||
|
||||
return .{
|
||||
.inotifyfd = inotifyfd,
|
||||
.sigfd = sigfd,
|
||||
.watches = WatchesMap.init(alloc),
|
||||
.inotifyrd = std.io.bufferedReader((std.fs.File{ .handle = inotifyfd }).reader()),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Notifier) void {
|
||||
std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &sigset, null);
|
||||
|
||||
std.posix.close(self.inotifyfd);
|
||||
std.posix.close(self.sigfd);
|
||||
|
||||
var w_iter = self.watches.iterator();
|
||||
while (w_iter.next()) |wkv| {
|
||||
self.watches.allocator.free(wkv.value_ptr.*);
|
||||
}
|
||||
self.watches.deinit();
|
||||
}
|
||||
|
||||
pub fn addDir(self: *Notifier, dirname: []const u8) !void {
|
||||
const fd = std.posix.inotify_add_watch(
|
||||
self.inotifyfd,
|
||||
dirname,
|
||||
std.os.linux.IN.MASK_CREATE | std.os.linux.IN.ONLYDIR | std.os.linux.IN.CLOSE_WRITE,
|
||||
) catch |e| switch (e) {
|
||||
error.WatchAlreadyExists => return,
|
||||
else => return e,
|
||||
};
|
||||
errdefer std.posix.inotify_rm_watch(self.inotifyfd, fd);
|
||||
|
||||
const dir_d = try self.watches.allocator.dupe(u8, dirname);
|
||||
errdefer self.watches.allocator.free(dir_d);
|
||||
|
||||
// SAFETY: This cannot cause UB. We have checked if the dir is already watched.
|
||||
std.debug.assert(!self.watches.contains(fd));
|
||||
try self.watches.putNoClobber(fd, dir_d);
|
||||
}
|
||||
|
||||
/// Caller must free returned memory.
|
||||
pub fn next(self: *Notifier) !Event {
|
||||
var pollfds = [2]std.posix.pollfd{
|
||||
.{ .fd = self.inotifyfd, .events = std.posix.POLL.IN, .revents = 0 },
|
||||
.{ .fd = self.sigfd, .events = std.posix.POLL.IN, .revents = 0 },
|
||||
};
|
||||
|
||||
const pending_data = self.inotifyrd.start != self.inotifyrd.end;
|
||||
|
||||
if (!pending_data)
|
||||
_ = try std.posix.poll(&pollfds, -1);
|
||||
|
||||
if (pending_data or pollfds[0].revents == std.posix.POLL.IN) {
|
||||
var ev: std.os.linux.inotify_event = undefined;
|
||||
try self.inotifyrd.reader().readNoEof(std.mem.asBytes(&ev));
|
||||
|
||||
// The inotify_event struct is optionally followed by ev.len bytes for the path name of
|
||||
// the watched file. We must read them here to avoid clobbering the next event.
|
||||
var name_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
std.debug.assert(ev.len <= name_buf.len);
|
||||
if (ev.len > 0)
|
||||
try self.inotifyrd.reader().readNoEof(name_buf[0..ev.len]);
|
||||
|
||||
const dirpath = self.watches.get(ev.wd) orelse
|
||||
@panic("inotifyfd returned invalid handle");
|
||||
|
||||
// Required as padding bytes may be included in read value
|
||||
const name = std.mem.sliceTo(&name_buf, 0);
|
||||
|
||||
return .{
|
||||
.file_changed =
|
||||
// This avoids inconsistent naming in the edge-case that we're observing the CWD
|
||||
if (std.mem.eql(u8, dirpath, "."))
|
||||
try self.watches.allocator.dupe(u8, name)
|
||||
else
|
||||
try std.fs.path.join(
|
||||
self.watches.allocator,
|
||||
&.{ dirpath, name },
|
||||
),
|
||||
};
|
||||
}
|
||||
if (pollfds[1].revents == std.posix.POLL.IN) {
|
||||
var ev: std.os.linux.signalfd_siginfo = undefined;
|
||||
std.debug.assert(try std.posix.read(self.sigfd, std.mem.asBytes(&ev)) ==
|
||||
@sizeOf(std.os.linux.signalfd_siginfo));
|
||||
|
||||
return .quit;
|
||||
}
|
||||
@panic("poll returned incorrectly");
|
||||
}
|
187
confgen/main.zig
187
confgen/main.zig
|
@ -2,6 +2,8 @@ const std = @import("std");
|
|||
const args = @import("args");
|
||||
const libcg = @import("libcg");
|
||||
|
||||
const Notifier = @import("Notifier.zig");
|
||||
|
||||
comptime {
|
||||
if (@import("builtin").is_test) {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
|
@ -23,6 +25,7 @@ const Args = struct {
|
|||
help: bool = false,
|
||||
eval: ?[]const u8 = null,
|
||||
@"post-eval": ?[]const u8 = null,
|
||||
watch: bool = false,
|
||||
|
||||
pub const shorthands = .{
|
||||
.c = "compile",
|
||||
|
@ -31,6 +34,7 @@ const Args = struct {
|
|||
.h = "help",
|
||||
.e = "eval",
|
||||
.p = "post-eval",
|
||||
.w = "watch",
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -42,9 +46,10 @@ const usage =
|
|||
\\ --compile, -c [TEMPLATE_FILE] Compile a template to Lua instead of running. Useful for debugging.
|
||||
\\ --json-opt, -j [CONFGENFILE] Write the given or all fields from cg.opt to stdout as JSON after running the given confgenfile instead of running.
|
||||
\\ --file, -f [TEMPLATE_FILE] [OUTFILE] Evaluate a single template and write the output instead of running.
|
||||
\\ --eval, -e [CODE] Evaluate code before the confgenfile
|
||||
\\ --post-eval, -p [CODE] Evaluate code after the confgenfile
|
||||
\\ --help, -h Show this help
|
||||
\\ --eval, -e [CODE] Evaluate code before the confgenfile .
|
||||
\\ --post-eval, -p [CODE] Evaluate code after the confgenfile.
|
||||
\\ --watch, -w Watch for changes of input files and re-generate them if changed.
|
||||
\\ --help, -h Show this help.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ confgen [CONFGENFILE] [OUTPATH] Generate configs according the the supplied configuration file.
|
||||
|
@ -177,24 +182,49 @@ pub fn run() !void {
|
|||
const l = try libcg.luaapi.initLuaState(&state);
|
||||
defer libcg.c.lua_close(l);
|
||||
|
||||
const tmplsrc = try std.fs.cwd().readFileAlloc(
|
||||
alloc,
|
||||
arg.positionals[0],
|
||||
std.math.maxInt(usize),
|
||||
);
|
||||
const tmplcode = try libcg.luagen.generateLua(
|
||||
alloc,
|
||||
tmplsrc,
|
||||
arg.positionals[0],
|
||||
);
|
||||
const genf = try libcg.luaapi.generate(l, tmplcode);
|
||||
defer alloc.free(genf.content);
|
||||
var content_buf = std.ArrayList(u8).init(alloc);
|
||||
defer content_buf.deinit();
|
||||
|
||||
const outfile = try std.fs.cwd().createFile(arg.positionals[1], .{ .mode = genf.mode });
|
||||
defer outfile.close();
|
||||
try outfile.writeAll(genf.content);
|
||||
const cgfile = libcg.luaapi.CgFile{
|
||||
.content = .{ .path = arg.positionals[0] },
|
||||
.copy = false,
|
||||
};
|
||||
|
||||
try genfile(
|
||||
alloc,
|
||||
l,
|
||||
cgfile,
|
||||
&content_buf,
|
||||
".",
|
||||
arg.positionals[1],
|
||||
);
|
||||
|
||||
libcg.luaapi.callOnDoneCallbacks(l, false);
|
||||
if (arg.options.watch) {
|
||||
var notif = try Notifier.init(alloc);
|
||||
defer notif.deinit();
|
||||
|
||||
try notif.addDir(std.fs.path.dirname(arg.positionals[0]) orelse ".");
|
||||
|
||||
while (true) switch (try notif.next()) {
|
||||
.quit => break,
|
||||
.file_changed => |p| {
|
||||
defer alloc.free(p);
|
||||
if (!std.mem.eql(u8, p, arg.positionals[0])) continue;
|
||||
|
||||
genfile(
|
||||
alloc,
|
||||
l,
|
||||
cgfile,
|
||||
&content_buf,
|
||||
".",
|
||||
arg.positionals[1],
|
||||
) catch |e| {
|
||||
std.log.err("generating {s}: {}", .{ arg.positionals[1], e });
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -228,7 +258,7 @@ pub fn run() !void {
|
|||
|
||||
try std.posix.chdir(state.rootpath);
|
||||
|
||||
const l = try libcg.luaapi.initLuaState(&state);
|
||||
var l = try libcg.luaapi.initLuaState(&state);
|
||||
defer libcg.c.lua_close(l);
|
||||
|
||||
if (arg.options.eval) |code| {
|
||||
|
@ -244,31 +274,100 @@ pub fn run() !void {
|
|||
var content_buf = std.ArrayList(u8).init(alloc);
|
||||
defer content_buf.deinit();
|
||||
|
||||
var errors = false;
|
||||
var iter = state.files.iterator();
|
||||
while (iter.next()) |kv| {
|
||||
const outpath = kv.key_ptr.*;
|
||||
const file = kv.value_ptr.*;
|
||||
{
|
||||
var errors = false;
|
||||
var iter = state.files.iterator();
|
||||
while (iter.next()) |kv| {
|
||||
const outpath = kv.key_ptr.*;
|
||||
const file = kv.value_ptr.*;
|
||||
|
||||
if (file.copy) {
|
||||
std.log.info("copying {s}", .{outpath});
|
||||
} else {
|
||||
std.log.info("generating {s}", .{outpath});
|
||||
genfile(
|
||||
alloc,
|
||||
l,
|
||||
file,
|
||||
&content_buf,
|
||||
output_abs,
|
||||
outpath,
|
||||
) catch |e| {
|
||||
errors = true;
|
||||
std.log.err("generating {s}: {}", .{ outpath, e });
|
||||
};
|
||||
}
|
||||
genfile(
|
||||
alloc,
|
||||
l,
|
||||
file,
|
||||
&content_buf,
|
||||
output_abs,
|
||||
outpath,
|
||||
) catch |e| {
|
||||
errors = true;
|
||||
std.log.err("generating {s}: {}", .{ outpath, e });
|
||||
};
|
||||
libcg.luaapi.callOnDoneCallbacks(l, errors);
|
||||
}
|
||||
|
||||
libcg.luaapi.callOnDoneCallbacks(l, errors);
|
||||
if (arg.options.watch) {
|
||||
var notif = try Notifier.init(alloc);
|
||||
defer notif.deinit();
|
||||
|
||||
{
|
||||
try notif.addDir(std.fs.path.dirname(cgfile) orelse ".");
|
||||
|
||||
var iter = state.files.iterator();
|
||||
while (iter.next()) |kv| {
|
||||
try notif.addDir(std.fs.path.dirname(kv.key_ptr.*) orelse ".");
|
||||
}
|
||||
}
|
||||
|
||||
while (true) switch (try notif.next()) {
|
||||
.quit => break,
|
||||
.file_changed => |p| {
|
||||
defer alloc.free(p);
|
||||
|
||||
if (std.mem.eql(u8, p, cgfile)) {
|
||||
std.log.info("Confgenfile changed; re-evaluating", .{});
|
||||
|
||||
// Destroy Lua state
|
||||
libcg.c.lua_close(l);
|
||||
l = try libcg.luaapi.initLuaState(&state);
|
||||
|
||||
// Reset CgState
|
||||
state.nfile_iters = 0; // old Lua state is dead, so no iterators.
|
||||
{
|
||||
var iter = state.files.iterator();
|
||||
while (iter.next()) |kv| {
|
||||
alloc.free(kv.key_ptr.*);
|
||||
kv.value_ptr.deinit(alloc);
|
||||
}
|
||||
state.files.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
// Evaluate cgfile and eval args
|
||||
if (arg.options.eval) |code| {
|
||||
try libcg.luaapi.evalUserCode(l, code);
|
||||
}
|
||||
|
||||
try libcg.luaapi.loadCGFile(l, cgfile.ptr);
|
||||
|
||||
if (arg.options.@"post-eval") |code| {
|
||||
try libcg.luaapi.evalUserCode(l, code);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to iterate here because the key of the map corresponds to the file's
|
||||
// output path. The input path may be entirely different.
|
||||
var iter = state.files.iterator();
|
||||
while (iter.next()) |kv| {
|
||||
if (kv.value_ptr.content != .path) continue;
|
||||
|
||||
if (std.mem.eql(u8, kv.value_ptr.content.path, p)) {
|
||||
genfile(
|
||||
alloc,
|
||||
l,
|
||||
kv.value_ptr.*,
|
||||
&content_buf,
|
||||
output_abs,
|
||||
kv.key_ptr.*,
|
||||
) catch |e| {
|
||||
std.log.err("generating {s}: {}", .{ p, e });
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn genfile(
|
||||
|
@ -281,6 +380,12 @@ fn genfile(
|
|||
) !void {
|
||||
const state = libcg.luaapi.getState(l);
|
||||
|
||||
if (file.copy) {
|
||||
std.log.info("copying {s}", .{file_outpath});
|
||||
} else {
|
||||
std.log.info("generating {s}", .{file_outpath});
|
||||
}
|
||||
|
||||
if (file.copy) {
|
||||
const to_path = try std.fs.path.join(
|
||||
alloc,
|
||||
|
@ -321,7 +426,7 @@ fn genfile(
|
|||
.string => |s| content = s,
|
||||
.path => |p| {
|
||||
fname = std.fs.path.basename(p);
|
||||
const path = try std.fs.path.join(alloc, &.{ state.rootpath, p });
|
||||
const path = try std.fs.path.resolve(alloc, &.{ state.rootpath, p });
|
||||
defer alloc.free(path);
|
||||
|
||||
const f = try std.fs.cwd().openFile(path, .{});
|
||||
|
|
|
@ -582,13 +582,13 @@ fn generateOptsJSON(self: *FileSystem) ![]const u8 {
|
|||
|
||||
fn eval(self: *FileSystem, code: []const u8) !void {
|
||||
if (libcg.c.luaL_loadbuffer(self.l, code.ptr, code.len, "<cgfs-eval>") != 0) {
|
||||
std.log.err("unable to load eval code: {s}", .{libcg.ffi.luaToString(self.l, -1)});
|
||||
std.log.err("unable to load eval code: {?s}", .{libcg.ffi.luaToString(self.l, -1)});
|
||||
libcg.c.lua_pop(self.l, 1);
|
||||
return error.InvalidEvalCode;
|
||||
}
|
||||
|
||||
if (libcg.c.lua_pcall(self.l, 0, 0, 0) != 0) {
|
||||
std.log.err("unable to run eval code: {s}", .{libcg.ffi.luaToString(self.l, -1)});
|
||||
std.log.err("unable to run eval code: {?s}", .{libcg.ffi.luaToString(self.l, -1)});
|
||||
libcg.c.lua_pop(self.l, 1);
|
||||
return error.InvalidEvalCode;
|
||||
}
|
||||
|
|
6
deps.nix
6
deps.nix
|
@ -2,11 +2,11 @@
|
|||
pkgs.linkFarm "zig-packages" [
|
||||
# zig-args
|
||||
{
|
||||
name = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829";
|
||||
name = "1220fe6ae56b668cc4a033282b5f227bfbb46a67ede6d84e9f9493fea9de339b5f37";
|
||||
path = pkgs.fetchgit {
|
||||
url = "https://git.mzte.de/mirrors/zig-args.git";
|
||||
rev = "89f18a104d9c13763b90e97d6b4ce133da8a3e2b";
|
||||
hash = "sha256-JY0UDJSKOh1Cg46/GnhVTNmgr6TJKoHXgt8FponPCPM=";
|
||||
rev = "872272205d95bdba33798c94e72c5387a31bc806";
|
||||
hash = "sha256-H/sT6JHun+jR37fJSbsauE9K3igV/frcnD/w4Pngzc4=";
|
||||
};
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1714076141,
|
||||
"narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
|
||||
"lastModified": 1719690277,
|
||||
"narHash": "sha256-0xSej1g7eP2kaUF+JQp8jdyNmpmCJKRpO12mKl/36Kc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7bb2ccd8cdc44c91edba16c48d2c8f331fb3d856",
|
||||
"rev": "2741b4b489b55df32afac57bc4bfd220e8bf617e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
dontConfigure = true;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
zig_0_12.hook
|
||||
zig_0_13.hook
|
||||
pkg-config
|
||||
luajit
|
||||
fuse3
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// FIXME: This whole thing badly needs to be reworked! It barely works, has some nonsensical design
|
||||
// in places, misses some errors like incorrect delimiters and tends to crash.
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("ffi.zig").c;
|
||||
|
||||
|
@ -49,6 +52,7 @@ pub fn next(self: *Parser) !?Token {
|
|||
self.pos = i + 1;
|
||||
current_type = .lua_literal;
|
||||
} else if (std.mem.eql(u8, charpair, "%>")) {
|
||||
// FIXME: underflow on mismatched delimiters
|
||||
depth -= 1;
|
||||
if (depth > 0)
|
||||
continue;
|
||||
|
@ -121,6 +125,8 @@ pub fn next(self: *Parser) !?Token {
|
|||
|
||||
self.pos = i;
|
||||
|
||||
// FIXME: Unclosed delimeters not properly detected! Example: "<%"
|
||||
|
||||
return tok;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,14 +11,17 @@ pub fn luaFunc(comptime func: anytype) c.lua_CFunction {
|
|||
return &struct {
|
||||
fn f(l: ?*c.lua_State) callconv(.C) c_int {
|
||||
return func(l.?) catch |e| {
|
||||
var buf: [128]u8 = undefined;
|
||||
const err_s = std.fmt.bufPrint(
|
||||
&buf,
|
||||
"Zig Error: {s}",
|
||||
.{@errorName(e)},
|
||||
) catch unreachable;
|
||||
c.lua_pushlstring(l, err_s.ptr, err_s.len);
|
||||
_ = c.lua_error(l);
|
||||
// If error.LuaError is returned, an error value must be on the stack.
|
||||
if (e != error.LuaError) {
|
||||
var buf: [128]u8 = undefined;
|
||||
const err_s = std.fmt.bufPrint(
|
||||
&buf,
|
||||
"Zig Error: {s}",
|
||||
.{@errorName(e)},
|
||||
) catch unreachable;
|
||||
luaPushString(l.?, err_s);
|
||||
}
|
||||
_ = c.lua_error(l.?);
|
||||
unreachable;
|
||||
};
|
||||
}
|
||||
|
@ -46,7 +49,44 @@ pub fn luaCheckString(l: *c.lua_State, idx: c_int) []const u8 {
|
|||
return c.luaL_checklstring(l, idx, &len)[0..len];
|
||||
}
|
||||
|
||||
pub fn luaToString(l: *c.lua_State, idx: c_int) []const u8 {
|
||||
pub fn luaToString(l: *c.lua_State, idx: c_int) ?[]const u8 {
|
||||
var len: usize = 0;
|
||||
return c.lua_tolstring(l, idx, &len)[0..len];
|
||||
return (@as(?[*]const u8, c.lua_tolstring(l, idx, &len)) orelse return null)[0..len];
|
||||
}
|
||||
|
||||
pub fn luaConvertString(l: *c.lua_State, idx: c_int) []const u8 {
|
||||
c.lua_pushvalue(l, idx);
|
||||
c.lua_getglobal(l, "tostring");
|
||||
c.lua_insert(l, -2);
|
||||
c.lua_call(l, 1, 1);
|
||||
const s = luaToString(l, -1) orelse unreachable;
|
||||
c.lua_pop(l, 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
pub inline fn luaPushString(l: *c.lua_State, s: []const u8) void {
|
||||
c.lua_pushlstring(l, s.ptr, s.len);
|
||||
}
|
||||
|
||||
const StackWriter = struct {
|
||||
l: *c.lua_State,
|
||||
written: u31 = 0,
|
||||
|
||||
const Writer = std.io.Writer(*StackWriter, error{}, write);
|
||||
|
||||
fn write(self: *StackWriter, bytes: []const u8) error{}!usize {
|
||||
luaPushString(self.l, bytes);
|
||||
self.written += 1;
|
||||
return bytes.len;
|
||||
}
|
||||
|
||||
fn writer(self: *StackWriter) Writer {
|
||||
return .{ .context = self };
|
||||
}
|
||||
};
|
||||
|
||||
pub fn luaFmtString(l: *c.lua_State, comptime fmt: []const u8, args: anytype) !void {
|
||||
var ctx = StackWriter{ .l = l };
|
||||
try std.fmt.format(ctx.writer(), fmt, args);
|
||||
c.lua_concat(l, ctx.written);
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ pub fn luaToJSON(l: *c.lua_State, stream: anytype) !void {
|
|||
// Need to duplicate the key in order to call luaToString.
|
||||
// Direct call may break lua_next
|
||||
c.lua_pushvalue(l, -2);
|
||||
try stream.objectField(ffi.luaToString(l, -1));
|
||||
try stream.objectField(ffi.luaConvertString(l, -1));
|
||||
c.lua_pop(l, 1);
|
||||
}
|
||||
try luaToJSON(l, stream);
|
||||
|
|
159
libcg/luaapi.zig
159
libcg/luaapi.zig
|
@ -8,11 +8,18 @@ const TemplateCode = luagen.TemplateCode;
|
|||
pub const state_key = "cg_state";
|
||||
pub const on_done_callbacks_key = "on_done_callbacks";
|
||||
|
||||
const iters_alive_errmsg = "Cannot add file as file iterators are still alive!";
|
||||
|
||||
pub const CgState = struct {
|
||||
rootpath: []const u8,
|
||||
files: std.StringHashMap(CgFile),
|
||||
|
||||
/// Number of currently alive iterators over the files map. This is in place to cause an error
|
||||
/// when the user attempts concurrent modification.
|
||||
nfile_iters: usize = 0,
|
||||
|
||||
pub fn deinit(self: *CgState) void {
|
||||
std.debug.assert(self.nfile_iters == 0);
|
||||
var iter = self.files.iterator();
|
||||
while (iter.next()) |kv| {
|
||||
self.files.allocator.free(kv.key_ptr.*);
|
||||
|
@ -88,6 +95,9 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
|
|||
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");
|
||||
|
||||
// add cg table to globals
|
||||
c.lua_setglobal(l, "cg");
|
||||
|
||||
|
@ -100,6 +110,7 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
|
|||
c.lua_setfield(l, c.LUA_REGISTRYINDEX, on_done_callbacks_key);
|
||||
|
||||
LTemplate.initMetatable(l);
|
||||
LFileIter.initMetatable(l);
|
||||
TemplateCode.initMetatable(l);
|
||||
|
||||
return l;
|
||||
|
@ -107,23 +118,23 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
|
|||
|
||||
pub fn loadCGFile(l: *c.lua_State, cgfile: [*:0]const u8) !void {
|
||||
if (c.luaL_loadfile(l, cgfile) != 0) {
|
||||
std.log.err("loading confgen file: {s}", .{ffi.luaToString(l, -1)});
|
||||
std.log.err("loading confgen file: {?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.RootfileExec;
|
||||
}
|
||||
|
||||
if (c.lua_pcall(l, 0, 0, 0) != 0) {
|
||||
std.log.err("running confgen file: {s}", .{ffi.luaToString(l, -1)});
|
||||
std.log.err("running confgen file: {?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.RootfileExec;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evalUserCode(l: *c.lua_State, code: []const u8) !void {
|
||||
if (c.luaL_loadbuffer(l, code.ptr, code.len, "<eval>") != 0) {
|
||||
std.log.err("loading user code: {s}", .{ffi.luaToString(l, -1)});
|
||||
std.log.err("loading user code: {?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.Explained;
|
||||
}
|
||||
if (c.lua_pcall(l, 0, 0, 0) != 0) {
|
||||
std.log.err("evaluating user code: {s}", .{ffi.luaToString(l, -1)});
|
||||
std.log.err("evaluating user code: {?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.Explained;
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +159,7 @@ pub fn generate(l: *c.lua_State, code: TemplateCode) !GeneratedFile {
|
|||
errdefer code.deinit();
|
||||
|
||||
if (c.luaL_loadbuffer(l, code.content.ptr, code.content.len, code.name) != 0) {
|
||||
std.log.err("failed to load template: {s}", .{ffi.luaToString(l, -1)});
|
||||
std.log.err("failed to load template: {?s}", .{ffi.luaToString(l, -1)});
|
||||
|
||||
return error.LoadTemplate;
|
||||
}
|
||||
|
@ -175,7 +186,7 @@ pub fn generate(l: *c.lua_State, code: TemplateCode) !GeneratedFile {
|
|||
_ = c.lua_setfenv(l, -2);
|
||||
|
||||
if (c.lua_pcall(l, 0, 0, 0) != 0) {
|
||||
std.log.err("failed to run template: {s}", .{ffi.luaToString(l, -1)});
|
||||
std.log.err("failed to run template: {?s}", .{ffi.luaToString(l, -1)});
|
||||
|
||||
return error.RunTemplate;
|
||||
}
|
||||
|
@ -197,7 +208,7 @@ pub fn callOnDoneCallbacks(l: *c.lua_State, errors: bool) void {
|
|||
c.lua_pushboolean(l, @intFromBool(errors));
|
||||
if (c.lua_pcall(l, 1, 0, 0) != 0) {
|
||||
const err_s = ffi.luaToString(l, -1);
|
||||
std.log.err("running onDone callback: {s}", .{err_s});
|
||||
std.log.err("running onDone callback: {?s}", .{err_s});
|
||||
c.lua_pop(l, 1);
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +224,7 @@ fn lPrint(l: *c.lua_State) !c_int {
|
|||
try writer.writeAll("\x1b[1;34mL:\x1b[0m ");
|
||||
|
||||
for (0..@intCast(nargs)) |i| {
|
||||
const s = ffi.luaToString(l, @intCast(i + 1));
|
||||
const s = ffi.luaConvertString(l, @intCast(i + 1));
|
||||
try writer.writeAll(s);
|
||||
if (i + 1 != nargs)
|
||||
try writer.writeByte('\t');
|
||||
|
@ -231,6 +242,11 @@ fn lAddString(l: *c.lua_State) !c_int {
|
|||
|
||||
const state = getState(l);
|
||||
|
||||
if (state.nfile_iters != 0) {
|
||||
ffi.luaPushString(l, iters_alive_errmsg);
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
const outpath_d = try state.files.allocator.dupe(u8, outpath);
|
||||
errdefer state.files.allocator.free(outpath_d);
|
||||
|
||||
|
@ -253,6 +269,11 @@ fn lAddPath(l: *c.lua_State) !c_int {
|
|||
|
||||
const state = getState(l);
|
||||
|
||||
if (state.nfile_iters != 0) {
|
||||
ffi.luaPushString(l, iters_alive_errmsg);
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
var dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
|
||||
defer dir.close();
|
||||
|
||||
|
@ -289,6 +310,11 @@ fn lAddPath(l: *c.lua_State) !c_int {
|
|||
fn lAddFile(l: *c.lua_State) !c_int {
|
||||
const state = getState(l);
|
||||
|
||||
if (state.nfile_iters != 0) {
|
||||
ffi.luaPushString(l, iters_alive_errmsg);
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
const argc = c.lua_gettop(l);
|
||||
|
||||
const inpath = ffi.luaCheckString(l, 1);
|
||||
|
@ -346,7 +372,7 @@ fn lDoTemplate(l: *c.lua_State) !c_int {
|
|||
|
||||
c.lua_getfield(l, 2, "name");
|
||||
if (!c.lua_isnil(l, -1)) {
|
||||
source_name = ffi.luaToString(l, -1);
|
||||
source_name = ffi.luaToString(l, -1) orelse return error.InvalidArgument;
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -362,9 +388,8 @@ fn lDoTemplate(l: *c.lua_State) !c_int {
|
|||
tmpl_code.content.len,
|
||||
tmpl_code.name.ptr,
|
||||
) != 0) {
|
||||
// TODO: turn this into a lua error
|
||||
std.log.err("loading template: {s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LoadTemplate;
|
||||
try ffi.luaFmtString(l, "loading template:\n{?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
// create env table
|
||||
|
@ -387,9 +412,8 @@ fn lDoTemplate(l: *c.lua_State) !c_int {
|
|||
_ = c.lua_setfenv(l, -2);
|
||||
|
||||
if (c.lua_pcall(l, 0, 0, 0) != 0) {
|
||||
// TODO: turn this into a lua error
|
||||
std.log.err("failed to run template: {s}", .{ffi.luaToString(l, -1)});
|
||||
return error.RunTemplate;
|
||||
try ffi.luaFmtString(l, "failed to run template:\n{?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
const output = try tmpl.getOutput(l);
|
||||
|
@ -432,9 +456,8 @@ fn lDoTemplateFile(l: *c.lua_State) !c_int {
|
|||
tmpl_code.content.len,
|
||||
tmpl_code.name.ptr,
|
||||
) != 0) {
|
||||
// TODO: turn this into a lua error
|
||||
std.log.err("loading template: {s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LoadTemplate;
|
||||
try ffi.luaFmtString(l, "loading template:\n{?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
// create env
|
||||
|
@ -456,9 +479,8 @@ fn lDoTemplateFile(l: *c.lua_State) !c_int {
|
|||
_ = c.lua_setfenv(l, -2);
|
||||
|
||||
if (c.lua_pcall(l, 0, 0, 0) != 0) {
|
||||
// TODO: turn this into a lua error
|
||||
std.log.err("failed to run template: {s}", .{ffi.luaToString(l, -1)});
|
||||
return error.RunTemplate;
|
||||
try ffi.luaFmtString(l, "failed to run template:\n{?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
const output = try tmpl.getOutput(l);
|
||||
|
@ -481,7 +503,7 @@ fn lOnDone(l: *c.lua_State) !c_int {
|
|||
return 0;
|
||||
}
|
||||
|
||||
pub fn lToJSON(l: *c.lua_State) !c_int {
|
||||
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;
|
||||
|
||||
|
@ -507,6 +529,83 @@ pub fn lToJSON(l: *c.lua_State) !c_int {
|
|||
return 1;
|
||||
}
|
||||
|
||||
fn lFileIter(l: *c.lua_State) !c_int {
|
||||
const state = getState(l);
|
||||
|
||||
state.nfile_iters += 1;
|
||||
|
||||
_ = (LFileIter{ .iter = state.files.iterator() }).push(l);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
pub const LFileIter = struct {
|
||||
pub const lua_registry_key = "confgen_file_iter";
|
||||
|
||||
iter: std.StringHashMap(CgFile).Iterator,
|
||||
|
||||
pub fn push(self: LFileIter, l: *c.lua_State) *LFileIter {
|
||||
const self_ptr = ffi.luaPushUdata(l, LFileIter);
|
||||
self_ptr.* = self;
|
||||
return self_ptr;
|
||||
}
|
||||
|
||||
fn lGC(l: *c.lua_State) !c_int {
|
||||
const state = getState(l);
|
||||
state.nfile_iters -= 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn lCall(l: *c.lua_State) !c_int {
|
||||
const self = ffi.luaGetUdata(LFileIter, l, 1);
|
||||
|
||||
if (self.iter.next()) |kv| {
|
||||
c.lua_createtable(l, 0, 3);
|
||||
|
||||
c.lua_pushlstring(l, kv.key_ptr.ptr, kv.key_ptr.len);
|
||||
c.lua_setfield(l, -2, "path");
|
||||
|
||||
c.lua_pushboolean(l, @intFromBool(kv.value_ptr.copy));
|
||||
c.lua_setfield(l, -2, "copy");
|
||||
|
||||
{ // content
|
||||
c.lua_createtable(l, 0, 1);
|
||||
|
||||
switch (kv.value_ptr.content) {
|
||||
.path => |p| {
|
||||
c.lua_pushlstring(l, p.ptr, p.len);
|
||||
c.lua_setfield(l, -2, "path");
|
||||
},
|
||||
.string => |s| {
|
||||
c.lua_pushlstring(l, s.ptr, s.len);
|
||||
c.lua_setfield(l, -2, "string");
|
||||
},
|
||||
}
|
||||
|
||||
c.lua_setfield(l, -2, "content");
|
||||
}
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
c.lua_pushnil(l);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn initMetatable(l: *c.lua_State) void {
|
||||
_ = c.luaL_newmetatable(l, lua_registry_key);
|
||||
|
||||
c.lua_pushcfunction(l, ffi.luaFunc(lGC));
|
||||
c.lua_setfield(l, -2, "__gc");
|
||||
|
||||
c.lua_pushcfunction(l, ffi.luaFunc(lCall));
|
||||
c.lua_setfield(l, -2, "__call");
|
||||
|
||||
c.lua_pop(l, 1);
|
||||
}
|
||||
};
|
||||
|
||||
pub const LTemplate = struct {
|
||||
pub const lua_registry_key = "confgen_template";
|
||||
|
||||
|
@ -567,12 +666,11 @@ pub const LTemplate = struct {
|
|||
|
||||
// call post processor
|
||||
if (c.lua_pcall(l, 1, 1, 0) != 0) {
|
||||
// TODO: return this instead of logging
|
||||
std.log.err("running post processor: {s}", .{ffi.luaToString(l, -1)});
|
||||
return error.PostProcessor;
|
||||
try ffi.luaFmtString(l, "running post processor: {?s}", .{ffi.luaToString(l, -1)});
|
||||
return error.LuaError;
|
||||
}
|
||||
|
||||
const out = ffi.luaToString(l, -1);
|
||||
const out = ffi.luaConvertString(l, -1);
|
||||
|
||||
return try self.output.allocator.dupe(u8, out);
|
||||
}
|
||||
|
@ -604,8 +702,11 @@ pub const LTemplate = struct {
|
|||
}
|
||||
|
||||
fn lPushValue(l: *c.lua_State) !c_int {
|
||||
c.luaL_checkany(l, 2);
|
||||
if (c.lua_isnil(l, 2)) return 0; // do nothing if passed nil
|
||||
|
||||
const self = ffi.luaGetUdata(LTemplate, l, 1);
|
||||
try self.output.appendSlice(ffi.luaCheckString(l, 2));
|
||||
try self.output.appendSlice(ffi.luaConvertString(l, 2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -632,7 +733,7 @@ pub const LTemplate = struct {
|
|||
|
||||
const mode = mode: {
|
||||
if (c.lua_isstring(l, 2) != 0) {
|
||||
const s = ffi.luaToString(l, 2);
|
||||
const s = ffi.luaToString(l, 2) orelse unreachable;
|
||||
if (s.len != 3) break :mode null;
|
||||
break :mode std.fmt.parseInt(u24, s, 8) catch null;
|
||||
} else if (c.lua_isnumber(l, 2) != 0) {
|
||||
|
@ -684,5 +785,7 @@ pub const LTemplate = struct {
|
|||
|
||||
c.lua_pushvalue(l, -1);
|
||||
c.lua_setfield(l, -2, "__index");
|
||||
|
||||
c.lua_pop(l, 1);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,6 +40,8 @@ pub const TemplateCode = struct {
|
|||
|
||||
c.lua_pushcfunction(l, ffi.luaFunc(lGC));
|
||||
c.lua_setfield(l, -2, "__gc");
|
||||
|
||||
c.lua_pop(l, 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
function __confgen_completion() {
|
||||
if [ "${COMP_CWORD}" -eq 1 ]; then
|
||||
COMPREPLY=($(compgen -A file -W "--compile -c --json-opt -j --help -h --file -f" -- "${COMP_WORDS[1]}"))
|
||||
COMPREPLY=($(compgen -A file -W "--compile -c --json-opt -j --help -h --file -f --eval -e --post-eval -p --watch -w" -- "${COMP_WORDS[1]}"))
|
||||
elif [ "${COMP_CWORD}" -eq 2 ]; then
|
||||
case "${COMP_WORDS[1]}" in
|
||||
"--help" | "-h") COMPREPLY=() ;;
|
||||
"--compile" | "-c" | "--json-opt" | "-j" | "--file" | "-f")
|
||||
"--compile" | "-c" | "--json-opt" | "-j" | "--file" | "-f" | "--watch" | "-w")
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
;;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
function __confgenfs_completion() {
|
||||
if [ "${COMP_CWORD}" -eq 1 ]; then
|
||||
COMPREPLY=($(compgen -A file -W "--help -h" -- "${COMP_WORDS[1]}"))
|
||||
COMPREPLY=($(compgen -A file -W "--help -h --eval -e --post-eval -p" -- "${COMP_WORDS[1]}"))
|
||||
elif [ "${COMP_CWORD}" -eq 2 ]; then
|
||||
case "${COMP_WORDS[1]}" in
|
||||
"--help" | "-h") COMPREPLY=() ;;
|
||||
|
|
|
@ -5,3 +5,6 @@ complete -c confgen -s h -l help -d "Show help"
|
|||
complete -c confgen -s c -l compile -d "Compile a template to Lua instead of running" -Fr
|
||||
complete -c confgen -s j -l json-opt -d "Write the given or all fields from cg.opt to stdout as JSON after running the given confgenfile instead of running" -Fr
|
||||
complete -c confgen -s f -l file -d "Evaluate a single template and write the output instead of running"
|
||||
complete -c confgen -s e -l eval -r -d "Evaluate the given lua code before loading the confgenfile"
|
||||
complete -c confgen -s p -l post-eval -r -d "Evaluate the given lua code after loading the confgenfile"
|
||||
complete -c confgen -s w -l watch -r -d "Watch for changes of input files and re-generate them if changed"
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
complete -c confgenfs -e
|
||||
|
||||
complete -c confgenfs -s h -l help -d "Show help"
|
||||
complete -c confgenfs -s e -l eval -r -d "Evaluate the given lua code before loading the confgenfile"
|
||||
complete -c confgenfs -s p -l post-eval -r -d "Evaluate the given lua code after loading the confgenfile"
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
.B confgen
|
||||
.RI [ CONFGENFILE ]
|
||||
.RI [ OUTPATH ]
|
||||
.RI < --eval\ [CODE] >
|
||||
.RI < --post-eval\ [CODE] >
|
||||
.RI < --watch >
|
||||
.br
|
||||
.B confgen --compile
|
||||
.RI [ TEMPLATE_FILE ]
|
||||
|
@ -81,6 +84,28 @@ The
|
|||
.I opt
|
||||
table will be empty and functions that normally add files to be generated will have no effect.
|
||||
|
||||
.TP
|
||||
.B --eval [CODE]
|
||||
Evaluate the given
|
||||
.I lua code
|
||||
before loading the
|
||||
.IR confgenfile .
|
||||
|
||||
.TP
|
||||
.B --post-eval [CODE]
|
||||
Evaluate the given
|
||||
.I lua code
|
||||
after loading the
|
||||
.IR confgenfile .
|
||||
|
||||
.TP
|
||||
.B --watch
|
||||
Watch for changes on
|
||||
.I input files
|
||||
and regenerate them when they're edited. If the
|
||||
.I confgenfile
|
||||
is changed, the entire state will be re-loaded.
|
||||
|
||||
.SH SEE ALSO
|
||||
.BR confgen (3),
|
||||
.BR confgen.lua (5),
|
||||
|
|
|
@ -9,6 +9,8 @@ template engine.
|
|||
.B confgenfs
|
||||
.RI [ CONFGENFILE ]
|
||||
.RI [ MOUNTPOINT ]
|
||||
.RI < --eval\ [CODE] >
|
||||
.RI < --post-eval\ [CODE] >
|
||||
.br
|
||||
.B confgenfs --help
|
||||
|
||||
|
@ -63,6 +65,21 @@ parameter.
|
|||
.B --help
|
||||
Show a help message.
|
||||
|
||||
.TP
|
||||
.B --eval [CODE]
|
||||
Evaluate the given
|
||||
.I lua code
|
||||
before loading the
|
||||
.IR confgenfile .
|
||||
|
||||
.TP
|
||||
.B --post-eval [CODE]
|
||||
Evaluate the given
|
||||
.I lua code
|
||||
after loading the
|
||||
.IR confgenfile .
|
||||
|
||||
|
||||
.SH EXAMPLES
|
||||
Change an option representing the currently in-use wayland compositor at runtime by executing
|
||||
the following shellcode in a startup script. The code assumes
|
||||
|
|
|
@ -146,6 +146,37 @@ will be serialized as
|
|||
Example:
|
||||
.B local json = cg.toJSON({ x = \(dqy\(dq }) -- '{\(dqx\(dq: \(dqy\(dq}'
|
||||
|
||||
.TP
|
||||
.B cg.fileIter()
|
||||
Obtain an
|
||||
.I iterator function
|
||||
over the files that have been added to confgen. Upon each invocation, it will return a table with
|
||||
the following fields:
|
||||
|
||||
.IP \(bu
|
||||
The
|
||||
.I path
|
||||
field will contain the path of the output file relative to the
|
||||
.IR destination\ directory .
|
||||
|
||||
.IP \(bu
|
||||
The
|
||||
.I copy
|
||||
field will be set to
|
||||
.I true
|
||||
if the file is not a template and will be simply copied to the destination and
|
||||
.I false
|
||||
if the file is a template and will have generation done first.
|
||||
|
||||
.IP \(bu
|
||||
The
|
||||
.I content
|
||||
field will be a table, either containing the file's source code in its
|
||||
.I string
|
||||
field, or the path to the source file in its
|
||||
.I path
|
||||
field.
|
||||
|
||||
.TP
|
||||
.B tmpl
|
||||
The global value
|
||||
|
@ -170,7 +201,9 @@ Example:
|
|||
.B tmpl:pushValue(value)
|
||||
Push a Lua value to the output buffer. The values will be converted to strings the same as Lua's
|
||||
.I tostring
|
||||
would. This function is rarely called manually and is instead what
|
||||
would, except that
|
||||
.I nil
|
||||
is ignored and nothing is appended. This function is rarely called manually and is instead what
|
||||
.I Confgen
|
||||
generates for
|
||||
.IR Lua\ expression\ blocks .
|
||||
|
|
|
@ -10,8 +10,14 @@ _confgen() {
|
|||
'--compile[Compile a template to Lua instead of running]:template_file:_files' \
|
||||
'-j[Write the given or all fields from cg.opt to stdout as JSON after running the given confgenfile instead of running]:confgenfile:_files' \
|
||||
'--json-opt[Write the given or all fields from cg.opt to stdout as JSON after running the given confgenfile instead of running]:confgenfile:_files' \
|
||||
"-f[Evaluate a single template and write the output instead of running]:template_file:_files" \
|
||||
"--file[Evaluate a single template and write the output instead of running]:template_file:_files"
|
||||
'-f[Evaluate a single template and write the output instead of running]:template_file:_files' \
|
||||
'--file[Evaluate a single template and write the output instead of running]:template_file:_files' \
|
||||
'-e[Evaluate the given lua code before loading the confgenfile]:code:' \
|
||||
'--eval[Evaluate the given lua code before loading the confgenfile]:code:' \
|
||||
'-p[Evaluate the given lua code after loading the confgenfile]:code:' \
|
||||
'--post-eval[Evaluate the given lua code after loading the confgenfile]:code:' \
|
||||
'-w[Watch for changes of input files and re-generate them if changed]' \
|
||||
'--watch[Watch for changes of input files and re-generate them if changed]'
|
||||
}
|
||||
|
||||
_confgen "$@"
|
||||
|
|
|
@ -7,6 +7,10 @@ _confgenfs() {
|
|||
'*:fuse_opts:' \
|
||||
'-h[Show help]' \
|
||||
'--help[Show help]'
|
||||
'-e[Evaluate the given lua code before loading the confgenfile]:code:' \
|
||||
'--eval[Evaluate the given lua code before loading the confgenfile]:code:' \
|
||||
'-p[Evaluate the given lua code after loading the confgenfile]:code:' \
|
||||
'--post-eval[Evaluate the given lua code after loading the confgenfile]:code:'
|
||||
}
|
||||
|
||||
_confgenfs "$@"
|
||||
|
|
Loading…
Reference in a new issue