Implement a table-like shortcut to rpm macros in Lua

Add rpm macro context as a global table-like entity named 'macros' for
a more Lua-native experience with rpm macros.

Support basic indexing syntaxes (macros[k] and macros.k) for access, define
and undefine operations. Undefined macros return nil here and assigning
to nil will undefine (pop) the macro.

As a specialty, parametric macros are returned as native callable
variadic Lua functions. A string argument is passed to expand as is,
but if arguments are passed as a table, eg `r = macros.foo({1, 2, 3})`,
they are passed entirely as-is.

The macro context pointer in the userdata is not consistently used for
all operations here, but then all the macro operations in Lua are hardwired
to the global context anyway so it doesn't matter in practise. Properly
passing the context around in all cases is left for later commits.
This commit is contained in:
Panu Matilainen 2020-10-13 12:28:15 +03:00
parent c072ef6bb8
commit 986be669fb
3 changed files with 161 additions and 0 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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([