Skip to content

Commit

Permalink
Update quarto extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
coatless committed Jan 22, 2025
1 parent 0bb679f commit 7b6602b
Show file tree
Hide file tree
Showing 13 changed files with 902 additions and 178 deletions.
4 changes: 2 additions & 2 deletions altdoc/_extensions/coatless-quarto/panelize/_extension.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: panelize
title: Panelize code cells
author: Carlos Scheidegger and James Joseph Balamuta
version: 0.0.0-dev.1
author: James Joseph Balamuta
version: 0.0.2
quarto-required: ">=1.4.554"
contributes:
filters:
Expand Down
287 changes: 195 additions & 92 deletions altdoc/_extensions/coatless-quarto/panelize/panelize.lua
Original file line number Diff line number Diff line change
@@ -1,105 +1,208 @@
-- Function to process code blocks
local function clean_code_block(el, language)

-- Check if the code block contains R code
if el.text:match("^```{{"..language) then
-- Remove the ```{{<language>}} and ``` lines
local cleaned_text = el.text:gsub("```{{".. language .."}}\n", ""):gsub("\n```", "")

-- Remove lines starting with #| (options)
cleaned_text = cleaned_text:gsub("#|.-\n", "")
-- Define helper types for clarity
---@class Block
---@field t string The type of the block
---@field attr table Block attributes
---@field content? table Block content
---@field classes table List of classes
---@field text? string Block text if CodeBlock

---@class Cell
---@field code_blocks Block[] List of code blocks
---@field outputs Block[] List of output blocks
---@field language string Programming language

---@class DocumentMetadata
---@field panelize table<string, any> Configuration options
---@field handler_added boolean Flag for handler addition

-- Store metadata at module level
---@type DocumentMetadata
local document_metadata = {
panelize = {},
handler_added = false
}

-- Helper function to detect language from a code block
---@param block Block The code block to analyze
---@return string|nil language The detected language
local function detect_language(block)
if block.attr.classes:includes("r") then
return "r"
elseif block.attr.classes:includes("python") then
return "python"
elseif block.text:match("^```{{r") then
return "r"
elseif block.text:match("^```{{python") then
return "python"
end
return nil
end

-- Add 'language' to the class list if not already present
if not el.attr.classes:includes(language) then
table.insert(el.attr.classes, 1, language)
-- Helper function to clean code block text
---@param block Block The code block to clean
---@param language string The programming language
---@param remove_fences boolean Whether to remove code fences and options
---@return string cleaned_text The processed text
local function clean_code_text(block, language, remove_fences)
local text = block.text
if remove_fences then
if text:match("^```{{" .. language .. "}") then
text = text:gsub("```{{" .. language .. "}}\n", ""):gsub("\n```", "")
end
text = text:gsub("#|.-\n", "")
end
return text
end

-- Return the modified code block
return pandoc.CodeBlock(cleaned_text, el.attr)
end
-- Helper function to extract cell content
---@param cell_div Block The cell div block
---@return Cell cell The processed cell content
local function extract_cell_content(cell_div)
local cell = {
blocks = {}, -- Will store alternating code blocks and their outputs
language = nil
}

-- Process blocks in sequence
for _, block in ipairs(cell_div.content) do
if block.t == "CodeBlock" and block.classes:includes("cell-code") then
table.insert(cell.blocks, {type = "code", content = block})
-- Detect language from first code block if not already set
if not cell.language then
cell.language = detect_language(block)
end
elseif block.t == "Div" and (
block.classes:includes("cell-output") or
block.classes:includes("cell-output-stdout") or
block.classes:includes("cell-output-display")
) then
table.insert(cell.blocks, {type = "output", content = block})
end
end

return cell
end

-- If not an R code block, return unchanged
return el
-- Helper function to create tab content
---@param cell Cell The cell content
---@param tab_type string The type of tab ("result", "source", or "interactive")
---@return pandoc.List content The tab content
local function create_tab_content(cell, tab_type)
local content = pandoc.List()

if tab_type == "interactive" then
-- For interactive tab, combine all code blocks into one
local combined_code = table.concat(
pandoc.List(cell.blocks)
:filter(function(block) return block.type == "code" end)
:map(function(block) return clean_code_text(block.content, cell.language, true) end),
"\n"
)

-- Create single code block with appropriate classes
local classes = cell.language == "r" and {"{webr-r}", "cell-code"} or {"{pyodide-python}", "cell-code"}
local attr = pandoc.Attr("", classes, {})
content:insert(pandoc.CodeBlock(combined_code, attr))
else
-- For result and source tabs, process blocks in sequence
for _, block in ipairs(cell.blocks) do
if block.type == "code" then
if tab_type == "result" then
-- For result tab, clean code but keep language class
local new_attr = block.content.attr:clone()
new_attr.classes = pandoc.List({cell.language})
local cleaned_text = clean_code_text(block.content, cell.language, true)
content:insert(pandoc.CodeBlock(cleaned_text, new_attr))
else
-- For source tab, use original code block
content:insert(block.content)
end
else -- output block
content:insert(block.content)
end
end
end

return content
end

