---
Makefile | 2 ++
README.org | 8 ++---
lua/lua.ha | 56 ++++++++++++++++++++++++++++++
man/thp.5.scd | 4 +++
mods/lua+lua.ha | 91 +++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 157 insertions(+), 4 deletions(-)
create mode 100644 lua/lua.ha
create mode 100644 mods/lua+lua.ha
diff --git a/Makefile b/Makefile
index a2bc2e9..626eb09 100644
--- a/Makefile
+++ b/Makefile
@@ -13,11 +13,13 @@ MANDIR := $(PREFIX)/share/man
DESTDIR :=
GITLIBS = $(patsubst -pthread,,$(shell $(PKG_CONFIG) --libs libgit2 --static))
+LUALIBS = $(shell $(PKG_CONFIG) --libs lua5.4)
HAREPATH := ./vendor:${HAREPATH}
MODS ?= host path sigil $(EXTRA_MODS)
THPD_LIBS = $(if $(filter git,$(MODS)),$(GITLIBS))
+THPD_LIBS+= $(if $(filter lua,$(MODS)),$(LUALIBS))
THPD_EXTRA_DEPS =$(if $(filter git,$(MODS)),git/*.ha) $(if $(filter timer,$(MODS)),timer/*.ha)
all: thpd thp docs
diff --git a/README.org b/README.org
index 5dc7400..d303802 100644
--- a/README.org
+++ b/README.org
@@ -29,16 +29,16 @@ You need ~hare~, follow the instructions [[https://harelang.org/installation][at
To build thp with all modules included, you need libgit2 and your platforms libgit2-dev equivalent installed, then issued
#+BEGIN_SRC sh :exports code
-make EXTRA_MODS="project git clock timer shind uptime moon sun star"
-make EXTRA_MODS="project git clock timer shind uptime moon sun star" test
+make EXTRA_MODS="project git clock timer shind uptime moon sun star lua"
+make EXTRA_MODS="project git clock timer shind uptime moon sun star lua" test
make PREFIX="${HOME}" install
#+END_SRC
To build with just the modules you use in your spec, use ~MODS~:
#+BEGIN_SRC sh :exports code
-make MODS="path host project timer uptime moon sun star"
-make MODS="path host project timer uptime moon sun star" test
+make MODS="path host project timer uptime moon sun star lua"
+make MODS="path host project timer uptime moon sun star lua" test
make PREFIX="${HOME}" install
#+END_SRC
diff --git a/lua/lua.ha b/lua/lua.ha
new file mode 100644
index 0000000..190faba
--- /dev/null
+++ b/lua/lua.ha
@@ -0,0 +1,56 @@
+// Author: Carlos Une <une@fastmail.fm>
+// Maintainer: Carlos Une <une@fastmail.fm>
+//
+// SPDX-FileCopyrightText: 2024 Carlos Une <une@fastmail.fm>
+// SPDX-License-Identifier: GPL-3.0-or-later
+use types::c;
+
+export type KContext = c::ptrdiff;
+export type KFunction = nullable *opaque;
+export type State = opaque;
+export type Integer = int;
+export def MULTRET: int = -1;
+export def OK: int = 0;
+
+export fn tostring(L: *State, i: int) *const c::char = {
+ return tolstring(L, i, null);
+};
+
+export fn pop(L: *State, n: int) void = settop(L, -(n)-1);
+
+export fn loadfile(L: *State, filename: *const c::char) int = {
+ return loadfilex(L, filename, null);
+};
+
+export fn pcall(L: *State, nargs: int, nresults: int, msgh: int) int = {
+ return pcallk(L, nargs, nresults, msgh, 0, null);
+};
+
+export fn dofile(L: *State, filename: *const c::char) int = {
+ let l = loadfilex(L, filename, null);
+ return if (l != 0) l else pcall(L, 0, MULTRET, 0);
+};
+
+export @symbol("luaL_newstate") fn newstate() *State;
+export @symbol("luaL_openlibs") fn openlibs(L: *State) void;
+export @symbol("luaL_loadstring") fn loadstring(L: *State,
+ s: *const c::char) int;
+export @symbol("lua_pcallk") fn pcallk(L: *State, nargs: int, nresults: int,
+ msgh: int, ctx: KContext, k: KFunction) int;
+export @symbol("lua_close") fn close(L: *State) void;
+export @symbol("luaL_loadfilex") fn loadfilex(L: *State,
+ filename: nullable *const c::char, mode: nullable *const c::char) int;
+export @symbol("lua_createtable") fn createtable(L: *State, narr: int,
+ nrec: int) void;
+export @symbol("lua_pushstring") fn pushstring(L: *State,
+ s: *const c::char) *const c::char;
+export @symbol("lua_pushboolean") fn pushboolean(L: *State, int) void;
+export @symbol("lua_pushinteger") fn pushinteger(L: *State, n: Integer) void;
+export @symbol("lua_setglobal") fn setglobal(L: *State,
+ name: *const c::char) void;
+export @symbol("lua_settable") fn settable(L: *State, idx: int) void;
+export @symbol("lua_isstring") fn isstring(L: *State, idx: int) int;
+export @symbol("lua_gettop") fn gettop(L: *State) int;
+export @symbol("lua_settop") fn settop(L: *State, n: int) void;
+export @symbol("lua_tolstring") fn tolstring(L: *State, n: int,
+ sz: nullable *size) *const c::char;
diff --git a/man/thp.5.scd b/man/thp.5.scd
index 5b3450a..0a94375 100644
--- a/man/thp.5.scd
+++ b/man/thp.5.scd
@@ -123,6 +123,10 @@ the modules configuration settings.
The location and time format can be configured. See thpd(5).
- *star*++
displays the position of a star as seen by an observer on Earth.
+- *lua*++
+ Runs a Lua script —_XDG_DATA_HOME_/thp/thp.lua — and copies its return value
+ to the prompt string. The script has read-only access to thp's internal
+ _env::env_ struct through a Lua table named _thp.env_.
# STYLES
diff --git a/mods/lua+lua.ha b/mods/lua+lua.ha
new file mode 100644
index 0000000..709bc38
--- /dev/null
+++ b/mods/lua+lua.ha
@@ -0,0 +1,91 @@
+// Author: Carlos Une <une@fastmail.fm>
+// Maintainer: Carlos Une <une@fastmail.fm>
+//
+// SPDX-FileCopyrightText: 2024 Carlos Une <une@fastmail.fm>
+// SPDX-License-Identifier: GPL-3.0-or-later
+use env;
+use dirs;
+use strings;
+use types::c;
+use lua;
+
+@init fn register_lua() void = {
+ register("lua", &mod_lua);
+};
+
+fn mkfield(L: *lua::State, k: str, v: (str | bool | uint)) void = {
+ pushstring(L, k);
+ match (v) {
+ case let x: str =>
+ pushstring(L, x);
+ case let x: bool =>
+ lua::pushboolean(L, if (x) 1 else 0);
+ case let x: uint =>
+ lua::pushinteger(L, x: int);
+ };
+ lua::settable(L, -3);
+};
+
+fn pushstring(L: *lua::State, s: str) void = {
+ let string = c::fromstr(s);
+ defer free(string);
+ lua::pushstring(L, string);
+};
+
+export fn mod_lua(pe: *env::env) str = {
+ let L = lua::newstate();
+ defer lua::close(L);
+
+ lua::openlibs(L);
+ lua::createtable(L, 0, 0);
+ pushstring(L, "env");
+ lua::createtable(L, 0, 0);
+ pushstring(L, "control");
+ lua::createtable(L, 0, 0);
+ mkfield(L, "remote", pe.control.remote);
+ mkfield(L, "zsh", pe.control.zsh);
+ mkfield(L, "bash", pe.control.bash);
+ mkfield(L, "exit", pe.control.exit);
+ mkfield(L, "reload", pe.control.reload);
+ mkfield(L, "timer", pe.control.timer);
+ mkfield(L, "root", pe.control.root);
+ mkfield(L, "wtitle", pe.control.wtitle);
+ lua::settable(L, -3);
+ mkfield(L, "home", pe.home);
+ mkfield(L, "columns", pe.columns);
+ mkfield(L, "user", pe.user);
+ mkfield(L, "host", pe.host);
+ mkfield(L, "pwd", pe.pwd);
+ mkfield(L, "pd", pe.pd);
+ mkfield(L, "cid", pe.cid);
+ lua::settable(L, -3);
+
+ let thp = c::fromstr("thp");
+ defer free(thp);
+ lua::setglobal(L, thp);
+
+ let script = strings::concat(dirs::data("thp"), "/thp.lua");
+ defer free(script);
+
+ let scriptz = c::fromstr(script);
+ defer free(scriptz);
+
+ if (lua::dofile(L, scriptz) != lua::OK) {
+ let errormsg = lua::tostring(L, -1);
+ lua::pop(L, 1);
+ return strings::concat("mod_lua:", c::tostr(errormsg)!);
+ };
+
+ if (lua::gettop(L) != 1) {
+ return strings::dup("mod_lua:script must return a value.");
+ };
+
+ if (lua::isstring(L, -1) == 0) {
+ lua::pop(L, 1);
+ return strings::dup("mod_lua:script must return a string.");
+ };
+
+ let r = lua::tostring(L, -1);
+ lua::pop(L, 1);
+ return strings::dup(c::tostr(r)!);
+};
--
2.39.2