From 8d38a56221dff0df80fad41dc61bbc4131d96169 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Wed, 4 Nov 2026 23:13:43 -1000 Subject: Put a Lua filter's print output where it runs LuaJIT and Lua 5.1 leave print's line in stdio's buffer until cgit exits, after the end of the page. --- MANUAL.txt | 3 +++ source/filter.c | 31 +++++++++++++++++++++++++++++++ tests/t0201-filters.sh | 20 ++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/MANUAL.txt b/MANUAL.txt index e17d5fd..4b1e8f5 100644 --- a/MANUAL.txt +++ b/MANUAL.txt @@ -844,6 +844,9 @@ specification with one of these strings. Url escapes for an argument and writes 'str' to the webpage. 'html_include(file)':: Includes 'file' in webpage. + 'print(...)':: + Writes its arguments to the webpage unescaped, a tab between + each and a newline at the end, as Lua's own print lays them out. Parameters are provided to filters as follows. diff --git a/source/filter.c b/source/filter.c index 6cc5e80..82d8725 100644 --- a/source/filter.c +++ b/source/filter.c @@ -293,6 +293,36 @@ static int script_html_include(lua_State *lua_state) return emit_unfiltered(lua_state, html_include); } +/* + * The print of LuaJIT and Lua 5.1 leaves its line in stdio's buffer, which + * reaches stdout only when cgit exits, after the end of the page. This one + * builds the same line, each value through tostring with a tab between them, + * and writes it as html does. + */ +static int script_print(lua_State *lua_state) +{ + int i, count = lua_gettop(lua_state); + luaL_Buffer line; + + lua_getglobal(lua_state, "tostring"); + luaL_buffinit(lua_state, &line); + for (i = 1; i <= count; i++) { + if (i > 1) + luaL_addchar(&line, '\t'); + lua_pushvalue(lua_state, count + 1); + lua_pushvalue(lua_state, i); + lua_call(lua_state, 1, 1); + if (!lua_isstring(lua_state, -1)) + return luaL_error(lua_state, "'tostring' must return a string to 'print'"); + luaL_addvalue(&line); + } + luaL_addchar(&line, '\n'); + luaL_pushresult(&line); + // emit_unfiltered takes its text from the first slot. + lua_replace(lua_state, 1); + return emit_unfiltered(lua_state, html); +} + static const struct { const char *name; lua_CFunction fn; @@ -303,6 +333,7 @@ static const struct { { "html_url_path", script_html_url_path }, { "html_url_arg", script_html_url_arg }, { "html_include", script_html_include }, + { "print", script_print }, }; static void init_lua_filter(struct lua_filter *filter) diff --git a/tests/t0201-filters.sh b/tests/t0201-filters.sh index 8d8743a..4b39f18 100755 --- a/tests/t0201-filters.sh +++ b/tests/t0201-filters.sh @@ -168,4 +168,24 @@ test_expect_success CGIT_LUA 'a lua filter can encode an argument and include a grep "a+b%26c|included bold" tmp ' +# The print of LuaJIT and Lua 5.1 leaves its line in stdio's buffer, which only +# reached the page as cgit exited, behind the closing tag. A print lands where +# the filter runs. +test_expect_success CGIT_LUA 'a lua filter prints where it runs' ' + cat >print.lua <<-\EOF && + function filter_open(...) end + function filter_write(str) + print("printed", 1, nil, true) + html(str) + end + function filter_close() return 0 end + EOF + sed "s|loud.lua|print.lua|" loudrc >printrc && + cgit_rc printrc "url=foo/commit" >tmp && + tab=$(printf "\t") && + grep "
printed${tab}1${tab}nil${tab}true$" tmp && + sed -n "/<\/html>/,\$p" tmp >tail && + ! grep printed tail +' + test_done -- cgit 2.12.1