-- Helper function to clone and update code block attributes
local function clone_and_update_code_block(code_block, new_classes)
local new_attr = code_block.attr:clone()
new_attr.classes = pandoc.List(new_classes)
return pandoc.CodeBlock(code_block.text, new_attr)
-- Process metadata
function Meta(meta)
if meta and meta.panelize then
for key, value in pairs(meta.panelize) do
document_metadata.panelize[key] = pandoc.utils.stringify(value)
end
end
return meta
end

-- Main processing function for divs
function Div(div)
local to_webr = div.classes:includes("to-webr")
local to_pyodide = div.classes:includes("to-pyodide")

-- Check if the `div` has the class "to-source"/"to-webr"/"to-pyodide"
if not (div.classes:includes("to-source") or to_webr or to_pyodide) then
return
end

-- Initialize local variables for code block, cell output, and language
local code_block = nil
local cell_output = nil
local language = nil

-- Walk through the content of the `div` to find `CodeBlock` and `Div` elements
div:walk({
CodeBlock = function(code)
-- If a `CodeBlock` with the class "cell-code" is found, assign it to `code_block`
if code.classes:includes("cell-code") then
code_block = code
-- Determine the language of the code block
if code.classes:includes("r") or code.text:match("^```{{r") then
language = "r"
elseif code.classes:includes("python") or code.text:match("^```{{python") then
language = "python"
else
quarto.log.error("Please only specify either R or Python code cells inside of the `to-panel` div.")
-- Check for required classes
local to_webr = div.classes:includes("to-webr")
local to_pyodide = div.classes:includes("to-pyodide")
local to_source = div.classes:includes("to-source")

if not (to_source or to_webr or to_pyodide) then
return div
end

-- Find cell div
local cell_div = nil
for _, block in ipairs(div.content) do
if block.t == "Div" and block.classes:includes("cell") then
cell_div = block
break
end
end
end,
Div = function(div)
-- If a `Div` with the class "cell-output" is found, assign it to `cell_output`
if div.classes:includes("cell-output") then
cell_output = div
end
end
})

local cleaned_code_cell = clean_code_block(code_block, language)

-- Determine the type of Tab to use
local tabs = nil

-- Check if the language matches the required condition
if to_webr or to_pyodide then
-- Create a tab for the Result
local result_tab = quarto.Tab({ title = "Result", content = pandoc.List({code_block, cell_output}) })

-- Pick attribute classes
local code_block_attr_classes = to_webr and {"{webr-r}", "cell-code"} or {"{pyodide-python}", "cell-code"}

-- Create a tab for the Source
local interactive_tab = quarto.Tab({ title = "Interactive", content = clone_and_update_code_block(code_block, code_block_attr_classes) })

-- Combine the tabs into a list
tabs = pandoc.List({ result_tab, interactive_tab })
else
-- Create a tab for the Rendered
local rendered_tab = quarto.Tab({ title = "Result", content = pandoc.List({cleaned_code_cell, cell_output}) })

-- Create a tab for the Source
local source_tab = quarto.Tab({ title = "Source", content = clone_and_update_code_block(code_block, {"md", "cell-code"}) })

-- Combine the tabs into a list
tabs = pandoc.List({ rendered_tab, source_tab })
end
if not cell_div then
return div
end

-- Extract cell content
local cell = extract_cell_content(cell_div)

if not cell.language then
quarto.log.error("Please specify either R or Python code cells inside of the .to-* div.")
return div
end

-- Create tabs
local tabs = pandoc.List()

if to_webr or to_pyodide then
-- Interactive environment tabs
tabs:insert(quarto.Tab({
title = "Result",
content = pandoc.Blocks(create_tab_content(cell, "result"))
}))
tabs:insert(quarto.Tab({
title = "Interactive",
content = pandoc.Blocks(create_tab_content(cell, "interactive"))
}))
else
-- Source code tabs
tabs:insert(quarto.Tab({
title = "Result",
content = pandoc.Blocks(create_tab_content(cell, "result"))
}))
tabs:insert(quarto.Tab({
title = "Source",
content = pandoc.Blocks(create_tab_content(cell, "source"))
}))
end

-- Return just the tabset, replacing the original div
return quarto.Tabset({
level = 3,
tabs = tabs,
attr = pandoc.Attr("", {"panel-tabset"}, {})
})
end

-- Return a `quarto.Tabset` with the created tabs and specific attributes
return quarto.Tabset({
level = 3,
tabs = tabs,
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.
})
end
-- Return the list of functions to register
return {
{Meta = Meta},
{Div = Div}
}
2 changes: 1 addition & 1 deletion altdoc/_extensions/coatless/webr/_extension.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: webr
title: Embedded webr code cells
author: James Joseph Balamuta
version: 0.4.2-dev.6
version: 0.4.3-dev.2
quarto-required: ">=1.4.554"
contributes:
filters:
Expand Down
4 changes: 4 additions & 0 deletions altdoc/_extensions/coatless/webr/qwebr-cell-initialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ qwebrInstance.then(
break;
case 'setup':
const activeDiv = document.getElementById(`qwebr-noninteractive-setup-area-${qwebrCounter}`);

// Store code in history
qwebrLogCodeToHistory(cellCode, entry.options);

// Run the code in a non-interactive state with all output thrown away
await mainWebR.evalRVoid(`${cellCode}`);
break;
Expand Down
Loading

0 comments on commit 7b6602b

Please sign in to comment.