diff options
Diffstat (limited to 'docs/handbook/cgit/lua-integration.md')
| -rw-r--r-- | docs/handbook/cgit/lua-integration.md | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/docs/handbook/cgit/lua-integration.md b/docs/handbook/cgit/lua-integration.md new file mode 100644 index 0000000000..26d605862e --- /dev/null +++ b/docs/handbook/cgit/lua-integration.md @@ -0,0 +1,428 @@ +# cgit — Lua Integration + +## Overview + +cgit supports Lua as an in-process scripting language for content filters. +Lua filters avoid the fork/exec overhead of shell-based filters and have +direct access to cgit's HTML output functions. Lua support is optional and +auto-detected at compile time. + +Source files: `filter.c` (Lua filter implementation), `cgit.mk` (Lua detection). + +## Compile-Time Detection + +Lua support is detected by `cgit.mk` using `pkg-config`: + +```makefile +ifndef NO_LUA +LUAPKGS := luajit lua lua5.2 lua5.1 +LUAPKG := $(shell for p in $(LUAPKGS); do \ + $(PKG_CONFIG) --exists $$p 2>/dev/null && echo $$p && break; done) +ifneq ($(LUAPKG),) + CGIT_CFLAGS += -DHAVE_LUA $(shell $(PKG_CONFIG) --cflags $(LUAPKG)) + CGIT_LIBS += $(shell $(PKG_CONFIG) --libs $(LUAPKG)) +endif +endif +``` + +Detection order: `luajit` → `lua` → `lua5.2` → `lua5.1`. + +To disable Lua even when available: + +```bash +make NO_LUA=1 +``` + +The `HAVE_LUA` preprocessor define gates all Lua-related code: + +```c +#ifdef HAVE_LUA +/* Lua filter implementation */ +#else +/* stub: cgit_new_filter() returns NULL for lua: prefix */ +#endif +``` + +## Lua Filter Structure + +```c +struct cgit_lua_filter { + struct cgit_filter base; /* common filter fields */ + char *script_file; /* path to Lua script */ + lua_State *lua_state; /* Lua interpreter state */ +}; +``` + +The `lua_State` is lazily initialized on first use and reused for subsequent +invocations of the same filter. + +## C API Exposed to Lua + +cgit registers these C functions in the Lua environment: + +### `html(str)` + +Writes raw HTML to stdout (no escaping): + +```c +static int lua_html(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + html(str); + return 0; +} +``` + +### `html_txt(str)` + +Writes HTML-escaped text: + +```c +static int lua_html_txt(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + html_txt(str); + return 0; +} +``` + +### `html_attr(str)` + +Writes attribute-escaped text: + +```c +static int lua_html_attr(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + html_attr(str); + return 0; +} +``` + +### `html_url_path(str)` + +Writes a URL-encoded path: + +```c +static int lua_html_url_path(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + html_url_path(str); + return 0; +} +``` + +### `html_url_arg(str)` + +Writes a URL-encoded query argument: + +```c +static int lua_html_url_arg(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); + html_url_arg(str); + return 0; +} +``` + +### `html_include(filename)` + +Includes a file's contents in the output: + +```c +static int lua_html_include(lua_State *L) +{ + const char *filename = luaL_checkstring(L, 1); + html_include(filename); + return 0; +} +``` + +## Lua Filter Lifecycle + +### Initialization + +On first `open()`, the Lua state is created and the script is loaded: + +```c +static int open_lua_filter(struct cgit_filter *base, ...) +{ + struct cgit_lua_filter *f = (struct cgit_lua_filter *)base; + + if (!f->lua_state) { + /* Create new Lua state */ + f->lua_state = luaL_newstate(); + luaL_openlibs(f->lua_state); + + /* Register C functions */ + lua_pushcfunction(f->lua_state, lua_html); + lua_setglobal(f->lua_state, "html"); + lua_pushcfunction(f->lua_state, lua_html_txt); + lua_setglobal(f->lua_state, "html_txt"); + lua_pushcfunction(f->lua_state, lua_html_attr); + lua_setglobal(f->lua_state, "html_attr"); + lua_pushcfunction(f->lua_state, lua_html_url_path); + lua_setglobal(f->lua_state, "html_url_path"); + lua_pushcfunction(f->lua_state, lua_html_url_arg); + lua_setglobal(f->lua_state, "html_url_arg"); + lua_pushcfunction(f->lua_state, lua_html_include); + lua_setglobal(f->lua_state, "include"); + + /* Load and execute the script file */ + if (luaL_dofile(f->lua_state, f->script_file)) + die("lua error: %s", + lua_tostring(f->lua_state, -1)); + } + + /* Redirect stdout writes to lua write() function */ + + /* Call filter_open() with filter-specific arguments */ + lua_getglobal(f->lua_state, "filter_open"); + /* push arguments from va_list */ + lua_call(f->lua_state, nargs, 0); + + return 0; +} +``` + +### Data Flow + +While the filter is open, data written to stdout is intercepted via a custom +`write()` function: + +```c +/* The fprintf callback for Lua filters */ +static void lua_fprintf(struct cgit_filter *base, FILE *f, + const char *fmt, ...) +{ + struct cgit_lua_filter *lf = (struct cgit_lua_filter *)base; + /* format the string */ + /* call the Lua write() function with the formatted text */ + lua_getglobal(lf->lua_state, "write"); + lua_pushstring(lf->lua_state, buf); + lua_call(lf->lua_state, 1, 0); +} +``` + +### Close + +```c +static int close_lua_filter(struct cgit_filter *base) +{ + struct cgit_lua_filter *f = (struct cgit_lua_filter *)base; + + /* Call filter_close() */ + lua_getglobal(f->lua_state, "filter_close"); + lua_call(f->lua_state, 0, 1); + + /* Get return code */ + int rc = lua_tointeger(f->lua_state, -1); + lua_pop(f->lua_state, 1); + + return rc; +} +``` + +### Cleanup + +```c +static void cleanup_lua_filter(struct cgit_filter *base) +{ + struct cgit_lua_filter *f = (struct cgit_lua_filter *)base; + if (f->lua_state) + lua_close(f->lua_state); +} +``` + +## Lua Script Interface + +### Required Functions + +A Lua filter script must define these functions: + +```lua +function filter_open(...) + -- Called when the filter opens + -- Arguments are filter-type specific +end + +function write(str) + -- Called with content chunks to process + -- Transform and output using html() functions +end + +function filter_close() + -- Called when filtering is complete + return 0 -- return exit code +end +``` + +### Available Global Functions + +| Function | Description | +|----------|-------------| +| `html(str)` | Output raw HTML | +| `html_txt(str)` | Output HTML-escaped text | +| `html_attr(str)` | Output attribute-escaped text | +| `html_url_path(str)` | Output URL-path-encoded text | +| `html_url_arg(str)` | Output URL-argument-encoded text | +| `include(filename)` | Include file contents in output | + +All standard Lua libraries are available (`string`, `table`, `math`, `io`, +`os`, etc.). + +## Example Filters + +### Source Highlighting Filter + +```lua +-- syntax-highlighting.lua +local filename = "" +local buffer = {} + +function filter_open(fn) + filename = fn + buffer = {} +end + +function write(str) + table.insert(buffer, str) +end + +function filter_close() + local content = table.concat(buffer) + local ext = filename:match("%.(%w+)$") or "" + + -- Simple keyword highlighting + local keywords = { + ["function"] = true, ["local"] = true, + ["if"] = true, ["then"] = true, + ["end"] = true, ["return"] = true, + ["for"] = true, ["while"] = true, + ["do"] = true, ["else"] = true, + } + + html("<pre><code>") + for line in content:gmatch("([^\n]*)\n?") do + html_txt(line) + html("\n") + end + html("</code></pre>") + + return 0 +end +``` + +### Email Obfuscation Filter + +```lua +-- email-obfuscate.lua +function filter_open(email, page) + -- email = the email address + -- page = current page name +end + +function write(str) + -- Replace @ with [at] for display + local obfuscated = str:gsub("@", " [at] ") + html_txt(obfuscated) +end + +function filter_close() + return 0 +end +``` + +### About/README Filter + +```lua +-- about-markdown.lua +local buffer = {} + +function filter_open(filename) + buffer = {} +end + +function write(str) + table.insert(buffer, str) +end + +function filter_close() + local content = table.concat(buffer) + -- Process markdown (using a Lua markdown library) + -- or shell out to a converter + local handle = io.popen("cmark", "w") + handle:write(content) + local result = handle:read("*a") + handle:close() + html(result) + return 0 +end +``` + +### Auth Filter (Lua) + +```lua +-- auth.lua +-- The auth filter receives 12 arguments +function filter_open(cookie, method, query, referer, path, + host, https, repo, page, accept, phase) + if phase == "cookie" then + -- Validate session cookie + if valid_session(cookie) then + return 0 -- authenticated + end + return 1 -- not authenticated + elseif phase == "post" then + -- Handle login form submission + elseif phase == "authorize" then + -- Check repository access + end +end + +function write(str) + html(str) +end + +function filter_close() + return 0 +end +``` + +## Performance + +Lua filters offer significant performance advantages over exec filters: + +| Aspect | Exec Filter | Lua Filter | +|--------|-------------|------------| +| Startup | fork() + exec() per request | One-time Lua state creation | +| Process | New process per invocation | In-process | +| Memory | Separate address space | Shared memory | +| Latency | ~1-5ms fork overhead | ~0.01ms function call | +| Libraries | Any language | Lua libraries only | + +## Limitations + +- Lua scripts run in the same process as cgit — a crash in the script + crashes cgit +- Standard Lua I/O functions (`print`, `io.write`) bypass cgit's output + pipeline — use `html()` and friends instead +- The Lua state persists between invocations within the same CGI process, + but CGI processes are typically short-lived +- Error handling is via `die()` — a Lua error terminates the CGI process + +## Configuration + +```ini +# Use Lua filter for source highlighting +source-filter=lua:/usr/share/cgit/filters/syntax-highlight.lua + +# Use Lua filter for about pages +about-filter=lua:/usr/share/cgit/filters/about-markdown.lua + +# Use Lua filter for authentication +auth-filter=lua:/usr/share/cgit/filters/simple-hierarchical-auth.lua + +# Use Lua filter for email display +email-filter=lua:/usr/share/cgit/filters/email-libravatar.lua +``` |
