├── .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 | ![`quarto-pyodide` Filter in Action](https://i.imgur.com/gzAyV8H.gif) 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 |

24 | 25 | 26 |
27 |
28 |
Output:
29 | 30 | 31 | 32 | 68 | 69 | 72 |
73 | 74 |
75 | 76 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /update-dev-quarto.sh: -------------------------------------------------------------------------------- 1 | QUARTO_VERSION=1.4.506 2 | wget -O quarto-latest.deb https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.deb 3 | sudo dpkg -i ./quarto-latest.deb 4 | rm quarto-latest.deb 5 | --------------------------------------------------------------------------------