-- 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