18
18
-- Store metadata at module level
19
19
--- @type DocumentMetadata
20
20
local document_metadata = {
21
- panelize = {},
22
- handler_added = false
21
+ panelize = {},
22
+ handler_added = false
23
23
}
24
24
25
25
-- Helper function to detect language from a code block
26
26
--- @param block Block The code block to analyze
27
27
--- @return string | nil language The detected language
28
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
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
39
end
40
40
41
41
-- Helper function to clean code block text
42
42
--- @param block Block The code block to clean
43
43
--- @param language string The programming language
44
+ --- @param remove_fences boolean Whether to remove code fences and options
44
45
--- @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 " , " " )
46
+ local function clean_code_text (block , language , remove_fences )
47
+ local text = block .text
48
+ if remove_fences then
49
+ if text :match (" ^```{{" .. language .. " }" ) then
50
+ text = text :gsub (" ```{{" .. language .. " }}\n " , " " ):gsub (" \n ```" , " " )
51
+ end
52
+ text = text :gsub (" #|.-\n " , " " )
53
+ end
54
+ return text
51
55
end
52
56
53
57
-- Helper function to extract cell content
54
58
--- @param cell_div Block The cell div block
55
59
--- @return Cell cell The processed cell content
56
60
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
78
- end
79
-
80
- return cell
61
+ local cell = {
62
+ blocks = {}, -- Will store alternating code blocks and their outputs
63
+ language = nil
64
+ }
65
+
66
+ -- Process blocks in sequence
67
+ for _ , block in ipairs (cell_div .content ) do
68
+ if block .t == " CodeBlock" and block .classes :includes (" cell-code" ) then
69
+ table.insert (cell .blocks , {type = " code" , content = block })
70
+ -- Detect language from first code block if not already set
71
+ if not cell .language then
72
+ cell .language = detect_language (block )
73
+ end
74
+ elseif block .t == " Div" and (
75
+ block .classes :includes (" cell-output" ) or
76
+ block .classes :includes (" cell-output-stdout" ) or
77
+ block .classes :includes (" cell-output-display" )
78
+ ) then
79
+ table.insert (cell .blocks , {type = " output" , content = block })
80
+ end
81
+ end
82
+
83
+ return cell
81
84
end
82
85
83
86
-- Helper function to create tab content
84
87
--- @param cell Cell The cell content
85
- --- @param interactive boolean Whether this is an interactive tab
88
+ --- @param tab_type string The type of tab ( " result " , " source " , or " interactive " )
86
89
--- @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
90
+ local function create_tab_content (cell , tab_type )
91
+ local content = pandoc .List ()
92
+
93
+ if tab_type == " interactive" then
94
+ -- For interactive tab, combine all code blocks into one
95
+ local combined_code = table.concat (
96
+ pandoc .List (cell .blocks )
97
+ :filter (function (block ) return block .type == " code" end )
98
+ :map (function (block ) return clean_code_text (block .content , cell .language , true ) end ),
99
+ " \n "
100
+ )
101
+
102
+ -- Create single code block with appropriate classes
103
+ local classes = cell .language == " r" and {" {webr-r}" , " cell-code" } or {" {pyodide-python}" , " cell-code" }
104
+ local attr = pandoc .Attr (" " , classes , {})
105
+ content :insert (pandoc .CodeBlock (combined_code , attr ))
106
+ else
107
+ -- For result and source tabs, process blocks in sequence
108
+ for _ , block in ipairs (cell .blocks ) do
109
+ if block .type == " code" then
110
+ if tab_type == " result" then
111
+ -- For result tab, clean code but keep language class
112
+ local new_attr = block .content .attr :clone ()
113
+ new_attr .classes = pandoc .List ({cell .language })
114
+ local cleaned_text = clean_code_text (block .content , cell .language , true )
115
+ content :insert (pandoc .CodeBlock (cleaned_text , new_attr ))
116
+ else
117
+ -- For source tab, use original code block
118
+ content :insert (block .content )
119
+ end
120
+ else -- output block
121
+ content :insert (block .content )
122
+ end
123
+ end
124
+ end
125
+
126
+ return content
115
127
end
116
128
117
129
-- Process metadata
118
130
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
131
+ if meta and meta .panelize then
132
+ for key , value in pairs (meta .panelize ) do
133
+ document_metadata .panelize [key ] = pandoc .utils .stringify (value )
134
+ end
135
+ end
136
+ return meta
125
137
end
126
138
127
139
-- Main processing function for divs
128
140
function Div (div )
129
- -- Check for required classes
130
- local to_webr = div .classes :includes (" to-webr" )
131
- local to_pyodide = div .classes :includes (" to-pyodide" )
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
136
- end
137
-
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
144
- end
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
-
162
- if to_webr or to_pyodide then
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
- }))
183
- end
184
-
185
- -- Return just the tabset, replacing the original div
186
- return quarto .Tabset ({
187
- level = 3 ,
188
- tabs = tabs ,
189
- attr = pandoc .Attr (" " , {" panel-tabset" }, {})
190
- })
141
+ -- Check for required classes
142
+ local to_webr = div .classes :includes (" to-webr" )
143
+ local to_pyodide = div .classes :includes (" to-pyodide" )
144
+ local to_source = div .classes :includes (" to-source" )
145
+
146
+ if not (to_source or to_webr or to_pyodide ) then
147
+ return div
148
+ end
149
+
150
+ -- Find cell div
151
+ local cell_div = nil
152
+ for _ , block in ipairs (div .content ) do
153
+ if block .t == " Div" and block .classes :includes (" cell" ) then
154
+ cell_div = block
155
+ break
156
+ end
157
+ end
158
+
159
+ if not cell_div then
160
+ return div
161
+ end
162
+
163
+ -- Extract cell content
164
+ local cell = extract_cell_content (cell_div )
165
+
166
+ if not cell .language then
167
+ quarto .log .error (" Please specify either R or Python code cells inside of the .to-* div." )
168
+ return div
169
+ end
170
+
171
+ -- Create tabs
172
+ local tabs = pandoc .List ()
173
+
174
+ if to_webr or to_pyodide then
175
+ -- Interactive environment tabs
176
+ tabs :insert (quarto .Tab ({
177
+ title = " Result" ,
178
+ content = pandoc .Blocks (create_tab_content (cell , " result" ))
179
+ }))
180
+ tabs :insert (quarto .Tab ({
181
+ title = " Interactive" ,
182
+ content = pandoc .Blocks (create_tab_content (cell , " interactive" ))
183
+ }))
184
+ else
185
+ -- Source code tabs
186
+ tabs :insert (quarto .Tab ({
187
+ title = " Result" ,
188
+ content = pandoc .Blocks (create_tab_content (cell , " result" ))
189
+ }))
190
+ tabs :insert (quarto .Tab ({
191
+ title = " Source" ,
192
+ content = pandoc .Blocks (create_tab_content (cell , " source" ))
193
+ }))
194
+ end
195
+
196
+ -- Return just the tabset, replacing the original div
197
+ return quarto .Tabset ({
198
+ level = 3 ,
199
+ tabs = tabs ,
200
+ attr = pandoc .Attr (" " , {" panel-tabset" }, {})
201
+ })
191
202
end
192
203
193
204
-- Return the list of functions to register
194
205
return {
195
- {Meta = Meta },
196
- {Div = Div }
206
+ {Meta = Meta },
207
+ {Div = Div }
197
208
}
0 commit comments