Expose lush global as a standard library (issue #26)
Register luaopen_lush() in stdlibs so the lush table is available as a global. Users can now extend the shell by adding functions to lush.builtins. The OP_LUSH VM access via integer keys is preserved.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 26 — Shell abbreviations and user-extensible builtins
|
||||
|
||||
**Status:** open
|
||||
**Status:** done
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -60,6 +60,15 @@ This gives power users access to the shell primitives directly, which is useful
|
||||
|
||||
Option B: Expose only `lush.builtins` — safer, sufficient for the abbreviation use case.
|
||||
|
||||
Consider the clarity of these endpoints. Would an alternative naming scheme be clearer? i.e.
|
||||
```lua
|
||||
lush.commands -- cd, exec, umask, user additions
|
||||
lush.run -- __command (backtick execution)
|
||||
lush.interactive -- __interactive (! execution) -- is there an idiotmatic name for this?
|
||||
lush.getenv -- __getenv ($VAR read)
|
||||
lush.setenv -- __setenv ($VAR write)
|
||||
```
|
||||
|
||||
### Relationship to f88b1795
|
||||
|
||||
That commit deliberately hid these from `_G` to avoid polluting the namespace. Exposing a single `lush` global is a middle ground: one clean entry point instead of scattered `__double_underscore` globals, and users can only extend via a structured API rather than accidentally shadowing internals.
|
||||
|
||||
31
lcmd.c
31
lcmd.c
@@ -1199,3 +1199,34 @@ int lushCmd_setenv (lua_State *L) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ===== lush standard library ===== */
|
||||
|
||||
static const luaL_Reg lushlib[] = {
|
||||
{"command", lushCmd_command},
|
||||
{"interactive", lushCmd_interactive},
|
||||
{"getenv", lushCmd_getenv},
|
||||
{"setenv", lushCmd_setenv},
|
||||
{"subcmd", lushCmd_subcmd},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
LUAMOD_API int luaopen_lush (lua_State *L) {
|
||||
luaL_newlib(L, lushlib);
|
||||
/* also store integer-keyed entries for OP_LUSH VM access */
|
||||
lua_pushcfunction(L, lushCmd_command);
|
||||
lua_rawseti(L, -2, LUSH_OP_COMMAND + 1);
|
||||
lua_pushcfunction(L, lushCmd_interactive);
|
||||
lua_rawseti(L, -2, LUSH_OP_INTERACTIVE + 1);
|
||||
lua_pushcfunction(L, lushCmd_getenv);
|
||||
lua_rawseti(L, -2, LUSH_OP_GETENV + 1);
|
||||
lua_pushcfunction(L, lushCmd_setenv);
|
||||
lua_rawseti(L, -2, LUSH_OP_SETENV + 1);
|
||||
lua_pushcfunction(L, lushCmd_subcmd);
|
||||
lua_rawseti(L, -2, LUSH_OP_SUBCMD + 1);
|
||||
/* store in registry for OP_LUSH access */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
||||
return 1;
|
||||
}
|
||||
|
||||
2
lcmd.h
2
lcmd.h
@@ -23,4 +23,6 @@ int lushCmd_getenv (lua_State *L);
|
||||
int lushCmd_setenv (lua_State *L);
|
||||
int lushCmd_subcmd (lua_State *L);
|
||||
|
||||
LUAMOD_API int luaopen_lush (lua_State *L);
|
||||
|
||||
#endif
|
||||
|
||||
26
linit.c
26
linit.c
@@ -39,30 +39,11 @@ static const luaL_Reg stdlibs[] = {
|
||||
{LUA_STRLIBNAME, luaopen_string},
|
||||
{LUA_TABLIBNAME, luaopen_table},
|
||||
{LUA_UTF8LIBNAME, luaopen_utf8},
|
||||
{LUA_LUSHLIBNAME, luaopen_lush},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** Store shell functions in a registry table at LUA_RIDX_LUSH.
|
||||
** Integer keys 1..5 hold the C functions (accessed by OP_LUSH).
|
||||
** String key "builtins" holds the builtins table (set by luaopen_builtins).
|
||||
*/
|
||||
static void opencommand (lua_State *L) {
|
||||
lua_createtable(L, LUSH_OP_COUNT, 1);
|
||||
lua_pushcfunction(L, lushCmd_command);
|
||||
lua_rawseti(L, -2, LUSH_OP_COMMAND + 1);
|
||||
lua_pushcfunction(L, lushCmd_interactive);
|
||||
lua_rawseti(L, -2, LUSH_OP_INTERACTIVE + 1);
|
||||
lua_pushcfunction(L, lushCmd_getenv);
|
||||
lua_rawseti(L, -2, LUSH_OP_GETENV + 1);
|
||||
lua_pushcfunction(L, lushCmd_setenv);
|
||||
lua_rawseti(L, -2, LUSH_OP_SETENV + 1);
|
||||
lua_pushcfunction(L, lushCmd_subcmd);
|
||||
lua_rawseti(L, -2, LUSH_OP_SUBCMD + 1);
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** require and preload selected standard libraries
|
||||
@@ -81,9 +62,8 @@ LUALIB_API void luaL_openselectedlibs (lua_State *L, int load, int preload) {
|
||||
lua_setfield(L, -2, lib->name); /* add library to PRELOAD table */
|
||||
}
|
||||
}
|
||||
lua_assert((mask >> 1) == LUA_UTF8LIBK);
|
||||
lua_assert((mask >> 1) == LUA_LUSHLIBK);
|
||||
lua_pop(L, 1); /* remove PRELOAD table */
|
||||
opencommand(L); /* register __command global */
|
||||
luaopen_builtins(L); /* register __builtins table */
|
||||
luaopen_builtins(L); /* register builtins in lush table */
|
||||
}
|
||||
|
||||
|
||||
4
lualib.h
4
lualib.h
@@ -54,6 +54,10 @@ LUAMOD_API int (luaopen_table) (lua_State *L);
|
||||
#define LUA_UTF8LIBK (LUA_TABLIBK << 1)
|
||||
LUAMOD_API int (luaopen_utf8) (lua_State *L);
|
||||
|
||||
#define LUA_LUSHLIBNAME "lush"
|
||||
#define LUA_LUSHLIBK (LUA_UTF8LIBK << 1)
|
||||
LUAMOD_API int (luaopen_lush) (lua_State *L);
|
||||
|
||||
|
||||
/* open selected libraries */
|
||||
LUALIB_API void (luaL_openselectedlibs) (lua_State *L, int load, int preload);
|
||||
|
||||
111
testes/lush/lushlib.lua
Normal file
111
testes/lush/lushlib.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
-- testes/lush/lushlib.lua
|
||||
-- Tests for the lush standard library (issue #26).
|
||||
|
||||
print "testing lush library"
|
||||
|
||||
-- === lush global exists and is a table ===
|
||||
|
||||
do
|
||||
assert(type(lush) == "table", "lush should be a table")
|
||||
end
|
||||
|
||||
|
||||
-- === named functions are accessible ===
|
||||
|
||||
do
|
||||
assert(type(lush.command) == "function", "lush.command missing")
|
||||
assert(type(lush.interactive) == "function", "lush.interactive missing")
|
||||
assert(type(lush.getenv) == "function", "lush.getenv missing")
|
||||
assert(type(lush.setenv) == "function", "lush.setenv missing")
|
||||
assert(type(lush.subcmd) == "function", "lush.subcmd missing")
|
||||
end
|
||||
|
||||
|
||||
-- === lush.command works like backtick ===
|
||||
|
||||
do
|
||||
local r = lush.command("echo hello")
|
||||
assert(r.code == 0, "lush.command failed")
|
||||
assert(r.stdout == "hello\n", "expected 'hello\\n', got: " .. r.stdout)
|
||||
end
|
||||
|
||||
|
||||
-- === lush.getenv / lush.setenv ===
|
||||
|
||||
do
|
||||
lush.setenv("_LUSH_TEST_VAR", "42")
|
||||
assert(lush.getenv("_LUSH_TEST_VAR") == "42")
|
||||
lush.setenv("_LUSH_TEST_VAR", nil)
|
||||
assert(lush.getenv("_LUSH_TEST_VAR") == nil)
|
||||
end
|
||||
|
||||
|
||||
-- === lush.builtins exists and has cd ===
|
||||
|
||||
do
|
||||
assert(type(lush.builtins) == "table", "lush.builtins missing")
|
||||
assert(type(lush.builtins.cd) == "function", "lush.builtins.cd missing")
|
||||
assert(type(lush.builtins.exec) == "function", "lush.builtins.exec missing")
|
||||
assert(type(lush.builtins.umask) == "function", "lush.builtins.umask missing")
|
||||
end
|
||||
|
||||
|
||||
-- === user-defined builtin via backtick ===
|
||||
|
||||
do
|
||||
lush.builtins.greet = function(cmd, name)
|
||||
-- builtins return {code, stdout, stderr}
|
||||
local t = {}
|
||||
t.code = 0
|
||||
t.stdout = "hello " .. (name or "world") .. "\n"
|
||||
t.stderr = ""
|
||||
return t
|
||||
end
|
||||
|
||||
local r = `greet lush`
|
||||
assert(r.code == 0, "user builtin failed")
|
||||
assert(r.stdout == "hello lush\n",
|
||||
"expected 'hello lush\\n', got: " .. r.stdout)
|
||||
|
||||
-- clean up
|
||||
lush.builtins.greet = nil
|
||||
end
|
||||
|
||||
|
||||
-- === user-defined builtin via lush.command ===
|
||||
|
||||
do
|
||||
lush.builtins.myecho = function(cmd, ...)
|
||||
local args = {...}
|
||||
local t = {}
|
||||
t.code = 0
|
||||
t.stdout = table.concat(args, " ") .. "\n"
|
||||
t.stderr = ""
|
||||
return t
|
||||
end
|
||||
|
||||
local r = lush.command("myecho foo bar")
|
||||
assert(r.code == 0)
|
||||
assert(r.stdout == "foo bar\n",
|
||||
"expected 'foo bar\\n', got: " .. r.stdout)
|
||||
|
||||
lush.builtins.myecho = nil
|
||||
end
|
||||
|
||||
|
||||
-- === OP_LUSH still works (backtick, $VAR, !cmd syntax) ===
|
||||
|
||||
do
|
||||
-- backtick
|
||||
local r = `echo works`
|
||||
assert(r.stdout == "works\n")
|
||||
|
||||
-- $VAR expansion
|
||||
lush.setenv("_LUSH_LIB_TEST", "yes")
|
||||
local val = $_LUSH_LIB_TEST
|
||||
assert(val == "yes", "expected 'yes', got: " .. tostring(val))
|
||||
lush.setenv("_LUSH_LIB_TEST", nil)
|
||||
end
|
||||
|
||||
|
||||
print "OK"
|
||||
Reference in New Issue
Block a user