1
- -- Function to process code blocks
2
- local function clean_code_block (el , language )
3
-
4
- -- Check if the code block contains R code
5
- if el .text :match (" ^```{{" .. language ) then
6
- -- Remove the ```{{<language>}} and ``` lines
7
- local cleaned_text = el .text :gsub (" ```{{" .. language .. " }}\n " , " " ):gsub (" \n ```" , " " )
8
-
9
- -- Remove lines starting with #| (options)
10
- cleaned_text = cleaned_text :gsub (" #|.-\n " , " " )
1
+ -- Define helper types for clarity
2
+ --- @class Block
3
+ --- @field t string The type of the block
4
+ --- @field attr table Block attributes
5
+ --- @field content ? table Block content
6
+ --- @field classes table List of classes
7
+ --- @field text ? string Block text if CodeBlock
8
+
9
+ --- @class Cell
10
+ --- @field code_blocks Block[] List of code blocks
11
+ --- @field outputs Block[] List of output blocks
12
+ --- @field language string Programming language
13
+
14
+ --- @class DocumentMetadata
15
+ --- @field panelize table<string , any> Configuration options
16
+ --- @field handler_added boolean Flag for handler addition
17
+
18
+ -- Store metadata at module level
19
+ --- @type DocumentMetadata
20
+ local document_metadata = {
21
+ panelize = {},
22
+ handler_added = false
23
+ }
24
+
25
+ -- Helper function to detect language from a code block
26
+ --- @param block Block The code block to analyze
27
+ --- @return string | nil language The detected language
28
+ local function detect_language (block )
29
+ if block .attr .classes :includes (" r" ) then
30
+ return " r"
31
+ elseif block .attr .classes :includes (" python" ) then
32
+ return " python"
33
+ elseif block .text :match (" ^```{{r" ) then
34
+ return " r"
35
+ elseif block .text :match (" ^```{{python" ) then
36
+ return " python"
37
+ end
38
+ return nil
39
+ end
11
40
12
- -- Add 'language' to the class list if not already present
13
- if not el .attr .classes :includes (language ) then
14
- table.insert (el .attr .classes , 1 , language )
15
- end
41
+ -- Helper function to clean code block text
42
+ --- @param block Block The code block to clean
43
+ --- @param language string The programming language
44
+ --- @return string cleaned_text The processed text
45
+ local function clean_code_text (block , language )
46
+ local text = block .text
47
+ if text :match (" ^```{{" .. language .. " }" ) then
48
+ text = text :gsub (" ```{{" .. language .. " }}\n " , " " ):gsub (" \n ```" , " " )
49
+ end
50
+ return text :gsub (" #|.-\n " , " " )
51
+ end
16
52
17
- -- Return the modified code block
18
- return pandoc .CodeBlock (cleaned_text , el .attr )
53
+ -- Helper function to extract cell content
54
+ --- @param cell_div Block The cell div block
55
+ --- @return Cell cell The processed cell content
56
+ local function extract_cell_content (cell_div )
57
+ local cell = {
58
+ code_blocks = {},
59
+ outputs = {},
60
+ language = nil
61
+ }
62
+
63
+ -- Process blocks in order to maintain sequence
64
+ for _ , block in ipairs (cell_div .content ) do
65
+ if block .t == " CodeBlock" and block .classes :includes (" cell-code" ) then
66
+ table.insert (cell .code_blocks , block )
67
+ -- Detect language from first code block if not already set
68
+ if not cell .language then
69
+ cell .language = detect_language (block )
70
+ end
71
+ elseif block .t == " Div" and (
72
+ block .classes :includes (" cell-output" ) or
73
+ block .classes :includes (" cell-output-stdout" ) or
74
+ block .classes :includes (" cell-output-display" )
75
+ ) then
76
+ table.insert (cell .outputs , block )
77
+ end
19
78
end
79
+
80
+ return cell
81
+ end
20
82
21
- -- If not an R code block, return unchanged
22
- return el
83
+ -- Helper function to create tab content
84
+ --- @param cell Cell The cell content
85
+ --- @param interactive boolean Whether this is an interactive tab
86
+ --- @return pandoc.List content The tab content
87
+ local function create_tab_content (cell , interactive )
88
+ local content = pandoc .List ()
89
+
90
+ if interactive then
91
+ -- For interactive tab, combine all code blocks into one
92
+ local combined_code = table.concat (
93
+ pandoc .List (cell .code_blocks ):map (function (block )
94
+ return clean_code_text (block , cell .language )
95
+ end ),
96
+ " \n "
97
+ )
98
+
99
+ -- Create single code block with appropriate classes
100
+ local classes = cell .language == " r" and {" {webr-r}" , " cell-code" } or {" {pyodide-python}" , " cell-code" }
101
+ local attr = pandoc .Attr (" " , classes , {})
102
+ content :insert (pandoc .CodeBlock (combined_code , attr ))
103
+ else
104
+ -- For result tab, keep original structure
105
+ for i , code_block in ipairs (cell .code_blocks ) do
106
+ content :insert (code_block )
107
+ -- Add corresponding output if it exists
108
+ if cell .outputs [i ] then
109
+ content :insert (cell .outputs [i ])
110
+ end
111
+ end
112
+ end
113
+
114
+ return content
23
115
end
24
116
25
- -- Helper function to clone and update code block attributes
26
- local function clone_and_update_code_block (code_block , new_classes )
27
- local new_attr = code_block .attr :clone ()
28
- new_attr .classes = pandoc .List (new_classes )
29
- return pandoc .CodeBlock (code_block .text , new_attr )
117
+ -- Process metadata
118
+ function Meta (meta )
119
+ if meta and meta .panelize then
120
+ for key , value in pairs (meta .panelize ) do
121
+ document_metadata .panelize [key ] = pandoc .utils .stringify (value )
122
+ end
123
+ end
124
+ return meta
30
125
end
31
126
127
+ -- Main processing function for divs
32
128
function Div (div )
129
+ -- Check for required classes
33
130
local to_webr = div .classes :includes (" to-webr" )
34
131
local to_pyodide = div .classes :includes (" to-pyodide" )
35
-
36
- -- Check if the `div` has the class "to-source"/"to-webr"/"to-pyodide"
37
- if not (div . classes : includes ( " to-source " ) or to_webr or to_pyodide ) then
38
- return
132
+ local to_source = div . classes : includes ( " to-source " )
133
+
134
+ if not (to_source or to_webr or to_pyodide ) then
135
+ return div
39
136
end
40
137
41
- -- Initialize local variables for code block, cell output, and language
42
- local code_block = nil
43
- local cell_output = nil
44
- local language = nil
45
-
46
- -- Walk through the content of the `div` to find `CodeBlock` and `Div` elements
47
- div :walk ({
48
- CodeBlock = function (code )
49
- -- If a `CodeBlock` with the class "cell-code" is found, assign it to `code_block`
50
- if code .classes :includes (" cell-code" ) then
51
- code_block = code
52
- -- Determine the language of the code block
53
- if code .classes :includes (" r" ) or code .text :match (" ^```{{r" ) then
54
- language = " r"
55
- elseif code .classes :includes (" python" ) or code .text :match (" ^```{{python" ) then
56
- language = " python"
57
- else
58
- quarto .log .error (" Please only specify either R or Python code cells inside of the `.to-*` div." )
59
- end
60
- end
61
- end ,
62
- Div = function (div )
63
- -- If a `Div` with the class "cell-output" is found, assign it to `cell_output`
64
- if div .classes :includes (" cell-output" ) then
65
- cell_output = div
138
+ -- Find cell div
139
+ local cell_div = nil
140
+ for _ , block in ipairs (div .content ) do
141
+ if block .t == " Div" and block .classes :includes (" cell" ) then
142
+ cell_div = block
143
+ break
66
144
end
67
- end
68
- })
69
-
70
- local cleaned_code_cell = clean_code_block (code_block , language )
71
-
72
- -- Determine the type of Tab to use
73
- local tabs = nil
74
-
75
- -- Check if the language matches the required condition
145
+ end
146
+
147
+ if not cell_div then
148
+ return div
149
+ end
150
+
151
+ -- Extract cell content
152
+ local cell = extract_cell_content (cell_div )
153
+
154
+ if not cell .language then
155
+ quarto .log .error (" Please specify either R or Python code cells inside of the .to-* div." )
156
+ return div
157
+ end
158
+
159
+ -- Create tabs
160
+ local tabs = pandoc .List ()
161
+
76
162
if to_webr or to_pyodide then
77
- -- Create a tab for the Result
78
- local result_tab = quarto .Tab ({ title = " Result " , content = pandoc . List ({ code_block , cell_output }) })
79
-
80
- -- Pick attribute classes
81
- local code_block_attr_classes = to_webr and { " {webr-r} " , " cell-code " } or { " {pyodide-python} " , " cell-code " }
82
-
83
- -- Create a tab for the Source
84
- local interactive_tab = quarto . Tab ({ title = " Interactive " , content = clone_and_update_code_block ( code_block , code_block_attr_classes ) } )
85
-
86
- -- Combine the tabs into a list
87
- tabs = pandoc . List ({ result_tab , interactive_tab })
88
- else
89
- -- Create a tab for the Rendered
90
- local rendered_tab = quarto . Tab ({ title = " Result " , content = pandoc .List ({ cleaned_code_cell , cell_output }) } )
91
-
92
- -- Create a tab for the Source
93
- local source_tab = quarto .Tab ({ title = " Source " , content = clone_and_update_code_block ( code_block , { " md " , " cell-code " }) })
94
-
95
- -- Combine the tabs into a list
96
- tabs = pandoc . List ({ rendered_tab , source_tab } )
163
+ -- Interactive environment tabs
164
+ tabs : insert ( quarto .Tab ({
165
+ title = " Result " ,
166
+ content = pandoc . Blocks ( create_tab_content ( cell , false ))
167
+ }))
168
+ tabs : insert ( quarto . Tab ({
169
+ title = " Interactive " ,
170
+ content = pandoc . Blocks ( create_tab_content ( cell , true ) )
171
+ }))
172
+ else
173
+ -- Source code tabs
174
+ tabs : insert ( quarto . Tab ({
175
+ title = " Result " ,
176
+ content = pandoc .Blocks ( create_tab_content ( cell , false ) )
177
+ }))
178
+ -- For source tab, show original code without execution
179
+ tabs : insert ( quarto .Tab ({
180
+ title = " Source " ,
181
+ content = pandoc . Blocks ( pandoc . List ( cell . code_blocks ))
182
+ }) )
97
183
end
98
-
99
- -- Return a `quarto.Tabset` with the created tabs and specific attributes
184
+
185
+ -- Return just the tabset, replacing the original div
100
186
return quarto .Tabset ({
101
- level = 3 ,
102
- tabs = tabs ,
103
- attr = pandoc .Attr (" " , {" panel-tabset" }) -- This attribute assignment shouldn't be necessary but addresses a known issue. Remove when using Quarto 1.5 or greater as required version.
187
+ level = 3 ,
188
+ tabs = tabs ,
189
+ attr = pandoc .Attr (" " , {" panel-tabset" }, {})
104
190
})
105
- end
191
+ end
192
+
193
+ -- Return the list of functions to register
194
+ return {
195
+ {Meta = Meta },
196
+ {Div = Div }
197
+ }
0 commit comments