Skip to content

Commit 1fe0f62

Browse files
committed
Roll initial filter version
1 parent 0333bb0 commit 1fe0f62

16 files changed

Lines changed: 3077 additions & 17 deletions
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
on:
2+
push:
3+
branches: [main, master]
4+
release:
5+
types: [published]
6+
workflow_dispatch: {}
7+
8+
name: demo-website
9+
10+
jobs:
11+
demo-page:
12+
runs-on: ubuntu-latest
13+
# Only restrict concurrency for non-PR jobs
14+
concurrency:
15+
group: quarto-publish-${{ github.event_name != 'pull_request' || github.run_id }}
16+
permissions:
17+
contents: read
18+
pages: write
19+
id-token: write
20+
steps:
21+
22+
- name: "Check out repository"
23+
uses: actions/checkout@v4
24+
25+
# To render using knitr, we need a few more setup steps...
26+
# If we didn't want the examples to use `engine: knitr`, we could
27+
# skip a few of the setup steps.
28+
- name: "Setup R"
29+
uses: r-lib/actions/setup-r@v2
30+
31+
- name: "Setup R dependencies for Quarto's knitr engine"
32+
uses: r-lib/actions/setup-r-dependencies@v2
33+
with:
34+
packages:
35+
any::knitr
36+
any::rmarkdown
37+
any::downlit
38+
any::xml2
39+
any::reticulate
40+
41+
- name: "Set up Quarto"
42+
uses: quarto-dev/quarto-actions/setup@v2
43+
with:
44+
version: "pre-release"
45+
46+
# Render the Quarto file
47+
- name: "Render working directory"
48+
uses: quarto-dev/quarto-actions/render@v2
49+
with:
50+
path: "docs"
51+
52+
53+
# Upload a tar file that will work with GitHub Pages
54+
# Make sure to set a retention day to avoid running into a cap
55+
# This artifact shouldn't be required after deployment onto pages was a success.
56+
- name: Upload Pages artifact
57+
uses: actions/upload-pages-artifact@v3
58+
with:
59+
retention-days: 1
60+
path: 'docs/_site'
61+
62+
63+
# Use an Action deploy to push the artifact onto GitHub Pages
64+
# This requires the `Action` tab being structured to allow for deployment
65+
# instead of using `docs/` or the `gh-pages` branch of the repository
66+
- name: Deploy to GitHub Pages
67+
id: deployment
68+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.luarc.json
2+
.DS_Store
3+
/docs/_site

README.md

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,94 @@
1-
# Quarto Extension Development with Lua in a Devcontainer
1+
# ripper <img src="docs/ripper-animated-logo.svg" align ="right" alt="" width ="150"/>
22

3-
This repository houses a devcontainer that setups a [Quarto extension development environment](https://quarto.org/docs/extensions/lua.html). The container is setup to work with [GitHub Codespaces](https://github.com/features/codespaces) to instantly have a cloud-based developer workflow.
3+
A Quarto extension that automatically extracts code blocks from your documents and splits them by programming language into separate, executable script files.
44

5-
You can try out the Codespace by clicking on the following button:
5+
## Installation
66

7-
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/coatless-devcontainer/quarto-extension-dev?quickstart=1)
7+
To install the `ripper` Quarto extension, follow these steps:
88

