diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index d5b2a76..40c0fa7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ +# zig .zig-cache zig-out zig-pkg + +# dev +.direnv/ *.tar diff --git a/.luaurc b/.luaurc new file mode 100644 index 0000000..1ad69be --- /dev/null +++ b/.luaurc @@ -0,0 +1,6 @@ +{ + "aliases": { + "maivi": "./lua/maivi", + "lune": "~/.lune/.typedefs/0.10.4/" + } +} diff --git a/.nvim.lua b/.nvim.lua new file mode 100644 index 0000000..4e25dab --- /dev/null +++ b/.nvim.lua @@ -0,0 +1,3 @@ +vim.lsp.enable("lua_ls", false) +-- vim.lsp.enable("emmylua_ls") +vim.lsp.enable("luau_lsp") diff --git a/build.zig b/build.zig index 8c8d9dc..f51b55d 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const std = @import("std"); // for defining build steps and express dependencies between them, allowing the // build runner to parallelize the build automatically (and the cache system to // know when a step doesn't need to be re-run). -pub fn build(b: *std.Build) void { +pub fn build(b: *std.Build) !void { // Standard target options allow the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options @@ -41,6 +41,30 @@ pub fn build(b: *std.Build) void { .target = target, }); + const exe_options = b.addOptions(); + mod.addOptions("opts", exe_options); + + const opt_version_string = b.option([]const u8, "version-string", "Override version string. Default is to find out with git."); + const version_slice = if (opt_version_string) |version| version else v: { + if (!std.process.can_spawn) { + std.debug.print("error: version info cannot be retrieved from git. version must be provided using -Dversion-string\n", .{}); + std.process.exit(1); + } + + var code: u8 = undefined; + const git_rev_untrimmed = try b.runAllowFail(&[_][]const u8{ + "git", + "-C", b.build_root.path orelse ".", // affects the --git-dir argument + "--git-dir", ".git", // affected by the -C argument + "rev-parse", "HEAD", + }, &code, .ignore); + const git_rev = std.mem.trim(u8, git_rev_untrimmed, " \n\r"); + + break :v git_rev; + }; + const version = try b.allocator.dupeZ(u8, version_slice); + exe_options.addOption([:0]const u8, "version", version); + // Here we define an executable. An executable needs to have a root module // which needs to expose a `main` function. While we could add a main function // to the module defined above, it's sometimes preferable to split business @@ -83,6 +107,15 @@ pub fn build(b: *std.Build) void { }), }); + const zlua = b.dependency("zlua", .{ + .target = target, + .optimize = optimize, + .lang = .luau, + }); + + mod.addImport("zlua", zlua.module("zlua")); + exe.root_module.addImport("zlua", zlua.module("zlua")); + const logz = b.dependency("logz", .{ .target = target, .optimize = optimize, diff --git a/build.zig.zon b/build.zig.zon index 4c55a3b..87da51a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -35,6 +35,10 @@ .url = "git+https://github.com/karlseguin/log.zig?ref=master#7fa8de39ad4d4adc54fce4d9bb0b6bc6c0076592", .hash = "logz-0.0.0-O9YWXsllAgBq-S0z6q8AInWjei8h7polkVVRzJIGE-jW", }, + .zlua = .{ + .url = "git+https://github.com/natecraddock/ziglua?ref=main#b23143c54b2fc5207825336fb7e61220756dc0a6", + .hash = "zlua-0.1.0-hGRpC4t-BQDQJLgY32R_bGhGNHNNULWAh5ltxho4_pQw", + }, }, .paths = .{ "build.zig", diff --git a/data/_index.md b/data/_index.md new file mode 100644 index 0000000..a0e79c8 --- /dev/null +++ b/data/_index.md @@ -0,0 +1,21 @@ +## 🪴 garden + +welcome to my internet garden, a cozy corner of the internet for storing my ideas and sharing what i'm working on. + +```bash +curl -L robinwobin.dev +``` + +in my garden you can find my [blog posts](/blog) and more frequently some small [notes](/notes). + +this is where i keep the tools i've built - small creations designed to make +development smoother, workflows simpler, and ideas easier to bring to life. + +- evergarden - a soft, nature-inspired colorscheme +- ivy - a nix-based neovim setup +- lynn.nvim - a lightweight neovim plugin manager _(you can read more about it [here](/blog/neovim-native-configuration))_ +- artio.nvim - a minimal, nature-infused file picker for neovim using the new extui window + +more tools are always growing here - check back later! 🌿 + +you can find links to some cool people on the [badges](/badges) page. diff --git a/data/about.md b/data/about.md new file mode 100644 index 0000000..d0df260 --- /dev/null +++ b/data/about.md @@ -0,0 +1,31 @@ ++++ +title = "about me :)" ++++ + +###### ✨ i'm passionate about: + +
`P` | `C` | Displayable +export type Displayable = string | number | boolean +export type Props = { [string]: Displayable } +export type Children = { Displayable } +export type PropsOrChildren = Props | Children | Displayable + +---@alias VNode Displayable | Displayable[] +---@alias Element
fun(args: PropsOrChildren<`P`, `C`>?): Displayable +---@alias Component
fun(props: `P`, children: `C`): VNode +export type VNode = Displayable | { Displayable } +export type Element = (args: PropsOrChildren?) -> Displayable +export type Component = (props: Props, children: Children) -> VNode diff --git a/lua/maivi/utils.luau b/lua/maivi/utils.luau new file mode 100644 index 0000000..fda720f --- /dev/null +++ b/lua/maivi/utils.luau @@ -0,0 +1,18 @@ +local M = {} + +---@param l (Displayable | Displayable[])[] +---@param handle_str fun(Displayable): string? +---@param sep? string +---@return string +function M.recursive_concat(l, handle_str, sep) + ---@type string[] + local parts = {} + + for _, v in ipairs(l) do + table.insert(parts, handle_str(v)) + end + + return table.concat(parts, sep) +end + +return M diff --git a/main.luau b/main.luau new file mode 100644 index 0000000..736b0ff --- /dev/null +++ b/main.luau @@ -0,0 +1,84 @@ +local fs = require("@lune/fs") + +read = fs.readFile + +h = require("@maivi/h") +this = { + base_url = "https://robinwobin.dev", + rev = "dev", + data = { + "index.md", + "about.md", + "badges.md", + ["blog/building-this-site.md"] = {}, + ["blog/creating-a-colorscheme.md"] = {}, + ["blog/neovim-artio-picker.md"] = {}, + ["blog/neovim-native-configuration.md"] = {}, + "notes/artio-live-mode-toggle.md", + "notes/artio-released.md", + "notes/aspen-released.md", + "notes/ebil-released.md", + "notes/evergarden-npm-release.md", + "notes/evergarden-site-release.md", + "notes/evergarden-sublime-text-release.md", + "notes/evergarden-userstyles-release.md", + "notes/lylla-sections.md", + "notes/mimi-released.md", + "notes/site-zig-minimal.md", + "tools/ivy.md", + "tools/lynn.md", + }, +} + +local root = ".rewritten" + +-- ============================================================================ + +local maivi = require("@maivi") + +if string.sub(root, -1) ~= "/" then + root = root .. "/" +end + +---@param path string +---@param mkdir? boolean +local function to_outpath(path, mkdir) + if #path == 0 then + path = "/" + end + + if string.sub(path, -1) == "/" then + path = path .. "index.html" + else + path = path .. "/index.html" + end + + if string.sub(path, 1, 1) == "/" then + path = root .. string.sub(path, 2) + end + + if mkdir then + local rev = string.reverse(path) + local i = string.find(rev, "/") + if i then + local dirname = string.reverse(string.sub(rev, i)) + fs.writeDir(dirname) + end + end + + return path +end + +-- ============================================================================ + +maivi.setup() + +for path, _ in pairs(maivi.router.paths) do + local p = to_outpath(path, true) + print(path) + local content = maivi.handle(path) + if not content or type(content) ~= "string" then + error(("invalid handle return type (%s)"):format(type(content))) + end + fs.writeFile(p, content) +end diff --git a/nix/shell.nix b/nix/shell.nix new file mode 100644 index 0000000..1186c77 --- /dev/null +++ b/nix/shell.nix @@ -0,0 +1,11 @@ +{ + mkShellNoCC, + luau-lsp, + lune, +}: +mkShellNoCC { + packages = [ + luau-lsp + lune + ]; +} diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..65872c6 --- /dev/null +++ b/selene.toml @@ -0,0 +1,5 @@ +[selene] +base = "lua51" + +[rules] +mixed_table = "allow" diff --git a/src/config.zig b/src/config.zig index 28edbdb..6f593ae 100644 --- a/src/config.zig +++ b/src/config.zig @@ -4,6 +4,6 @@ const Self = @This(); root: []const u8, // try local fs if path not found in virtual fs -try_local: bool = false, +no_local: bool = false, // use local fs (do not init virtual fs) use_local: bool = false, diff --git a/src/handler.zig b/src/handler.zig index 6744113..40553b1 100644 --- a/src/handler.zig +++ b/src/handler.zig @@ -22,21 +22,6 @@ pub fn init(vfs: *Vfs, cfg: Config) !Self { config: Config, vfs: *Vfs, -pub fn logreq(req: *helpers.Request) logz.Logger { - var l = maivi.log("handler") - .int("id", req.id) - .string("msg", "request") - .level(.Info); - var it = req.request.headers.iterator(); - while (it.next()) |header| { - const k = std.mem.join(maivi.allocator, "/", &.{ "header", header.key }) catch header.key; - l = l.string(k, header.value); - } - return l - .any("method", req.request.method) - .string("path", req.path); -} - const SanitizationError = error{NoTrailingSlashes} || std.mem.Allocator.Error; fn sanitize(allocator: std.mem.Allocator, path: []const u8) ![]u8 { var buf = try allocator.alloc(u8, path.len); @@ -59,12 +44,25 @@ pub fn dispatch(self: *Self, action: helpers.Action(*Self), req: *httpz.Request, var request = try helpers.Request.init(allocator, req, null); var response = helpers.Response.init(res); - logreq(&request).log(); + var l = maivi.log("handler.dispatch") + .int("id", request.id) + .string("msg", "request") + .level(.Info); + var it = req.headers.iterator(); + while (it.next()) |header| { + const k = std.mem.join(maivi.allocator, "/", &.{ "header", header.key }) catch header.key; + defer maivi.allocator.free(k); + l = l.string(k, header.value); + } + l + .any("method", req.method) + .string("path", request.path) + .log(); const timer = std.Io.Timestamp.now(maivi.io, .cpu_thread); defer { const elapsed = std.Io.Timestamp.durationTo(timer, std.Io.Timestamp.now(maivi.io, .cpu_thread)).toMilliseconds(); - var l = maivi.log("handler") + l = maivi.log("handler.dispatch") .int("id", request.id) .string("msg", "response") .level(switch (res.status) { @@ -80,9 +78,10 @@ pub fn dispatch(self: *Self, action: helpers.Action(*Self), req: *httpz.Request, l = l.string("content-type", @tagName(content_type)); } - var it = res.headers.iterator(); + it = res.headers.iterator(); while (it.next()) |header| { const k = std.mem.join(allocator, "/", &.{ "header", header.key }) catch header.key; + defer allocator.free(k); l = l.string(k, header.value); } diff --git a/src/helpers.zig b/src/helpers.zig index 9e5afef..b27b88e 100644 --- a/src/helpers.zig +++ b/src/helpers.zig @@ -84,7 +84,7 @@ pub const Response = struct { return; } - if (!h.config.try_local and !h.config.use_local) return error.FileNotFound; + if (h.config.no_local and !h.config.use_local) return error.FileNotFound; maivi.log("Response.set_file").level(.Debug).string("msg", "using generator").string("path", path).log(); @@ -157,7 +157,7 @@ pub fn get_local_file(h: *Handler, path: []const u8) SetFileContentError![]const var buf: [0x400]u8 = undefined; var file_reader = file.reader(maivi.io, &buf); const reader = &file_reader.interface; - const contents = try reader.readAlloc(maivi.allocator, size); + const contents = try reader.readAlloc(h.vfs.map.allocator, size); return contents; } diff --git a/src/lua.zig b/src/lua.zig new file mode 100644 index 0000000..2245ecf --- /dev/null +++ b/src/lua.zig @@ -0,0 +1,147 @@ +const std = @import("std"); +const maivi = @import("root.zig"); + +const zlua = @import("zlua"); +const Lua = zlua.Lua; + +const opts = @import("opts"); +const Config = @import("config.zig"); + +pub var lua: *Lua = undefined; + +pub var config: *const Config = undefined; + +pub fn init(cfg: *const Config) !void { + maivi.log("lua.init").level(.Debug).log(); + + config = cfg; + + lua = try Lua.init(maivi.allocator); + lua.openLibs(); + lua.register("require", zlua.wrap(require)); + + // h ========================================================================== + + _ = lua.getGlobal("require"); + _ = lua.pushString("@maivi/h"); + lua.call(.{ .args = 1, .results = 1 }); + lua.setGlobal("h"); + + // this ======================================================================= + + lua.newTable(); + _ = lua.pushString("https://robinwobin.dev"); + lua.setField(-2, "base_url"); + _ = lua.pushString(opts.version); + lua.setField(-2, "rev"); + + // data ======================================================================= + + lua.newTable(); + + // l.newTable(); + // l.setField(-2, "index.md"); + + lua.setField(-2, "data"); + + lua.setGlobal("this"); + + // read ======================================================================= + + lua.register("read", zlua.wrap(lua_read)); + + // maivi ====================================================================== + + _ = lua.getGlobal("require"); + _ = lua.pushString("@maivi"); + lua.call(.{ .args = 1, .results = 1 }); + lua.setGlobal("maivi"); + + // maivi.setup ================================================================ + + _ = lua.getGlobal("maivi"); + _ = lua.getField(-1, "setup"); + lua.call(.{ .args = 0, .results = 0 }); +} + +fn read_file(path: []const u8) ![]const u8 { + maivi.log("maivi.read_file").level(.Debug).string("path", path).log(); + + const dir = try std.Io.Dir.openDirAbsolute(maivi.io, config.root, .{}); + + const file = try dir.openFile(maivi.io, path, .{ .mode = .read_only }); + + const stat = try file.stat(maivi.io); + if (stat.kind == .directory) return error.IsDirectory; + if (stat.kind != .file) return error.NonFile; + const size = stat.size; + defer file.close(maivi.io); + + var buf: [0x400]u8 = undefined; + var file_reader = file.reader(maivi.io, &buf); + const reader = &file_reader.interface; + const contents = try reader.readAlloc(maivi.allocator, size); + return contents; +} + +fn lua_read(l: *Lua) !i32 { + const path = try l.toString(-1); + + const contents = try read_file(path); + defer maivi.allocator.free(contents); + _ = l.pushString(contents); + + return 1; +} + +fn dofile(l: *Lua, path: []const u8) !void { + const contents = try read_file(path); + defer maivi.allocator.free(contents); + + const str = try maivi.allocator.dupeZ(u8, contents); + defer maivi.allocator.free(str); + try l.doString(str); +} + +pub fn require(l: *Lua) !i32 { + var modname_ = try l.toString(-1); + maivi.log("lua.require").level(.Debug).string("modname", modname_).log(); + if (std.mem.startsWith(u8, modname_, "@")) modname_ = modname_[1..]; + + const modname = try maivi.allocator.dupe(u8, modname_); + defer maivi.allocator.free(modname); + const path = try std.mem.join(maivi.allocator, "/", &.{ "lua", modname }); + defer maivi.allocator.free(path); + + dofile(l, path) catch |err| switch (err) { + error.IsDirectory => { + const new_path = try std.mem.concat(maivi.allocator, u8, &.{ path, "/init.luau" }); + defer maivi.allocator.free(new_path); + try dofile(l, new_path); + }, + error.FileNotFound => { + const new_path = try std.mem.concat(maivi.allocator, u8, &.{ path, ".luau" }); + defer maivi.allocator.free(new_path); + try dofile(l, new_path); + }, + else => return 0, + }; + + return 1; +} + +pub fn handle(h: *maivi.Handler, path: []const u8) ![]const u8 { + _ = lua.getGlobal("maivi"); + _ = lua.getField(-1, "handle"); + _ = lua.pushString(path); + lua.call(.{ .args = 1, .results = 1 }); + if (lua.isNil(-1)) { + return error.FileNotFound; + } + const str = lua.toString(-1) catch { + maivi.log("lua.handle").level(.Error).string("msg", "got non string type").log(); + return error.FileNotFound; + }; + const buf = try h.vfs.map.allocator.dupe(u8, str); + return buf; +} diff --git a/src/main.zig b/src/main.zig index e7b7418..6972a81 100644 --- a/src/main.zig +++ b/src/main.zig @@ -24,11 +24,14 @@ pub fn main(init: std.process.Init) !void { const config: maivi.Config = .{ .root = init.environ_map.get("MAIVI_ROOT") orelse cwd, - .try_local = if (init.environ_map.get("MAIVI_TRY_LOCAL")) |env| std.mem.eql(u8, env, "1") else false, + .no_local = if (init.environ_map.get("MAIVI_NO_LOCAL")) |env| std.mem.eql(u8, env, "1") else false, .use_local = if (init.environ_map.get("MAIVI_USE_LOCAL")) |env| std.mem.eql(u8, env, "1") else false, }; maivi.log("main").string("root", config.root).log(); + try maivi.lua.init(&config); + defer maivi.lua.lua.deinit(); + var arena = std.heap.ArenaAllocator.init(maivi.allocator); defer arena.deinit(); const allocator = arena.allocator(); diff --git a/src/root.zig b/src/root.zig index 17c3783..ff5e058 100644 --- a/src/root.zig +++ b/src/root.zig @@ -9,6 +9,8 @@ pub const Handler = @import("handler.zig"); pub const helpers = @import("helpers.zig"); const Status = helpers.Status; +pub const lua = @import("lua.zig"); + const logz = @import("logz"); const httpz = @import("httpz"); @@ -27,6 +29,8 @@ pub fn init(pr: std.process.Init) !void { .level = .Debug, .output = .stderr, }); + + log("maivi.init").level(.Debug).log(); } pub fn deinit() void { @@ -56,13 +60,15 @@ pub fn handle(h: *Handler, req: *helpers.Request, res: *helpers.Response) !void } else { var it = std.mem.splitBackwardsScalar(u8, path, '/'); if (!std.mem.containsAtLeast(u8, it.first(), 1, ".")) { - path = try std.mem.join(allocator, "/", &.{ path, "index.html" }); + const old_path = path; + defer allocator.free(old_path); + path = try std.mem.join(allocator, "/", &.{ old_path, "index.html" }); } } log("maivi.handle").level(.Debug).int("id", req.id).string("msg", "using fs").string("path", path).log(); - res.set_file(h, path, null) catch |err| switch (err) { + res.set_file(h, path, lua.handle) catch |err| switch (err) { error.IsDirectory => { const new_path = try std.mem.join(allocator, "/", &.{ path, "index.html" }); try req.set_path(new_path); diff --git a/src/vfs.zig b/src/vfs.zig index 355c5b6..4d5e8ff 100644 --- a/src/vfs.zig +++ b/src/vfs.zig @@ -41,7 +41,6 @@ pub fn init(allocator: std.mem.Allocator) Self { } pub fn deinit(self: *Self) void { - maivi.log("Vfs.deinit").level(.Debug).log(); self.map.deinit(); } diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..5957975 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,9 @@ +indent_type = "Spaces" +indent_width = 2 +column_width = 120 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" +line_endings = "Unix" + +[sort_requires] +enabled = true diff --git a/test.luau b/test.luau new file mode 100644 index 0000000..782302b --- /dev/null +++ b/test.luau @@ -0,0 +1,19 @@ +local Iter = require("@maivi/iter") + +-- Iter.lst({ +-- "a", +-- "b", +-- "c", +-- }):map() +print(Iter.lst({ + "a", + "b", + "c", + "d", +}) + :skip(1) + :take(2) + :map(function(k, v) + return string.format("%s%s", k, v) + end) + :to())