diff --git a/doc/manual/lua.md b/doc/manual/lua.md index 5c953898c..4b7cc9599 100644 --- a/doc/manual/lua.md +++ b/doc/manual/lua.md @@ -65,6 +65,19 @@ end } ``` +Macros can be accessed via a global `macros` table in the Lua environment. +Lua makes no difference between index and field name syntax so +`macros.foo` and `macros['foo']` are equivalent, use what better suits the +purpose. Like any real Lua table, non-existent items are returned as `nil`, +and assignment can be used to define or undefine macros. + +Parametric macros (including all built-in macros) can be called in a Lua +native manner via the `macros` table. The argument can be either a +single string (`macros.with('thing')`), in which case it's expanded +and split with the macro-native rules, or it can be a table +`macros.dostuff({'one', 'two', 'three'})` in which case the table contents +are used as literal arguments that are not expanded in any way. + ## Available Lua extensions in RPM In addition to all Lua standard libraries (subject to the Lua version rpm is linked to), a few custom extensions are available in the RPM internal Lua interpreter. These can be used in all contexts where the internal Lua can be used. diff --git a/rpmio/rpmlua.c b/rpmio/rpmlua.c index 88c6c29ae..ebf7cd335 100644 --- a/rpmio/rpmlua.c +++ b/rpmio/rpmlua.c @@ -1034,6 +1034,104 @@ static const luaL_Reg fd_m[] = { {NULL, NULL} }; +static rpmMacroContext *checkmc(lua_State *L, int ix) +{ + rpmMacroContext *mc = lua_touserdata(L, ix); + luaL_checkudata(L, ix, "rpm.mc"); + return mc; +} + +static int mc_call(lua_State *L) +{ + rpmMacroContext *mc = checkmc(L, lua_upvalueindex(1)); + const char *name = lua_tostring(L, lua_upvalueindex(2)); + int rc = 0; + + if (lua_gettop(L) > 1) + luaL_error(L, "too many arguments"); + + if (lua_isstring(L, 1)) { + lua_pushfstring(L, "%%{%s %s}", name, lua_tostring(L, 1)); + /* throw out previous args and call expand() with our result string */ + lua_rotate(L, 1, 1); + lua_settop(L, 1); + rc = rpm_expand(L); + } else if (lua_istable(L, 1)) { + ARGV_t argv = NULL; + char *buf = NULL; + int nitem = lua_rawlen(L, 1); + + for (int i = 1; i <= nitem; i++) { + lua_rawgeti(L, 1, i); + argvAdd(&argv, lua_tostring(L, -1)); + lua_pop(L, 1); + } + + if (rpmExpandThisMacro(*mc, name, argv, &buf, 0) >= 0) { + rc = 1; + lua_pushstring(L, buf); + free(buf); + } + argvFree(argv); + } else { + luaL_argerror(L, 1, "string or table expected"); + } + + return rc; +} + +static int mc_index(lua_State *L) +{ + rpmMacroContext *mc = checkmc(L, 1); + const char *a = luaL_checkstring(L, 2); + int rc = 0; + + if (rpmMacroIsDefined(*mc, a)) { + if (rpmMacroIsParametric(*mc, a)) {; + /* closure with the macro context and the name */ + lua_pushcclosure(L, &mc_call, 2); + rc = 1; + } else { + lua_pushfstring(L, "%%{%s}", a); + lua_rotate(L, 1, 1); + lua_settop(L, 1); + rc = rpm_expand(L); + } + } + return rc; +} + +static int mc_newindex(lua_State *L) +{ + rpmMacroContext *mc = checkmc(L, 1); + const char *name = luaL_checkstring(L, 2); + if (lua_isnil(L, 3)) { + if (rpmPopMacro(*mc, name)) + luaL_error(L, "error undefining macro %s", name); + } else { + const char *body = luaL_checkstring(L, 3); + char *s = rstrscat(NULL, name, " ", body, NULL); + if (rpmDefineMacro(*mc, s, 0)) + luaL_error(L, "error defining macro %s", name); + free(s); + } + return 0; +} + +static const luaL_Reg mc_m[] = { + {"__index", mc_index}, + {"__newindex", mc_newindex}, + {NULL, NULL} +}; + +static void createmt(lua_State *L, const char *name, rpmMacroContext mc) +{ + lua_pushglobaltable(L); + newinstance(L, "rpm.mc", mc); + lua_setfield(L, -2, name); + lua_pop(L, 1); +} + static const luaL_Reg rpmlib[] = { {"b64encode", rpm_b64encode}, {"b64decode", rpm_b64decode}, @@ -1059,6 +1157,10 @@ static int luaopen_rpm(lua_State *L) { createclass(L, "rpm.ver", ver_m); createclass(L, "rpm.fd", fd_m); + createclass(L, "rpm.mc", mc_m); + + createmt(L, "macros", rpmGlobalMacroContext); + luaL_newlib(L, rpmlib); return 1; } diff --git a/tests/rpmmacro.at b/tests/rpmmacro.at index cadf66379..5eb39bea1 100644 --- a/tests/rpmmacro.at +++ b/tests/rpmmacro.at @@ -593,6 +593,52 @@ runroot rpm \ ]) AT_CLEANUP +AT_SETUP([lua macros table]) +AT_KEYWORDS([macros lua]) +AT_SKIP_IF([$LUA_DISABLED]) +AT_CHECK([[ +runroot rpm \ + --define "qtst() %{lua:for i=1, #arg do print(' '..i..':'..arg[i]) end}"\ + --eval "%{lua:print(macros.with('zap'), macros.without('zap'))}" \ + --eval "%{lua:print(macros.aaa)}" \ + --eval "%{lua:macros.aaa='bbb'; macros['yyy']='zzz'}" \ + --eval "%{lua:print(macros['aaa'], macros.yyy)}" \ + --eval "%{lua:macros.aaa=nil"} \ + --eval "%{lua:print(macros.aaa)}" \ + --eval "%{lua:print(macros.qtst('a c'))}" \ + --eval "%{lua:print(macros.qtst({'a', '', 'c'}))}" \ + --eval "%{lua:print(macros.qtst('%{?aaa} %{yyy}'))}" \ + --eval "%{lua:print(macros.qtst({'%{?aaa}', '%{yyy}'}))}" \ + --eval "%{lua:macros.define({'this', 'that'}); print(macros.this)}" +]], +[0], +[0 1 +nil + +bbb zzz + +nil + 1:a 2:c + 1:a 2: 3:c + 1:zzz + 1:%{?aaa} 2:%{yyy} +that +]) +AT_CLEANUP + +AT_SETUP([lua macros recursion]) +AT_KEYWORDS([macros lua]) +AT_SKIP_IF([$LUA_DISABLED]) +AT_CHECK([[ +runroot rpm \ + --define "%recurse() %{lua:io.write(' '..#arg); if #arg < 16 then table.insert(arg, #arg); macros.recurse(arg) end;}" \ + --eval "%recurse" +]], +[0], +[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +]) +AT_CLEANUP + AT_SETUP([lua rpm extensions 1]) AT_KEYWORDS([macros lua]) AT_CHECK([