Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/pandoc/datadir/init.lua
12922 views
1
if pandoc.system.os == "mingw32" then
2
3
local function get_windows_ansi_codepage()
4
-- Reading the code page directly out of the registry was causing
5
-- Microsoft Defender to massively slow down pandoc (e.g. 1400ms instead of 140ms)
6
-- So instead, look that up outside this filter and pass it in, which appears speed(ier)
7
8
-- local pipe = assert(io.popen[[reg query HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage /v ACP]])
9
-- local codepage = pipe:read"*a":match"%sACP%s+REG_SZ%s+(.-)%s*$"
10
-- pipe:close()
11
-- return assert(codepage, "Failed to determine Windows ANSI codepage from Windows registry")
12
local codepage = os.getenv("QUARTO_WIN_CODEPAGE")
13
if codepage == nil then
14
codepage = "1252"
15
end
16
return codepage
17
end
18
19
local codepage = get_windows_ansi_codepage()
20
-- print("Your codepage is "..codepage)
21
22
local success = pcall(function()
23
pandoc.text.toencoding("test", "CP" .. codepage)
24
end)
25
if not success then
26
error("Failed to convert to CP" .. codepage .. " encoding. Please check your codepage or set it explicitly with the QUARTO_WIN_CODEPAGE environment variable.")
27
exit(1)
28
end
29
30
function convert_from_utf8(utf8str)
31
return pandoc.text.toencoding(utf8str, "CP" .. codepage)
32
end
33
34
local orig_os_rename = os.rename
35
36
function os.rename(old, new)
37
return orig_os_rename(convert_from_utf8(old), convert_from_utf8(new))
38
end
39
40
local orig_os_remove = os.remove
41
42
function os.remove(filename)
43
return orig_os_remove(convert_from_utf8(filename))
44
end
45
46
local orig_os_execute = os.execute
47
48
function os.execute(command)
49
if command then
50
command = convert_from_utf8(command)
51
end
52
return orig_os_execute(command)
53
end
54
55
local orig_io_open = io.open
56
57
function io.open(filename, ...)
58
return orig_io_open(convert_from_utf8(filename), ...)
59
end
60
61
local orig_io_popen = io.popen
62
63
function io.popen(prog, ...)
64
return orig_io_popen(convert_from_utf8(prog), ...)
65
end
66
67
local orig_io_lines = io.lines
68
69
function io.lines(filename, ...)
70
if filename then
71
filename = convert_from_utf8(filename)
72
return orig_io_lines(filename, ...)
73
else
74
return orig_io_lines()
75
end
76
end
77
78
local orig_dofile = dofile
79
80
function dofile(filename)
81
if filename then
82
filename = convert_from_utf8(filename)
83
end
84
return orig_dofile(filename)
85
end
86
87
local orig_loadfile = loadfile
88
89
function loadfile(filename, ...)
90
if filename then
91
filename = convert_from_utf8(filename)
92
end
93
return orig_loadfile(filename, ...)
94
end
95
96
local orig_require = require
97
98
function require(modname)
99
modname = convert_from_utf8(modname)
100
return orig_require(modname)
101
end
102
103
local orig_io_input = io.input
104
105
function io.input(file)
106
if type(file) == "string" then
107
file = convert_from_utf8(file)
108
end
109
return orig_io_input(file)
110
end
111
112
local orig_io_output = io.output
113
114
function io.output(file)
115
if type(file) == "string" then
116
file = convert_from_utf8(file)
117
end
118
return orig_io_output(file)
119
end
120
121
end
122
123
-- Bootstrap our common libraries by adding our filter pandoc to the lib path
124
local sharePath = os.getenv("QUARTO_SHARE_PATH");
125
-- TODO: Need to ensure that we are resolving ahead of the other path
126
-- and understand consequences
127
-- Be aware of user filters which may be using require - need to be able load their modules safely
128
-- Maybe namespace quarto modules somehow or alter path for user filters
129
if sharePath ~= nil then
130
local sep = package.config:sub(1,1)
131
package.path = package.path .. ";" .. sharePath .. sep .. 'pandoc' .. sep .. 'datadir' .. sep .. '?.lua'
132
end
133
134
-- dependency types that will be emitted to the dedendencies file
135
-- (use streamed json to write a single line of json for each dependency with
136
-- the type and the contents)
137
local kDependencyTypeHtml = "html";
138
local kDependencyTypeLatex = "latex";
139
local kDependencyTypeFile = "file";
140
local kDependencyTypeText = "text";
141
142
-- locations that dependencies may be injected
143
local kBeforeBody = "before-body";
144
local kAfterBody = "after-body";
145
local kInHeader = "in-header";
146
147
-- common requires
148
-- this is in the global scope - anyone downstream of init may use this
149
local format = require '_format'
150
local base64 = require '_base64'
151
local json = require '_json'
152
local utils = require '_utils'
153
local logging = require 'logging'
154
155
-- determines whether a path is a relative path
156
local function isRelativeRef(ref)
157
return ref:find("^/") == nil and
158
ref:find("^%a+://") == nil and
159
ref:find("^data:") == nil and
160
ref:find("^#") == nil
161
end
162
163
-- This is a function that returns the current script
164
-- path. Shortcodes can use an internal function
165
-- to set and clear the local value that will be used
166
-- instead of pandoc's filter path when a shortcode is executing
167
local scriptFile = {}
168
169
local function scriptDirs()
170
if PANDOC_SCRIPT_FILE == nil then
171
return {}
172
end
173
local dirs = { pandoc.path.directory(PANDOC_SCRIPT_FILE) }
174
for i = 1, #scriptFile do
175
dirs[#dirs+1] = pandoc.path.directory(scriptFile[i])
176
end
177
return dirs
178
end
179
180
local function scriptDir()
181
if #scriptFile > 0 then
182
return pandoc.path.directory(scriptFile[#scriptFile])
183
else
184
-- hard fallback
185
return pandoc.path.directory(PANDOC_SCRIPT_FILE)
186
end
187
end
188
189
-- splits a string on a separator
190
local function split(str, sep)
191
local fields = {}
192
193
local sep = sep or " "
194
local pattern = string.format("([^%s]+)", sep)
195
local _ignored = string.gsub(str, pattern, function(c) fields[#fields + 1] = c end)
196
197
return fields
198
end
199
200
function is_absolute_path(path)
201
if path:sub(1, pandoc.path.separator:len()) == pandoc.path.separator then
202
return true
203
end
204
-- handle windows paths
205
if path:sub(2, 2) == ":" and path:sub(3, 3) == pandoc.path.separator then
206
return true
207
end
208
return false
209
end
210
211
local files_in_flight = {}
212
function absolute_searcher(modname)
213
if not is_absolute_path(modname) then
214
return nil -- not an absolute path, let someone else handle it
215
end
216
local function loader()
217
local file_to_load = modname .. '.lua'
218
if files_in_flight[file_to_load] then
219
error("Circular dependency detected when attempting to load module: " .. file_to_load)
220
error("The following files are involved:")
221
for k, v in pairs(files_in_flight) do
222
error(" " ..k)
223
end
224
os.exit(1)
225
end
226
files_in_flight[file_to_load] = true
227
local result = dofile(file_to_load)
228
files_in_flight[file_to_load] = nil
229
return result
230
end
231
return loader
232
end
233
table.insert(package.searchers, 1, absolute_searcher)
234
235
-- TODO: Detect the root of the project and disallow paths
236
-- which are both outside of the project root and outside
237
-- quarto's own root
238
local function resolve_relative_path(path)
239
local segments = split(path, pandoc.path.separator)
240
local resolved = {}
241
if path:sub(1, 1) == pandoc.path.separator then
242
resolved[1] = ""
243
end
244
for i = 1, #segments do
245
local segment = segments[i]
246
if segment == ".." then
247
resolved[#resolved] = nil
248
elseif segment ~= "." then
249
resolved[#resolved + 1] = segment
250
end
251
end
252
return table.concat(resolved, pandoc.path.separator)
253
end
254
255
-- Add modules base path to package.path so we can require('modules/...') from
256
-- any path
257
package.path = package.path .. ';' .. pandoc.path.normalize(PANDOC_STATE.user_data_dir .. '/../../filters/?.lua')
258
259
-- patch require to look in current scriptDirs as well as supporting
260
-- relative requires
261
local orig_require = require
262
function require(modname)
263
-- This supports relative requires. We need to resolve them carefully in two ways:
264
--
265
-- first, we need to ensure it is resolved relative to the current script.
266
-- second, we need to make sure that different paths that resolve to the
267
-- same file are cached as the same module.
268
--
269
-- this means we need to put ourselves in front of the standard require()
270
-- call, since it checks cache by `modname` and we need to make sure that
271
-- `modname` is always the same for the same file.
272
--
273
-- We achieve both by forcing the call to orig_require in relative requires
274
-- to always take a fully-qualified path.
275
--
276
-- This strategy is not going to work in general, in the presence of symlinks
277
-- and other things that can make two paths resolve to the same file. But
278
-- it's good enough for our purposes.
279
if modname:sub(1, 1) == "." then
280
local calling_file = debug.getinfo(2, "S").source:sub(2, -1)
281
local calling_dir = pandoc.path.directory(calling_file)
282
if calling_dir == "." then
283
-- resolve to current working directory
284
calling_dir = scriptDir()
285
end
286
if calling_dir == "." then
287
-- last-ditch effort, use the current working directory
288
calling_dir = pandoc.system.get_working_directory()
289
end
290
local resolved_path = resolve_relative_path(pandoc.path.normalize(pandoc.path.join({calling_dir, modname})))
291
return require(resolved_path)
292
end
293
local old_path = package.path
294
local new_path = package.path
295
local dirs = scriptDirs()
296
for i, v in ipairs(dirs) do
297
new_path = new_path .. ';' .. pandoc.path.join({v, '?.lua'})
298
end
299
300
package.path = new_path
301
local mod = orig_require(modname)
302
package.path = old_path
303
return mod
304
end
305
306
if os.getenv("QUARTO_LUACOV") ~= nil then
307
require("luacov")
308
end
309
310
-- resolves a path, providing either the original path
311
-- or if relative, a path that is based upon the
312
-- script location
313
local function resolvePath(path)
314
if isRelativeRef(path) then
315
local wd = pandoc.system.get_working_directory()
316
return pandoc.path.join({wd, pandoc.path.normalize(path)})
317
else
318
return path
319
end
320
end
321
322
local function resolvePathExt(path)
323
if isRelativeRef(path) then
324
return resolvePath(pandoc.path.join({scriptDir(), pandoc.path.normalize(path)}))
325
else
326
return path
327
end
328
end
329
-- converts the friendly Quartio location names
330
-- in the pandoc location
331
local function resolveLocation(location)
332
if (location == kInHeader) then
333
return "header-includes"
334
elseif (location == kAfterBody) then
335
return "include-after"
336
elseif (location == kBeforeBody) then
337
return "include-before"
338
else
339
error("Illegal value for dependency location. " .. location .. " is not a valid location.")
340
end
341
end
342
343
-- Provides the path to the dependency file
344
-- The dependency file can be used to persist dependencies across filter
345
-- passes, but will also be inspected after pandoc is
346
-- done running to deterine any files that should be copied
347
local function dependenciesFile()
348
local dependenciesFile = os.getenv("QUARTO_FILTER_DEPENDENCY_FILE")
349
if dependenciesFile == nil then
350
error('Missing expected dependency file environment variable QUARTO_FILTER_DEPENDENCY_FILE')
351
else
352
return pandoc.utils.stringify(dependenciesFile)
353
end
354
end
355
356
-- creates a dependency object
357
local function dependency(dependencyType, dependency)
358
return {
359
type = dependencyType,
360
content = dependency
361
}
362
end
363
364
-- writes a dependency object to the dependency file
365
local function writeToDependencyFile(dependency)
366
local dependencyJson = json.encode(dependency)
367
local file = io.open(dependenciesFile(), "a")
368
if file ~= nil then
369
file:write(dependencyJson .. "\n")
370
file:close()
371
else
372
fail('Error opening dependencies file at ' .. dependenciesFile())
373
end
374
end
375
376
-- process a file dependency (read the contents of the file)
377
-- and include it verbatim in the specified location
378
local function processFileDependency(dependency, meta)
379
-- read file contents
380
local rawFile = dependency.content
381
local f = io.open(pandoc.utils.stringify(rawFile.path), "r")
382
if f ~= nil then
383
local fileContents = f:read("*all")
384
local blockFormat
385
f:close()
386
387
-- Determine the format with special treatment for verbatim HTML
388
if format.isFormat("html") then
389
blockFormat = "html"
390
else
391
blockFormat = FORMAT
392
end
393
394
-- place the contents of the file right where it belongs
395
meta[rawFile.location]:insert(pandoc.Blocks({ pandoc.RawBlock(blockFormat, fileContents) }))
396
else
397
fail('Error reading dependencies from ' .. rawFile.path)
398
end
399
400
401
end
402
403
-- process a text dependency, placing it in the specified location
404
local function processTextDependency(dependency, meta)
405
local rawText = dependency.content
406
local textLoc = rawText.location
407
408
if meta[textLoc] == nil then
409
meta[textLoc] = pandoc.List{}
410
end
411
meta[textLoc]:insert(pandoc.Blocks{pandoc.RawBlock(FORMAT, rawText.text)})
412
end
413
414
-- make the usePackage statement
415
local function usePackage(package, option)
416
local text = ''
417
if option == nil then
418
text = "\\makeatletter\n\\@ifpackageloaded{" .. package .. "}{}{\\usepackage{" .. package .. "}}\n\\makeatother"
419
else
420
text = "\\makeatletter\n\\@ifpackageloaded{" .. package .. "}{}{\\usepackage[" .. option .. "]{" .. package .. "}}\n\\makeatother"
421
end
422
return pandoc.Blocks({ pandoc.RawBlock("latex", text) })
423
end
424
425
-- generate a latex usepackage statement
426
local function processUsePackageDependency(dependency, meta)
427
local rawPackage = dependency.content
428
429
local headerLoc = resolveLocation(kInHeader)
430
if meta[headerLoc] == nil then
431
meta[headerLoc] = pandoc.List{}
432
end
433
meta[headerLoc]:insert(usePackage(rawPackage.package, rawPackage.options))
434
end
435
436
437
-- process the dependencies that are present in the dependencies
438
-- file, injecting appropriate meta content and replacing
439
-- the contents of the dependencies file with paths to
440
-- file dependencies that should be copied by Quarto
441
local function processDependencies(meta)
442
local dependenciesFile = dependenciesFile()
443
444
-- holds a list of hashes for dependencies that
445
-- have been processed. Process each dependency
446
-- only once
447
local injectedText = pandoc.List{}
448
local injectedFile = pandoc.List{}
449
local injectedPackage = pandoc.List{}
450
451
-- each line was written as a dependency.
452
-- process them and contribute the appropriate headers
453
for line in io.lines(dependenciesFile) do
454
local dependency = json.decode(line)
455
if dependency.type == 'text' then
456
if not utils.table.contains(injectedText, dependency.content) then
457
processTextDependency(dependency, meta)
458
injectedText:insert(dependency.content)
459
end
460
elseif dependency.type == "file" then
461
if not utils.table.contains(injectedFile, dependency.content.path) then
462
processFileDependency(dependency, meta)
463
injectedFile:insert(dependency.content.path)
464
end
465
elseif dependency.type == "usepackage" then
466
if not utils.table.contains(injectedPackage, dependency.content.package) then
467
processUsePackageDependency(dependency, meta)
468
injectedPackage:insert(dependency.content.package)
469
end
470
end
471
end
472
end
473
474
-- resolves the file paths for an array/list of depependency files
475
local function resolveDependencyFilePaths(dependencyFiles)
476
if dependencyFiles ~= nil then
477
for i,v in ipairs(dependencyFiles) do
478
v.path = resolvePathExt(v.path)
479
end
480
return dependencyFiles
481
else
482
return nil
483
end
484
end
485
486
-- resolves the hrefs for an array/list of link tags
487
local function resolveDependencyLinkTags(linkTags)
488
if linkTags ~= nil then
489
for i, v in ipairs(linkTags) do
490
v.href = resolvePath(v.href)
491
end
492
return linkTags
493
else
494
return nil
495
end
496
end
497
498
-- Convert dependency files which may be just a string (path) or
499
-- incomplete objects into valid file dependencies
500
local function resolveFileDependencies(name, dependencyFiles)
501
if dependencyFiles ~= nil then
502
503
-- make sure this is an array
504
if type(dependencyFiles) ~= "table" or not utils.table.isarray(dependencyFiles) then
505
error("Invalid HTML Dependency: " .. name .. " property must be an array")
506
end
507
508
509
local finalDependencies = {}
510
for i, v in ipairs(dependencyFiles) do
511
if type(v) == "table" then
512
-- fill in the name, if one is not provided
513
if v.name == nil then
514
v.name = pandoc.path.filename(v.path)
515
end
516
finalDependencies[i] = v
517
elseif type(v) == "string" then
518
-- turn a string into a name and path
519
finalDependencies[i] = {
520
name = pandoc.path.filename(v),
521
path = v
522
}
523
else
524
-- who knows what this is!
525
error("Invalid HTML Dependency: " .. name .. " property contains an unexpected type.")
526
end
527
end
528
return finalDependencies
529
else
530
return nil
531
end
532
end
533
534
-- Convert dependency files which may be just a string (path) or
535
-- incomplete objects into valid file dependencies
536
local function resolveServiceWorkers(serviceworkers)
537
if serviceworkers ~= nil then
538
-- make sure this is an array
539
if type(serviceworkers) ~= "table" or not utils.table.isarray(serviceworkers) then
540
error("Invalid HTML Dependency: serviceworkers property must be an array")
541
end
542
543
local finalServiceWorkers = {}
544
for i, v in ipairs(serviceworkers) do
545
if type(v) == "table" then
546
-- fill in the destination as the root, if one is not provided
547
if v.source == nil then
548
error("Invalid HTML Dependency: a serviceworker must have a source.")
549
else
550
v.source = resolvePathExt(v.source)
551
end
552
finalServiceWorkers[i] = v
553
554
elseif type(v) == "string" then
555
-- turn a string into a name and path
556
finalServiceWorkers[i] = {
557
source = resolvePathExt(v)
558
}
559
else
560
-- who knows what this is!
561
error("Invalid HTML Dependency: serviceworkers property contains an unexpected type.")
562
end
563
end
564
return finalServiceWorkers
565
else
566
return nil
567
end
568
end
569
570
-- Lua Patterns for LaTeX Table Environment
571
572
-- 1. \begin{table}[h] ... \end{table}
573
local latexTablePatternWithPos_table = { "\\begin{table}%[[^%]]+%]", ".*", "\\end{table}" }
574
local latexTablePattern_table = { "\\begin{table}", ".*", "\\end{table}" }
575
576
-- 2. \begin{longtable}[c*]{l|r|r}
577
-- FIXME: These two patterns with longtable align options do no account for newlines in options,
578
-- however pandoc will break align options over lines. This leads to specific treatment needed
579
-- as latexLongtablePattern_table will be the pattern, matching options in content.
580
-- see split_longtable_start() usage in src\resources\filters\customnodes\floatreftarget.lua
581
local latexLongtablePatternWithPosAndAlign_table = { "\\begin{longtable}%[[^%]]+%]{[^\n]*}", ".*", "\\end{longtable}" }
582
local latexLongtablePatternWithAlign_table = { "\\begin{longtable}{[^\n]*}", ".*", "\\end{longtable}" }
583
local latexLongtablePatternWithPos_table = { "\\begin{longtable}%[[^%]]+%]", ".*", "\\end{longtable}" }
584
local latexLongtablePattern_table = { "\\begin{longtable}", ".*", "\\end{longtable}" }
585
586
-- 3. \begin{tabular}[c]{l|r|r}
587
local latexTabularPatternWithPosAndAlign_table = { "\\begin{tabular}%[[^%]]+%]{[^\n]*}", ".*", "\\end{tabular}" }
588
local latexTabularPatternWithPos_table = { "\\begin{tabular}%[[^%]]+%]", ".*", "\\end{tabular}" }
589
local latexTabularPatternWithAlign_table = { "\\begin{tabular}{[^\n]*}", ".*", "\\end{tabular}" }
590
local latexTabularPattern_table = { "\\begin{tabular}", ".*", "\\end{tabular}" }
591
592
-- Lua Pattern for Caption environment
593
local latexCaptionPattern_table = { "\\caption{", ".-", "}[^\n]*\n" }
594
595
-- global quarto params
596
local paramsJson = base64.decode(os.getenv("QUARTO_FILTER_PARAMS"))
597
local quartoParams = json.decode(paramsJson)
598
599
function param(name, default)
600
local value = quartoParams[name]
601
if value == nil then
602
value = default
603
end
604
return value
605
end
606
607
local function projectDirectory()
608
return os.getenv("QUARTO_PROJECT_DIR")
609
end
610
611
local function projectOutputDirectory()
612
local outputDir = param("project-output-dir", "")
613
local projectDir = projectDirectory()
614
if projectDir then
615
return pandoc.path.join({projectDir, outputDir})
616
else
617
return nil
618
end
619
end
620
621
-- Provides the project relative path to the current input
622
-- if this render is in the context of a project
623
local function projectRelativeOutputFile()
624
625
-- the project directory
626
local projDir = projectDirectory()
627
628
-- the offset to the project
629
if projDir then
630
-- relative from project directory to working directory
631
local workingDir = pandoc.system.get_working_directory()
632
local projRelFolder = pandoc.path.make_relative(workingDir, projDir, false)
633
634
-- add the file output name and normalize
635
local projRelPath = pandoc.path.join({projRelFolder, PANDOC_STATE['output_file']})
636
return pandoc.path.normalize(projRelPath);
637
else
638
return nil
639
end
640
end
641
642
local function inputFile()
643
local source = param("quarto-source", "")
644
if pandoc.path.is_absolute(source) then
645
return source
646
else
647
local projectDir = projectDirectory()
648
if projectDir then
649
return pandoc.path.join({projectDir, source})
650
else
651
-- outside of a project, quarto already changes
652
-- pwd to the file's directory prior to calling pandoc,
653
-- so we should just use the filename
654
-- https://github.com/quarto-dev/quarto-cli/issues/7424
655
local path_parts = pandoc.path.split(source)
656
return pandoc.path.join({pandoc.system.get_working_directory(), path_parts[#path_parts]})
657
end
658
end
659
end
660
661
local function outputFile()
662
local projectOutDir = projectOutputDirectory()
663
if projectOutDir then
664
local projectDir = projectDirectory()
665
if projectDir then
666
local input = pandoc.path.directory(inputFile())
667
local relativeDir = pandoc.path.make_relative(input, projectDir)
668
if relativeDir and relativeDir ~= '.' then
669
return pandoc.path.join({projectOutDir, relativeDir, PANDOC_STATE['output_file']})
670
end
671
end
672
return pandoc.path.join({projectOutDir, PANDOC_STATE['output_file']})
673
else
674
return pandoc.path.join({pandoc.system.get_working_directory(), PANDOC_STATE['output_file']})
675
end
676
end
677
678
local function version()
679
local versionString = param('quarto-version', 'unknown')
680
local success, versionObject = pcall(pandoc.types.Version, versionString)
681
if success then
682
return versionObject
683
else
684
return versionString
685
end
686
end
687
688
local function projectProfiles()
689
return param('quarto_profile', {})
690
end
691
692
local function projectOffset()
693
return param('project-offset', nil)
694
end
695
696
local function file_exists(name)
697
local f = io.open(name, 'r')
698
if f ~= nil then
699
io.close(f)
700
return true
701
else
702
return false
703
end
704
end
705
706
707
local function write_file(path, contents, mode)
708
pandoc.system.make_directory(pandoc.path.directory(path), true)
709
mode = mode or "a"
710
local file = io.open(path, mode)
711
if file then
712
file:write(contents)
713
file:close()
714
return true
715
else
716
return false
717
end
718
end
719
720
local function read_file(path)
721
local file = io.open(path, "rb")
722
if not file then return nil end
723
local content = file:read "*a"
724
file:close()
725
return content
726
end
727
728
local function remove_file(path)
729
return os.remove(path)
730
end
731
732
-- Quarto internal module - makes functions available
733
-- through the filters
734
_quarto = {
735
processDependencies = processDependencies,
736
format = format,
737
-- Each list in patterns below contains Lua pattern as table,
738
-- where elements are ordered from more specific match to more generic one.
739
-- They are meant to be used with _quarto.modules.patterns.match_in_list_of_patterns()
740
patterns = {
741
latexTableEnvPatterns = pandoc.List({
742
latexTablePatternWithPos_table,
743
latexTablePattern_table
744
}),
745
latexTabularEnvPatterns = pandoc.List({
746
latexTabularPatternWithPosAndAlign_table,
747
latexTabularPatternWithPos_table,
748
latexTabularPatternWithAlign_table,
749
latexTabularPattern_table
750
}),
751
latexLongtableEnvPatterns = pandoc.List({
752
latexLongtablePatternWithPosAndAlign_table,
753
latexLongtablePatternWithPos_table,
754
latexLongtablePatternWithAlign_table,
755
latexLongtablePattern_table
756
}),
757
-- This is all table env patterns
758
latexAllTableEnvPatterns = pandoc.List({
759
latexTablePatternWithPos_table,
760
latexTablePattern_table,
761
latexLongtablePatternWithPosAndAlign_table,
762
latexLongtablePatternWithPos_table,
763
latexLongtablePatternWithAlign_table,
764
latexLongtablePattern_table,
765
latexTabularPatternWithPosAndAlign_table,
766
latexTabularPatternWithPos_table,
767
latexTabularPatternWithAlign_table,
768
latexTabularPattern_table,
769
}),
770
latexCaptionPatterns = pandoc.List({
771
latexCaptionPattern_table
772
})
773
},
774
traverser = utils.walk,
775
utils = utils,
776
withScriptFile = function(file, callback)
777
table.insert(scriptFile, file)
778
local result = callback()
779
table.remove(scriptFile, #scriptFile)
780
return result
781
end,
782
projectOffset = projectOffset,
783
file = {
784
read = read_file,
785
write = function(path, contents)
786
return write_file(path, contents, "wb")
787
end,
788
write_text = function(path, contents)
789
return write_file(path, contents, "a")
790
end,
791
exists = file_exists,
792
remove = remove_file
793
}
794
}
795
796
-- this injection here is ugly but gets around
797
-- a hairy order-of-import issue that would otherwise happen
798
-- because string_to_inlines requires some filter code that is only
799
-- later imported
800
801
_quarto.utils.string_to_inlines = function(s)
802
return string_to_quarto_ast_inlines(s)
803
end
804
_quarto.utils.string_to_blocks = function(s)
805
return string_to_quarto_ast_blocks(s)
806
end
807
_quarto.utils.render = function(n)
808
return _quarto.ast.walk(n, render_extended_nodes())
809
end
810
811
-- The main exports of the quarto module
812
quarto = {
813
format = format,
814
doc = {
815
add_html_dependency = function(htmlDependency)
816
817
-- validate the dependency
818
if htmlDependency.name == nil then
819
error("HTML dependencies must include a name")
820
end
821
822
if htmlDependency.meta == nil and
823
htmlDependency.links == nil and
824
htmlDependency.scripts == nil and
825
htmlDependency.stylesheets == nil and
826
htmlDependency.resources == nil and
827
htmlDependency.serviceworkers == nil and
828
htmlDependency.head == nil then
829
error("HTML dependencies must include at least one of meta, links, scripts, stylesheets, serviceworkers, or resources. All appear empty.")
830
end
831
832
-- validate that the meta is as expected
833
if htmlDependency.meta ~= nil then
834
if type(htmlDependency.meta) ~= 'table' then
835
error("Invalid HTML Dependency: meta value must be a table")
836
elseif utils.table.isarray(htmlDependency.meta) then
837
error("Invalid HTML Dependency: meta value must must not be an array")
838
end
839
end
840
841
-- validate link tags
842
if htmlDependency.links ~= nil then
843
if type(htmlDependency.links) ~= 'table' or not utils.table.isarray(htmlDependency.links) then
844
error("Invalid HTML Dependency: links must be an array")
845
else
846
for i, v in ipairs(htmlDependency.links) do
847
if type(v) ~= "table" or (v.href == nil or v.rel == nil) then
848
error("Invalid HTML Dependency: each link must be a table containing both rel and href properties.")
849
end
850
end
851
end
852
end
853
854
-- resolve names so they aren't required
855
htmlDependency.scripts = resolveFileDependencies("scripts", htmlDependency.scripts)
856
htmlDependency.stylesheets = resolveFileDependencies("stylesheets", htmlDependency.stylesheets)
857
htmlDependency.resources = resolveFileDependencies("resources", htmlDependency.resources)
858
859
-- pass the dependency through to the file
860
writeToDependencyFile(dependency("html", {
861
name = htmlDependency.name,
862
version = htmlDependency.version,
863
external = true,
864
meta = htmlDependency.meta,
865
links = resolveDependencyLinkTags(htmlDependency.links),
866
scripts = resolveDependencyFilePaths(htmlDependency.scripts),
867
stylesheets = resolveDependencyFilePaths(htmlDependency.stylesheets),
868
resources = resolveDependencyFilePaths(htmlDependency.resources),
869
serviceworkers = resolveServiceWorkers(htmlDependency.serviceworkers),
870
head = htmlDependency.head,
871
}))
872
end,
873
874
attach_to_dependency = function(name, pathOrFileObj)
875
876
if name == nil then
877
fail("The target dependency name for an attachment cannot be nil. Please provide a valid dependency name.")
878
end
879
880
-- path can be a string or an obj { name, path }
881
local resolvedFile = {}
882
if type(pathOrFileObj) == "table" then
883
884
-- validate that there is at least a path
885
if pathOrFileObj.path == nil then
886
fail("Error attaching to dependency '" .. name .. "'.\nYou must provide a 'path' when adding an attachment to a dependency.")
887
end
888
889
-- resolve a name, if one isn't provided
890
local name = pathOrFileObj.name
891
if name == nil then
892
name = pandoc.path.filename(pathOrFileObj.path)
893
end
894
895
-- the full resolved file
896
resolvedFile = {
897
name = name,
898
path = resolvePathExt(pathOrFileObj.path)
899
}
900
else
901
resolvedFile = {
902
name = pandoc.path.filename(pathOrFileObj),
903
path = resolvePathExt(pathOrFileObj)
904
}
905
end
906
907
writeToDependencyFile(dependency("html-attachment", {
908
name = name,
909
file = resolvedFile
910
}))
911
end,
912
913
use_latex_package = function(package, options)
914
writeToDependencyFile(dependency("usepackage", {package = package, options = options }))
915
end,
916
917
add_format_resource = function(path)
918
writeToDependencyFile(dependency("format-resources", { file = resolvePathExt(path)}))
919
end,
920
921
add_resource = function(path)
922
writeToDependencyFile(dependency("resources", { file = resolvePathExt(path)}))
923
end,
924
925
add_supporting = function(path)
926
writeToDependencyFile(dependency("supporting", { file = resolvePathExt(path)}))
927
end,
928
929
include_text = function(location, text)
930
writeToDependencyFile(dependency("text", { text = text, location = resolveLocation(location)}))
931
end,
932
933
include_file = function(location, path)
934
writeToDependencyFile(dependency("file", { path = resolvePathExt(path), location = resolveLocation(location)}))
935
end,
936
937
is_format = format.isFormat,
938
939
cite_method = function()
940
local citeMethod = param('cite-method', nil)
941
return citeMethod
942
end,
943
pdf_engine = function()
944
local engine = param('pdf-engine', 'pdflatex')
945
return engine
946
end,
947
has_bootstrap = function()
948
local hasBootstrap = param('has-bootstrap', false)
949
return hasBootstrap
950
end,
951
is_filter_active = function(filter)
952
return quarto_global_state.active_filters[filter]
953
end,
954
955
output_file = outputFile(),
956
input_file = inputFile(),
957
crossref = {},
958
language = param("language", nil)
959
},
960
project = {
961
directory = projectDirectory(),
962
offset = projectOffset(),
963
profile = pandoc.List(projectProfiles()),
964
output_directory = projectOutputDirectory()
965
},
966
utils = {
967
dump = utils.dump,
968
table = utils.table,
969
type = utils.type,
970
resolve_path = resolvePathExt,
971
resolve_path_relative_to_document = resolvePath,
972
as_inlines = utils.as_inlines,
973
as_blocks = utils.as_blocks,
974
is_empty_node = utils.is_empty_node,
975
string_to_blocks = utils.string_to_blocks,
976
string_to_inlines = utils.string_to_inlines,
977
render = utils.render,
978
match = utils.match,
979
add_to_blocks = utils.add_to_blocks,
980
},
981
paths = {
982
-- matches the path from `quartoEnvironmentParams` from src/command/render/filters.ts
983
rscript = function()
984
return param('quarto-environment', nil).paths.Rscript
985
end,
986
tinytex_bin_dir = function()
987
return param('quarto-environment', nil).paths.TinyTexBinDir
988
end,
989
typst = function()
990
return param('quarto-environment', nil).paths.Typst
991
end,
992
},
993
json = json,
994
base64 = base64,
995
log = logging,
996
version = version(),
997
-- map to quartoConfig information on TS side
998
config = {
999
cli_path = function() return param('quarto-cli-path', nil) end,
1000
version = function() return version() end
1001
},
1002
shortcode = {
1003
read_arg = function (args, n)
1004
local arg = args[n or 1]
1005
local varName
1006
if arg == nil then
1007
return nil
1008
end
1009
if type(arg) ~= "string" then
1010
varName = inlinesToString(arg)
1011
else
1012
varName = arg --[[@as string]]
1013
end
1014
return varName
1015
end,
1016
error_output = function (shortcode, message_or_args, context)
1017
if type(message_or_args) == "table" then
1018
message_or_args = table.concat(message_or_args, " ")
1019
end
1020
local message = "?" .. shortcode .. ":" .. message_or_args
1021
if context == "block" then
1022
return pandoc.Blocks { pandoc.Strong( pandoc.Inlines { pandoc.Str(message) } ) }
1023
elseif context == "inline" then
1024
return pandoc.Inlines { pandoc.Strong( pandoc.Inlines { pandoc.Str(message) } ) }
1025
elseif context == "text" then
1026
return message
1027
else
1028
warn("Unknown context for " .. shortcode .. " shortcode error: " .. context)
1029
return { }
1030
end
1031
end,
1032
},
1033
metadata = {
1034
get = function(key)
1035
return option(key, nil)
1036
end
1037
},
1038
variables = {
1039
get = function(name)
1040
local value = var(name, nil)
1041
if value then
1042
value = pandoc.utils.stringify(value)
1043
end
1044
return value
1045
end
1046
}
1047
}
1048
1049
-- alias old names for backwards compatibility
1050
quarto.doc.addHtmlDependency = quarto.doc.add_html_dependency
1051
quarto.doc.attachToDependency = quarto.doc.attach_to_dependency
1052
quarto.doc.useLatexPackage = quarto.doc.use_latex_package
1053
quarto.doc.addFormatResource = quarto.doc.add_format_resource
1054
quarto.doc.includeText = quarto.doc.include_text
1055
quarto.doc.includeFile = quarto.doc.include_file
1056
quarto.doc.isFormat = quarto.doc.is_format
1057
quarto.doc.citeMethod = quarto.doc.cite_method
1058
quarto.doc.pdfEngine = quarto.doc.pdf_engine
1059
quarto.doc.hasBootstrap = quarto.doc.has_bootstrap
1060
quarto.doc.project_output_file = projectRelativeOutputFile
1061
quarto.utils.resolvePath = quarto.utils.resolve_path
1062
1063
-- since Pandoc 3, pandoc.Null is no longer an allowed constructor.
1064
-- this workaround makes it so that our users extensions which use pandoc.Null
1065
-- still work, assuming they call pandoc.Null() in a "simple" way.
1066
pandoc.Null = function()
1067
return {}
1068
end
1069
1070