Path: blob/main/src/resources/pandoc/datadir/init.lua
12926 views
if pandoc.system.os == "mingw32" then12local function get_windows_ansi_codepage()3-- Reading the code page directly out of the registry was causing4-- Microsoft Defender to massively slow down pandoc (e.g. 1400ms instead of 140ms)5-- So instead, look that up outside this filter and pass it in, which appears speed(ier)67-- local pipe = assert(io.popen[[reg query HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage /v ACP]])8-- local codepage = pipe:read"*a":match"%sACP%s+REG_SZ%s+(.-)%s*$"9-- pipe:close()10-- return assert(codepage, "Failed to determine Windows ANSI codepage from Windows registry")11local codepage = os.getenv("QUARTO_WIN_CODEPAGE")12if codepage == nil then13codepage = "1252"14end15return codepage16end1718local codepage = get_windows_ansi_codepage()19-- print("Your codepage is "..codepage)2021local success = pcall(function()22pandoc.text.toencoding("test", "CP" .. codepage)23end)24if not success then25error("Failed to convert to CP" .. codepage .. " encoding. Please check your codepage or set it explicitly with the QUARTO_WIN_CODEPAGE environment variable.")26exit(1)27end2829function convert_from_utf8(utf8str)30return pandoc.text.toencoding(utf8str, "CP" .. codepage)31end3233local orig_os_rename = os.rename3435function os.rename(old, new)36return orig_os_rename(convert_from_utf8(old), convert_from_utf8(new))37end3839local orig_os_remove = os.remove4041function os.remove(filename)42return orig_os_remove(convert_from_utf8(filename))43end4445local orig_os_execute = os.execute4647function os.execute(command)48if command then49command = convert_from_utf8(command)50end51return orig_os_execute(command)52end5354local orig_io_open = io.open5556function io.open(filename, ...)57return orig_io_open(convert_from_utf8(filename), ...)58end5960local orig_io_popen = io.popen6162function io.popen(prog, ...)63return orig_io_popen(convert_from_utf8(prog), ...)64end6566local orig_io_lines = io.lines6768function io.lines(filename, ...)69if filename then70filename = convert_from_utf8(filename)71return orig_io_lines(filename, ...)72else73return orig_io_lines()74end75end7677local orig_dofile = dofile7879function dofile(filename)80if filename then81filename = convert_from_utf8(filename)82end83return orig_dofile(filename)84end8586local orig_loadfile = loadfile8788function loadfile(filename, ...)89if filename then90filename = convert_from_utf8(filename)91end92return orig_loadfile(filename, ...)93end9495local orig_require = require9697function require(modname)98modname = convert_from_utf8(modname)99return orig_require(modname)100end101102local orig_io_input = io.input103104function io.input(file)105if type(file) == "string" then106file = convert_from_utf8(file)107end108return orig_io_input(file)109end110111local orig_io_output = io.output112113function io.output(file)114if type(file) == "string" then115file = convert_from_utf8(file)116end117return orig_io_output(file)118end119120end121122-- Bootstrap our common libraries by adding our filter pandoc to the lib path123local sharePath = os.getenv("QUARTO_SHARE_PATH");124-- TODO: Need to ensure that we are resolving ahead of the other path125-- and understand consequences126-- Be aware of user filters which may be using require - need to be able load their modules safely127-- Maybe namespace quarto modules somehow or alter path for user filters128if sharePath ~= nil then129local sep = package.config:sub(1,1)130package.path = package.path .. ";" .. sharePath .. sep .. 'pandoc' .. sep .. 'datadir' .. sep .. '?.lua'131end132133-- dependency types that will be emitted to the dedendencies file134-- (use streamed json to write a single line of json for each dependency with135-- the type and the contents)136local kDependencyTypeHtml = "html";137local kDependencyTypeLatex = "latex";138local kDependencyTypeFile = "file";139local kDependencyTypeText = "text";140141-- locations that dependencies may be injected142local kBeforeBody = "before-body";143local kAfterBody = "after-body";144local kInHeader = "in-header";145146-- common requires147-- this is in the global scope - anyone downstream of init may use this148local format = require '_format'149local base64 = require '_base64'150local json = require '_json'151local utils = require '_utils'152local logging = require 'logging'153154-- determines whether a path is a relative path155local function isRelativeRef(ref)156return ref:find("^/") == nil and157ref:find("^%a+://") == nil and158ref:find("^data:") == nil and159ref:find("^#") == nil160end161162-- This is a function that returns the current script163-- path. Shortcodes can use an internal function164-- to set and clear the local value that will be used165-- instead of pandoc's filter path when a shortcode is executing166local scriptFile = {}167168local function scriptDirs()169if PANDOC_SCRIPT_FILE == nil then170return {}171end172local dirs = { pandoc.path.directory(PANDOC_SCRIPT_FILE) }173for i = 1, #scriptFile do174dirs[#dirs+1] = pandoc.path.directory(scriptFile[i])175end176return dirs177end178179local function scriptDir()180if #scriptFile > 0 then181return pandoc.path.directory(scriptFile[#scriptFile])182else183-- hard fallback184return pandoc.path.directory(PANDOC_SCRIPT_FILE)185end186end187188-- splits a string on a separator189local function split(str, sep)190local fields = {}191192local sep = sep or " "193local pattern = string.format("([^%s]+)", sep)194local _ignored = string.gsub(str, pattern, function(c) fields[#fields + 1] = c end)195196return fields197end198199function is_absolute_path(path)200if path:sub(1, pandoc.path.separator:len()) == pandoc.path.separator then201return true202end203-- handle windows paths204if path:sub(2, 2) == ":" and path:sub(3, 3) == pandoc.path.separator then205return true206end207return false208end209210local files_in_flight = {}211function absolute_searcher(modname)212if not is_absolute_path(modname) then213return nil -- not an absolute path, let someone else handle it214end215local function loader()216local file_to_load = modname .. '.lua'217if files_in_flight[file_to_load] then218error("Circular dependency detected when attempting to load module: " .. file_to_load)219error("The following files are involved:")220for k, v in pairs(files_in_flight) do221error(" " ..k)222end223os.exit(1)224end225files_in_flight[file_to_load] = true226local result = dofile(file_to_load)227files_in_flight[file_to_load] = nil228return result229end230return loader231end232table.insert(package.searchers, 1, absolute_searcher)233234-- TODO: Detect the root of the project and disallow paths235-- which are both outside of the project root and outside236-- quarto's own root237local function resolve_relative_path(path)238local segments = split(path, pandoc.path.separator)239local resolved = {}240if path:sub(1, 1) == pandoc.path.separator then241resolved[1] = ""242end243for i = 1, #segments do244local segment = segments[i]245if segment == ".." then246resolved[#resolved] = nil247elseif segment ~= "." then248resolved[#resolved + 1] = segment249end250end251return table.concat(resolved, pandoc.path.separator)252end253254-- Add modules base path to package.path so we can require('modules/...') from255-- any path256package.path = package.path .. ';' .. pandoc.path.normalize(PANDOC_STATE.user_data_dir .. '/../../filters/?.lua')257258-- patch require to look in current scriptDirs as well as supporting259-- relative requires260local orig_require = require261function require(modname)262-- This supports relative requires. We need to resolve them carefully in two ways:263--264-- first, we need to ensure it is resolved relative to the current script.265-- second, we need to make sure that different paths that resolve to the266-- same file are cached as the same module.267--268-- this means we need to put ourselves in front of the standard require()269-- call, since it checks cache by `modname` and we need to make sure that270-- `modname` is always the same for the same file.271--272-- We achieve both by forcing the call to orig_require in relative requires273-- to always take a fully-qualified path.274--275-- This strategy is not going to work in general, in the presence of symlinks276-- and other things that can make two paths resolve to the same file. But277-- it's good enough for our purposes.278if modname:sub(1, 1) == "." then279local calling_file = debug.getinfo(2, "S").source:sub(2, -1)280local calling_dir = pandoc.path.directory(calling_file)281if calling_dir == "." then282-- resolve to current working directory283calling_dir = scriptDir()284end285if calling_dir == "." then286-- last-ditch effort, use the current working directory287calling_dir = pandoc.system.get_working_directory()288end289local resolved_path = resolve_relative_path(pandoc.path.normalize(pandoc.path.join({calling_dir, modname})))290return require(resolved_path)291end292local old_path = package.path293local new_path = package.path294local dirs = scriptDirs()295for i, v in ipairs(dirs) do296new_path = new_path .. ';' .. pandoc.path.join({v, '?.lua'})297end298299package.path = new_path300local mod = orig_require(modname)301package.path = old_path302return mod303end304305if os.getenv("QUARTO_LUACOV") ~= nil then306require("luacov")307end308309-- resolves a path, providing either the original path310-- or if relative, a path that is based upon the311-- script location312local function resolvePath(path)313if isRelativeRef(path) then314local wd = pandoc.system.get_working_directory()315return pandoc.path.join({wd, pandoc.path.normalize(path)})316else317return path318end319end320321local function resolvePathExt(path)322if isRelativeRef(path) then323return resolvePath(pandoc.path.join({scriptDir(), pandoc.path.normalize(path)}))324else325return path326end327end328-- converts the friendly Quartio location names329-- in the pandoc location330local function resolveLocation(location)331if (location == kInHeader) then332return "header-includes"333elseif (location == kAfterBody) then334return "include-after"335elseif (location == kBeforeBody) then336return "include-before"337else338error("Illegal value for dependency location. " .. location .. " is not a valid location.")339end340end341342-- Provides the path to the dependency file343-- The dependency file can be used to persist dependencies across filter344-- passes, but will also be inspected after pandoc is345-- done running to deterine any files that should be copied346local function dependenciesFile()347local dependenciesFile = os.getenv("QUARTO_FILTER_DEPENDENCY_FILE")348if dependenciesFile == nil then349error('Missing expected dependency file environment variable QUARTO_FILTER_DEPENDENCY_FILE')350else351return pandoc.utils.stringify(dependenciesFile)352end353end354355-- creates a dependency object356local function dependency(dependencyType, dependency)357return {358type = dependencyType,359content = dependency360}361end362363-- writes a dependency object to the dependency file364local function writeToDependencyFile(dependency)365local dependencyJson = json.encode(dependency)366local file = io.open(dependenciesFile(), "a")367if file ~= nil then368file:write(dependencyJson .. "\n")369file:close()370else371fail('Error opening dependencies file at ' .. dependenciesFile())372end373end374375-- process a file dependency (read the contents of the file)376-- and include it verbatim in the specified location377local function processFileDependency(dependency, meta)378-- read file contents379local rawFile = dependency.content380local f = io.open(pandoc.utils.stringify(rawFile.path), "r")381if f ~= nil then382local fileContents = f:read("*all")383local blockFormat384f:close()385386-- Determine the format with special treatment for verbatim HTML387if format.isFormat("html") then388blockFormat = "html"389else390blockFormat = FORMAT391end392393-- place the contents of the file right where it belongs394meta[rawFile.location]:insert(pandoc.Blocks({ pandoc.RawBlock(blockFormat, fileContents) }))395else396fail('Error reading dependencies from ' .. rawFile.path)397end398399400end401402-- process a text dependency, placing it in the specified location403local function processTextDependency(dependency, meta)404local rawText = dependency.content405local textLoc = rawText.location406407if meta[textLoc] == nil then408meta[textLoc] = pandoc.List{}409end410meta[textLoc]:insert(pandoc.Blocks{pandoc.RawBlock(FORMAT, rawText.text)})411end412413-- make the usePackage statement414local function usePackage(package, option)415local text = ''416if option == nil then417text = "\\makeatletter\n\\@ifpackageloaded{" .. package .. "}{}{\\usepackage{" .. package .. "}}\n\\makeatother"418else419text = "\\makeatletter\n\\@ifpackageloaded{" .. package .. "}{}{\\usepackage[" .. option .. "]{" .. package .. "}}\n\\makeatother"420end421return pandoc.Blocks({ pandoc.RawBlock("latex", text) })422end423424-- generate a latex usepackage statement425local function processUsePackageDependency(dependency, meta)426local rawPackage = dependency.content427428local headerLoc = resolveLocation(kInHeader)429if meta[headerLoc] == nil then430meta[headerLoc] = pandoc.List{}431end432meta[headerLoc]:insert(usePackage(rawPackage.package, rawPackage.options))433end434435436-- process the dependencies that are present in the dependencies437-- file, injecting appropriate meta content and replacing438-- the contents of the dependencies file with paths to439-- file dependencies that should be copied by Quarto440local function processDependencies(meta)441local dependenciesFile = dependenciesFile()442443-- holds a list of hashes for dependencies that444-- have been processed. Process each dependency445-- only once446local injectedText = pandoc.List{}447local injectedFile = pandoc.List{}448local injectedPackage = pandoc.List{}449450-- each line was written as a dependency.451-- process them and contribute the appropriate headers452for line in io.lines(dependenciesFile) do453local dependency = json.decode(line)454if dependency.type == 'text' then455if not utils.table.contains(injectedText, dependency.content) then456processTextDependency(dependency, meta)457injectedText:insert(dependency.content)458end459elseif dependency.type == "file" then460if not utils.table.contains(injectedFile, dependency.content.path) then461processFileDependency(dependency, meta)462injectedFile:insert(dependency.content.path)463end464elseif dependency.type == "usepackage" then465if not utils.table.contains(injectedPackage, dependency.content.package) then466processUsePackageDependency(dependency, meta)467injectedPackage:insert(dependency.content.package)468end469end470end471end472473-- resolves the file paths for an array/list of depependency files474local function resolveDependencyFilePaths(dependencyFiles)475if dependencyFiles ~= nil then476for i,v in ipairs(dependencyFiles) do477v.path = resolvePathExt(v.path)478end479return dependencyFiles480else481return nil482end483end484485-- resolves the hrefs for an array/list of link tags486local function resolveDependencyLinkTags(linkTags)487if linkTags ~= nil then488for i, v in ipairs(linkTags) do489v.href = resolvePath(v.href)490end491return linkTags492else493return nil494end495end496497-- Convert dependency files which may be just a string (path) or498-- incomplete objects into valid file dependencies499local function resolveFileDependencies(name, dependencyFiles)500if dependencyFiles ~= nil then501502-- make sure this is an array503if type(dependencyFiles) ~= "table" or not utils.table.isarray(dependencyFiles) then504error("Invalid HTML Dependency: " .. name .. " property must be an array")505end506507508local finalDependencies = {}509for i, v in ipairs(dependencyFiles) do510if type(v) == "table" then511-- fill in the name, if one is not provided512if v.name == nil then513v.name = pandoc.path.filename(v.path)514end515finalDependencies[i] = v516elseif type(v) == "string" then517-- turn a string into a name and path518finalDependencies[i] = {519name = pandoc.path.filename(v),520path = v521}522else523-- who knows what this is!524error("Invalid HTML Dependency: " .. name .. " property contains an unexpected type.")525end526end527return finalDependencies528else529return nil530end531end532533-- Convert dependency files which may be just a string (path) or534-- incomplete objects into valid file dependencies535local function resolveServiceWorkers(serviceworkers)536if serviceworkers ~= nil then537-- make sure this is an array538if type(serviceworkers) ~= "table" or not utils.table.isarray(serviceworkers) then539error("Invalid HTML Dependency: serviceworkers property must be an array")540end541542local finalServiceWorkers = {}543for i, v in ipairs(serviceworkers) do544if type(v) == "table" then545-- fill in the destination as the root, if one is not provided546if v.source == nil then547error("Invalid HTML Dependency: a serviceworker must have a source.")548else549v.source = resolvePathExt(v.source)550end551finalServiceWorkers[i] = v552553elseif type(v) == "string" then554-- turn a string into a name and path555finalServiceWorkers[i] = {556source = resolvePathExt(v)557}558else559-- who knows what this is!560error("Invalid HTML Dependency: serviceworkers property contains an unexpected type.")561end562end563return finalServiceWorkers564else565return nil566end567end568569-- Lua Patterns for LaTeX Table Environment570571-- 1. \begin{table}[h] ... \end{table}572local latexTablePatternWithPos_table = { "\\begin{table}%[[^%]]+%]", ".*", "\\end{table}" }573local latexTablePattern_table = { "\\begin{table}", ".*", "\\end{table}" }574575-- 2. \begin{longtable}[c*]{l|r|r}576-- FIXME: These two patterns with longtable align options do no account for newlines in options,577-- however pandoc will break align options over lines. This leads to specific treatment needed578-- as latexLongtablePattern_table will be the pattern, matching options in content.579-- see split_longtable_start() usage in src\resources\filters\customnodes\floatreftarget.lua580local latexLongtablePatternWithPosAndAlign_table = { "\\begin{longtable}%[[^%]]+%]{[^\n]*}", ".*", "\\end{longtable}" }581local latexLongtablePatternWithAlign_table = { "\\begin{longtable}{[^\n]*}", ".*", "\\end{longtable}" }582local latexLongtablePatternWithPos_table = { "\\begin{longtable}%[[^%]]+%]", ".*", "\\end{longtable}" }583local latexLongtablePattern_table = { "\\begin{longtable}", ".*", "\\end{longtable}" }584585-- 3. \begin{tabular}[c]{l|r|r}586local latexTabularPatternWithPosAndAlign_table = { "\\begin{tabular}%[[^%]]+%]{[^\n]*}", ".*", "\\end{tabular}" }587local latexTabularPatternWithPos_table = { "\\begin{tabular}%[[^%]]+%]", ".*", "\\end{tabular}" }588local latexTabularPatternWithAlign_table = { "\\begin{tabular}{[^\n]*}", ".*", "\\end{tabular}" }589local latexTabularPattern_table = { "\\begin{tabular}", ".*", "\\end{tabular}" }590591-- Lua Pattern for Caption environment592local latexCaptionPattern_table = { "\\caption{", ".-", "}[^\n]*\n" }593594-- global quarto params595local paramsJson = base64.decode(os.getenv("QUARTO_FILTER_PARAMS"))596local quartoParams = json.decode(paramsJson)597598function param(name, default)599local value = quartoParams[name]600if value == nil then601value = default602end603return value604end605606local function projectDirectory()607return os.getenv("QUARTO_PROJECT_DIR")608end609610local function projectOutputDirectory()611local outputDir = param("project-output-dir", "")612local projectDir = projectDirectory()613if projectDir then614return pandoc.path.join({projectDir, outputDir})615else616return nil617end618end619620-- Provides the project relative path to the current input621-- if this render is in the context of a project622local function projectRelativeOutputFile()623624-- the project directory625local projDir = projectDirectory()626627-- the offset to the project628if projDir then629-- relative from project directory to working directory630local workingDir = pandoc.system.get_working_directory()631local projRelFolder = pandoc.path.make_relative(workingDir, projDir, false)632633-- add the file output name and normalize634local projRelPath = pandoc.path.join({projRelFolder, PANDOC_STATE['output_file']})635return pandoc.path.normalize(projRelPath);636else637return nil638end639end640641local function inputFile()642local source = param("quarto-source", "")643if pandoc.path.is_absolute(source) then644return source645else646local projectDir = projectDirectory()647if projectDir then648return pandoc.path.join({projectDir, source})649else650-- outside of a project, quarto already changes651-- pwd to the file's directory prior to calling pandoc,652-- so we should just use the filename653-- https://github.com/quarto-dev/quarto-cli/issues/7424654local path_parts = pandoc.path.split(source)655return pandoc.path.join({pandoc.system.get_working_directory(), path_parts[#path_parts]})656end657end658end659660local function outputFile()661local projectOutDir = projectOutputDirectory()662if projectOutDir then663local projectDir = projectDirectory()664if projectDir then665local input = pandoc.path.directory(inputFile())666local relativeDir = pandoc.path.make_relative(input, projectDir)667if relativeDir and relativeDir ~= '.' then668return pandoc.path.join({projectOutDir, relativeDir, PANDOC_STATE['output_file']})669end670end671return pandoc.path.join({projectOutDir, PANDOC_STATE['output_file']})672else673return pandoc.path.join({pandoc.system.get_working_directory(), PANDOC_STATE['output_file']})674end675end676677local function version()678local versionString = param('quarto-version', 'unknown')679local success, versionObject = pcall(pandoc.types.Version, versionString)680if success then681return versionObject682else683return versionString684end685end686687local function projectProfiles()688return param('quarto_profile', {})689end690691local function projectOffset()692return param('project-offset', nil)693end694695local function file_exists(name)696local f = io.open(name, 'r')697if f ~= nil then698io.close(f)699return true700else701return false702end703end704705706local function write_file(path, contents, mode)707pandoc.system.make_directory(pandoc.path.directory(path), true)708mode = mode or "a"709local file = io.open(path, mode)710if file then711file:write(contents)712file:close()713return true714else715return false716end717end718719local function read_file(path)720local file = io.open(path, "rb")721if not file then return nil end722local content = file:read "*a"723file:close()724return content725end726727local function remove_file(path)728return os.remove(path)729end730731-- Quarto internal module - makes functions available732-- through the filters733_quarto = {734processDependencies = processDependencies,735format = format,736-- Each list in patterns below contains Lua pattern as table,737-- where elements are ordered from more specific match to more generic one.738-- They are meant to be used with _quarto.modules.patterns.match_in_list_of_patterns()739patterns = {740latexTableEnvPatterns = pandoc.List({741latexTablePatternWithPos_table,742latexTablePattern_table743}),744latexTabularEnvPatterns = pandoc.List({745latexTabularPatternWithPosAndAlign_table,746latexTabularPatternWithPos_table,747latexTabularPatternWithAlign_table,748latexTabularPattern_table749}),750latexLongtableEnvPatterns = pandoc.List({751latexLongtablePatternWithPosAndAlign_table,752latexLongtablePatternWithPos_table,753latexLongtablePatternWithAlign_table,754latexLongtablePattern_table755}),756-- This is all table env patterns757latexAllTableEnvPatterns = pandoc.List({758latexTablePatternWithPos_table,759latexTablePattern_table,760latexLongtablePatternWithPosAndAlign_table,761latexLongtablePatternWithPos_table,762latexLongtablePatternWithAlign_table,763latexLongtablePattern_table,764latexTabularPatternWithPosAndAlign_table,765latexTabularPatternWithPos_table,766latexTabularPatternWithAlign_table,767latexTabularPattern_table,768}),769latexCaptionPatterns = pandoc.List({770latexCaptionPattern_table771})772},773traverser = utils.walk,774utils = utils,775withScriptFile = function(file, callback)776table.insert(scriptFile, file)777local result = callback()778table.remove(scriptFile, #scriptFile)779return result780end,781projectOffset = projectOffset,782file = {783read = read_file,784write = function(path, contents)785return write_file(path, contents, "wb")786end,787write_text = function(path, contents)788return write_file(path, contents, "a")789end,790exists = file_exists,791remove = remove_file792}793}794795-- this injection here is ugly but gets around796-- a hairy order-of-import issue that would otherwise happen797-- because string_to_inlines requires some filter code that is only798-- later imported799800_quarto.utils.string_to_inlines = function(s)801return string_to_quarto_ast_inlines(s)802end803_quarto.utils.string_to_blocks = function(s)804return string_to_quarto_ast_blocks(s)805end806_quarto.utils.render = function(n)807return _quarto.ast.walk(n, render_extended_nodes())808end809810-- The main exports of the quarto module811quarto = {812format = format,813doc = {814add_html_dependency = function(htmlDependency)815816-- validate the dependency817if htmlDependency.name == nil then818error("HTML dependencies must include a name")819end820821if htmlDependency.meta == nil and822htmlDependency.links == nil and823htmlDependency.scripts == nil and824htmlDependency.stylesheets == nil and825htmlDependency.resources == nil and826htmlDependency.serviceworkers == nil and827htmlDependency.head == nil then828error("HTML dependencies must include at least one of meta, links, scripts, stylesheets, serviceworkers, or resources. All appear empty.")829end830831-- validate that the meta is as expected832if htmlDependency.meta ~= nil then833if type(htmlDependency.meta) ~= 'table' then834error("Invalid HTML Dependency: meta value must be a table")835elseif utils.table.isarray(htmlDependency.meta) then836error("Invalid HTML Dependency: meta value must must not be an array")837end838end839840-- validate link tags841if htmlDependency.links ~= nil then842if type(htmlDependency.links) ~= 'table' or not utils.table.isarray(htmlDependency.links) then843error("Invalid HTML Dependency: links must be an array")844else845for i, v in ipairs(htmlDependency.links) do846if type(v) ~= "table" or (v.href == nil or v.rel == nil) then847error("Invalid HTML Dependency: each link must be a table containing both rel and href properties.")848end849end850end851end852853-- resolve names so they aren't required854htmlDependency.scripts = resolveFileDependencies("scripts", htmlDependency.scripts)855htmlDependency.stylesheets = resolveFileDependencies("stylesheets", htmlDependency.stylesheets)856htmlDependency.resources = resolveFileDependencies("resources", htmlDependency.resources)857858-- pass the dependency through to the file859writeToDependencyFile(dependency("html", {860name = htmlDependency.name,861version = htmlDependency.version,862external = true,863meta = htmlDependency.meta,864links = resolveDependencyLinkTags(htmlDependency.links),865scripts = resolveDependencyFilePaths(htmlDependency.scripts),866stylesheets = resolveDependencyFilePaths(htmlDependency.stylesheets),867resources = resolveDependencyFilePaths(htmlDependency.resources),868serviceworkers = resolveServiceWorkers(htmlDependency.serviceworkers),869head = htmlDependency.head,870}))871end,872873attach_to_dependency = function(name, pathOrFileObj)874875if name == nil then876fail("The target dependency name for an attachment cannot be nil. Please provide a valid dependency name.")877end878879-- path can be a string or an obj { name, path }880local resolvedFile = {}881if type(pathOrFileObj) == "table" then882883-- validate that there is at least a path884if pathOrFileObj.path == nil then885fail("Error attaching to dependency '" .. name .. "'.\nYou must provide a 'path' when adding an attachment to a dependency.")886end887888-- resolve a name, if one isn't provided889local name = pathOrFileObj.name890if name == nil then891name = pandoc.path.filename(pathOrFileObj.path)892end893894-- the full resolved file895resolvedFile = {896name = name,897path = resolvePathExt(pathOrFileObj.path)898}899else900resolvedFile = {901name = pandoc.path.filename(pathOrFileObj),902path = resolvePathExt(pathOrFileObj)903}904end905906writeToDependencyFile(dependency("html-attachment", {907name = name,908file = resolvedFile909}))910end,911912use_latex_package = function(package, options)913writeToDependencyFile(dependency("usepackage", {package = package, options = options }))914end,915916add_format_resource = function(path)917writeToDependencyFile(dependency("format-resources", { file = resolvePathExt(path)}))918end,919920add_resource = function(path)921writeToDependencyFile(dependency("resources", { file = resolvePathExt(path)}))922end,923924add_supporting = function(path)925writeToDependencyFile(dependency("supporting", { file = resolvePathExt(path)}))926end,927928include_text = function(location, text)929writeToDependencyFile(dependency("text", { text = text, location = resolveLocation(location)}))930end,931932include_file = function(location, path)933writeToDependencyFile(dependency("file", { path = resolvePathExt(path), location = resolveLocation(location)}))934end,935936is_format = format.isFormat,937938cite_method = function()939local citeMethod = param('cite-method', nil)940return citeMethod941end,942pdf_engine = function()943local engine = param('pdf-engine', 'pdflatex')944return engine945end,946has_bootstrap = function()947local hasBootstrap = param('has-bootstrap', false)948return hasBootstrap949end,950is_filter_active = function(filter)951return quarto_global_state.active_filters[filter]952end,953954output_file = outputFile(),955input_file = inputFile(),956crossref = {},957language = param("language", nil)958},959project = {960directory = projectDirectory(),961offset = projectOffset(),962profile = pandoc.List(projectProfiles()),963output_directory = projectOutputDirectory()964},965utils = {966dump = utils.dump,967table = utils.table,968type = utils.type,969resolve_path = resolvePathExt,970resolve_path_relative_to_document = resolvePath,971as_inlines = utils.as_inlines,972as_blocks = utils.as_blocks,973is_empty_node = utils.is_empty_node,974string_to_blocks = utils.string_to_blocks,975string_to_inlines = utils.string_to_inlines,976render = utils.render,977match = utils.match,978add_to_blocks = utils.add_to_blocks,979},980paths = {981-- matches the path from `quartoEnvironmentParams` from src/command/render/filters.ts982rscript = function()983return param('quarto-environment', nil).paths.Rscript984end,985tinytex_bin_dir = function()986return param('quarto-environment', nil).paths.TinyTexBinDir987end,988typst = function()989return param('quarto-environment', nil).paths.Typst990end,991},992json = json,993base64 = base64,994log = logging,995version = version(),996-- map to quartoConfig information on TS side997config = {998cli_path = function() return param('quarto-cli-path', nil) end,999version = function() return version() end1000},1001shortcode = {1002read_arg = function (args, n)1003local arg = args[n or 1]1004local varName1005if arg == nil then1006return nil1007end1008if type(arg) ~= "string" then1009varName = inlinesToString(arg)1010else1011varName = arg --[[@as string]]1012end1013return varName1014end,1015error_output = function (shortcode, message_or_args, context)1016if type(message_or_args) == "table" then1017message_or_args = table.concat(message_or_args, " ")1018end1019local message = "?" .. shortcode .. ":" .. message_or_args1020if context == "block" then1021return pandoc.Blocks { pandoc.Strong( pandoc.Inlines { pandoc.Str(message) } ) }1022elseif context == "inline" then1023return pandoc.Inlines { pandoc.Strong( pandoc.Inlines { pandoc.Str(message) } ) }1024elseif context == "text" then1025return message1026else1027warn("Unknown context for " .. shortcode .. " shortcode error: " .. context)1028return { }1029end1030end,1031},1032metadata = {1033get = function(key)1034return option(key, nil)1035end1036},1037variables = {1038get = function(name)1039local value = var(name, nil)1040if value then1041value = pandoc.utils.stringify(value)1042end1043return value1044end1045}1046}10471048-- alias old names for backwards compatibility1049quarto.doc.addHtmlDependency = quarto.doc.add_html_dependency1050quarto.doc.attachToDependency = quarto.doc.attach_to_dependency1051quarto.doc.useLatexPackage = quarto.doc.use_latex_package1052quarto.doc.addFormatResource = quarto.doc.add_format_resource1053quarto.doc.includeText = quarto.doc.include_text1054quarto.doc.includeFile = quarto.doc.include_file1055quarto.doc.isFormat = quarto.doc.is_format1056quarto.doc.citeMethod = quarto.doc.cite_method1057quarto.doc.pdfEngine = quarto.doc.pdf_engine1058quarto.doc.hasBootstrap = quarto.doc.has_bootstrap1059quarto.doc.project_output_file = projectRelativeOutputFile1060quarto.utils.resolvePath = quarto.utils.resolve_path10611062-- since Pandoc 3, pandoc.Null is no longer an allowed constructor.1063-- this workaround makes it so that our users extensions which use pandoc.Null1064-- still work, assuming they call pandoc.Null() in a "simple" way.1065pandoc.Null = function()1066return {}1067end106810691070