Louis/scripts/replace-tex-macros.lua
2026-06-09 14:45:18 +02:00

200 lines
5.7 KiB
Lua

-- Replace Obsidian embed blocks like ![[macros.tex]] with the content of the
-- corresponding LaTeX macro file stored in *.tex.md.
local macro_cache = {}
-- Keep a small explicit map for quick lookups if desired, but resolution
-- will try relative locations from the current document by default.
local macro_files = {
-- leave empty or add overrides if needed
}
-- Mapping from macro name to the include path to inject in Quarto shortcode
local macro_include = {
["macros.tex"] = "../../macros.tex.md",
["local_macros.tex"] = "local_macros.tex.md",
}
local function read_file(path)
local handle = io.open(path, "r")
if not handle then
return nil
end
local content = handle:read("*a")
handle:close()
return content
end
local function strip_markdown_suffix(target)
return target:gsub("%.md$", "")
end
local function find_macro_file(target)
local key = strip_markdown_suffix(target):match("([^/\\]+)$")
if not key then
return nil
end
if macro_cache[key] then
return macro_cache[key]
end
-- Helper utilities
local function file_exists(path)
local f = io.open(path, "r")
if f then
f:close(); return true
end
return false
end
local function dirname(path)
if not path then return nil end
local dir = path:match("(.*/)")
if dir then
-- strip trailing '/'
return dir:gsub("/$", "")
end
return '.'
end
local function join(a, b)
if not a or a == '' or a == '.' then return b end
if a:sub(-1) == '/' then return a .. b end
return a .. '/' .. b
end
-- Try quick explicit mapping first
if macro_files[key] and file_exists(macro_files[key]) then
macro_cache[key] = macro_files[key]
return macro_files[key]
end
-- Determine current input file directory from Pandoc state
local input_file = nil
if PANDOC_STATE and PANDOC_STATE.input_files and #PANDOC_STATE.input_files > 0 then
input_file = PANDOC_STATE.input_files[1]
end
local cur_dir = dirname(input_file) or '.'
-- Search candidates in order of preference
local candidates = {
join(cur_dir, key .. ".md"),
join(cur_dir, key),
key .. ".md",
key,
}
-- Walk up directories from cur_dir to try to find a project root (look for _quarto.yml)
local function find_project_root(start)
local dir = start or '.'
for i = 1, 16 do
local qcfg = join(dir, '_quarto.yml')
if file_exists(qcfg) then return dir end
-- go up one
local parent = dir:match("(.*/).-$")
if not parent then break end
dir = parent:gsub("/$", "")
end
return nil
end
local project_root = find_project_root(cur_dir)
if project_root then
table.insert(candidates, join(project_root, key .. ".md"))
table.insert(candidates, join(project_root, key))
end
-- Finally check workspace root (pwd) as a fallback
-- Use os.getenv("PWD") which Quarto/Pandoc normally runs with
local pwd = os.getenv('PWD')
if pwd then
table.insert(candidates, join(pwd, key .. ".md"))
table.insert(candidates, join(pwd, key))
end
for _, cand in ipairs(candidates) do
if cand and file_exists(cand) then
macro_cache[key] = cand
return cand
end
end
return nil
end
local function expand_macro_embed(el)
local text = pandoc.utils.stringify(el)
local trimmed = text:gsub("^%s+", ""):gsub("%s+$", "")
local target = trimmed:match("^!%[%[(.-)%]%]$") or trimmed:match("^%[%[(.-)%]%]$")
if not target then
return nil
end
local path = find_macro_file(target)
if not path then
return nil
end
-- Compute include path relative to current input file directory
local function dirname(path)
if not path then return nil end
local dir = path:match("(.*/)")
if dir then
return dir:gsub("/$", "")
end
return '.'
end
local input_file = nil
if PANDOC_STATE and PANDOC_STATE.input_files and #PANDOC_STATE.input_files > 0 then
input_file = PANDOC_STATE.input_files[1]
end
local cur_dir = dirname(input_file) or '.'
local function split(path)
local t = {}
for part in path:gmatch("[^/]+") do table.insert(t, part) end
return t
end
local function relative_path(from, to)
if not from or not to then return to end
-- handle same path
if from == to then return "." end
local from_abs = (from:sub(1, 1) == '/')
local to_abs = (to:sub(1, 1) == '/')
-- if one is absolute and other not, fall back to `to`
if from_abs ~= to_abs then return to end
local from_parts = split(from)
local to_parts = split(to)
-- find common prefix
local i = 1
while i <= #from_parts and i <= #to_parts and from_parts[i] == to_parts[i] do
i = i + 1
end
local up = #from_parts - i + 1
local rel_parts = {}
for j = 1, up do table.insert(rel_parts, "..") end
for j = i, #to_parts do table.insert(rel_parts, to_parts[j]) end
if #rel_parts == 0 then return "." end
return table.concat(rel_parts, "/")
end
local include_path = relative_path(cur_dir, path)
if not include_path then
-- fallback to absolute path if relative couldn't be computed
include_path = path
end
local shortcode = string.format("{{< include %s >}}", include_path)
return pandoc.RawBlock("markdown", shortcode)
end
function Para(el)
return expand_macro_embed(el)
end
function Plain(el)
return expand_macro_embed(el)
end