Skip to content

Commit 01099df

Browse files
committed
Bump and release v0.0.2
1 parent 434e793 commit 01099df

File tree

4 files changed

+246
-88
lines changed

4 files changed

+246
-88
lines changed

_extensions/panelize/_extension.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: panelize
22
title: Panelize code cells
33
author: James Joseph Balamuta
4-
version: 0.0.1-dev.1
4+
version: 0.0.2
55
quarto-required: ">=1.4.554"
66
contributes:
77
filters:

_extensions/panelize/panelize.lua

+179-87
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,197 @@
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
1140

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
1652

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
1978
end
79+
80+
return cell
81+
end
2082

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
23115
end
24116

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
30125
end
31126

127+
-- Main processing function for divs
32128
function Div(div)
129+
-- Check for required classes
33130
local to_webr = div.classes:includes("to-webr")
34131
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
39136
end
40137

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
66144
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+
76162
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+
}))
97183
end
98-
99-
-- Return a `quarto.Tabset` with the created tabs and specific attributes
184+
185+
-- Return just the tabset, replacing the original div
100186
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"}, {})
104190
})
105-
end
191+
end
192+
193+
-- Return the list of functions to register
194+
return {
195+
{Meta = Meta},
196+
{Div = Div}
197+
}

docs/qpanelize-pyodide.qmd

+37
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,40 @@ print(x)
7373
```
7474
:::
7575

76+
### Graphs
77+
78+
We can also use the same approach for plotting graphs, e.g.
79+
80+
:::{.to-pyodide}
81+
```{python}
82+
import matplotlib.pyplot as plt
83+
84+
x = [1, 2, 3, 4, 5]
85+
y = [1, 4, 9, 16, 25]
86+
87+
plt.plot(x, y)
88+
plt.show()
89+
```
90+
:::
91+
92+
### Multiline
93+
94+
We can also use the same approach for multiline code cells, e.g.
95+
96+
:::{.to-pyodide}
97+
```{python}
98+
x = list(range(1, 11))
99+
x
100+
101+
y = [i**2 for i in x]
102+
y
103+
104+
import matplotlib.pyplot as plt
105+
106+
x = [1, 2, 3, 4, 5]
107+
y = [1, 4, 9, 16, 25]
108+
109+
plt.plot(x, y)
110+
plt.show()
111+
```
112+
:::

docs/qpanelize-webr.qmd

+29
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ As a result, we now have access to two tabs: Result and Interactive.
7070
```
7171
:::
7272

73+
The `Result` tab shows the output of the code cell, while the `Interactive` tab allows us to interact with the code cell.
74+
75+
### Graphs
76+
77+
We can also use the same approach for plotting graphs, e.g.
78+
79+
:::{.to-webr}
80+
```{r}
81+
plot(1:10)
82+
```
83+
:::
84+
85+
### Multiline
86+
87+
We can also use the same approach for multiline code cells, e.g.
88+
89+
:::{.to-webr}
90+
```{r}
91+
x <- 1:10
92+
x
93+
94+
y <- x^2
95+
y
96+
97+
plot(x, y)
98+
```
99+
:::
100+
101+
73102
## Autorun Code
74103

75104
You may wish to allow the interactive cells to be automatically run when the document opens by specifying in the document header:

0 commit comments

Comments
 (0)