# IPython-like RPM Lua interactive console
Recently, I've found myself in a position of writing and debugging some Lua based RPM macros and generators:
- [speeding up python(abi) dependency generator by rewriting it from Bash to parametric Lua macro](https://github.com/rpm-software-management/rpm/pull/1153)
- [adding automated provides to replace most of the manual %python_provide calls](https://src.fedoraproject.org/rpms/python-rpm-generators/pull-request/7)
- [reimplementing %python_provide from scratch as %py_provides](https://src.fedoraproject.org/rpms/python-rpm-macros/pull-request/52)
The capabilities of the [embedded Lua interpreter in RPM](https://rpm.org/user_doc/lua.html) are endless. My Lua skills are not. I need to test the code in small chunks to understand what am I doing.

When I need to do this in Python, I use the [IPython](https://ipython.org/) console (my second favorite software in the Python ecosystem after [pytest](https://docs.pytest.org/)).

For Lua, I've used the `lua` command for a while. It lets me test basic Lua concepts in an interactive console, but it's not that powerful as the IPython console and it lacks the RPM provided Lua libraries.

I've asked Panu (my colleague and an RPM developer I work with when submitting changes to the RPM project) whether there is an interactive console for the Lua interpreter embedded in RPM [and the answer was yes](https://github.com/rpm-software-management/rpm/pull/1153#discussion_r401514078). I can use `rpm --eval "%{lua:rpm.interactive()}"`. But it has some quirks.

Mostly, there is no command history or even line editing, and [the output is hard to get](https://github.com/rpm-software-management/rpm/issues/1215).
## ILua
Naturally, I've searched for an IPython like Lua console and I've found the [list of Jupyter Kerneles](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels) (IPython console is a frontend to [Jupyter](https://jupyter.org/)). There are 3 Lua kernels there:
- [Lua Kernel](https://github.com/neomantra/lua_ipython_kernel) (discontinued)
- [IPyLua](https://github.com/pakozm/IPyLua) (a fork of the above, not much active either)
- [ILua](https://github.com/guysv/ilua)
ILua intrigued me mostly because it says right away: *"Lua-implementation agnostic, should work with any Lua interpreter out of the box."* That's exactly what I need. Maybe I can use it with `rpm --eval "%{lua:rpm.interactive()}"`.
Turns out I can, [but it's not that simple](https://github.com/guysv/ilua/issues/10). The used Lua interpreter needs to respect the `$LUA_PATH` environment variable and execute the file given to it as a command-line argument. Naturally, the simplistic `rpm --eval "%{lua:rpm.interactive()}"` does neither.
So I've created a wrapper:
```bash
#!/usr/bin/bash
exec rpm --eval '%{lua:package.path = "'${LUA_PATH}';" .. package.path;'"$(cat "$@")"';rpm.interactive()}'
```
And it mostly worked. Except for the [slight problem with missing print output](https://github.com/rpm-software-management/rpm/issues/1215). (The executed ILua script does the interactivity, so I've removed the `rpm.interactive()` call at the end.)

I've tried to figure out how to launch the RPM Lua interpreter, not in the RPM macro expansion mode (that thing eats all the print output until the end). I've done a little RPM symbols lookup and source reading and figured out there is an `rpmluaRunScript()` function in `librpmio`. So I've tried to use it:
```python
#!/usr/bin/python3
from ctypes import cdll, c_char_p
librpmio = cdll.LoadLibrary("librpmio.so.9")
librpmio.rpmluaRunScript(None, c_char_p(b"rpm.interactive()"), None)
```
It works. A little tweaking to support ILua as well as other use cases:
```python
#!/usr/bin/python3
import sys
from ctypes import cdll, c_char_p
librpmio = cdll.LoadLibrary("librpmio.so.9")
adjust_path = b"""
if os.getenv("LUA_PATH") then
package.path = os.getenv("LUA_PATH") .. ";" .. package.path
end
"""
# first argument is an "rpmlua" pointer, but uses global one when NULL
# second argument is code
# third argument is "name", used in errors, reasonable default when NULL
librpmio.rpmluaRunScript(None, c_char_p(adjust_path), None)
if len(sys.argv) > 1:
sys.argv[-1] = '/dev/stdin' if sys.argv[-1] == '-' else sys.argv[-1]
# first argument as above, second argument is path
librpmio.rpmluaRunScriptFile(None, c_char_p(sys.argv[-1].encode("utf-8")))
else:
librpmio.rpmluaRunScript(None, c_char_p(b"rpm.interactive()"), None)
```
Note by Panu: *`rpmluaRunScript()` and `rpmluaRunScriptFile()` are not considered public API and are not available in the public headers on C side, although the symbols are accessible in the ABI. So they are subject to change without further notice, although the likelihood of that happening doesn't seem that great, they've been exactly the way are since their inception 16 years ago.*
And now I have an interactive IPython-like shell for RPM embedded Lua with command history, line editing, completion and more:

Enjoy! PS: ILua is on [package review for Fedora](https://bugzilla.redhat.com/show_bug.cgi?id=1834280), but can be safely pip-installed in the meantime.