9-
**Note:** Codespaces are available to Students and Teachers for free [up to 180 core hours per month](https://docs.github.com/en/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-ide/using-github-codespaces-with-github-classroom#about-github-codespaces) through [GitHub Education](https://education.github.com/). Otherwise, you will have [up to 60 core hours and 15 GB free per month](https://github.com/features/codespaces#pricing).
9+
1. Open your terminal.
10+
2. Execute the following command:
1011

11-
The devcontainer contains:
12+
```sh
13+
quarto add coatless-quarto/ripper
14+
```
1215

13-
- The latest [pre-release](https://quarto.org/docs/download/prerelease) version of Quarto.
14-
- [Quarto VS Code Extension](https://marketplace.visualstudio.com/items?itemName=quarto.quarto).
15-
- [Lua LSP VS Code Extension](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) for Lua code intelligence.
16-
- [GitHub copilot VS Code Extension](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot).
17-
- `R` and `Python`
18-
- `knitr` and `jupyter`
16+
This command will download and install the Quarto extension under the `_extensions` subdirectory of your Quarto project. If you are using version control, ensure that you include this directory in your repository.
1917

20-
## References
18+
## Usage
19+
20+
Add the `ripper` filter to your document's YAML front matter:
21+
22+
````md
23+
---
24+
title: "My Analysis"
25+
filters:
26+
- ripper
27+
---
28+
29+
```{r}
30+
data <- mtcars
31+
summary(data)
32+
```
33+
34+
```{python}
35+
import pandas as pd
36+
df = pd.DataFrame({"x": [1, 2, 3]})
37+
```
38+
````
39+
40+
Render your document with Quarto:
41+
42+
```bash
43+
quarto render my-analysis.qmd
44+
```
45+
46+
Extracted scripts will be created in the same directory with names based on your document file name, e.g.
47+
48+
- `my-analysis.R` - All R code extracted
49+
- `my-analysis.py` - All Python code extracted
50+
51+
---
52+
53+
## Configuration
54+
55+
Ripper supports a single global option called `include-yaml` under `extensions.ripper`:
56+
57+
```yaml
58+
---
59+
filters:
60+
- ripper
61+
extensions:
62+
ripper:
63+
include-yaml: false # Include YAML as comments (default: true)
64+
---
65+
```
66+
67+
### Option: `include-yaml`
68+
69+
- **`true` (default)**: Includes YAML frontmatter as commented lines at the top of each script
70+
- **`false`**: Extracts only code with no YAML comments
71+
72+
## Supported Languages
73+
74+
16 languages supported with appropriate file extensions and comment styles:
75+
76+
| Language | Extension | Comment |
77+
|------------|-----------|---------|
78+
| R | .R | #' |
79+
| Python | .py | #' |
80+
| Julia | .jl | #' |
81+
| Bash | .sh | #' |
82+
| JavaScript | .js | //' |
83+
| TypeScript | .ts | //' |
84+
| SQL | .sql | --' |
85+
| Rust | .rs | //' |
86+
| Go | .go | //' |
87+
| C++ | .cpp | //' |
88+
| C | .c | //' |
89+
| Java | .java | //' |
90+
| Scala | .scala | //' |
91+
| Ruby | .rb | #' |
92+
| Perl | .pl | #' |
93+
| PHP | .php | //' |
2194

22-
- [Quarto: Lua API Reference](https://quarto.org/docs/extensions/lua-api.html)
23-
- [Quarto: Lua Development](https://quarto.org/docs/extensions/lua.html)
24-
- [Pandoc: Lua Filters](https://pandoc.org/lua-filters.html)
25-
- [Lua: Manual](https://www.lua.org/manual/5.4/)

_extensions/ripper/_extension.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
title: Ripper
2+
author: James Joseph Balamuta
3+
version: 0.0.0-dev.1
4+
quarto-required: ">=1.7.0"
5+
contributes:
6+
filters:
7+
- ripper.lua

_extensions/ripper/ripper.lua

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
local code_blocks = {}
2+
local yaml_meta = nil
3+
local include_yaml = true
4+
local output_name = nil
5+
6+
-- Language to file extension mapping
7+
local lang_extensions = {
8+
r = ".R",
9+
python = ".py",
10+
julia = ".jl",
11+
bash = ".sh",
12+
javascript = ".js",
13+
typescript = ".ts",
14+
sql = ".sql",
15+
rust = ".rs",
16+
go = ".go",
17+
cpp = ".cpp",
18+
c = ".c",
19+
java = ".java",
20+
scala = ".scala",
21+
ruby = ".rb",
22+
perl = ".pl",
23+
php = ".php"
24+
}
25+
26+
-- Comment style mapping for different languages
27+
local comment_styles = {
28+
r = "#'",
29+
python = "#'",
30+
julia = "#'",
31+
bash = "#'",
32+
sql = "--'",
33+
javascript = "//'",
34+
typescript = "//'",
35+
rust = "//'",
36+
go = "//'",
37+
cpp = "//'",
38+
c = "//'",
39+
java = "//'",
40+
scala = "//'",
41+
ruby = "#'",
42+
perl = "#'",
43+
php = "//'",
44+
}
45+
46+
-- Get comment prefix for a language
47+
local function get_comment_prefix(lang)
48+
return comment_styles[lang] or "#'"
49+
end
50+
51+
-- Convert YAML metadata to commented lines
52+
local function format_yaml_header(meta, lang)
53+
if not include_yaml then
54+
return ""
55+
end
56+
57+
local comment = get_comment_prefix(lang)
58+
local lines = {comment .. " ---"}
59+
60+
-- Extract common metadata fields
61+
if meta.title then
62+
local title = pandoc.utils.stringify(meta.title)
63+
table.insert(lines, comment .. " title: " .. title)
64+
end
65+
66+
if meta.author then
67+
local author = pandoc.utils.stringify(meta.author)
68+
table.insert(lines, comment .. " author: " .. author)
69+
end
70+
71+
if meta.date then
72+
local date = pandoc.utils.stringify(meta.date)
73+
table.insert(lines, comment .. " date: " .. date)
74+
end
75+
76+
if meta.format then
77+
local format = pandoc.utils.stringify(meta.format)
78+
table.insert(lines, comment .. " format: " .. format)
79+
end
80+
81+
table.insert(lines, comment .. " ---")
82+
table.insert(lines, comment .. " ")
83+
84+
return table.concat(lines, "\n") .. "\n"
85+
end
86+
87+
-- Initialize code blocks storage for a language
88+
local function init_language(lang)
89+
if not code_blocks[lang] then
90+
code_blocks[lang] = {}
91+
end
92+
end
93+
94+
-- Process Meta block to get configuration and metadata
95+
function Meta(meta)
96+
yaml_meta = meta
97+
98+
-- Check for ripper configuration under extensions.ripper
99+
if meta.extensions and meta.extensions.ripper then
100+
local config = meta.extensions.ripper
101+
102+
if config["include-yaml"] ~= nil then
103+
include_yaml = config["include-yaml"]
104+
end
105+
end
106+
107+
return meta
108+
end
109+
110+
-- Process code blocks
111+
function CodeBlock(block)
112+
-- Get the language (classes[1] is typically the language)
113+
local lang = block.classes[1]
114+
115+
if lang and lang_extensions[lang] then
116+
init_language(lang)
117+
118+
-- Store just the code text
119+
table.insert(code_blocks[lang], block.text)
120+
end
121+
122+
return block
123+
end
124+
125+
-- Write all collected code to separate files
126+
function Pandoc(doc)
127+
-- Get the output filename base (without extension)
128+
if not output_name then
129+
output_name = pandoc.path.split_extension(PANDOC_STATE.output_file or "output")
130+
end
131+
132+
-- Write a file for each language
133+
for lang, blocks in pairs(code_blocks) do
134+
if #blocks > 0 then
135+
local extension = lang_extensions[lang]
136+
local filename = output_name .. extension
137+
138+
-- Build file content
139+
local content = {}
140+
141+
-- Add YAML header if requested
142+
local yaml_header = ""
143+
if include_yaml and yaml_meta then
144+
yaml_header = format_yaml_header(yaml_meta, lang)
145+
end
146+
147+
-- Only add yaml_header if it's not empty
148+
if yaml_header ~= "" then
149+
table.insert(content, yaml_header)
150+
end
151+
152+
-- Add all code blocks for this language
153+
for i, code in ipairs(blocks) do
154+
if i > 1 then
155+
table.insert(content, "\n") -- Separator between blocks
156+
end
157+
table.insert(content, code)
158+
end
159+
160+
-- Write to file
161+
local file = io.open(filename, "w")
162+
if file then
163+
file:write(table.concat(content, "\n"))
164+
file:write("\n") -- Ensure file ends with newline
165+
file:close()
166+
167+
-- Log the file creation
168+
quarto.log.output("Created: " .. filename)
169+
else
170+
quarto.log.error("Failed to create file: " .. filename)
171+
end
172+
end
173+
end
174+
175+
return doc
176+
end
177+
178+
-- Return the filter functions
179+
return {
180+
{ Meta = Meta },
181+
{ CodeBlock = CodeBlock },
182+
{ Pandoc = Pandoc }
183+
}

docs/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.quarto/
2+
**/*.quarto_ipynb

docs/_extensions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../_extensions/

0 commit comments

Comments
 (0)