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:
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:
make NO_LUA=1
The HAVE_LUA preprocessor define gates all Lua-related code:
#ifdef HAVE_LUA
/* Lua filter implementation */
#else
/* stub: cgit_new_filter() returns NULL for lua: prefix */
#endif
Lua Filter Structure
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):
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:
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:
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:
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:
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:
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:
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:
/* 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
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
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:
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
-- 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
-- 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
-- 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)
-- 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 — usehtml()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
# 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