Something went wrong. Try again.
🦦 a tiny cute lua web framework
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236const std = @import("std");const Io = std.Io;const Allocator = std.mem.Allocator;
const fern = @import("fern");const zlua = @import("zlua");const Lua = zlua.Lua;
const maivi = @import("root.zig");const lua = maivi.lua;
// init =======================================================================
var arena: std.heap.ArenaAllocator = undefined;var allocator: Allocator = undefined;
pub fn init() void { arena = .init(maivi.allocator); allocator = arena.allocator();}
pub fn deinit() void { arena.deinit();}
// Data =======================================================================
pub const Frontmatter = struct { pub const Date = packed struct { year: c_int = 0, month: c_int = 0, day: c_int = 0,
// needs format YYYY-MM-DD pub fn parse(str: []const u8) !Frontmatter.Date { if (str.len != 10) return error.InvalidLength;
return .{ .year = try std.fmt.parseInt(c_int, str[0..4], 10), .month = try std.fmt.parseInt(c_int, str[5..7], 10), .day = try std.fmt.parseInt(c_int, str[8..10], 10), }; } };
pub const Item = union(enum) { str: []const u8, date: Date, list: [][]const u8 };
const KV = struct { k: []const u8, v: Item }; pub const KeyPairs = std.ArrayList(KV);
/// parse if the first line == '+++' until first next line that == '+++' pub fn parse(data_allocator: Allocator, reader: *Io.Reader) !Frontmatter.KeyPairs { var props: Frontmatter.KeyPairs = try .initCapacity(data_allocator, 16);
const first = reader.take(3) catch |e| switch (e) { error.EndOfStream => return error.NoFrontMatter, else => return e, }; if (!std.mem.eql(u8, first, "+++")) return error.NoFrontMatter;
w: while (true) { const line = reader.takeDelimiterExclusive('\n') catch break :w; p: { _ = reader.peek(1) catch break :p; reader.toss(1); } if (std.mem.eql(u8, line, "+++")) break :w;
var it = std.mem.splitScalar(u8, line, '='); const key = std.mem.trim(u8, it.first(), " "); const value = if (it.peek()) |v| std.mem.trim(u8, v, " ") else continue :w;
if (value.len == 0) continue :w;
if (value[0] == '"') { if (value[value.len - 1] != '"') continue :w; const str = value[1 .. value.len - 1]; try props.append(data_allocator, .{ .k = key, .v = .{ .str = str } }); continue :w; } if (std.mem.findScalar(u8, value, ',')) |_| { var list: std.ArrayList([]const u8) = try .initCapacity(data_allocator, 16);
var l_it = std.mem.splitScalar(u8, value, ','); while (l_it.next()) |item| if (item.len > 0) try list.append(data_allocator, item);
try props.append(data_allocator, .{ .k = key, .v = .{ .list = try list.toOwnedSlice(data_allocator) } }); continue :w; }
const date = Date.parse(value) catch |err| { fern.err().ctx("data.Data.parse") .msg("date parse failed") .str("v", value) .err(err).log(); continue :w; }; try props.append(data_allocator, .{ .k = key, .v = .{ .date = date } }); }
return props; }};
// load =======================================================================
pub fn load(io: Io) !void { fern.trace().ctx("data.load").log(); const rootdir = try Io.Dir.openDirAbsolute(io, maivi.config.root, .{}); defer rootdir.close(io); const data_dir = Io.Dir.openDir(rootdir, io, "data", .{ .iterate = true, .access_sub_paths = true }) catch |err| switch (err) { error.FileNotFound, error.NotDir => { fern.warn().ctx("data") .msg("could not parse data/ directory; it is either missing or not a directory.") .log(); return; }, else => return err, }; defer data_dir.close(io); try parseDir(io, data_dir);}
fn parseFile(io: Io, file: Io.File) !void { var real_path: [0x400]u8 = undefined; const i = try file.realPath(io, &real_path); const path = std.mem.cutPrefix(u8, real_path[0..i], maivi.config.root) orelse return error.Unexpected;
var buf: [0x400]u8 = undefined; var file_reader = file.reader(io, &buf); const reader = &file_reader.interface;
var a: std.heap.ArenaAllocator = .init(maivi.allocator); defer a.deinit(); const data_allocator = a.allocator(); var data: ?Frontmatter.KeyPairs = Frontmatter.parse(data_allocator, reader) catch |err| switch (err) { error.NoFrontMatter => null, else => return err, }; defer if (data != null) data.?.deinit(data_allocator);
fern.debug().ctx("data.parse_file") .msg("adding data") .str("path", path).log(); try setData(maivi.lua.lua, path, data);}
fn parseDir(io: Io, dir: Io.Dir) !void { var it = dir.iterate();
return while (try it.next(io)) |x| switch (x.kind) { .file => { const file = try dir.openFile(io, x.name, .{}); defer file.close(io); try parseFile(io, file); }, .directory => { const sub_dir = try dir.openDir(io, x.name, .{ .iterate = true, .access_sub_paths = true }); defer sub_dir.close(io); try parseDir(io, sub_dir); }, else => continue, };}
// lua ========================================================================
pub fn setData(l: *Lua, path: []const u8, fmtable: ?Frontmatter.KeyPairs) !void { _ = l.getGlobal("this"); _ = l.getField(-1, "data"); defer l.pop(2); // pops "data" and "this"
_ = l.pushString(path); if (fmtable) |t| _ = try pushData(l, t) else l.newTable(); l.setTable(-3);}
// helpers ====================================================================
fn pushData(l: *Lua, d: Frontmatter.KeyPairs) !i32 { l.createTable(0, @as(i32, @intCast(d.items.len)));
for (d.items) |kv| { _ = l.pushString(kv.k); errdefer l.pop(1); defer l.setTable(-3);
_ = switch (kv.v) { .str => l.pushString(kv.v.str), .list => try pushList(l, []const u8, kv.v.list), .date => try pushDate(l, kv.v.date), }; }
return 1;}
/// * Pops from Stack: `0`/// * Pushes to Stack: `1`fn pushList(l: *Lua, comptime T: anytype, lst: []T) !i32 { l.createTable(@as(i32, @intCast(lst.len)), 0); errdefer l.pop(1); for (lst, 1..lst.len + 1) |item, n| { _ = try l.pushAny(item); l.setIndexRaw(-2, @as(c_int, @intCast(n))); } return 1;}
/// * Pops from Stack: `0`/// * Pushes to Stack: `1`fn pushDate(l: *Lua, date: Frontmatter.Date) !i32 { _ = l.getGlobal("os"); _ = l.getField(-1, "time"); l.remove(l.absIndex(-2)); if (!l.isFunction(-1)) return error.Unexpected;
// { year = ..., month = ..., day = ... } l.createTable(0, 3); l.pushInteger(date.year); l.setField(-2, "year"); l.pushInteger(date.month); l.setField(-2, "month"); l.pushInteger(date.day); l.setField(-2, "day");
try lua.pcall(l, .{ .args = 1, .results = 1 }); return 1;}