├── .devcontainer
└── devcontainer.json
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ ├── config.yml
│ ├── feature-request.yml
│ └── question.yml
└── workflows
│ └── publish-docs.yml
├── .gitignore
├── .quartoignore
├── README.md
├── _extensions
└── pyodide
│ ├── _extension.yml
│ ├── qpyodide-cell-classes.js
│ ├── qpyodide-cell-initialization.js
│ ├── qpyodide-document-engine-initialization.js
│ ├── qpyodide-document-settings.js
│ ├── qpyodide-document-status.js
│ ├── qpyodide-monaco-editor-init.html
│ ├── qpyodide-styling.css
│ └── qpyodide.lua
├── docs
├── .gitignore
├── _extensions
├── _quarto.yml
├── index.qmd
├── qpyodide-acknowledgements.md
├── qpyodide-code-cell-demo.qmd
├── qpyodide-deployment-templates.qmd
├── qpyodide-dev-notes.qmd
├── qpyodide-faq.qmd
├── qpyodide-first-steps.qmd
└── qpyodide-release-notes.qmd
├── examples
├── blog
│ ├── .gitignore
│ ├── _extensions
│ ├── _quarto.yml
│ ├── about.qmd
│ ├── index.qmd
│ ├── posts
│ │ ├── _metadata.yml
│ │ ├── embed-slides
│ │ │ ├── image.jpg
│ │ │ └── index.qmd
│ │ └── post-with-code
│ │ │ ├── image.jpg
│ │ │ └── index.qmd
│ ├── profile.jpg
│ └── styles.css
├── book
│ ├── .gitignore
│ ├── _extensions
│ ├── _quarto.yml
│ ├── example-page.qmd
│ ├── index.qmd
│ └── slide-embed.qmd
├── html-document
│ ├── .gitignore
│ ├── _extensions
│ ├── _quarto.yml
│ └── index.qmd
├── readme
│ ├── _extensions
│ ├── _quarto.yml
│ └── index.qmd
├── revealjs
│ ├── .gitignore
│ ├── README.md
│ ├── _quarto.yml
│ └── index.qmd
└── website
│ ├── .gitignore
│ ├── _extensions
│ ├── _quarto.yml
│ ├── example-page.qmd
│ ├── index.qmd
│ └── slide-embed.qmd
├── logo-quarto-pyodide.png
├── pyodide.code-workspace
├── tests
├── .gitignore
├── _extensions
├── _quarto.yml
├── index.qmd
├── qpyodide-test-graphic-output.qmd
├── qpyodide-test-internal-cell.qmd
├── qpyodide-test-url.qmd
└── standalone
│ └── monaco-pyodide-dependency-bug.html
└── update-dev-quarto.sh
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // Config options: https://github.com/rocker-org/devcontainer-templates/tree/main/src/r-ver
2 | {
3 | "name": "R (rocker/r-ver base)",
4 | "image": "ghcr.io/rocker-org/devcontainer/r-ver:4.3",
5 | // Add software
6 | "features": {
7 | // Required to test with knitr
8 | // R package config: https://github.com/rocker-org/devcontainer-features/blob/main/src/r-rig/README.md
9 | "ghcr.io/rocker-org/devcontainer-features/r-rig:1": {
10 | "version": "none",
11 | "installRMarkdown": true,
12 | "installJupyterlab": true,
13 | "installRadian": true
14 | },
15 | // You may wish to switch prerelease to latest for stable development
16 | // Quarto configuration : https://github.com/rocker-org/devcontainer-features/blob/main/src/quarto-cli/README.md
17 | "ghcr.io/rocker-org/devcontainer-features/quarto-cli:1": {
18 | "version": "prerelease"
19 | }
20 | },
21 | "customizations": {
22 | "vscode": {
23 | "settings": {
24 | "r.rterm.linux": "/usr/local/bin/radian",
25 | "r.bracketedPaste": true,
26 | "r.plot.useHttpgd": true,
27 | "[r]": {
28 | "editor.wordSeparators": "`~!@#%$^&*()-=+[{]}\\|;:'\",<>/?"
29 | }
30 | },
31 | // Enable a development set of extensions for Lua and Quarto
32 | "extensions": ["quarto.quarto", "sumneko.lua", "GitHub.copilot"]
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Report an error or unexpected behavior
3 | labels: ['t: bug', 's: triage-needed']
4 | title: "[Bug]: "
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Welcome to the quarto-pyodide Extension GitHub repository!
10 |
11 | We're sorry to hear that you might have encounter a bug; we're happy that you want to let us know so that we can make the extension even better.
12 |
13 | Thank you in advance for your feedback.
14 |
15 | - type: textarea
16 | attributes:
17 | label: Bug description
18 | description: Description of the bug and why you think its a bug.
19 | placeholder: Please describe the bug here.
20 |
21 | - type: textarea
22 | attributes:
23 | label: Steps to reproduce
24 | description: |
25 | Tell us how to reproduce this bug.
26 |
27 | Please include a minimal, fully reproducible example as a self-contained Quarto document or a link to a Git repository.
28 | placeholder: |
29 | You can share a Quarto document using the following syntax, _i.e._, using more backticks than you have in your document (usually four ` ```` `).
30 |
31 | ````qmd
32 | ---
33 | title: "Reproducible pyodide powered Quarto Document"
34 | format: html
35 | filter:
36 | - pyodide
37 | ---
38 |
39 | This is a reproducible Quarto document using `format: html` with
40 | the `pyodide` filter active. It contains a pyodide code cell that
41 | should create a plot using the cars data.
42 |
43 | ```{pyodide-python}
44 | [x**2 for x in range(0, 5)]
45 | ```
46 |
47 | The end.
48 | ````
49 |
50 | - type: textarea
51 | attributes:
52 | label: Your environment
53 | description: |
54 | Please document the IDE (_e.g._ VSCode, RStudio, NVim), its version, and the operating system you're running (_e.g., MacOS Ventura 13.4, Windows 11, Linux Debian 11, _etc._).
55 | placeholder: |
56 | - IDE: RStudio 2023.06.2+561
57 | - OS: MacOS Ventura 13.4
58 |
59 | - type: textarea
60 | attributes:
61 | label: Quarto check output
62 | description: |
63 | Please provide the output of `quarto check` so we know which version of quarto and its dependencies you're running.
64 | placeholder: |
65 | ```bash
66 | quarto check
67 | ```
68 |
69 | - type: markdown
70 | attributes:
71 | value: "_Thanks for submitting this bug report!_"
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Ask for a feature to be added
3 | labels: ['t: feature-request']
4 | title: "[Feature]: "
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | First, please check [`quarto-pyodide` Issue Tracker](https://github.com/coatless-quarto/pyodide/issues?q=is%3Aissue+) to see if your feature request has already been discussed.
10 | If it has, please consider adding a comment to the existing issue instead of creating a new one.
11 |
12 | After checking, if you are not sure if your feature request has already been discussed, please create a new issue.
13 | We look forward to hearing from you.
14 |
15 | Finally, try to describe the best you can what you want to achieve and why you think it is important.
16 | This will help us to understand your request and prioritise it.
17 |
18 | _Thank you for opening this feature request!_
19 | - type: textarea
20 | attributes:
21 | label: Feature Description
22 | description: Please discuss your feature request here!
23 | validations:
24 | required: true
25 | - type: markdown
26 | attributes:
27 | value: |
28 | _Thank you for opening this feature request!_
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.yml:
--------------------------------------------------------------------------------
1 | name: Question
2 | description: Ask a question
3 | labels: ['t: question', 's: question-needs-answer']
4 | title: "[Q&A]: "
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | **Before You Begin:**
10 | 1. Check the [**quarto-pyodide Issue Tracker**](https://github.com/coatless-quarto-pyodide/issues?q=is%3Aissue+) to ensure your question hasn't already been addressed.
11 |
12 | 2. If you're uncertain, feel free to create a new issue; we appreciate your engagement.
13 |
14 | **Creating a New Issue:**
15 |
16 | If you're creating a new issue, please provide the following information:
17 |
18 | 1. **Description**: Clearly state your question or problem.
19 |
20 | 2. **Goal**: Explain what you're trying to achieve or the challenge you're facing.
21 |
22 | 3. **Significance**: Share why this issue matters to you or the project.
23 |
24 | 4. **Additional Information**: Include any relevant context, such as your system setup, actions taken so far, and any potential solutions you've considered.
25 |
26 | **Thank you for reaching out!**
27 | - type: textarea
28 | attributes:
29 | label: What's your question?
30 | description: |
31 | You can include pyodide-powered Quarto document code with your question by using:
32 |
33 | ````qmd
34 | ---
35 | title: "Hello quarto-pyodide!"
36 | format: html
37 | filters:
38 | - pyodide
39 | ---
40 |
41 | ```{pyodide-python}
42 | 1 + 1
43 | ```
44 | ````
45 |
46 | validations:
47 | required: true
48 | - type: markdown
49 | attributes:
50 | value: |
51 | _Thank you for opening this issue to ask a question!_
--------------------------------------------------------------------------------
/.github/workflows/publish-docs.yml:
--------------------------------------------------------------------------------
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-website:
12 | runs-on: ubuntu-latest
13 | # Only restrict concurrency for non-PR jobs
14 | concurrency:
15 | group: quarto-build-${{ github.event_name != 'pull_request' || github.run_id }}
16 | permissions:
17 | contents: write
18 | steps:
19 | - name: "Check out repository"
20 | uses: actions/checkout@v4
21 |
22 | - name: "Set up Quarto"
23 | uses: quarto-dev/quarto-actions/setup@v2
24 | with:
25 | version: "pre-release"
26 |
27 | - name: Add extensions for RevealJS shortcode
28 | working-directory: ./examples/revealjs
29 | run: |
30 | quarto add --no-prompt gadenbuie/countdown/quarto
31 | quarto add --no-prompt coatless-quarto/pyodide
32 |
33 | # Generate the documentation website
34 | - name: Render Documentation website
35 | uses: quarto-dev/quarto-actions/render@v2
36 | with:
37 | path: "docs"
38 |
39 | # Attempt to render the nested deployment template Quarto projects
40 | - name: Render README example
41 | uses: quarto-dev/quarto-actions/render@v2
42 | with:
43 | path: "examples/readme/index.qmd"
44 |
45 | - name: Render sample deployment RevealJS presentation template
46 | uses: quarto-dev/quarto-actions/render@v2
47 | with:
48 | path: "examples/revealjs/index.qmd"
49 |
50 | - name: Render sample deployment HTML document template
51 | uses: quarto-dev/quarto-actions/render@v2
52 | with:
53 | path: "examples/html-document/index.qmd"
54 |
55 | - name: Render sample deployment website template
56 | uses: quarto-dev/quarto-actions/render@v2
57 | with:
58 | path: "examples/website"
59 |
60 | - name: Render sample deployment blog template
61 | uses: quarto-dev/quarto-actions/render@v2
62 | with:
63 | path: "examples/blog"
64 |
65 | - name: Render sample deployment book template
66 | uses: quarto-dev/quarto-actions/render@v2
67 | with:
68 | path: "examples/book"
69 |
70 | - name: Render test suite
71 | uses: quarto-dev/quarto-actions/render@v2
72 | with:
73 | path: "tests"
74 |
75 | # Collect the output into the staging/ directory
76 | - name: Copy documentation portal & examples into the staging directory
77 | run: |
78 | mkdir -p staging/{examples,tests} && \
79 | cp -rp docs/_site/* staging/ && \
80 | cp -rp tests/_site/* staging/tests/ && \
81 | cp -rp examples/book/_book staging/examples/book && \
82 | cp -rp examples/website/_site staging/examples/website && \
83 | cp -rp examples/blog/_site staging/examples/blog && \
84 | cp -rp examples/html-document/ staging/examples/html-document && \
85 | cp -rp examples/revealjs/ staging/examples/revealjs && \
86 | cp -rp examples/readme/ staging/examples/readme
87 |
88 | # Remove symlinks
89 | - name: Delete symlinks
90 | run: |
91 | rm -rf staging/examples/*/_extensions && \
92 | rm -rf staging/tests/_extensions
93 |
94 | # Publish the docs directory onto gh-pages
95 | - name: Deploy 🚀
96 | uses: JamesIves/github-pages-deploy-action@v4
97 | with:
98 | folder: staging # The folder the action should deploy.
99 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | !_extensions/*/*/*
2 | *.pdf
3 | *_files/
4 | *_site/
5 | *_book/
6 | /.luarc.json
7 | /*.html
8 | *.DS_Store
--------------------------------------------------------------------------------
/.quartoignore:
--------------------------------------------------------------------------------
1 | logo-quarto-pyodide.png
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pyodide Extension for Quarto HTML Documents
2 |
3 | > [!IMPORTANT]
4 | >
5 | > Looking for the official Quarto WebAssembly backend? Check out [`quarto-live`](https://github.com/r-wasm/quarto-live)!
6 |
7 | This community developed Quarto extension enables the [Pyodide](https://pyodide.org/en/stable/) code cell within various [Quarto](https://quarto.org/) formats, including [HTML](https://quarto.org/docs/output-formats/html-basics.html), [RevealJS](https://quarto.org/docs/presentations/revealjs/), [Websites](https://quarto.org/docs/websites/), [Blogs](https://quarto.org/docs/websites/website-blog.html), and [Books](https://quarto.org/docs/books).
8 |
9 | 
10 |
11 | Check out the above example of the extension in action [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/readme)!
12 |
13 | Interested in an R version? Take a look at [`quarto-webr`](https://github.com/coatless/quarto-webr)!
14 |
15 | > [!NOTE]
16 | > Please note that the `{quarto-pyodide}` Quarto extension is a community-driven initiative and is **not** affiliated with Posit, Quarto, or the main [Pyodide](https://pyodide.org/en/stable/) project. Its evolution and maintenance stem solely from the collective efforts of community members.
17 |
18 | ## Installation
19 |
20 | To use this extension in a [Quarto project](https://quarto.org/docs/projects/quarto-projects.html), install it from within the project's working directory by typing into **Terminal**:
21 |
22 | ``` bash
23 | quarto add coatless-quarto/pyodide
24 | ```
25 |
26 | After the installation process is finished, the extension will be readily available for Quarto documents within the designated working directory. Please note that if you are working on projects located in different directories, you will need to repeat this installation step for each of those directories.
27 |
28 | ## Usage
29 |
30 | For each document, place the `pyodide` filter in the document's header:
31 |
32 | ```yaml
33 | filters:
34 | - pyodide
35 | ```
36 |
37 | Then, place the Python code for `Pyodide` in a code block marked with `{pyodide-python}`
38 |
39 | ````markdown
40 | ---
41 | title: Pyodide in Quarto HTML Documents
42 | format: html
43 | filters:
44 | - pyodide
45 | ---
46 |
47 | This is a pyodide-enabled code cell in a Quarto HTML document.
48 |
49 | ```{pyodide-python}
50 | n = 5
51 | while n > 0:
52 | print(n)
53 | n = n - 1
54 |
55 | print('Blastoff!')
56 | ```
57 | ````
58 |
59 | The rendered document can be viewed online [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/readme).
60 |
61 | For help setting up other use cases, please see our [Templates](https://quarto.thecoatlessprofessor.com/pyodide/qpyodide-deployment-templates.html) collection.
62 |
63 | ## Help
64 |
65 | To report a bug, please [add an issue](https://github.com/coatless-quarto/pyodide/issues/new) to the repository's [bug tracker](https://github.com/coatless-quarto/pyodide/issues).
66 |
67 | Want to contribute a feature? Please open an issue ticket to discuss the feature before sending a pull request.
68 |
69 | ## Acknowledgements
70 |
71 | Please see our [acknowledgements page](https://quarto.thecoatlessprofessor.com/pyodide/qpyodide-acknowledgements.html).
--------------------------------------------------------------------------------
/_extensions/pyodide/_extension.yml:
--------------------------------------------------------------------------------
1 | title: pyodide
2 | author: James Joseph Balamuta
3 | version: 0.0.1-dev.3
4 | quarto-required: ">=1.4.549"
5 | contributes:
6 | filters:
7 | - qpyodide.lua
8 |
9 |
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-cell-classes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Factory function to create different types of cells based on options.
3 | * @param {Object} cellData - JSON object containing code, id, and options.
4 | * @returns {BaseCell} Instance of the appropriate cell class.
5 | */
6 | globalThis.qpyodideCreateCell = function(cellData) {
7 | switch (cellData.options.context) {
8 | case 'interactive':
9 | return new InteractiveCell(cellData);
10 | case 'output':
11 | return new OutputCell(cellData);
12 | case 'setup':
13 | return new SetupCell(cellData);
14 | default:
15 | return new InteractiveCell(cellData);
16 | // throw new Error('Invalid cell type specified in options.');
17 | }
18 | }
19 |
20 | /**
21 | * CellContainer class for managing a collection of cells.
22 | * @class
23 | */
24 | class CellContainer {
25 | /**
26 | * Constructor for CellContainer.
27 | * Initializes an empty array to store cells.
28 | * @constructor
29 | */
30 | constructor() {
31 | this.cells = [];
32 | }
33 |
34 | /**
35 | * Add a cell to the container.
36 | * @param {BaseCell} cell - Instance of a cell (BaseCell or its subclasses).
37 | */
38 | addCell(cell) {
39 | this.cells.push(cell);
40 | }
41 |
42 | /**
43 | * Execute all cells in the container.
44 | */
45 | async executeAllCells() {
46 | for (const cell of this.cells) {
47 | await cell.executeCode();
48 | }
49 | }
50 |
51 | /**
52 | * Execute all cells in the container.
53 | */
54 | async autoRunExecuteAllCells() {
55 | for (const cell of this.cells) {
56 | await cell.autoRunExecuteCode();
57 | }
58 | }
59 | }
60 |
61 |
62 | /**
63 | * BaseCell class for handling code execution using Pyodide.
64 | * @class
65 | */
66 | class BaseCell {
67 | /**
68 | * Constructor for BaseCell.
69 | * @constructor
70 | * @param {Object} cellData - JSON object containing code, id, and options.
71 | */
72 | constructor(cellData) {
73 | this.code = cellData.code;
74 | this.id = cellData.id;
75 | this.options = cellData.options;
76 | this.insertionLocation = document.getElementById(`qpyodide-insertion-location-${this.id}`);
77 | this.executionLock = false;
78 | }
79 |
80 | cellOptions() {
81 | // Subclass this?
82 | console.log(this.options);
83 | return this.options;
84 | }
85 |
86 | /**
87 | * Execute the Python code using Pyodide.
88 | * @returns {*} Result of the code execution.
89 | */
90 | async executeCode() {
91 | // Execute code using Pyodide
92 | const result = getPyodide().runPython(this.code);
93 | return result;
94 | }
95 | };
96 |
97 | /**
98 | * InteractiveCell class for creating editable code editor with Monaco Editor.
99 | * @class
100 | * @extends BaseCell
101 | */
102 | class InteractiveCell extends BaseCell {
103 |
104 | /**
105 | * Constructor for InteractiveCell.
106 | * @constructor
107 | * @param {Object} cellData - JSON object containing code, id, and options.
108 | */
109 | constructor(cellData) {
110 | super(cellData);
111 | this.editor = null;
112 | this.setupElement();
113 | this.setupMonacoEditor();
114 | }
115 |
116 | /**
117 | * Set up the interactive cell elements
118 | */
119 | setupElement() {
120 |
121 | // Create main div element
122 | var mainDiv = document.createElement('div');
123 | mainDiv.id = `qpyodide-interactive-area-${this.id}`;
124 | mainDiv.className = `qpyodide-interactive-area`;
125 | if (this.options.classes) {
126 | mainDiv.className += " " + this.options.classes
127 | }
128 |
129 | // Add a unique cell identifier that users can customize
130 | if (this.options.label) {
131 | mainDiv.setAttribute('data-id', this.options.label);
132 | }
133 |
134 | // Create toolbar div
135 | var toolbarDiv = document.createElement('div');
136 | toolbarDiv.className = 'qpyodide-editor-toolbar';
137 | toolbarDiv.id = `qpyodide-editor-toolbar-${this.id}`;
138 |
139 | // Create a div to hold the left buttons
140 | var leftButtonsDiv = document.createElement('div');
141 | leftButtonsDiv.className = 'qpyodide-editor-toolbar-left-buttons';
142 |
143 | // Create a div to hold the right buttons
144 | var rightButtonsDiv = document.createElement('div');
145 | rightButtonsDiv.className = 'qpyodide-editor-toolbar-right-buttons';
146 |
147 | // Create Run Code button
148 | var runCodeButton = document.createElement('button');
149 | runCodeButton.className = 'btn btn-default qpyodide-button qpyodide-button-run';
150 | runCodeButton.disabled = true;
151 | runCodeButton.type = 'button';
152 | runCodeButton.id = `qpyodide-button-run-${this.id}`;
153 | runCodeButton.textContent = '🟡 Loading Pyodide...';
154 | runCodeButton.title = `Run code (Shift + Enter)`;
155 |
156 | // Append buttons to the leftButtonsDiv
157 | leftButtonsDiv.appendChild(runCodeButton);
158 |
159 | // Create Reset button
160 | var resetButton = document.createElement('button');
161 | resetButton.className = 'btn btn-light btn-xs qpyodide-button qpyodide-button-reset';
162 | resetButton.type = 'button';
163 | resetButton.id = `qpyodide-button-reset-${this.id}`;
164 | resetButton.title = 'Start over';
165 | resetButton.innerHTML = '';
166 |
167 | // Create Copy button
168 | var copyButton = document.createElement('button');
169 | copyButton.className = 'btn btn-light btn-xs qpyodide-button qpyodide-button-copy';
170 | copyButton.type = 'button';
171 | copyButton.id = `qpyodide-button-copy-${this.id}`;
172 | copyButton.title = 'Copy code';
173 | copyButton.innerHTML = '';
174 |
175 | // Append buttons to the rightButtonsDiv
176 | rightButtonsDiv.appendChild(resetButton);
177 | rightButtonsDiv.appendChild(copyButton);
178 |
179 | // Create console area div
180 | var consoleAreaDiv = document.createElement('div');
181 | consoleAreaDiv.id = `qpyodide-console-area-${this.id}`;
182 | consoleAreaDiv.className = 'qpyodide-console-area';
183 |
184 | // Create editor div
185 | var editorDiv = document.createElement('div');
186 | editorDiv.id = `qpyodide-editor-${this.id}`;
187 | editorDiv.className = 'qpyodide-editor';
188 |
189 | // Create output code area div
190 | var outputCodeAreaDiv = document.createElement('div');
191 | outputCodeAreaDiv.id = `qpyodide-output-code-area-${this.id}`;
192 | outputCodeAreaDiv.className = 'qpyodide-output-code-area';
193 | outputCodeAreaDiv.setAttribute('aria-live', 'assertive');
194 |
195 | // Create pre element inside output code area
196 | var preElement = document.createElement('pre');
197 | preElement.style.visibility = 'hidden';
198 | outputCodeAreaDiv.appendChild(preElement);
199 |
200 | // Create output graph area div
201 | var outputGraphAreaDiv = document.createElement('div');
202 | outputGraphAreaDiv.id = `qpyodide-output-graph-area-${this.id}`;
203 | outputGraphAreaDiv.className = 'qpyodide-output-graph-area';
204 |
205 | // Append buttons to the toolbar
206 | toolbarDiv.appendChild(leftButtonsDiv);
207 | toolbarDiv.appendChild(rightButtonsDiv);
208 |
209 | // Append all elements to the main div
210 | mainDiv.appendChild(toolbarDiv);
211 | consoleAreaDiv.appendChild(editorDiv);
212 | consoleAreaDiv.appendChild(outputCodeAreaDiv);
213 | mainDiv.appendChild(consoleAreaDiv);
214 | mainDiv.appendChild(outputGraphAreaDiv);
215 |
216 | // Insert the dynamically generated object at the document location.
217 | this.insertionLocation.appendChild(mainDiv);
218 | }
219 |
220 | /**
221 | * Set up Monaco Editor for code editing.
222 | */
223 | setupMonacoEditor() {
224 |
225 | // Retrieve the previously created document elements
226 | this.runButton = document.getElementById(`qpyodide-button-run-${this.id}`);
227 | this.resetButton = document.getElementById(`qpyodide-button-reset-${this.id}`);
228 | this.copyButton = document.getElementById(`qpyodide-button-copy-${this.id}`);
229 | this.editorDiv = document.getElementById(`qpyodide-editor-${this.id}`);
230 | this.outputCodeDiv = document.getElementById(`qpyodide-output-code-area-${this.id}`);
231 | this.outputGraphDiv = document.getElementById(`qpyodide-output-graph-area-${this.id}`);
232 |
233 | // Store reference to the object
234 | var thiz = this;
235 |
236 | // Load the Monaco Editor and create an instance
237 | require(['vs/editor/editor.main'], function () {
238 | thiz.editor = monaco.editor.create(
239 | thiz.editorDiv, {
240 | value: thiz.code,
241 | language: 'python',
242 | theme: 'vs-light',
243 | automaticLayout: true, // Works wonderfully with RevealJS
244 | scrollBeyondLastLine: false,
245 | minimap: {
246 | enabled: false
247 | },
248 | fontSize: '17.5pt', // Bootstrap is 1 rem
249 | renderLineHighlight: "none", // Disable current line highlighting
250 | hideCursorInOverviewRuler: true, // Remove cursor indictor in right hand side scroll bar
251 | readOnly: thiz.options['read-only'] ?? false
252 | }
253 | );
254 |
255 | // Store the official counter ID to be used in keyboard shortcuts
256 | thiz.editor.__qpyodideCounter = thiz.id;
257 |
258 | // Store the official div container ID
259 | thiz.editor.__qpyodideEditorId = `qpyodide-editor-${thiz.id}`;
260 |
261 | // Store the initial code value and options
262 | thiz.editor.__qpyodideinitialCode = thiz.code;
263 | thiz.editor.__qpyodideOptions = thiz.options;
264 |
265 | // Set at the model level the preferred end of line (EOL) character to LF.
266 | // This prevent `\r\n` from being given to the Pyodide engine if the user is on Windows.
267 | // See details in: https://github.com/coatless/quarto-Pyodide/issues/94
268 | // Associated error text:
269 | // Error: :1:7 unexpected input
270 |
271 | // Retrieve the underlying model
272 | const model = thiz.editor.getModel();
273 | // Set EOL for the model
274 | model.setEOL(monaco.editor.EndOfLineSequence.LF);
275 |
276 | // Dynamically modify the height of the editor window if new lines are added.
277 | let ignoreEvent = false;
278 | const updateHeight = () => {
279 | const contentHeight = thiz.editor.getContentHeight();
280 | // We're avoiding a width change
281 | //editorDiv.style.width = `${width}px`;
282 | thiz.editorDiv.style.height = `${contentHeight}px`;
283 | try {
284 | ignoreEvent = true;
285 |
286 | // The key to resizing is this call
287 | thiz.editor.layout();
288 | } finally {
289 | ignoreEvent = false;
290 | }
291 | };
292 |
293 | // Helper function to check if selected text is empty
294 | function isEmptyCodeText(selectedCodeText) {
295 | return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === "");
296 | }
297 |
298 | // Registry of keyboard shortcuts that should be re-added to each editor window
299 | // when focus changes.
300 | const addPyodideKeyboardShortCutCommands = () => {
301 | // Add a keydown event listener for Shift+Enter to run all code in cell
302 | thiz.editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
303 | // Retrieve all text inside the editor
304 | thiz.runCode(thiz.editor.getValue());
305 | });
306 |
307 | // Add a keydown event listener for CMD/Ctrl+Enter to run selected code
308 | thiz.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
309 | // Get the selected text from the editor
310 | const selectedText = thiz.editor.getModel().getValueInRange(thiz.editor.getSelection());
311 | // Check if no code is selected
312 | if (isEmptyCodeText(selectedText)) {
313 | // Obtain the current cursor position
314 | let currentPosition = thiz.editor.getPosition();
315 | // Retrieve the current line content
316 | let currentLine = thiz.editor.getModel().getLineContent(currentPosition.lineNumber);
317 |
318 | // Propose a new position to move the cursor to
319 | let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1);
320 |
321 | // Check if the new position is beyond the last line of the editor
322 | if (newPosition.lineNumber > thiz.editor.getModel().getLineCount()) {
323 | // Add a new line at the end of the editor
324 | thiz.editor.executeEdits("addNewLine", [{
325 | range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1),
326 | text: "\n",
327 | forceMoveMarkers: true,
328 | }]);
329 | }
330 |
331 | // Run the entire line of code.
332 | thiz.runCode(currentLine);
333 |
334 | // Move cursor to new position
335 | thiz.editor.setPosition(newPosition);
336 | } else {
337 | // Code to run when Ctrl+Enter is pressed with selected code
338 | thiz.runCode(selectedText);
339 | }
340 | });
341 | }
342 |
343 | // Register an on focus event handler for when a code cell is selected to update
344 | // what keyboard shortcut commands should work.
345 | // This is a workaround to fix a regression that happened with multiple
346 | // editor windows since Monaco 0.32.0
347 | // https://github.com/microsoft/monaco-editor/issues/2947
348 | thiz.editor.onDidFocusEditorText(addPyodideKeyboardShortCutCommands);
349 |
350 | // Register an on change event for when new code is added to the editor window
351 | thiz.editor.onDidContentSizeChange(updateHeight);
352 |
353 | // Manually re-update height to account for the content we inserted into the call
354 | updateHeight();
355 |
356 | });
357 |
358 |
359 | // Add a click event listener to the run button
360 | thiz.runButton.onclick = function () {
361 | thiz.runCode(
362 | thiz.editor.getValue()
363 | );
364 | };
365 |
366 | // Add a click event listener to the reset button
367 | thiz.copyButton.onclick = function () {
368 | // Retrieve current code data
369 | const data = thiz.editor.getValue();
370 |
371 | // Write code data onto the clipboard.
372 | navigator.clipboard.writeText(data || "");
373 | };
374 |
375 | // Add a click event listener to the copy button
376 | thiz.resetButton.onclick = function () {
377 | thiz.editor.setValue(thiz.editor.__qpyodideinitialCode);
378 | };
379 | }
380 |
381 | disableInteractiveCells() {
382 | // Enable locking of execution for the cell
383 | this.executionLock = true;
384 |
385 | // Disallowing execution of other code cells
386 | document.querySelectorAll(".qpyodide-button-run").forEach((btn) => {
387 | btn.disabled = true;
388 | });
389 | }
390 |
391 | enableInteractiveCells() {
392 | // Remove locking of execution for the cell
393 | this.executionLock = false;
394 |
395 | // All execution of other code cells
396 | document.querySelectorAll(".qpyodide-button-run").forEach((btn) => {
397 | btn.disabled = false;
398 | });
399 | }
400 |
401 | /**
402 | * Execute the Python code inside the editor.
403 | */
404 | async runCode(code) {
405 |
406 | // Check if we have an execution lock
407 | if (this.executeLock) return;
408 |
409 | this.disableInteractiveCells();
410 |
411 | // Force wait procedure
412 | await mainPyodide;
413 |
414 | // Clear the output stock
415 | qpyodideResetOutputArray();
416 |
417 | // Generate a new canvas element, avoid attaching until the end
418 | let graphFigure = document.createElement("figure");
419 | document.pyodideMplTarget = graphFigure;
420 |
421 | console.log("Running code!");
422 | // Obtain results from the base class
423 | try {
424 | // Always check to see if the user adds new packages
425 | await mainPyodide.loadPackagesFromImports(code);
426 |
427 | // Process result
428 | const output = await mainPyodide.runPythonAsync(code);
429 |
430 | // Add output
431 | qpyodideAddToOutputArray(output, "stdout");
432 | } catch (err) {
433 | // Add error message
434 | qpyodideAddToOutputArray(err, "stderr");
435 | // TODO: There has to be a way to remove the Pyodide portion of the errors...
436 | }
437 |
438 | const result = qpyodideRetrieveOutput();
439 |
440 | // Nullify the output area of content
441 | this.outputCodeDiv.innerHTML = "";
442 | this.outputGraphDiv.innerHTML = "";
443 |
444 | // Design an output object for messages
445 | const pre = document.createElement("pre");
446 | if (/\S/.test(result)) {
447 | // Display results as HTML elements to retain output styling
448 | const div = document.createElement("div");
449 | div.innerHTML = result;
450 | pre.appendChild(div);
451 | } else {
452 | // If nothing is present, hide the element.
453 | pre.style.visibility = "hidden";
454 | }
455 |
456 | // Add output under interactive div
457 | this.outputCodeDiv.appendChild(pre);
458 |
459 | // Place the graphics onto the page
460 | if (graphFigure) {
461 |
462 | if (this.options['fig-cap']) {
463 | // Create figcaption element
464 | const figcaptionElement = document.createElement('figcaption');
465 | figcaptionElement.innerText = this.options['fig-cap'];
466 | // Append figcaption to figure
467 | graphFigure.appendChild(figcaptionElement);
468 | }
469 |
470 | this.outputGraphDiv.appendChild(graphFigure);
471 | }
472 |
473 | // Re-enable execution
474 | this.enableInteractiveCells();
475 | }
476 | };
477 |
478 | /**
479 | * OutputCell class for customizing and displaying output.
480 | * @class
481 | * @extends BaseCell
482 | */
483 | class OutputCell extends BaseCell {
484 | /**
485 | * Constructor for OutputCell.
486 | * @constructor
487 | * @param {Object} cellData - JSON object containing code, id, and options.
488 | */
489 | constructor(cellData) {
490 | super(cellData);
491 | }
492 |
493 | /**
494 | * Display customized output on the page.
495 | * @param {*} output - Result to be displayed.
496 | */
497 | displayOutput(output) {
498 | const results = this.executeCode();
499 | return results;
500 | }
501 | }
502 |
503 | /**
504 | * SetupCell class for suppressed output.
505 | * @class
506 | * @extends BaseCell
507 | */
508 | class SetupCell extends BaseCell {
509 | /**
510 | * Constructor for SetupCell.
511 | * @constructor
512 | * @param {Object} cellData - JSON object containing code, id, and options.
513 | */
514 | constructor(cellData) {
515 | super(cellData);
516 | }
517 |
518 | /**
519 | * Execute the Python code without displaying the results.
520 | */
521 | runSetupCode() {
522 | // Execute code without displaying output
523 | this.executeCode();
524 | }
525 | };
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-cell-initialization.js:
--------------------------------------------------------------------------------
1 | // Handle cell initialization initialization
2 | qpyodideCellDetails.map(
3 | (entry) => {
4 | // Handle the creation of the element
5 | qpyodideCreateCell(entry);
6 | }
7 | );
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-document-engine-initialization.js:
--------------------------------------------------------------------------------
1 | // Create a logging setup
2 | globalThis.qpyodideMessageArray = []
3 |
4 | // Add messages to array
5 | globalThis.qpyodideAddToOutputArray = function(message, type) {
6 | qpyodideMessageArray.push({ message, type });
7 | }
8 |
9 | // Function to reset the output array
10 | globalThis.qpyodideResetOutputArray = function() {
11 | qpyodideMessageArray = [];
12 | }
13 |
14 | globalThis.qpyodideRetrieveOutput = function() {
15 | return qpyodideMessageArray.map(entry => entry.message).join('\n');
16 | }
17 |
18 | // Start a timer
19 | const initializePyodideTimerStart = performance.now();
20 |
21 | // Encase with a dynamic import statement
22 | globalThis.qpyodideInstance = await import(
23 | qpyodideCustomizedPyodideOptions.indexURL + "pyodide.mjs").then(
24 | async({ loadPyodide }) => {
25 |
26 | console.log("Start loading Pyodide");
27 |
28 | // Populate Pyodide options with defaults or new values based on `pyodide`` meta
29 | let mainPyodide = await loadPyodide(
30 | qpyodideCustomizedPyodideOptions
31 | );
32 |
33 | // Setup a namespace for global scoping
34 | // await loadedPyodide.runPythonAsync("globalScope = {}");
35 |
36 | // Update status to reflect the next stage of the procedure
37 | qpyodideUpdateStatusHeaderSpinner("Initializing Python Packages");
38 |
39 | // Load the `micropip` package to allow installation of packages.
40 | await mainPyodide.loadPackage("micropip");
41 | await mainPyodide.runPythonAsync(`import micropip`);
42 |
43 | // Load the `pyodide_http` package to shim uses of `requests` and `urllib3`.
44 | // This allows for `pd.read_csv(url)` to work flawlessly.
45 | // Details: https://github.com/coatless-quarto/pyodide/issues/9
46 | await mainPyodide.loadPackage("pyodide_http");
47 | await mainPyodide.runPythonAsync(`
48 | import pyodide_http
49 | pyodide_http.patch_all() # Patch all libraries
50 | `);
51 |
52 | // Load the `matplotlib` package with necessary environment hook
53 | await mainPyodide.loadPackage("matplotlib");
54 |
55 | // Set the backend for matplotlib to be interactive.
56 | await mainPyodide.runPythonAsync(`
57 | import matplotlib
58 | matplotlib.use("module://matplotlib_pyodide.html5_canvas_backend")
59 | from matplotlib import pyplot as plt
60 | `);
61 |
62 | // Unlock interactive buttons
63 | qpyodideSetInteractiveButtonState(
64 | ` Run Code`,
65 | true
66 | );
67 |
68 | // Set document status to viable
69 | qpyodideUpdateStatusHeader(
70 | "🟢 Ready!"
71 | );
72 |
73 | // Assign Pyodide into the global environment
74 | globalThis.mainPyodide = mainPyodide;
75 |
76 | console.log("Completed loading Pyodide");
77 | return mainPyodide;
78 | }
79 | );
80 |
81 | // Stop timer
82 | const initializePyodideTimerEnd = performance.now();
83 |
84 | // Create a function to retrieve the promise object.
85 | globalThis._qpyodideGetInstance = function() {
86 | return qpyodideInstance;
87 | }
88 |
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-document-settings.js:
--------------------------------------------------------------------------------
1 | // Document level settings ----
2 |
3 | // Determine if we need to install python packages
4 | globalThis.qpyodideInstallPythonPackagesList = [{{INSTALLPYTHONPACKAGESLIST}}];
5 |
6 | // Check to see if we have an empty array, if we do set to skip the installation.
7 | globalThis.qpyodideSetupPythonPackages = !(qpyodideInstallPythonPackagesList.indexOf("") !== -1);
8 |
9 | // Display a startup message?
10 | globalThis.qpyodideShowStartupMessage = {{SHOWSTARTUPMESSAGE}};
11 |
12 | // Describe the webR settings that should be used
13 | globalThis.qpyodideCustomizedPyodideOptions = {
14 | "indexURL": "{{INDEXURL}}",
15 | "env": {
16 | "HOME": "{{HOMEDIR}}",
17 | },
18 | stdout: (text) => {qpyodideAddToOutputArray(text, "out");},
19 | stderr: (text) => {qpyodideAddToOutputArray(text, "error");}
20 | }
21 |
22 | // Store cell data
23 | globalThis.qpyodideCellDetails = {{QPYODIDECELLDETAILS}};
24 |
25 |
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-document-status.js:
--------------------------------------------------------------------------------
1 | // Declare startupMessageqpyodide globally
2 | globalThis.qpyodideStartupMessage = document.createElement("p");
3 |
4 | // Function to set the button text
5 | globalThis.qpyodideSetInteractiveButtonState = function(buttonText, enableCodeButton = true) {
6 | document.querySelectorAll(".qpyodide-button-run").forEach((btn) => {
7 | btn.innerHTML = buttonText;
8 | btn.disabled = !enableCodeButton;
9 | });
10 | }
11 |
12 | // Function to update the status message in non-interactive cells
13 | globalThis.qpyodideUpdateStatusMessage = function(message) {
14 | document.querySelectorAll(".qpyodide-status-text.qpyodide-cell-needs-evaluation").forEach((elem) => {
15 | elem.innerText = message;
16 | });
17 | }
18 |
19 | // Function to update the status message
20 | globalThis.qpyodideUpdateStatusHeader = function(message) {
21 |
22 | if (!qpyodideShowStartupMessage) return;
23 |
24 | qpyodideStartupMessage.innerHTML = message;
25 | }
26 |
27 | // Status header update with customized spinner message
28 | globalThis.qpyodideUpdateStatusHeaderSpinner = function(message) {
29 |
30 | qpyodideUpdateStatusHeader(`
31 |
32 | ${message}
33 | `);
34 | }
35 |
36 |
37 | // Function that attaches the document status message
38 | function qpyodideDisplayStartupMessage(showStartupMessage) {
39 | if (!showStartupMessage) {
40 | return;
41 | }
42 |
43 | // Get references to header elements
44 | const headerHTML = document.getElementById("title-block-header");
45 | const headerRevealJS = document.getElementById("title-slide");
46 |
47 | // Create the outermost div element for metadata
48 | const quartoTitleMeta = document.createElement("div");
49 | quartoTitleMeta.classList.add("quarto-title-meta");
50 |
51 | // Create the first inner div element
52 | const firstInnerDiv = document.createElement("div");
53 | firstInnerDiv.setAttribute("id", "qpyodide-status-message-area");
54 |
55 | // Create the second inner div element for "Pyodide Status" heading and contents
56 | const secondInnerDiv = document.createElement("div");
57 | secondInnerDiv.setAttribute("id", "qpyodide-status-message-title");
58 | secondInnerDiv.classList.add("quarto-title-meta-heading");
59 | secondInnerDiv.innerText = "Pyodide Status";
60 |
61 | // Create another inner div for contents
62 | const secondInnerDivContents = document.createElement("div");
63 | secondInnerDivContents.setAttribute("id", "qpyodide-status-message-body");
64 | secondInnerDivContents.classList.add("quarto-title-meta-contents");
65 |
66 | // Describe the Pyodide state
67 | qpyodideStartupMessage.innerText = "🟡 Loading...";
68 | qpyodideStartupMessage.setAttribute("id", "qpyodide-status-message-text");
69 | // Add `aria-live` to auto-announce the startup status to screen readers
70 | qpyodideStartupMessage.setAttribute("aria-live", "assertive");
71 |
72 | // Append the startup message to the contents
73 | secondInnerDivContents.appendChild(qpyodideStartupMessage);
74 |
75 | // Combine the inner divs and contents
76 | firstInnerDiv.appendChild(secondInnerDiv);
77 | firstInnerDiv.appendChild(secondInnerDivContents);
78 | quartoTitleMeta.appendChild(firstInnerDiv);
79 |
80 | // Determine where to insert the quartoTitleMeta element
81 | if (headerHTML || headerRevealJS) {
82 | // Append to the existing "title-block-header" element or "title-slide" div
83 | (headerHTML || headerRevealJS).appendChild(quartoTitleMeta);
84 | } else {
85 | // If neither headerHTML nor headerRevealJS is found, insert after "Pyodide-monaco-editor-init" script
86 | const monacoScript = document.getElementById("qpyodide-monaco-editor-init");
87 | const header = document.createElement("header");
88 | header.setAttribute("id", "title-block-header");
89 | header.appendChild(quartoTitleMeta);
90 | monacoScript.after(header);
91 | }
92 | }
93 |
94 | qpyodideDisplayStartupMessage(qpyodideShowStartupMessage);
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-monaco-editor-init.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide-styling.css:
--------------------------------------------------------------------------------
1 | .monaco-editor pre {
2 | background-color: unset !important;
3 | }
4 |
5 | .qpyodide-editor-toolbar {
6 | width: 100%;
7 | display: flex;
8 | justify-content: space-between;
9 | box-sizing: border-box;
10 | }
11 |
12 | .qpyodide-editor-toolbar-left-buttons, .qpyodide-editor-toolbar-right-buttons {
13 | display: flex;
14 | }
15 |
16 | .qpyodide-non-interactive-loading-container.qpyodide-cell-needs-evaluation, .qpyodide-non-interactive-loading-container.qpyodide-cell-evaluated {
17 | justify-content: center;
18 | display: flex;
19 | background-color: rgba(250, 250, 250, 0.65);
20 | border: 1px solid rgba(233, 236, 239, 0.65);
21 | border-radius: 0.5rem;
22 | margin-top: 15px;
23 | margin-bottom: 15px;
24 | }
25 |
26 | .qpyodide-r-project-logo {
27 | color: #2767B0; /* R Project's blue color */
28 | }
29 |
30 | .qpyodide-icon-status-spinner {
31 | color: #7894c4;
32 | }
33 |
34 | .qpyodide-icon-run-code {
35 | color: #0d9c29
36 | }
37 |
38 | .qpyodide-output-code-stdout {
39 | color: #111;
40 | }
41 |
42 | .qpyodide-output-code-stderr {
43 | color: #db4133;
44 | }
45 |
46 | .qpyodide-editor {
47 | border: 1px solid #EEEEEE;
48 | }
49 |
50 | .qpyodide-editor-toolbar {
51 | background-color: #EEEEEE;
52 | padding: 0.2rem 0.5rem;
53 | }
54 |
55 | .qpyodide-button {
56 | background-color: #EEEEEE;
57 | display: inline-block;
58 | font-weight: 400;
59 | line-height: 1;
60 | text-decoration: none;
61 | text-align: center;
62 | color: #000;
63 | border-color: #dee2e6;
64 | border: 1px solid rgba(0,0,0,0);
65 | padding: 0.375rem 0.75rem;
66 | font-size: .9rem;
67 | border-radius: 0.25rem;
68 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
69 | }
70 |
71 | .qpyodide-button:hover {
72 | color: #000;
73 | background-color: #d9dce0;
74 | border-color: #c8ccd0;
75 | }
76 |
77 | .qpyodide-button:disabled,.qpyodide-button.disabled,fieldset:disabled .qpyodide-button {
78 | pointer-events: none;
79 | opacity: .65
80 | }
81 |
82 | .qpyodide-button-reset {
83 | color: #696969; /*#4682b4;*/
84 | }
85 |
86 | .qpyodide-button-copy {
87 | color: #696969;
88 | }
89 |
90 |
91 | /* Custom styling for RevealJS Presentations*/
92 |
93 | /* Reset the style of the interactive area */
94 | .reveal div.qpyodide-interactive-area {
95 | display: block;
96 | box-shadow: none;
97 | max-width: 100%;
98 | max-height: 100%;
99 | margin: 0;
100 | padding: 0;
101 | }
102 |
103 | /* Provide space to entries */
104 | .reveal div.qpyodide-output-code-area pre div {
105 | margin: 1px 2px 1px 10px;
106 | }
107 |
108 | /* Collapse the inside code tags to avoid extra space between line outputs */
109 | .reveal pre div code.qpyodide-output-code-stdout, .reveal pre div code.qpyodide-output-code-stderr {
110 | padding: 0;
111 | display: contents;
112 | }
113 |
114 | .reveal pre div code.qpyodide-output-code-stdout {
115 | color: #111;
116 | }
117 |
118 | .reveal pre div code.qpyodide-output-code-stderr {
119 | color: #db4133;
120 | }
121 |
122 |
123 | /* Create a border around console and output (does not effect graphs) */
124 | .reveal div.qpyodide-console-area {
125 | border: 1px solid #EEEEEE;
126 | box-shadow: 2px 2px 10px #EEEEEE;
127 | }
128 |
129 | /* Cap output height and allow text to scroll */
130 | /* TODO: Is there a better way to fit contents/max it parallel to the monaco editor size? */
131 | .reveal div.qpyodide-output-code-area pre {
132 | max-height: 400px;
133 | overflow: scroll;
134 | }
--------------------------------------------------------------------------------
/_extensions/pyodide/qpyodide.lua:
--------------------------------------------------------------------------------
1 | ----
2 | --- Setup variables for default initialization
3 |
4 | -- Define a variable to check if pyodide is present.
5 | local missingPyodideCell = true
6 |
7 | -- Define a variable to only include the initialization once
8 | local hasDonePyodideSetup = false
9 |
10 | --- Setup default initialization values
11 | -- Default values taken from:
12 | -- https://pyodide.org/en/stable/usage/api/js-api.html#globalThis.loadPyodide
13 |
14 | -- Define a base compatibile version
15 | local baseVersionPyodide = "0.27.2"
16 |
17 | -- Define where Pyodide can be found. Default:
18 | -- https://cdn.jsdelivr.net/pyodide/v0.z.y/full/
19 | -- https://cdn.jsdelivr.net/pyodide/v0.z.y/debug/
20 | local baseUrl = "https://cdn.jsdelivr.net/pyodide/v".. baseVersionPyodide .."/"
21 | local buildVariant = "full/"
22 | local indexURL = baseUrl .. buildVariant
23 |
24 | -- Define user directory
25 | local homeDir = "/home/pyodide"
26 |
27 | -- Define whether a startup status message should be displayed
28 | local showStartUpMessage = "true"
29 |
30 | -- Define an empty string if no packages need to be installed.
31 | local installPythonPackagesList = "''"
32 | ----
33 |
34 | --- Setup variables for tracking number of code cells
35 |
36 | -- Define a counter variable
37 | local qPyodideCounter = 0
38 |
39 | -- Initialize a table to store the CodeBlock elements
40 | local qPyodideCapturedCodeBlocks = {}
41 |
42 | -- Initialize a table that contains the default cell-level options
43 | local qPyodideDefaultCellOptions = {
44 | ["context"] = "interactive",
45 | ["warning"] = "true",
46 | ["message"] = "true",
47 | ["results"] = "markup",
48 | ["read-only"] = "false",
49 | ["output"] = "true",
50 | ["comment"] = "",
51 | ["label"] = "",
52 | ["autorun"] = "",
53 | ["classes"] = "",
54 | ["dpi"] = 72,
55 | ["fig-cap"] = "",
56 | ["fig-width"] = 7,
57 | ["fig-height"] = 5,
58 | ["out-width"] = "700px",
59 | ["out-height"] = ""
60 | }
61 |
62 | ----
63 | --- Process initialization
64 |
65 | -- Check if variable missing or an empty string
66 | local function isVariableEmpty(s)
67 | return s == nil or s == ''
68 | end
69 |
70 | -- Check if variable is present
71 | local function isVariablePopulated(s)
72 | return not isVariableEmpty(s)
73 | end
74 |
75 | -- Copy the top level value and its direct children
76 | -- Details: http://lua-users.org/wiki/CopyTable
77 | local function shallowcopy(original)
78 | -- Determine if its a table
79 | if type(original) == 'table' then
80 | -- Copy the top level to remove references
81 | local copy = {}
82 | for key, value in pairs(original) do
83 | copy[key] = value
84 | end
85 | -- Return the copy
86 | return copy
87 | else
88 | -- If original is not a table, return it directly since it's already a copy
89 | return original
90 | end
91 | end
92 |
93 | -- Custom method for cloning a table with a shallow copy.
94 | function table.clone(original)
95 | return shallowcopy(original)
96 | end
97 |
98 | local function mergeCellOptions(localOptions)
99 | -- Copy default options to the mergedOptions table
100 | local mergedOptions = table.clone(qPyodideDefaultCellOptions)
101 |
102 | -- Override default options with local options
103 | for key, value in pairs(localOptions) do
104 | if type(value) == "string" then
105 | value = value:gsub("[\"']", "")
106 | end
107 | mergedOptions[key] = value
108 | end
109 |
110 | -- Return the customized options
111 | return mergedOptions
112 | end
113 |
114 | -- Parse the different Pyodide options set in the YAML frontmatter, e.g.
115 | --
116 | -- ```yaml
117 | -- ----
118 | -- pyodide:
119 | -- base-url: https://cdn.jsdelivr.net/pyodide/[version]
120 | -- build-variant: full
121 | -- packages: ['matplotlib', 'pandas']
122 | -- ----
123 | -- ```
124 | --
125 | --
126 | function setPyodideInitializationOptions(meta)
127 |
128 | -- Let's explore the meta variable data!
129 | -- quarto.log.output(meta)
130 |
131 | -- Retrieve the pyodide options from meta
132 | local pyodide = meta.pyodide
133 |
134 | -- Does this exist? If not, just return meta as we'll just use the defaults.
135 | if isVariableEmpty(pyodide) then
136 | return meta
137 | end
138 |
139 | -- The base URL used for downloading Python WebAssembly binaries
140 | if isVariablePopulated(pyodide["base-url"]) then
141 | baseUrl = pandoc.utils.stringify(pyodide["base-url"])
142 | end
143 |
144 | -- The build variant for Python WebAssembly binaries. Default: 'full'
145 | if isVariablePopulated(pyodide["build-variant"]) then
146 | buildVariant = pandoc.utils.stringify(pyodide["build-variant"])
147 | end
148 |
149 | if isVariablePopulated(pyodide["build-variant"]) or isVariablePopulated(pyodide["base-url"]) then
150 | indexURL = baseUrl .. buildVariant
151 | end
152 |
153 | -- The WebAssembly user's home directory and initial working directory. Default: '/home/pyodide'
154 | if isVariablePopulated(pyodide['home-dir']) then
155 | homeDir = pandoc.utils.stringify(pyodide["home-dir"])
156 | end
157 |
158 | -- Display a startup message indicating the pyodide state at the top of the document.
159 | if isVariablePopulated(pyodide['show-startup-message']) then
160 | showStartUpMessage = pandoc.utils.stringify(pyodide["show-startup-message"])
161 | end
162 |
163 | -- Attempt to install different packages.
164 | if isVariablePopulated(pyodide["packages"]) then
165 | -- Create a custom list
166 | local package_list = {}
167 |
168 | -- Iterate through each list item and enclose it in quotes
169 | for _, package_name in pairs(pyodide["packages"]) do
170 | table.insert(package_list, "'" .. pandoc.utils.stringify(package_name) .. "'")
171 | end
172 |
173 | installPythonPackagesList = table.concat(package_list, ", ")
174 | end
175 |
176 | return meta
177 | end
178 |
179 |
180 | -- Obtain a template file
181 | function readTemplateFile(template)
182 | -- Establish a hardcoded path to where the .html partial resides
183 | -- Note, this should be at the same level as the lua filter.
184 | -- This is crazy fragile since Lua lacks a directory representation (!?!?)
185 | -- https://quarto.org/docs/extensions/lua-api.html#includes
186 | local path = quarto.utils.resolve_path(template)
187 |
188 | -- Let's hopefully read the template file...
189 |
190 | -- Open the template file
191 | local file = io.open(path, "r")
192 |
193 | -- Check if null pointer before grabbing content
194 | if not file then
195 | error("\nWe were unable to read the template file `" .. template .. "` from the extension directory.\n\n" ..
196 | "Double check that the extension is fully available by comparing the \n" ..
197 | "`_extensions/coatless-quarto/pyodide` directory with the main repository:\n" ..
198 | "https://github.com/coatless-quarto/pyodide/tree/main/_extensions/pyodide\n\n" ..
199 | "You may need to modify `.gitignore` to allow the extension files using:\n" ..
200 | "!_extensions/*/*/*\n")
201 | return nil
202 | end
203 |
204 | -- *a or *all reads the whole file
205 | local content = file:read "*a"
206 |
207 | -- Close the file
208 | file:close()
209 |
210 | -- Return contents
211 | return content
212 | end
213 |
214 | -- Define a function that escape control sequence
215 | function escapeControlSequences(str)
216 | -- Perform a global replacement on the control sequence character
217 | return str:gsub("[\\%c]", function(c)
218 | if c == "\\" then
219 | -- Escape backslash
220 | return "\\\\"
221 | end
222 | end)
223 | end
224 |
225 | function initializationPyodide()
226 |
227 | -- Setup different Pyodide specific initialization variables
228 | local substitutions = {
229 | ["INDEXURL"] = indexURL,
230 | ["HOMEDIR"] = homeDir,
231 | ["SHOWSTARTUPMESSAGE"] = showStartUpMessage,
232 | ["INSTALLPYTHONPACKAGESLIST"] = installPythonPackagesList,
233 | ["QPYODIDECELLDETAILS"] = quarto.json.encode(qPyodideCapturedCodeBlocks),
234 | }
235 |
236 | -- Make sure we perform a copy
237 | local initializationTemplate = readTemplateFile("qpyodide-document-settings.js")
238 |
239 | -- Make the necessary substitutions
240 | local initializedPyodideConfiguration = substitute_in_file(initializationTemplate, substitutions)
241 |
242 | return initializedPyodideConfiguration
243 | end
244 |
245 | local function generateHTMLElement(tag)
246 | -- Store a map containing opening and closing tabs
247 | local tagMappings = {
248 | module = { opening = "" },
249 | js = { opening = "" },
250 | css = { opening = "" }
251 | }
252 |
253 | -- Find the tag
254 | local tagMapping = tagMappings[tag]
255 |
256 | -- If present, extract tag and return
257 | if tagMapping then
258 | return tagMapping.opening, tagMapping.closing
259 | else
260 | quarto.log.error("Invalid tag specified")
261 | end
262 | end
263 |
264 | -- Custom functions to include values into Quarto
265 | -- https://quarto.org/docs/extensions/lua-api.html#includes
266 |
267 | local function includeTextInHTMLTag(location, text, tag)
268 |
269 | -- Obtain the HTML element opening and closing tag
270 | local openingTag, closingTag = generateHTMLElement(tag)
271 |
272 | -- Insert the file into the document using the correct opening and closing tags
273 | quarto.doc.include_text(location, openingTag .. text .. closingTag)
274 |
275 | end
276 |
277 | local function includeFileInHTMLTag(location, file, tag)
278 |
279 | -- Obtain the HTML element opening and closing tag
280 | local openingTag, closingTag = generateHTMLElement(tag)
281 |
282 | -- Retrieve the file contents
283 | local fileContents = readTemplateFile(file)
284 |
285 | -- Insert the file into the document using the correct opening and closing tags
286 | quarto.doc.include_text(location, openingTag .. fileContents .. closingTag)
287 |
288 | end
289 |
290 |
291 | -- Setup Pyodide's pre-requisites once per document.
292 | function ensurePyodideSetup()
293 |
294 | -- If we've included the initialization, then bail.
295 | if hasDonePyodideSetup then
296 | return
297 | end
298 |
299 | -- Otherwise, let's include the initialization script _once_
300 | hasDonePyodideSetup = true
301 |
302 | local initializedConfigurationPyodide = initializationPyodide()
303 |
304 | -- Insert different partial files to create a monolithic document.
305 | -- https://quarto.org/docs/extensions/lua-api.html#includes
306 |
307 | -- Embed Support Files to Avoid Resource Registration Issues
308 | -- Note: We're not able to use embed-resources due to the web assembly binary and the potential for additional service worker files.
309 | quarto.doc.include_text("in-header", [[
310 |
311 |
312 | ]])
313 |
314 | -- Insert CSS styling and external style sheets
315 | includeFileInHTMLTag("in-header", "qpyodide-styling.css", "css")
316 |
317 | -- Insert the Pyodide initialization routine
318 | includeTextInHTMLTag("in-header", initializedConfigurationPyodide, "module")
319 |
320 | -- Insert JS routine to add document status header
321 | includeFileInHTMLTag("in-header", "qpyodide-document-status.js", "module")
322 |
323 | -- Insert JS routine to bring Pyodide online
324 | includeFileInHTMLTag("in-header", "qpyodide-document-engine-initialization.js", "module")
325 |
326 | -- Insert the Monaco Editor initialization
327 | quarto.doc.include_file("before-body", "qpyodide-monaco-editor-init.html")
328 |
329 | -- Insert the cell data at the end of the document
330 | includeFileInHTMLTag("after-body", "qpyodide-cell-classes.js", "module")
331 |
332 | includeFileInHTMLTag("after-body", "qpyodide-cell-initialization.js", "module")
333 |
334 | end
335 |
336 | -- Define a function to replace keywords given by {{ WORD }}
337 | -- Is there a better lua-approach?
338 | function substitute_in_file(contents, substitutions)
339 |
340 | -- Substitute values in the contents of the file
341 | contents = contents:gsub("{{%s*(.-)%s*}}", substitutions)
342 |
343 | -- Return the contents of the file with substitutions
344 | return contents
345 | end
346 |
347 | local function qPyodideJSCellInsertionCode(counter)
348 | local insertionLocation = '\n'
349 | local noscriptWarning = ''
350 | return insertionLocation .. noscriptWarning
351 | end
352 |
353 | -- Extract Quarto code cell options from the block's text
354 | local function extractCodeBlockOptions(block)
355 |
356 | -- Access the text aspect of the code block
357 | local code = block.text
358 |
359 | -- Define two local tables:
360 | -- the block's attributes
361 | -- the block's code lines
362 | local cellOptions = {}
363 | local newCodeLines = {}
364 |
365 | -- Iterate over each line in the code block
366 | for line in code:gmatch("([^\r\n]*)[\r\n]?") do
367 | -- Check if the line starts with "#|" and extract the key-value pairing
368 | -- e.g. #| key: value goes to cellOptions[key] -> value
369 | local key, value = line:match("^#|%s*(.-):%s*(.-)%s*$")
370 |
371 | -- If a special comment is found, then add the key-value pairing to the cellOptions table
372 | if key and value then
373 | cellOptions[key] = value
374 | else
375 | -- Otherwise, it's not a special comment, keep the code line
376 | table.insert(newCodeLines, line)
377 | end
378 | end
379 |
380 | -- Merge cell options with default options
381 | cellOptions = mergeCellOptions(cellOptions)
382 |
383 | -- Set the codeblock text to exclude the special comments.
384 | cellCode = table.concat(newCodeLines, '\n')
385 |
386 | -- Return the code alongside options
387 | return cellCode, cellOptions
388 | end
389 |
390 |
391 |
392 | -- Replace the code cell with a Pyodide interactive editor
393 | function enablePyodideCodeCell(el)
394 |
395 | -- Let's see what's going on here:
396 | -- quarto.log.output(el)
397 |
398 | -- Should display the following elements:
399 | -- https://pandoc.org/lua-filters.html#type-codeblock
400 |
401 | -- Verify the element has attributes and the document type is HTML
402 | -- not sure if this will work with an epub (may need html:js)
403 | if not (el.attr and (quarto.doc.is_format("html") or quarto.doc.is_format("markdown"))) then
404 | return el
405 | end
406 |
407 | -- Check for the new engine syntax that allows for the cell to be
408 | -- evaluated in VS Code or RStudio editor views, c.f.
409 | -- https://github.com/quarto-dev/quarto-cli/discussions/4761#discussioncomment-5338631
410 | if not el.attr.classes:includes("{pyodide-python}") then
411 | return el
412 | end
413 |
414 | -- We detected a webR cell
415 | missingPyodideCell = false
416 |
417 | -- Local code cell storage
418 | local cellOptions = {}
419 | local cellCode = ''
420 |
421 | -- Convert webr-specific option commands into attributes
422 | cellCode, cellOptions = extractCodeBlockOptions(el)
423 |
424 | -- Modify the counter variable each time this is run to create
425 | -- unique code cells
426 | qPyodideCounter = qPyodideCounter + 1
427 |
428 | -- Create a new table for the CodeBlock
429 | local codeBlockData = {
430 | id = qPyodideCounter,
431 | code = cellCode,
432 | options = cellOptions
433 | }
434 |
435 | -- Store the CodeDiv in the global table
436 | table.insert(qPyodideCapturedCodeBlocks, codeBlockData)
437 |
438 | -- Return an insertion point inside the document
439 | return pandoc.RawInline('html', qPyodideJSCellInsertionCode(qPyodideCounter))
440 | end
441 |
442 | local function stitchDocument(doc)
443 |
444 | -- Do not attach webR as the page lacks any active webR cells
445 | if missingPyodideCell then
446 | return doc
447 | end
448 |
449 | -- Release injections into the HTML document after each cell
450 | -- is visited and we have collected all the content.
451 | ensurePyodideSetup()
452 |
453 | return doc
454 | end
455 |
456 | return {
457 | {
458 | Meta = setPyodideInitializationOptions
459 | },
460 | {
461 | CodeBlock = enablePyodideCodeCell
462 | },
463 | {
464 | Pandoc = stitchDocument
465 | }
466 | }
467 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
2 | _site/*
--------------------------------------------------------------------------------
/docs/_extensions:
--------------------------------------------------------------------------------
1 | ../_extensions/
--------------------------------------------------------------------------------
/docs/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 |
4 | website:
5 | title: "quarto-pyodide"
6 | reader-mode: true
7 | repo-url: https://github.com/coatless-quarto/pyodide
8 | repo-actions: [edit, issue]
9 | repo-subdir: "docs"
10 | google-analytics: "G-ZV56KS59C3"
11 | sidebar:
12 | style: "floating"
13 | search: true
14 | tools:
15 | - icon: github
16 | href: https://github.com/coatless-quarto/pyodide/
17 | contents:
18 | - section: "Getting Started"
19 | contents:
20 | - href: qpyodide-first-steps.qmd
21 | text: "Your first Pyodide-powered Quarto document"
22 | - section: "Support"
23 | contents:
24 | - href: qpyodide-faq.qmd
25 | text: "Frequently Asked Questions"
26 | - href: https://github.com/coatless-quarto/pyodide/issues/new
27 | text: Submit an issue
28 | - section: "Demos"
29 | contents:
30 | - href: https://quarto.thecoatlessprofessor.com/pyodide/examples/readme/
31 | text: README Example
32 | - href: qpyodide-code-cell-demo.qmd
33 | text: Pyodide Interactive Cell Demo
34 | - section: "Deployment"
35 | contents:
36 | - href: qpyodide-deployment-templates.qmd
37 | text: Templates
38 | - section: "Extra Information"
39 | contents:
40 | - href: qpyodide-release-notes.qmd
41 | text: Release Notes
42 | - href: qpyodide-acknowledgements.md
43 | text: Acknowledgements
44 | - href: https://quarto.thecoatlessprofessor.com/pyodide/tests/
45 | text: Test Suite
46 | announcement:
47 | icon: info-circle
48 | dismissable: true
49 | content: "Looking for the official Quarto WebAssembly backend? Check out [`quarto-live`](https://github.com/r-wasm/quarto-live)!"
50 | type: primary
51 | body-footer: |
52 | :::{.callout-important}
53 | This Quarto extension is open source software and is **not affiliated with** Posit, Quarto, or Pyodide. The extension is at best a community effort to simplify the integration of Pyodide inside of Quarto generated documents.
54 | :::
55 |
56 | filters:
57 | - pyodide
--------------------------------------------------------------------------------
/docs/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Hello from quarto-pyodide!
3 | subtitle: Unleash Interactive Python in Your Quarto Documents
4 | format: html
5 | filters:
6 | - pyodide
7 | ---
8 |
9 | Welcome to the documentation site for the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) extension – your key to unlocking the endless possibilities of [Pyodide](https://pyodide.org/en/stable/) within various [Quarto](https://quarto.org/) formats, including [HTML](https://quarto.org/docs/output-formats/html-basics.html), [RevealJS](https://quarto.org/docs/presentations/revealjs/), [Websites](https://quarto.org/docs/websites/), [Blogs](https://quarto.org/docs/websites/website-blog.html), and [Books](https://quarto.org/docs/books).
10 |
11 | Ready for an exciting journey into the world of Pyodide's interactive code cells? Click the "Run Code" button below to experience it firsthand:
12 |
13 | ```{pyodide-python}
14 | # Welcome to Python!
15 |
16 | print("Hello, Quarto-Pyodide!")
17 | print("You've just entered the world of Python programming.")
18 |
19 | # Let's make a graph
20 | import matplotlib.pyplot as plt
21 | plt.plot([1, 2, 3, 4], [1, 4, 9, 16])
22 | plt.ylabel('Magic Numbers')
23 | plt.show()
24 |
25 | # Feel free to add your own code below and have fun with Python!
26 | ```
27 |
28 | At its core, the [`quarto-pyodide` extension](https://github.com/coatless-quarto/pyodide) is designed to empower you to run _Python_ code directly in your web browser using familiar reporting tools, all without the need for an external Python server. Moreover, the extension abstracts away the need to know HTML or JavaScript to use Pyodide. Just write Python code like you usually would! Though, it's worth noting that you can also choose to unlock the full potential of Pyodide and create more complex applications independently by directly using [Pyodide's JavaScript API](https://pyodide.org/en/stable/), granting you unparalleled freedom to harness the power of Python in innovative ways.
29 |
--------------------------------------------------------------------------------
/docs/qpyodide-acknowledgements.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Acknowledgments and Collaborations"
3 | subtitle: "Bringing Pyodide to Quarto"
4 | date: "08-12-2023"
5 | date-modified: last-modified
6 | format:
7 | html:
8 | toc: true
9 | ---
10 |
11 | We would like to express our heartfelt gratitude to several individuals and teams who played a pivotal role in bringing [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) to life as an extension for Quarto. Without their dedication and contributions, this project would not have been possible.
12 |
13 | ## quarto-pyodide Extension Developers
14 |
15 | - [James Joseph Balamuta](https://github.com/coatless)
16 |
17 | ## pyodide Developers
18 |
19 | - [Michael Droettboom](https://github.com/mdboom)
20 | - [Pyodide Contributors](https://zenodo.org/records/5156931)
21 |
22 | ## Quarto Team Assistance
23 |
24 | We would like to acknowledge the Quarto team for their assistance.
25 |
26 | ## Inspirations
27 |
28 | Our project built upon the initial proof of concept for a standalone Quarto HTML document, made possible by the work of the [coatless-r-n-d/pyodide-quarto-demo](https://github.com/coatless-r-n-d/pyodide-quarto-demo) repository.
29 |
30 | We would also like to thank [George Stagg](https://github.com/georgestagg) and his work on [webR](https://webr.r-wasm.org/), which greatly contributed to moving forward with the [`quarto-webr`](https://github.com/coatless/quarto-webr) extension that allowed interactive R code inside of Quarto.
31 |
32 | # Fin
33 |
34 | Once again, thank you to everyone who contributed, collaborated, and provided support throughout this project. Your dedication and expertise have made this achievement possible.
--------------------------------------------------------------------------------
/docs/qpyodide-code-cell-demo.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Python Interactive Code Cells"
3 | subtitle: "Demos showing interactive use cases of Python in Your Quarto Documents"
4 | author: "James Balamuta"
5 | date: "08-08-2023"
6 | format:
7 | html:
8 | toc: true
9 | ---
10 |
11 | Welcome to the world of interactive code cells, unlocked by the `quarto-pyodide` extension.
12 | These cells allow you to run Python code directly within your Quarto HTML documents, enabling real-time computations and more. Let's explore the impressive capabilities `pyodide` offers.
13 | pyodide-enabled code cell are established by using `{pyodide-python}` in a Quarto HTML document.
14 |
15 | # Creating pyodide-Enabled Code Cells
16 |
17 | To create a pyodide-enabled code cell, simply use the `{pyodide-python}` tag in your Quarto HTML document, like this:
18 |
19 | ```{pyodide-python}
20 | 1 + 1
21 | ```
22 |
23 | For example, the code cell above, powered by `pyodide`, was generated by entering the following into the Quarto document:
24 |
25 | ```{{pyodide-python}}
26 | 1 + 1
27 | ```
28 |
29 | # Sample Use Cases
30 |
31 | Now, let's delve into some practical scenarios where interactive code cells shine.
32 |
33 | ## Sample Calculations
34 |
35 | Let's start off with a quick calculation
36 |
37 | ```{pyodide-python}
38 | 1 + 1
39 | ```
40 |
41 | ## Strings
42 |
43 | Viewing string data
44 |
45 | ```{pyodide-python}
46 | greet = 'Hello'
47 | greet
48 | ```
49 |
50 | ## Retrieving prior objects
51 |
52 | Checking string length
53 |
54 | ```{pyodide-python}
55 | len(greet)
56 | ```
57 |
58 |
59 | ## Line-by-line Execution
60 |
61 | In this section, we'll explore the built-in keyboard shortcuts for executing code within the interactive code cell. You can run either the selected code or specific lines or the entire cell with the following keyboard shortcuts:
62 |
63 | - Run selected code:
64 | - macOS: ⌘ + ↩/Return
65 | - Windows/Linux: Ctrl + ↩/Enter
66 |
67 | - To run the entire code cell, you can simply click the "Run code" button, or use the keyboard shortcut:
68 | - Shift + ↩
69 |
70 | Feel free to try it out in the following code cell:
71 |
72 | ```{pyodide-python}
73 | # Try running selected code at the start of the line
74 | print("Hello quarto-pyodide World!")
75 |
76 | # Try highlight only -3 or 5 and pressing the keys required
77 | # for the "selected code" approach
78 | -3 + 5
79 |
80 | # Finally, try running the entire code cell by using Shift + ↩
81 | ```
82 |
83 | By using these shortcuts, you can run code conveniently and efficiently. This practice can also help you become familiar with keyboard shortcuts when transitioning to integrated development environments (IDEs) like [RStudio](https://posit.co/products/open-source/rstudio/) or [Visual Studio Code with Python](https://code.visualstudio.com/docs/languages/python).
84 |
85 |
86 | ## Preventing Modifications to Code
87 |
88 | Code cells can be locked to their initial state by specifying `#| read-only: true`.
89 |
90 | ::: {.panel-tabset}
91 | ## `{quarto-pyodide}` Output
92 |
93 | ```{pyodide-python}
94 | #| read-only: true
95 | 1 + 1
96 | ```
97 |
98 | ## Cell code
99 |
100 | ```{{pyodide-python}}
101 | #| read-only: true
102 | 1 + 1
103 | ```
104 | :::
105 |
106 |
107 | ## Define and Call Functions
108 |
109 | Functions can be defined in one cell and called.
110 |
111 | ```{pyodide-python}
112 | def square(x):
113 | return x**2
114 |
115 | square(8)
116 | ```
117 |
118 | Similarly, they persist to other cells.
119 |
120 | ```{pyodide-python}
121 | num_list = [1, 2, 3]
122 | [square(num)for num in num_list]
123 | ```
124 |
125 |
126 | ## Load a package
127 |
128 | There are two types of Python packages that will work within an interactive cell.
129 |
130 | - pure Python packages
131 | - denoted by `*py3-none-any.whl` on PyPI
132 | - Python packages compiled for Pyodide
133 | - denoted by `*-cp310-cp310-emscripten_3_1_27_wasm32.whl` that require a specific Python and Emscripten versions
134 |
135 | The latter option makes up part of the Pyodide "core" or "base" set of Python packages.
136 |
137 | :::{.callout-important}
138 | Not all functionality of a Python package may be available in the browser due to limitations or different versions being present.
139 | :::
140 |
141 | ### Loading a Pyodide core package
142 |
143 | For packages that are part of [Pyodide core](https://pyodide.org/en/stable/usage/packages-in-pyodide.html), we've enabled _dynamic_ package detection to handle importing packages into the environment. The _dynamic_ part comes from detecting whether a Python package is being used through an import statement and automatically taking care of the installation process behind the scenes.
144 |
145 | :::{.callout-note}
146 | Importing a package for the first time will require more time. Subsequent import statements will be resolve quicker.
147 | :::
148 |
149 | ```{pyodide-python}
150 | import pandas as pd
151 |
152 | df = pd.DataFrame({
153 | 'Name': ['JJB', 'H', 'Alex', 'Steve'],
154 | 'Age': [18, 25, 33, 42]
155 | })
156 |
157 | df
158 | ```
159 |
160 | ```{pyodide-python}
161 | import pandas as pd
162 |
163 | df.Age
164 | ```
165 |
166 | ### Loading non-core Pyodide Python Packages
167 |
168 | In the above example, everything just worked as `pandas` in available as part of [Pyodide's built-in packages](https://pyodide.org/en/stable/usage/packages-in-pyodide.html). However, if we need a package that is not part of the built-in list, then there either needs to be a pure Python wheel (no compiled code present) or a specially compiled version of the Package for Python.
169 |
170 | In this case, we can install the `palmerpenguins` package from PyPI with:
171 |
172 | ```{pyodide-python}
173 | await micropip.install([
174 | "palmerpenguins",
175 | "setuptools" # dependency
176 | ])
177 | ```
178 |
179 | Then, we have:
180 |
181 | ```{pyodide-python}
182 | # Core pyodide Python package
183 | import pandas as pd
184 |
185 | # External Python package
186 | from palmerpenguins import load_penguins
187 |
188 | # Load data in the package
189 | penguins = load_penguins()
190 |
191 | # Display the first 5 rows of the data using Pandas
192 | penguins.head()
193 | ```
194 |
195 | ## Graphing
196 |
197 | We provide support for generating graphs through the interactive HTML5 backend, e.g. [`module://matplotlib_pyodide.html5_canvas_backend`](https://github.com/pyodide/matplotlib-pyodide). At the end of each graph call, you must include a `plt.show()` call for the graph to render.
198 |
199 | ```{pyodide-python}
200 | import matplotlib.pyplot as plt
201 |
202 | x = [1, 5, 3, -2]
203 | y = [-5, 8, 9, 4]
204 |
205 | plt.plot(x, y)
206 | plt.show()
207 | ```
208 |
209 | ## External Data
210 |
211 | Interactive cells also allow for a limited subset of operations when working with external data. For example, we can use Pandas' `read_csv()` function to ingest data from a URL.
212 | ```{pyodide-python}
213 | import pandas as pd
214 |
215 | df = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv")
216 | df.head()
217 | ```
--------------------------------------------------------------------------------
/docs/qpyodide-deployment-templates.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Deployment Templates"
3 | date: "02-19-2024"
4 | date-modified: last-modified
5 | ---
6 |
7 | Discover a range of sample deployment templates for the `quarto-pyodide` extension at our GitHub repository [here](https://github.com/coatless-quarto/pyodide/tree/main/examples). These templates serve as convenient starting points for various web-based projects that harness the power of the `quarto-pyodide` extension, enabling interactive Python code cells inside of a web browser. Whether you're embarking on an individual report, creating an interactive website, or compiling a digital book, these templates simplify the process, making it effortless to kickstart your own projects.
8 |
9 | # HTML Document Template
10 |
11 | This template is designed for creating standalone HTML documents with interactive Pyodide functionality. It's suitable for individual reports or interactive documents.
12 |
13 | - **Example**: You can find an example of an HTML document template [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/html-document/).
14 | - **Source Code**: Access the source code for this template [here](https://github.com/coatless-quarto/pyodide/tree/main/examples/html-document).
15 |
16 | # RevealJS Presentation Template
17 |
18 | This template is designed for creating standalone RevealJS presentations with interactive Pyodide functionality. It's suitable for creating lecture slides.
19 |
20 | - **Example**: You can find an example of a RevealJS presentation template [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/revealjs/).
21 | - **Source Code**: Access the source code for this template [here](https://github.com/coatless-quarto/pyodide/tree/main/examples/revealjs).
22 |
23 | # Website Template
24 |
25 | This template is meant for building interactive websites with multiple Pyodide-powered pages. It's ideal for websites that have multiple piece of web content that requires interactive data analysis at the "top-level".
26 |
27 | - **Example**: Explore an example of a website template [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/website/).
28 | - **Source Code**: Access the source code for this template [here](https://github.com/coatless-quarto/pyodide/tree/main/examples/website).
29 |
30 | # Blog Template
31 |
32 | For users who want to periodically use Pyodide on their Quarto blog, please use the following template.
33 |
34 | - **Example**: Explore an example of a blog website template [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/blog/).
35 | - **Source Code**: Access the source code for this template [here](https://github.com/coatless-quarto/pyodide/tree/main/examples/blog).
36 |
37 | # Book Template
38 |
39 | The book template is designed for creating interactive web-based books or documentation. It allows you to compile a collection of chapters, sections, and interactive content into a cohesive digital book.
40 |
41 | - **Example**: You can view an example of a book template [here](https://quarto.thecoatlessprofessor.com/pyodide/examples/book).
42 | - **Source Code**: Access the source code for this template [here](https://github.com/coatless-quarto/pyodide/tree/main/examples/book).
--------------------------------------------------------------------------------
/docs/qpyodide-dev-notes.qmd:
--------------------------------------------------------------------------------
1 | - Pyodide
2 | - [Using Pyodide](https://pyodide.org/en/stable/usage/index.html)
3 | - Quarto Extensions
4 | - [`quarto-ext/shinylive`](https://github.com/quarto-ext/shinylive)
5 | - [`mcanouil/quarto-elevator`](https://github.com/mcanouil/quarto-elevator)
6 | - [`shafayetShafee/downloadthis`](https://github.com/shafayetShafee/downloadthis/tree/main)
7 | - Quarto Documentation
8 | - [Filters Documentation](https://quarto.org/docs/extensions/filters.html)
9 | - [Lua Development Tips](https://quarto.org/docs/extensions/lua.html)
10 | - [Lua API](https://quarto.org/docs/extensions/lua-api.html)
11 | - Pandoc Documentation
12 | - [Example Filters](https://pandoc.org/lua-filters.html#examples)
13 | - [CodeBlock](https://pandoc.org/lua-filters.html#type-codeblock)
14 | - matplotlib patches
15 | - [`gzuidhof/starboard-notebook` graphics patch](https://github.com/gzuidhof/starboard-notebook/blob/4127a5991399532f496da225ecc4ffcc27aa5529/packages/starboard-python/src/pyodide/matplotlib.ts)
16 | - [`jupyterlite/pyodide-kernel` graphics patch](https://github.com/jupyterlite/pyodide-kernel/blob/395525f14b827968cb89a0e507123ae2932d399a/packages/pyodide-kernel/py/pyodide-kernel/pyodide_kernel/patches.py)
17 |
--------------------------------------------------------------------------------
/docs/qpyodide-faq.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Frequently Asked Questions"
3 | date: "02-09-2024"
4 | date-modified: last-modified
5 | engine: markdown
6 | format:
7 | html:
8 | toc: true
9 | ---
10 |
11 | Welcome to our Frequently Asked Questions (FAQ) page, your go-to resource for finding answers to common queries about the `quarto-pyodide` extension. If you can't find the answer you're looking for, don't hesitate to reach out to our community for additional support by opening a [question](https://github.com/coatless-quarto/pyodide/issues/new?assignees=&labels=q%26a&projects=&template=question.yml&title=%5BQ%26A%5D%3A+) or a [bug report](https://github.com/coatless-quarto/pyodide/issues/new?assignees=&labels=bug%2Ctriage-needed&projects=&template=bug-report.yml&title=%5BBug%5D%3A+) on the [issue tracker](https://github.com/coatless-quarto/pyodide/issues).
12 |
13 | [quarto]: https://quarto.org/
14 | [python]: https://www.python.org/
15 | [pyodide]: https://pyodide.org/en/stable/
16 | [pyscript]: https://pyscript.net/
17 | [thebe]: https://thebe.readthedocs.io/en/stable/
18 | [html]: https://quarto.org/docs/output-formats/html-basics.html
19 | [revealjs]: https://quarto.org/docs/presentations/revealjs/
20 | [websites]: https://quarto.org/docs/websites/
21 | [books]: https://quarto.org/docs/books/
22 |
23 | # General Information
24 |
25 | ## What is Quarto?
26 |
27 | [Quarto][quarto] is a versatile, open-source scientific and technical publishing system. Documents can be authored that
28 | contain prose alongside of dynamic content generated by running Python, R, Julia, and Observable code.
29 |
30 | ## What is the `quarto-pyodide` extension for Quarto?
31 |
32 | The `quarto-pyodide` extension is a tool designed to embed interactive [Python cells][python] within an HTML document using [Pyodide][pyodide]. This is possible because Pyodide is a Python runtime that runs entirely in the browser.
33 |
34 | ---
35 |
36 | ## How Quarto Extension Works
37 |
38 | ### How does the Quarto extension work?
39 |
40 | The Quarto extension works by providing a convenient syntax to embed Python code cells directly within a Quarto document. These Python cells are then executed using Pyodide, and the results are displayed directly in the HTML document. This allows for interactive and dynamic content creation.
41 |
42 | ### What are the key features of the Quarto extension?
43 |
44 | The key features of the Quarto extension include:
45 |
46 | - Seamless integration of Python code within an HTML document.
47 | - Interactive execution of Python cells using Pyodide.
48 | - Real-time display of Python code outputs, including plots and visualizations.
49 | - Support for data analysis, scientific computing, and interactive storytelling.
50 |
51 | ## Installation
52 |
53 | ### How do I install the Quarto extension?
54 |
55 | To install the Quarto extension, please see our installation page.
56 |
57 | ## Packages
58 |
59 | ### Can I use external libraries and packages in my Python cells?
60 |
61 | Yes, you can use external libraries and packages within your Python cells. Pyodide includes a wide range of [pre-installed libraries](https://pyodide.org/en/stable/usage/packages-in-pyodide.html). These libraries can be downloaded and used on the fly once they are detected in an `import` statement.
62 |
63 | You can also install additional packages by using the `micropip` package loaded at the start of each Pyodide session. These packages can either be pure Python packages (denoted by `*py3-none-any.whl` on PyPI) or Python packages compiled for Pyodide (denoted by `*-cp310-cp310-emscripten_3_1_27_wasm32.whl` that require a specific Python and Emscripten versions). For instance, the following will download and install the [`seaborn`](https://seaborn.pydata.org/) visualization library.
64 |
65 | ```python
66 | import micropip
67 | micropip.install("seaborn")
68 | ```
69 |
70 | Once done, please make sure to import the package:
71 |
72 | ```python
73 | import seaborn as sns
74 | ```
75 |
76 | More details can be found on Pyodide's [Loading package](https://pyodide.org/en/stable/usage/loading-packages.html#installing-wheels-from-arbitrary-urls) documentation.
77 |
78 |
79 | ## Sharing Documents
80 |
81 | ### Can I share my Quarto document with others who may not have Python installed?
82 |
83 | Yes, one of the advantages of using Pyodide is that it runs entirely in the browser, eliminating the need for users to have Python installed locally. You can easily share your Quarto document with others, and they can interact with the embedded Python cells directly in their web browser.
84 |
85 | ## Documentation and Resources
86 |
87 | ### Where can I find more documentation and examples for the Quarto extension?
88 |
89 | You can find additional documentation and examples on this website. Moreover, you can learn more about Quarto by visiting their documentation website at [Quarto][quarto]. Additionally, you can explore the [Pyodide][pyodide] documentation for more information on Python in the browser.
90 |
91 |
92 | ## Limitations and Considerations
93 |
94 | ### Are there any limitations to using Pyodide with the Quarto extension?
95 |
96 | While Pyodide is a powerful tool, there are some limitations. Not all Python packages are available in Pyodide, and performance may vary for computationally intensive tasks. However, for a wide range of data analysis and visualization tasks, Pyodide and the Quarto extension provide a robust solution.
97 |
98 | ### How does the Quarto extension differ from other similar libraries like pyscript and Thebe?
99 |
100 | While [Pyscript][pyscript] and [Thebe][thebe] also provide solutions for embedding interactive code in HTML documents, the Quarto extension distinguishes itself by leveraging [Pyodide][pyodide] directly. When compared to Thebe, the Pyodide approach places the computation on the users computer through additional data needing to be downloaded. With respect to Pyscript, the pyodide approach simplifies the feature set present to allow for greater customization. The choice between these tools may depend on specific use cases and preferences.
101 |
--------------------------------------------------------------------------------
/docs/qpyodide-first-steps.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Making your first Pyodide-powered Quarto document"
3 | author: "James Joseph Balamuta"
4 | date: "02-19-2024"
5 | date-modified: last-modified
6 | format:
7 | html:
8 | toc: true
9 | filters:
10 | - pyodide
11 | ---
12 |
13 | ## Installation
14 |
15 | To use this extension in a [Quarto project](https://quarto.org/docs/projects/quarto-projects.html), install it from within the project's working directory by typing into **Terminal**:
16 |
17 | ``` bash
18 | quarto add coatless-quarto/pyodide
19 | ```
20 |
21 | :::callout-note
22 | Quarto extensions are project-specific installations and are not stored in a global library. This means that for every new Quarto project or directory where you create a Quarto Document, you'll need to install the extension again.
23 | :::
24 |
25 | ## Usage
26 |
27 | Once the extension is successfully installed, you can begin utilizing it in your Quarto documents located within the same working directory as the `_extensions` folder. To activate the `Pyodide` functionality in those documents, follow these steps:
28 |
29 | 1. **Add `pyodide` Filter**: In the header of your Quarto document, add the `pyodide` filter to the list of filters:
30 |
31 | ```yaml
32 | filters:
33 | - pyodide
34 | ```
35 |
36 | 2. **Use `{pyodide-python}` Code Blocks**: Write your Python code within code blocks marked with `{pyodide-python}`. Here's an example:
37 |
38 | ````markdown
39 | ---
40 | title: Pyodide in Quarto HTML Documents
41 | format: html
42 | filters:
43 | - pyodide
44 | ---
45 |
46 | This is a Pyodide-enabled code cell in a Quarto HTML document.
47 |
48 | ```{pyodide-python}
49 | n = 5
50 | while n > 0:
51 | print(n)
52 | n = n - 1
53 |
54 | print('Blastoff!')
55 | ```
56 | ````
57 |
58 | 3. **Render Your Document**: You can now render your Quarto document by clicking on the **Preview** button in the upper right side of the markdown editor window (or use the keyboard shortcut ⇧⌘K on macOS or Ctrl+Shift+K on Windows/Linux). The document will execute under `engine: jupyter` by default, but you can specify a different engine like `knitr` if needed.
59 |
60 | :::callout-note
61 | If an engine is not specified, Quarto will attempt to use the `jupyter` compute engine by default. This may cause an error if `jupyter` is not installed on your computer.
62 | :::
63 |
64 | 4. **Explore interactivity**: Try out the rendered code cell by pressing the "Run Code" button or using a keyboard shortcut of Shift + ↩.
65 |
66 | ```{pyodide-python}
67 | n = 5
68 | while n > 0:
69 | print(n)
70 | n = n - 1
71 |
72 | print('Blastoff!')
73 | ```
74 |
75 | # Fin
76 |
77 | In summary, this guide has provided an overview of how to incorporate the `quarto-pyodide` extension into your Quarto HTML documents using VS Code. We explored key workflow changes necessary for incorporating interactive Python code into your Quarto HTML documents, from installation to document rendering.
78 |
79 | For your next steps consider looking at different use cases for [interactive options](qpyodide-code-cell-demo.qmd).
--------------------------------------------------------------------------------
/docs/qpyodide-release-notes.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Release Notes"
3 | date: "08-12-2023"
4 | date-modified: last-modified
5 | engine: markdown
6 | format:
7 | html:
8 | toc: true
9 | ---
10 |
11 | [pyodide]: https://pyodide.org/en/stable/
12 | [python]: https://www.python.org/
13 | [quarto]: https://quarto.org/
14 |
15 |
16 | # 0.0.1.dev-3: ??? (??-??-????)
17 |
18 | ## Features
19 |
20 | - Updated the version of Pyodide from 0.26.2 to 0.27.2 ([#26](https://github.com/coatless-quarto/pyodide/pull/26))
21 | - New code cell option that set the interactive cell to be read-only. ([#4](https://github.com/coatless-quarto/pyodide/issues/4))
22 |
23 | ## Changes
24 |
25 | - Updated the version of Pyodide from 0.25.0 to 0.26.1 ([#20](https://github.com/coatless-quarto/pyodide/pull/20))
26 | - We now load the `micropip` and `pyodide_http` packages during document initialization.
27 | - `micropip` package allows for installation of pure Python or Pyodide-compiled Python packages.
28 | ([#3](https://github.com/coatless-quarto/pyodide/issues/3))
29 | - `pyodide_http` provides the necessary shims to ensure uses of `requests` and `urllib3` are
30 | able to be processed instead of returning a URL error. ([#9](https://github.com/coatless-quarto/pyodide/issues/9))
31 |
32 | # 0.0.1: What does the Python Say? (02-19-2024)
33 |
34 | ## Features
35 |
36 | - Enable an interactive [Python][python] code cells using [Pyodide][pyodide] inside of a [Quarto][quarto] document.
37 |
38 | ````md
39 | This is a pyodide code cell in a Quarto document.
40 |
41 | ```{pyodide-python}
42 | def say_hello(name):
43 | print(f"Hello there {name}"!)
44 | ```
45 | ````
46 |
47 | - Execute code in the code cell using keyboard shortcuts:
48 | - Run selected code using: `[Cmd + Enter]` on macOS or `[Ctrl+Enter]` on Windows
49 | - Run the entire code area using: `[Shift+Enter]`
50 |
51 | ## Documentation
52 |
53 | - Provided an extension documentation website at:
54 | - Included several [deployment templates](https://quarto.thecoatlessprofessor.com/pyodide/qpyodide-deployment-templates.html).
55 |
56 | # 0.0.0-dev.0: One Python (08-12-2023)
57 |
58 | ## Features
59 |
60 | - An initial proof of concept of the code cell.
--------------------------------------------------------------------------------
/examples/blog/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
2 | /_site/*
3 |
--------------------------------------------------------------------------------
/examples/blog/_extensions:
--------------------------------------------------------------------------------
1 | ../../_extensions
--------------------------------------------------------------------------------
/examples/blog/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 |
4 | website:
5 | title: "My Awesome {quarto-pyodide} Blog"
6 | navbar:
7 | right:
8 | - about.qmd
9 | - icon: github
10 | href: https://github.com/
11 | - icon: twitter
12 | href: https://twitter.com
13 | format:
14 | html:
15 | theme: cosmo
16 | css: styles.css
17 |
18 | # Enable Pyodide on all post pages
19 | filters:
20 | - pyodide
21 |
--------------------------------------------------------------------------------
/examples/blog/about.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "About"
3 | image: profile.jpg
4 | about:
5 | template: jolla
6 | links:
7 | - icon: github
8 | text: GitHub
9 | href: https://github.com/coatless-quarto/pyodide
10 | ---
11 |
12 | This blog is a demo showing how to use the `quarto-pyodide` extension.
13 |
--------------------------------------------------------------------------------
/examples/blog/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "blog"
3 | listing:
4 | contents: posts
5 | sort: "date desc"
6 | type: default
7 | categories: true
8 | sort-ui: false
9 | filter-ui: false
10 | page-layout: full
11 | title-block-banner: true
12 | ---
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/blog/posts/_metadata.yml:
--------------------------------------------------------------------------------
1 | # options specified here will apply to all posts in this folder
2 |
3 | # freeze computational output
4 | # (see https://quarto.org/docs/projects/code-execution.html#freeze)
5 | freeze: true
6 |
7 | # Enable banner style title blocks
8 | title-block-banner: true
9 |
--------------------------------------------------------------------------------
/examples/blog/posts/embed-slides/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coatless-quarto/pyodide/002cec50c92dbbd7de3ce07f43cd0ab821fe0c4b/examples/blog/posts/embed-slides/image.jpg
--------------------------------------------------------------------------------
/examples/blog/posts/embed-slides/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Post Containing Slides with Interactive Python Code"
3 | author: "James Balamuta"
4 | date: "2024-02-19"
5 | categories: [news, code, analysis]
6 | image: "image.jpg"
7 | ---
8 |
9 | This is a post that contains slides with interactive code on a [Quarto Blog](https://quarto.org/docs/websites/website-blog.html) through the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) extension. The configuration setup for the `quarto-pyodide` extension is taken care of in the `_quarto.yml` file to avoid needing to re-specify options multiple times.
10 |
11 | ## Presentation
12 |
13 |
20 |
21 |
22 | ```{=html}
23 |
24 | ```
25 |
26 |
27 | ## Embed Code
28 |
29 | Place the following code inside of the Quarto Document:
30 |
31 | ````html
32 |
39 |
40 |
41 | ```{=html}
42 |
43 | ```
44 |
45 | ````
46 |
--------------------------------------------------------------------------------
/examples/blog/posts/post-with-code/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coatless-quarto/pyodide/002cec50c92dbbd7de3ce07f43cd0ab821fe0c4b/examples/blog/posts/post-with-code/image.jpg
--------------------------------------------------------------------------------
/examples/blog/posts/post-with-code/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Post With Interactive Code"
3 | author: "James Balamuta"
4 | date: "2024-03-12"
5 | categories: [news, code, analysis]
6 | image: "image.jpg"
7 | ---
8 |
9 | This is a post with interactive code on a [Quarto Blog](https://quarto.org/docs/websites/website-blog.html) through the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) extension. The configuration setup for the `quarto-pyodide` extension is taken care of in the `_quarto.yml` file to avoid needing to re-specify options multiple times.
10 |
11 | ```{pyodide-python}
12 | print("Hello {quarto-pyodide} blog world!")
13 | ```
14 |
--------------------------------------------------------------------------------
/examples/blog/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coatless-quarto/pyodide/002cec50c92dbbd7de3ce07f43cd0ab821fe0c4b/examples/blog/profile.jpg
--------------------------------------------------------------------------------
/examples/blog/styles.css:
--------------------------------------------------------------------------------
1 | /* css styles */
2 |
--------------------------------------------------------------------------------
/examples/book/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
2 |
--------------------------------------------------------------------------------
/examples/book/_extensions:
--------------------------------------------------------------------------------
1 | ../../_extensions/
--------------------------------------------------------------------------------
/examples/book/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: book
3 |
4 | book:
5 | title: "Sample quarto-pyodide Book Project"
6 | author: "JJB"
7 | date: today
8 | url: https://quarto.thecoatlessprofessor.com/pyodide/examples/book
9 | repo-url: https://github.com/coatless-quarto/pyodide/tree/main/examples/book
10 | repo-actions: edit
11 | search: true
12 | reader-mode: true
13 | sidebar:
14 | style: "docked"
15 | chapters:
16 | - index.qmd
17 | - part: "Exploring Python"
18 | chapters:
19 | - example-page.qmd
20 | - slide-embed.qmd
21 | - href: ../../
22 | text: Documentation Portal
23 | page-footer:
24 | left: "An example book with quarto-pyodide."
25 | right:
26 | - icon: github
27 | href: https://github.com/coatless-quarto/pyodide
28 |
29 | # Set the language that should be used for Quarto book
30 | # https://github.com/quarto-dev/quarto-cli/tree/main/src/resources/language
31 | # lang: en
32 |
33 | # Set default options for every webpage that may or may not include Pyodide.
34 | pyodide:
35 | show-startup-message: false # Display status of Pyodide initialization
36 |
37 | # Attach pyodide to every page
38 | filters:
39 | - pyodide
40 |
--------------------------------------------------------------------------------
/examples/book/example-page.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Example page"
3 | pyodide:
4 | show-startup-message: true # Display status of Pyodide initialization
5 | ---
6 |
7 | On this page, we set two options directly in the Quarto document. The rest of the options are coming from the `_quarto.yml` project file. These options are considered "global".
8 |
9 | Let's do a quick multi-cell example where we generate data and, then, create a graph. We'll use a modification of the [matplotlib `simple_plot` example](https://matplotlib.org/stable/gallery/lines_bars_and_markers/simple_plot.html).
10 |
11 | First, let's generate some data.
12 | ```{pyodide-python}
13 | import numpy as np
14 |
15 | # Data for plotting
16 | t = np.arange(0.0, 2.0, 0.01)
17 | s = 1 + np.sin(2 * np.pi * t)
18 |
19 | # Print t
20 | print(f"Displaying 10 observations of `t`:\n{t[:10]}\n")
21 |
22 | print("Showing the first 10 values of `s`:")
23 | # The last object in the cell does not need print.
24 | s[:10]
25 | ```
26 |
27 | Next, let's retrieve the `t` and `s` variables and create a graph using `matplotlib`.
28 |
29 | ```{pyodide-python}
30 | import matplotlib.pyplot as plt
31 |
32 | fig, ax = plt.subplots()
33 | ax.plot(t, s)
34 |
35 | ax.set(xlabel='time (s)', ylabel='voltage (mV)',
36 | title='About as simple as it gets, folks')
37 | ax.grid()
38 |
39 | plt.show()
40 | ```
41 |
42 | :::callout-important
43 | You must include a `plt.show()` call to have the graph be shown.
44 | :::
--------------------------------------------------------------------------------
/examples/book/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Quarto Pyodide Demo Book"
3 | ---
4 |
5 | # Welcome
6 |
7 | Welcome to a demo Book that uses the [`quarto-pyodide`](https://github.com/coatless/quarto-pyodide) extension to generate interactive code cells with [Quarto](https://quarto.org) and [Pyodide](https://pyodide.org/en/stable/).
8 |
9 | ```{pyodide-python}
10 | print("Hello there! Welcome to a {quarto-pyodide} powered book!")
11 | ```
12 |
13 |
--------------------------------------------------------------------------------
/examples/book/slide-embed.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Embed Slides"
3 | ---
4 |
5 | On this page, we show how we can embed a RevealJS Presentation inside of a Quarto Book.
6 |
7 | ## Presentation
8 |
9 |
16 |
17 |
18 | ```{=html}
19 |
20 |
21 | ```
22 |
23 |
24 | ## Embed Code
25 |
26 | Place the following code inside of the Quarto Document:
27 |
28 | ````html
29 |
36 |
37 |
38 | ```{=html}
39 |
40 | ```
41 |
42 | ````
--------------------------------------------------------------------------------
/examples/html-document/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
--------------------------------------------------------------------------------
/examples/html-document/_extensions:
--------------------------------------------------------------------------------
1 | ../../_extensions/
--------------------------------------------------------------------------------
/examples/html-document/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | title: "index"
3 |
--------------------------------------------------------------------------------
/examples/html-document/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "quarto-pyodide Demo HTML Document"
3 | format: html
4 | filters:
5 | - pyodide
6 | ---
7 |
8 | # Welcome
9 |
10 | Welcome to a demo HTML Document that uses the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) extension to generate interactive code cells with [Quarto](https://quarto.org) and [Pyodide](https://pyodide.org/en/stable/).
11 |
12 | ```{pyodide-python}
13 | print("Hello there! Welcome to a quarto-pyodide powered HTML Document!")
14 | ```
15 |
16 | Let's go back to the [documentation portal](../../)
17 |
--------------------------------------------------------------------------------
/examples/readme/_extensions:
--------------------------------------------------------------------------------
1 | ../../_extensions/
--------------------------------------------------------------------------------
/examples/readme/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | title: "index"
3 |
--------------------------------------------------------------------------------
/examples/readme/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Pyodide in Quarto HTML Documents
3 | format: html
4 | filters:
5 | - pyodide
6 | ---
7 |
8 | This is a pyodide-enabled code cell in a Quarto HTML document.
9 |
10 | ```{pyodide-python}
11 | n = 5
12 | while n > 0:
13 | print(n)
14 | n = n - 1
15 |
16 | print('Blastoff!')
17 | ```
18 |
19 | Return to the [documentation website](https://quarto.thecoatlessprofessor.com/pyodide) or [GitHub Repository](https://github.com/coatless-quarto/pyodide).
--------------------------------------------------------------------------------
/examples/revealjs/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
2 |
--------------------------------------------------------------------------------
/examples/revealjs/README.md:
--------------------------------------------------------------------------------
1 | # RevealJS Demo
2 |
3 | The RevealJS demo uses an external extension called `{quarto-countdown}`.
4 | In addition to installing the `{quarto-pyodide}` extension, you must also install the `countdown` shortcode extension.
5 |
6 | You can do this by running in your Quarto Project:
7 |
8 | ```sh
9 | # Install `quarto-countdown`
10 | quarto add gadenbuie/countdown/quarto
11 |
12 | # Install `quarto-pyodide`
13 | quarto add coatless-quarto/pyodide
14 | ```
15 |
--------------------------------------------------------------------------------
/examples/revealjs/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | title: "index"
3 |
--------------------------------------------------------------------------------
/examples/revealjs/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "{quarto-pyodide} Demo RevealJS Document"
3 | format: revealjs
4 | filters:
5 | - pyodide
6 | ---
7 |
8 | ## Welcome
9 |
10 | Welcome to a demo RevealJS presentation that uses the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) extension to generate interactive code cells with [Quarto](https://quarto.org) and [Pyodide](https://pyodide.org/en/stable/).
11 |
12 | Not the right template? Let's go back to the [documentation portal](../../)
13 |
14 | ## Pyodide in RevealJS
15 |
16 | This is a Pyodide-enabled code cell in a Quarto HTML document.
17 |
18 | ```{pyodide-python}
19 | n = 5
20 | while n > 0:
21 | print(n)
22 | n = n - 1
23 |
24 | print('Blastoff!')
25 | ```
26 |
27 | ## matplotlib Graphing with Pyodide
28 |
29 | ```{pyodide-python}
30 | import matplotlib.pyplot as plt
31 | import numpy as np
32 |
33 | x = np.linspace(0, 2*np.pi, 100)
34 | y = np.sin(x)
35 | plt.plot(x, y)
36 | plt.title('Sine wave')
37 | plt.show()
38 | ```
39 |
40 | ## quarto-{pyodide + countdown}
41 |
42 | Pair code cells with a [countdown timer](https://github.com/gadenbuie/countdown/tree/main/quarto) to allow for practice
43 |
44 | {{< countdown 00:05 top = 0 >}}
45 |
46 | Fill in the following function to say hi!
47 |
48 | ```{pyodide-python}
49 | def say_hello(name):
50 | _________(f"Hello there {name}!")
51 |
52 | say_hello("quarto-pyodide")
53 | ```
54 |
55 |
56 | ## Keyboard Shortcuts
57 |
58 | - Run selected code using either:
59 | - macOS: ⌘ + ↩/Return
60 | - Windows/Linux: Ctrl + ↩/Enter
61 | - Run the entire code by clicking the "Run code" button or pressing Shift+↩.
62 |
63 | ```{pyodide-python}
64 | print("Hello quarto-pyodide RevealJS world!")
65 |
66 | [x**2 for x in range(0, 5)]
67 |
68 | 3 + 5
69 | ```
70 |
71 | ## Fin
72 |
73 | Thanks for checking out the demo! Let's head back to
74 | the [documentation portal](../../).
--------------------------------------------------------------------------------
/examples/website/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
2 | /_site/*
3 |
--------------------------------------------------------------------------------
/examples/website/_extensions:
--------------------------------------------------------------------------------
1 | ../../_extensions/
--------------------------------------------------------------------------------
/examples/website/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 |
4 | website:
5 | title: "Demo Quarto Pyodide Website"
6 | search: true
7 | reader-mode: true
8 | navbar:
9 | left:
10 | - href: index.qmd
11 | text: Home
12 | - href: example-page.qmd
13 | text: Example page
14 | - href: slide-embed.qmd
15 | text: Embed Slides
16 | - href: ../../
17 | text: Documentation Portal
18 | sidebar:
19 | style: "floating"
20 | contents:
21 | - section: "Content"
22 | contents:
23 | - index.qmd
24 | - example-page.qmd
25 | - slide-embed.qmd
26 | - href: ../../
27 | text: Documentation Portal
28 |
29 | page-footer:
30 | left: "An example website with quarto-pyodide."
31 | right:
32 | - icon: github
33 | href: https://github.com/coatless-quarto/pyodide
34 |
35 | # Set the language that should be used for Quarto websites
36 | # https://github.com/quarto-dev/quarto-cli/tree/main/src/resources/language
37 | # lang: en
38 |
39 | # Set default options for every webpage that may or may not include Pyodide.
40 | pyodide:
41 | show-startup-message: false # Display status of Pyodide initialization
42 |
43 | # Attach Pyodide to every page
44 | filters:
45 | - pyodide
46 |
--------------------------------------------------------------------------------
/examples/website/example-page.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Example page"
3 | pyodide:
4 | show-startup-message: true # Display status of pyodide initialization
5 | ---
6 |
7 | On this page, we set two options directly in the Quarto document. The rest of the options are coming from the `_quarto.yml` project file. These options are considered "global".
8 |
9 | ```{pyodide-python}
10 | import pandas as pd
11 | import numpy as np
12 |
13 | df = pd.DataFrame(
14 | {
15 | "A": 1.0,
16 | "B": pd.Timestamp("20130102"),
17 | "C": pd.Series(1, index=list(range(4)), dtype="float32"),
18 | "D": np.array([3] * 4, dtype="int32"),
19 | "E": pd.Categorical(["test", "train", "test", "train"]),
20 | "F": "foo",
21 | }
22 | )
23 |
24 |
25 | df
26 | ```
--------------------------------------------------------------------------------
/examples/website/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Quarto pyodide Demo Website"
3 | ---
4 |
5 | # Welcome
6 |
7 | Welcome to a demo website that uses the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide) extension to generate interactive code cells with [Quarto](https://quarto.org) and [Pyodide](https://pyodide.org/en/stable/).
8 |
9 | ```{pyodide-python}
10 | print("Hello there! Welcome to a quarto-pyodide powered website!")
11 | ```
12 |
13 |
--------------------------------------------------------------------------------
/examples/website/slide-embed.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Embed Slides"
3 | ---
4 |
5 | On this page, we show how we can embed a RevealJS Presentation inside of a Quarto Website.
6 |
7 | ## Presentation
8 |
9 |
16 |
17 |
18 | ```{=html}
19 |
20 | ```
21 |
22 |
23 | ## Embed Code
24 |
25 | Place the following code inside of the Quarto Document:
26 |
27 | ````html
28 |
35 |
36 |
37 | ```{=html}
38 |
39 | ```
40 |
41 | ````
42 |
--------------------------------------------------------------------------------
/logo-quarto-pyodide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coatless-quarto/pyodide/002cec50c92dbbd7de3ce07f43cd0ab821fe0c4b/logo-quarto-pyodide.png
--------------------------------------------------------------------------------
/pyodide.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ],
7 | "settings": {}
8 | }
--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------
1 | /.quarto/
2 | *_site/
--------------------------------------------------------------------------------
/tests/_extensions:
--------------------------------------------------------------------------------
1 | ../_extensions
--------------------------------------------------------------------------------
/tests/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 |
4 | website:
5 | title: "Test Suite for quarto-pyodide"
6 | search: true
7 | reader-mode: true
8 | sidebar:
9 | style: "floating"
10 | contents:
11 | - href: index.qmd
12 | text: Home
13 | - href: ../../
14 | text: Main Website
15 | - section: "Tests"
16 | contents:
17 | - auto: "*test*.qmd"
18 |
19 |
20 | page-footer:
21 | left: "Test suite for quarto-pyodide."
22 | right:
23 | - icon: github
24 | href: https://github.com/coatless-quarto/pyodide
25 |
26 | # Set the language that should be used for Quarto websites
27 | # https://github.com/quarto-dev/quarto-cli/tree/main/src/resources/language
28 | # lang: en
29 |
30 | # Attach pyodide to every page
31 | filters:
32 | - pyodide
--------------------------------------------------------------------------------
/tests/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "quarto-pyodide Test Website"
3 | subtitle: "Extension Test Suite"
4 | ---
5 |
6 | # Welcome
7 |
8 | Welcome to the test suite website for the [`quarto-pyodide`](https://github.com/coatless-quarto/pyodide). This website is meant to examine how different features of the extension are working or not working across different browsers.
9 |
10 | ```{pyodide-python}
11 | print("Welcome to behind the scenes of quarto-pyodide!")
12 | ```
--------------------------------------------------------------------------------
/tests/qpyodide-test-graphic-output.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Test: Graphics Output"
3 | format: html
4 | filters:
5 | - pyodide
6 | ---
7 |
8 | This webpage tests the interactive and output contexts for showing a graph.
9 |
10 | :::callout-important
11 | A call to `matplotlib.pyplot.show()` is required for displaying the graphic.
12 | :::
13 |
14 | ## Interactive
15 |
16 | ```{pyodide-python}
17 | import matplotlib.pyplot as plt
18 |
19 | x = [1, 5, 3, -2]
20 | y = [-5, 8, 9, 4]
21 |
22 | plt.plot(x, y)
23 | plt.show()
24 | ```
25 |
--------------------------------------------------------------------------------
/tests/qpyodide-test-internal-cell.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Test: Cell Context Options"
3 | format: html
4 | filters:
5 | - pyodide
6 | ---
7 |
8 | Test page for verifying cell context options set explicitly with `context`.
9 |
10 | ## Interactive
11 |
12 | ### Editable
13 | ```{pyodide-python}
14 | #| context: interactive
15 | 1 + 1
16 | ```
17 |
18 | ### Read-only
19 | ```{pyodide-python}
20 | #| context: interactive
21 | #| read-only: true
22 | 1 + 1
23 | ```
24 |
25 | ## Setup
26 |
27 | Hidden cell that sets `x` and `y` vector values.
28 |
29 | ```{pyodide-python}
30 | #| context: setup
31 | x = [1, 5, 3, -2]
32 | y = [-5, 8, 9, 4]
33 | ```
34 |
35 | ## Output
36 |
37 | Hidden cell that retrieves previously set `x` and `y` vector values and displays the data.
38 |
39 | ```{pyodide-python}
40 | #| context: output
41 | import matplotlib.pyplot as plt
42 |
43 | print(f"x: {x}")
44 |
45 | print(f"y: {y}")
46 |
47 | plt.plot(x, y)
48 | ```
--------------------------------------------------------------------------------
/tests/qpyodide-test-url.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Test: Accessing Data via URL"
3 | format: html
4 | filters:
5 | - pyodide
6 | ---
7 |
8 | This test is designed to check if HTTP requests are shimmed so that the operation can be performed.
9 |
10 | ```{pyodide-python}
11 | import pandas as pd
12 |
13 | df = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv")
14 | df.head()
15 | ```
--------------------------------------------------------------------------------
/tests/standalone/monaco-pyodide-dependency-bug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
20 |
21 | You can execute any Python code. Just enter something in the box below and
22 | click the button.
23 |