Code execution#
The exec directive allows executing code from the browser.
create table countries (
country text not null,
country_code text not null,
dial_code text not null,
capital text not null,
population int not null,
food text
);
insert into countries values
('Switzerland', 'CH', '+41', 'Bern', 8776000, 'fondue!'),
('France', 'FR', '+33', 'Paris', 67970000, null),
('Germany', 'DE', '+49', 'Berlin', 83800000, null),
('Italy', 'IT', '+39', 'Rome', 58940000, null),
('Austria', 'AT', '+43', 'Vienna', 9042000, 'Kaiserschmarrn'),
('Lichtenstein', 'LI', '+423', 'Vaduz', 39327, null);
Directive#
- {exec} runner#
This directive is similar to
code-block, but it allows executing the code in the browser, and optionally editing it.runneris one of the supported code runners (html,micropython,python,sql).execdirectives generate<tdoc-exec>elements.Options
- :after: name [name ...]#
Execute one or more
execblocks before this block, in the same environment.
- :class: [name ...] (IDs)#
A space-separated list of CSS classes to add to the
<tdoc-exec>element.
- :console-style: property: value; [property: value; ...]#
CSS styles to apply to console output generated by the code block, e.g.
max-height: 10rem;.
- :editor: [ID]#
Make the code editable. If
IDis provided, the content of the editor is saved, and restored on reload.IDmust be unique across all documents, e.g. a UUID. The valuenonedisables the editor.
- :editor-config: config#
Configuration for the editor, as a JSON5 object (without enclosing
{}). The following keys are supported:store: Where to store editor content.'local'(default): Store in browserlocalStorage.'cloud': Store in the cloud.
- :env: [name]#
The environment in which the code must be executed. Code executed in distinct environments is isolated from each other. The default environment name is the empty string.
- :include: path [path ...] (relative paths)#
Prepend the content of one or more files to the block's content.
- :linenos: [value] (true | false)#
When
trueor empty, display line numbers next to the code. The default istrueif theexecblock is editable, andfalseotherwise.
- :name: name#
A reference target for the directive, and a name to be referenced in
:after:and:then:options.
- :output-style: property: value; [property: value; ...]#
CSS styles to apply to the output generated by the code block, e.g.
height: 30rem;. To which element the styles are applied is runner-specific.
- :reset: value (show | hide | auto)#
Specify if the "Reset" button () should be visible (
show), hidden (hide, the default), or if it should be shown only when the initial editor content isn't empty (auto).
- :style: property: value; [property: value; ...]#
CSS styles to apply to the code block, e.g.
max-height: 20rem.
- :when: [trigger ...]#
Define the triggers that cause the block's code is executed. The arguments can include zero or more of the following values:
load: Execute the code when the page loads.click: Execute the code on user request.
The default is
click. When no triggers are specified, the code isn't executed, except as part of a sequence.
Trigger#
By default, exec blocks are executed on click
(:when: click), with controls displayed next to the
block. The controls displayed depend on the type of block.
select * from countries where country_code = 'LI';
They can also be executed immediately on load
(:when: load).
select * from countries where country_code = 'LI';
Both can be combined (:when: load click), to execute
immediately on load and on click.
1select * from countries where country_code = 'LI';
If no triggers are specified (an empty :when: value), the
block isn't executed on its own. It can still be executed as part of a
sequence.
Editor#
Blocks can be made editable with the :editor: option.
1select * from countries
2 where population > 10000000
3 order by country_code;
The option takes an optional editor ID. If provided, the content of the editor
is saved, and restored on page reload. Using the value none as the editor ID
disables the editor.
By default, the editor content is saved in browser local storage. This works for anonymous and logged-in users.
1select * from countries where country_code = 'LI';
Editor content can also be saved in remote storage, by adding store: 'cloud'
to :editor-config:. This makes editors fully
collaborative, i.e. the same text can be edited simultaneously from multiple
clients. This only works for logged-in users, and requires a permanent internet
connection.
1select * from countries where country_code = 'LI';
Warning
Remote storage is currently experimental.
Sequencing#
The :after: option allows referencing one or more
exec blocks on the same page to be executed before the block, in the
same environment. The referenced blocks can themselves have
:after: options, forming a dependency graph.
Similarly, the :then: option allows referencing one or
more exec blocks to be executed after the block. Unlike
:after:, only the blocks referenced by the
:then: option of the block itself are executed; the
:then: options of referenced blocks are ignored.
If a block appears more than once in the graph, only the first occurrence is executed.
-- :name: sql-people
create table people (first_name text not null, last_name text not null);
-- :name: sql-people-select
select * from people;
1-- :after: sql-people
2-- :then: sql-people-select
3insert into people values ('Joe', 'Bar'), ('Jack', 'Sparrow');
Include file content#
The :include: option allows including the content of
one or more external files. The content of the files is prepended to the block's
content.
create table people (name text not null, height real, favorite_food text);
insert into people values
('Joe', 1.83, null),
('Jack', 1.55, 'burgers'),
('Jim', null, 'pizza'),
('Anthony', 1.78, null);
select * from people;
Output#
The output of running a code block (if any) is displayed below the block. Depending on the runner, blocks can display text, images, or arbitrary HTML. The output can change dynamically.
Image coordinates#
When a block outputs an <svg> image or uses a <canvas>, the
coordinates within the element can be viewed by holding the Ctrl key and
moving the mouse pointer over the element. The displayed coordinates can be
pasted into an editor with Shift+Ctrl+X.
This functionality can be disabled by adding the no-coords class to the
exec directive.
Libraries#
tdoc/exec.js#
This module
(source)
provides functionality to support exec directives.
Classes
- class exec.ExecElement()#
The class implementing
<tdoc-exec>custom elements. It extendsTdocElement.Properties
- class exec.Runner()#
The base class for runners that control code execution in specific languages.
Properties
- Runner.[static] name#
The name of the runner, as specified in the first argument of the
execdirective.
- Runner.text#
The text content of the code block.
Methods
- Runner.[static] init(cls)#
Perform per-runner type initialization. This method is called once during runner registration.
- Runner.init()#
Perform per-runner initialization. This method is called on each
Runnerinstance, just after object construction.
- Runner.addControls(controls)#
Add execution controls to the blocks. This method is called during rendering of the directive, and allows subclasses to add language-specific controls.
- Arguments:
controls (
HTMLDivElement) -- The<div>element containing the controls.
- Runner.*codeBlocks()#
Return a generator that yields the code from the directives in the
:after:and:then:chain of the directive. Yields{node, code}objects, wherenodeis a<tdoc-exec>element in the chain, andcodeis the code contained in that element.
- Runner.run()#
This method runs the code of the directive.
- Runner.stop()#
This method stops code execution.
Runners#
HTML#
The {exec} html runner displays a complete HTML document as an
<iframe>, with limited browsing functionality. Console output produced by
console methods
is displayed below the rendered HTML.
Note
Adblockers can interfere with {exec} html blocks served by
the local server if they prevent access to localhost, for example uBlock
Origin with the "Block outsider intrusion into LAN" filter list. As a
workaround, disable the corresponding filter list.
This issue does not happen on the deployed site.
MicroPython#
The {exec} micropython runner connects to an embedded system
running MicroPython. The code can either be run from
RAM (transient, programs disappear on reset) or be written to the file main.py
in flash memory (permanent, runs at boot-time).
Note
The micropython runner only works on devices supporting the
WebSerial API.
Currently, this limits the use to
Chromium-based browsers
(e.g. Chrome, Edge).
The target device must already be programmed with a MicroPython firmware. The procedure depends on the target device type.
BBC micro:bit V2: Download the
.hexfile for the latest release. Connect the target and mount its filesystem, then copy the file to it.Raspberry Pi Pico: Follow the documentation to download the appropriate firmware file and program the device.
To enable connecting to a target device, it must first be paired with the browser. Connect the target via USB, select " Connect" in the "Tools" menu (), and select the device in the list. This needs to be done only once; once a device is paired, it will be connected automatically when the page loads, unless multiple paired devices are available.
To run a program from RAM, click the "Run" button (). Text input can be sent to the target via the input field, and the program can be interrupted with the "Stop" button (). When the program terminates, the target returns to the REPL and accepts further commands.
To write a program permanently to a target's flash memory, select
" Write to main.py"
in the "Tools" menu. The program can be removed again with
" Remove main.py".
Python#
The {exec} python runner executes code through
Pyodide. Each environment uses a distinct,
single-threaded interpreter, and all Python blocks specifying the same
environment execute on the same interpreter.
The interpreter for the main environment runs on the main browser thread.
Interpreter initialization, as well as blocking Python code, will therefore
block the main thread and affect rendering, user input, etc. It should be used
only when really necessary, e.g. for
code using Pygame. All other interpreters run in
their own web worker, and don't block user interactions.
Pyodide can be configured via the exec.python metadata. This
enables the following functionality:
Load packages: Packages outside of the standard library need to be loaded explicitly by listing them in the
packageskey. Each entry is either a package name or a URL referencing a wheel (a.whlfile). The packages in this list can be loaded by name; others must be loaded by URL or withmicropip.Copy files to the filesystem: The
fileskey is a mapping of URL to target path. Relative URLs are resolved relative to the_staticdirectory. Relative target paths are resolved relative to$HOME(/home/pyodide). If the target path ends with a/, the filename part of the URL is used as the target filename.Define environment variables: The
envkey is a mapping of environment variable name to value. This currently only works in themainenvironment.
exec:
python:
packages: [numpy]
files:
input.txt: # .../_static/input.txt => $HOME/input.txt
db/init.sql: /tmp/ # .../_static/db/init.sql => /tmp/init.sql
../index.html: homepage.html # .../index.html => $HOME/homepage.html
env:
DEBUG: true
The following conf.py options enable site-wide customization of Pyodide.
- tdoc_python_modules#
- Type:
list- Default:
[]
A list of directories, relative to the directory containing
conf.py, containing Python files to be loaded into Pyodide. A_pythonentry is added automatically if such a directory exists next toconf.py.
Load packages with micropip#
Packages can be installed directly from PyPI or other
package registries using micropip (which must
itself be added to the exec.python.packages metadata). The
installation should be performed only once per interpreter, e.g. by making it
conditional on a once() call.
if once('install-snowballstemmer'): # Install only once per interpreter
import micropip
await micropip.install(['snowballstemmer'])
import snowballstemmer
# ...
Note
Installing from PyPI introduces a serving dependency on PyPI servers. This
can reduce the availability of the site. It's also not very nice to the
operators of PyPI to drive traffic their way (though browser caching should
alleviate the issue). For high-traffic pages, the .whl files should be
included in the site's _static and installed via the exec.python.packages
metadata.
Synchronous calls to async functions#
async functions can be invoked synchronously with pyodide.ffi.run_sync().
This requires the experimental
WebAssembly JavaScript promise integration API
(JSPI), which isn't widely supported yet.
Chromium-based browsers (Google Chrome, Microsoft Edge): JSPI is supported since Chromium 137.
Firefox: Firefox is still working on the implementation and doesn't have an origin trial yet. For local testing, the feature can be enabled with a config (
javascript.options.wasm_js_promise_integration), but it doesn't work with Pyodide yet.Safari: Safari doesn't currently support JSPI.
Note
Using pyodide.ffi.run_sync() instead of async concurrency prevents
interrupting running code via the button.
SQL#
The {exec} sql runner uses a WebAssembly build of
SQLite. Each block execution is performed against a new,
empty database. Foreign key constraint enforcement is enabled by default.
Custom runners#
It is possible to create custom runners in site repositories. In the following,
substitute RUNNER with the name to be used in {exec}
directives.
Create the file
tdoc/exec-RUNNER.jsin the_staticdirectory.Import
./exec.js.Subclass
Runner, set the static attributenameand implement the desired functionality in the class.Call
Runner.register()with the subclass at the end of the module.
import {Runner} from './exec.js'; class MyRunner extends Runner { static name = 'RUNNER'; // Implement runner functionality } Runner.register(MyRunner);
In
conf.py, add a configurationdictfor the runner to theexeckey of to themetadataoption. Optionally, set thehighlightkey to the language to use for syntax highlighting (available lexers; the default istext, i.e. no highlighting).metadata = { # ... 'exec': {'RUNNER': {'highlight': 'lua'}}, }
The existing runners
(html,
micropython,
python,
sql)
can serve as examples for the creation of new ones.