I have turned off the Lua filter discussed in this post. It was running everytime I rendered/published the blog. I have copy-pasted the original output for reference.
The following is a Lua filter which looks through a qmd
file for Rust code associated with {rust}
, compiles that code using rustc
, runs the compiled Rust program and collects its output, and inserts the output to be rendered by pandoc.
local io = require("io")
local os = require("os")
local tempfile = require("os").tmpname
local log_file
-- Function to initialize the log file
local function init_log()
log_file = io.open("rust_executor_debug.log", "w")
end
-- Function to log messages to file and stderr
local function log(...)
local args = {...}
for i = 1, #args do
args[i] = tostring(args[i])
end
local message = table.concat(args, " ")
if log_file then
log_file:write(message .. "\n")
log_file:flush()
end
io.stderr:write(message .. "\n")
io.stderr:flush()
end
-- Helper function to execute Rust code and return the output
local function execute_rust_code(code)
local temp_file = tempfile() .. ".rs"
("Temporary Rust file:", temp_file)
loglocal source_file, err = io.open(temp_file, "w")
if not source_file then
("Failed to create source file:", err)
logerror("Failed to create source file: " .. err)
end
source_file:write(code)
source_file:close()
local temp_bin = tempfile()
("Temporary binary file:", temp_bin)
log
local compile_command = "rustc " .. temp_file .. " -o " .. temp_bin .. " 2>&1"
("Compile Command:", compile_command)
loglocal compile_pipe = io.popen(compile_command)
local compile_output = compile_pipe:read("*a")
local compile_result = compile_pipe:close()
if compile_result ~= true then
os.remove(temp_file)
("Rust compilation failed. Output:", compile_output)
logerror("Rust compilation failed. Output: " .. compile_output)
end
local exec_command = temp_bin .. " 2>&1"
("Exec Command:", exec_command)
loglocal exec_pipe = io.popen(exec_command)
local output = exec_pipe:read("*a")
exec_pipe:close()
local ok, rm_err = pcall(function()
os.remove(temp_file)
os.remove(temp_bin)
end)
if not ok then
("Failed to clean up temporary files:", rm_err)
logerror("Failed to clean up temporary files: " .. rm_err)
end
("Output:", output)
logreturn output
end
local echo_global = true
function Meta(meta)
if meta.echo ~= nil then
echo_global = pandoc.utils.stringify(meta.echo) == "true"
end
end
-- Lua filter function
function CodeBlock(elem)
if not log_file then
()
init_logend
local is_rust_code = elem.attr.classes:includes("{rust}")
if is_rust_code then
("Processing Rust code block")
loglocal output = execute_rust_code(elem.text)
output = output:gsub("%s+$", "")
local blocks = {}
if echo_global then
-- Render Rust code as a formatted block
table.insert(blocks, pandoc.CodeBlock(elem.text, {class="rust"}))
end
-- Always return the output
table.insert(blocks, pandoc.Para(pandoc.Str(output)))
return blocks
else
("Skipping non-Rust code block")
logend
end
-- Ensure log file is closed properly at the end
function Pandoc(doc)
if log_file then
log_file:close()
end
return doc
end
Let’s try some examples.
Here is some Rust code that will be executed and rendered.
fn main() {
println!("Galen Seilis is learning Rust!");
println!("Time to get Rusty!");
}
Galen Seilis is learning Rust! Time to get Rusty!
Now let us try some Rust code that will not be executed.
fn main() {
println!("Meow");
}
Now let us run a longer example from Rust by Example.
fn main() {
// Integer addition
println!("1 + 2 = {}", 1u32 + 2);
// Integer subtraction
println!("1 - 2 = {}", 1i32 - 2);
// TODO ^ Try changing `1i32` to `1u32` to see why the type is important
// Scientific notation
println!("1e4 is {}, -2.5e-3 is {}", 1e4, -2.5e-3);
// Short-circuiting boolean logic
println!("true AND false is {}", true && false);
println!("true OR false is {}", true || false);
println!("NOT true is {}", !true);
// Bitwise operations
println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);
println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);
println!("0011 XOR 0101 is {:04b}\n\n\n", 0b0011u32 ^ 0b0101);
println!("1 << 5 is {}", 1u32 << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);
// Use underscores to improve readability!
println!("One million is written as {}", 1_000_000u32);
}
1 + 2 = 3 1 - 2 = -1 1e4 is 10000, -2.5e-3 is -0.0025 true AND false is false true OR false is true NOT true is false 0011 AND 0101 is 0001 0011 OR 0101 is 0111 0011 XOR 0101 is 0110 1 << 5 is 32 0x80 >> 2 is 0x20 One million is written as 1000000
In the current state there are a couple of glaring issues I have with this implementation. The first is that Rust code blocks will be run regardless of whether echo: false
is used. The second is that all the outputs are being rendered on a single, notwithstanding Quarto’s line wrapping.
There is also an enhancement which is desirable, which is to render other types of things from Rust that are not just plaintext. Instead of developing this kind of functionality myself, it would make sense to take a closer look at integrating tools such as the Evcxr Jupyter kernel.