mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
Compare commits
32 Commits
antocuni/p
...
fpliger/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9e1abf6d1 | ||
|
|
24da768959 | ||
|
|
e750fa7393 | ||
|
|
5a15199a3a | ||
|
|
1801472fc4 | ||
|
|
ab15ac37ff | ||
|
|
0955a6be49 | ||
|
|
d58237ea15 | ||
|
|
2d50ca86a6 | ||
|
|
f1a46be738 | ||
|
|
3e2a67d434 | ||
|
|
aef028be6e | ||
|
|
c8ec29a3d8 | ||
|
|
e81830a2ea | ||
|
|
54df7171a2 | ||
|
|
b31af823d1 | ||
|
|
72f266532b | ||
|
|
d9bf5cae12 | ||
|
|
cd95a42e5e | ||
|
|
e67eb06d8b | ||
|
|
28d37cdead | ||
|
|
13604e0a47 | ||
|
|
aeb6f1a755 | ||
|
|
92e6f711b7 | ||
|
|
a24113f42b | ||
|
|
7a6f8ab3ad | ||
|
|
6dd242f3ce | ||
|
|
88fa82c61a | ||
|
|
2299ba5f61 | ||
|
|
117df6ca38 | ||
|
|
72955bced9 | ||
|
|
c63c4043d3 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -11,5 +11,5 @@
|
|||||||
<!-- Note: Only user-facing changes require a changelog entry. Internal-only API changes do not require a changelog entry. Changes in documentation do not require a changelog entry. -->
|
<!-- Note: Only user-facing changes require a changelog entry. Internal-only API changes do not require a changelog entry. Changes in documentation do not require a changelog entry. -->
|
||||||
|
|
||||||
- [ ] All tests pass locally
|
- [ ] All tests pass locally
|
||||||
- [ ] I have updated `docs/changelog.md`
|
- [ ] I have updated `CHANGELOG.md`
|
||||||
- [ ] I have created documentation for this(if applicable)
|
- [ ] I have created documentation for this(if applicable)
|
||||||
|
|||||||
2
.github/workflows/prepare-release.yml
vendored
2
.github/workflows/prepare-release.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm install
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|||||||
2
.github/workflows/publish-release.yml
vendored
2
.github/workflows/publish-release.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: npm install
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|||||||
2
.github/workflows/publish-snapshot.yml
vendored
2
.github/workflows/publish-snapshot.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
- name: Build Pyscript.core
|
- name: Build Pyscript.core
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|||||||
2
.github/workflows/publish-unstable.yml
vendored
2
.github/workflows/publish-unstable.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
${{ runner.os }}-
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: NPM Install
|
- name: NPM Install
|
||||||
run: npm install
|
run: npm install && npx playwright install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|||||||
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@@ -57,15 +57,24 @@ jobs:
|
|||||||
- name: setup Miniconda
|
- name: setup Miniconda
|
||||||
uses: conda-incubator/setup-miniconda@v2
|
uses: conda-incubator/setup-miniconda@v2
|
||||||
|
|
||||||
- name: Setup Environment
|
- name: Create and activate virtual environment
|
||||||
run: make setup
|
run: |
|
||||||
|
python3 -m venv test_venv
|
||||||
|
source test_venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
echo VIRTUAL_ENV=$VIRTUAL_ENV >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup dependencies in virtual environment
|
||||||
|
run: |
|
||||||
|
make setup
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make build
|
run: make build
|
||||||
|
|
||||||
- name: Integration Tests
|
- name: Integration Tests
|
||||||
#run: make test-integration-parallel
|
#run: make test-integration-parallel
|
||||||
run: make test-integration
|
run: |
|
||||||
|
make test-integration
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -144,6 +144,8 @@ test_results
|
|||||||
# @pyscript/core npm artifacts
|
# @pyscript/core npm artifacts
|
||||||
pyscript.core/core.*
|
pyscript.core/core.*
|
||||||
pyscript.core/dist
|
pyscript.core/dist
|
||||||
pyscript.core/dist
|
pyscript.core/dist.zip
|
||||||
pyscript.core/src/plugins.js
|
pyscript.core/src/plugins.js
|
||||||
pyscript.core/src/stdlib/pyscript.js
|
pyscript.core/src/stdlib/pyscript.js
|
||||||
|
pyscript.core/src/3rd-party/*
|
||||||
|
!pyscript.core/src/3rd-party/READMEmd
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ repos:
|
|||||||
rev: "v3.0.0-alpha.6"
|
rev: "v3.0.0-alpha.6"
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
exclude: pyscript\.core/test|pyscript\.core/dist|pyscript\.core/types|pyscript.core/src/stdlib/pyscript.js|pyscript\.sw/
|
exclude: pyscript\.core/test|pyscript\.core/dist|pyscript\.core/types|pyscript.core/src/stdlib/pyscript.js|pyscript\.sw/|pyscript.core/src/3rd-party
|
||||||
args: [--tab-width, "4"]
|
args: [--tab-width, "4"]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
|
|||||||
87
CHANGELOG.md
Normal file
87
CHANGELOG.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Release Notes
|
||||||
|
|
||||||
|
## 2023.05.01
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Added the `xterm` attribute to `py-config`. When set to `True` or `xterm`, an (output-only) [xterm.js](http://xtermjs.org/) terminal will be used in place of the default py-terminal.
|
||||||
|
- The default version of Pyodide is now `0.23.2`. See the [Pyodide Changelog](https://pyodide.org/en/stable/project/changelog.html#version-0-23-2) for a detailed list of changes.
|
||||||
|
- Added the `@when` decorator for attaching Python functions as event handlers
|
||||||
|
- The `py-mount` attribute on HTML elements has been deprecated, and will be removed in a future release.
|
||||||
|
|
||||||
|
#### Runtime py- attributes
|
||||||
|
|
||||||
|
- Added logic to react to `py-*` attributes changes, removal, `py-*` attributes added to already live nodes but also `py-*` attributes added or defined via injected nodes (either appended or via `innerHTML` operations). ([#1435](https://github.com/pyscript/pyscript/pull/1435))
|
||||||
|
|
||||||
|
#### <script type="py">
|
||||||
|
|
||||||
|
- Added the ability to optionally use `<script type="py">`, `<script type="pyscript">` or `<script type="py-script">` instead of a `<py-script>` custom element, in order to tackle cases where the content of the `<py-script>` tag, inevitably parsed by browsers, could accidentally contain _HTML_ able to break the surrounding page layout. ([#1396](https://github.com/pyscript/pyscript/pull/1396))
|
||||||
|
|
||||||
|
#### <py-terminal>
|
||||||
|
|
||||||
|
- Added a `docked` field and attribute for the `<py-terminal>` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution.
|
||||||
|
|
||||||
|
#### <py-script>
|
||||||
|
|
||||||
|
- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||||
|
- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063))
|
||||||
|
|
||||||
|
#### <py-repl>
|
||||||
|
|
||||||
|
- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()`
|
||||||
|
- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||||
|
- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results.
|
||||||
|
- Load code from the attribute src of py-repl and preload it into the corresponding py-repl tag by use the attribute `str` in your `py-repl` tag([#1292](https://github.com/pyscript/pyscript/pull/1292))
|
||||||
|
- <py-repl> elements now have a `getPySrc()` method, which returns the code inside the REPL as a string.([#1516](https://github.com/pyscript/pyscript/pull/1292))
|
||||||
|
|
||||||
|
#### Plugins
|
||||||
|
|
||||||
|
- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106))
|
||||||
|
|
||||||
|
#### Web worker support
|
||||||
|
|
||||||
|
- introduced the new experimental `execution_thread` config option: if you set `execution_thread = "worker"`, the python interpreter runs inside a web worker
|
||||||
|
- worker support is still **very** experimental: not everything works, use it at your own risk
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fixes [#1280](https://github.com/pyscript/pyscript/issues/1280), which describes the errors on the PyRepl tests related to having auto-gen tags that shouldn't be there.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- Py-REPL tests now run on both osx and non osx OSs
|
||||||
|
- migrated from _rollup_ to _esbuild_ to create artifacts
|
||||||
|
- updated `@codemirror` dependency to its latest
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
- Add docs for event handlers
|
||||||
|
|
||||||
|
## 2023.03.1
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054))
|
||||||
|
- Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058))
|
||||||
|
- Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064))
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122))
|
||||||
|
- You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052))
|
||||||
|
- Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138))
|
||||||
|
- Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156))
|
||||||
|
- Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125))
|
||||||
|
|
||||||
|
### Deprecations and Removals
|
||||||
|
|
||||||
|
- The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082))
|
||||||
|
- The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||||
|
- The pyscript elements `py-button`, `py-inputbox`, `py-box` and `py-title` have now completed their deprecation cycle and have been removed. ([#1084](https://github.com/pyscript/pyscript/pull/1084))
|
||||||
|
- The attributes `pys-onClick` and `pys-onKeyDown` have been removed. Use `py-click` and `py-keydown` instead ([#1361](https://github.com/pyscript/pyscript/pull/1361))
|
||||||
130
Makefile
130
Makefile
@@ -1,122 +1,92 @@
|
|||||||
tag := latest
|
MIN_NODE_VER := 20
|
||||||
git_hash ?= $(shell git log -1 --pretty=format:%h)
|
|
||||||
|
|
||||||
base_dir ?= $(shell git rev-parse --show-toplevel)
|
|
||||||
examples ?= ../$(base_dir)/examples
|
|
||||||
app_dir ?= $(shell git rev-parse --show-prefix)
|
|
||||||
|
|
||||||
CONDA_EXE := conda
|
|
||||||
CONDA_ENV ?= $(base_dir)/env
|
|
||||||
env := $(CONDA_ENV)
|
|
||||||
conda_run := $(CONDA_EXE) run -p $(env)
|
|
||||||
PYTEST_EXE := $(CONDA_ENV)/bin/pytest
|
|
||||||
|
|
||||||
MIN_NODE_VER := 14
|
|
||||||
MIN_NPM_VER := 6
|
MIN_NPM_VER := 6
|
||||||
|
MIN_PY3_VER := 8
|
||||||
NODE_VER := $(shell node -v | cut -d. -f1 | sed 's/^v\(.*\)/\1/')
|
NODE_VER := $(shell node -v | cut -d. -f1 | sed 's/^v\(.*\)/\1/')
|
||||||
NPM_VER := $(shell npm -v | cut -d. -f1)
|
NPM_VER := $(shell npm -v | cut -d. -f1)
|
||||||
|
PY3_VER := $(shell python3 -c "import sys;t='{v[1]}'.format(v=list(sys.version_info[:2]));print(t)")
|
||||||
|
PY_OK := $(shell python3 -c "print(int($(PY3_VER) >= $(MIN_PY3_VER)))")
|
||||||
|
|
||||||
ifeq ($(shell uname -s), Darwin)
|
all:
|
||||||
SED_I_ARG := -i ''
|
@echo "\nThere is no default Makefile target right now. Try:\n"
|
||||||
else
|
@echo "make setup - check your environment and install the dependencies."
|
||||||
SED_I_ARG := -i
|
@echo "make clean - clean up auto-generated assets."
|
||||||
endif
|
@echo "make build - build PyScript."
|
||||||
|
@echo "make precommit-check - run the precommit checks (run eslint)."
|
||||||
|
@echo "make test-integration - run all integration tests sequentially."
|
||||||
|
@echo "make fmt - format the code."
|
||||||
|
@echo "make fmt-check - check the code formatting.\n"
|
||||||
|
|
||||||
.PHONY: check-node
|
.PHONY: check-node
|
||||||
check-node:
|
check-node:
|
||||||
@if [ $(NODE_VER) -lt $(MIN_NODE_VER) ]; then \
|
@if [ $(NODE_VER) -lt $(MIN_NODE_VER) ]; then \
|
||||||
echo "Build requires Node $(MIN_NODE_VER).x or higher: $(NODE_VER) detected"; \
|
echo "\033[0;31mBuild requires Node $(MIN_NODE_VER).x or higher: $(NODE_VER) detected.\033[0m"; \
|
||||||
false; \
|
false; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: check-npm
|
.PHONY: check-npm
|
||||||
check-npm:
|
check-npm:
|
||||||
@if [ $(NPM_VER) -lt $(MIN_NPM_VER) ]; then \
|
@if [ $(NPM_VER) -lt $(MIN_NPM_VER) ]; then \
|
||||||
echo "Build requires Node $(MIN_NPM_VER).x or higher: $(NPM_VER) detected"; \
|
echo "\033[0;31mBuild requires Node $(MIN_NPM_VER).x or higher: $(NPM_VER) detected.\033[0m"; \
|
||||||
false; \
|
false; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
setup: check-node check-npm
|
.PHONY: check-python
|
||||||
cd pyscript.core && npm install && cd ..
|
check-python:
|
||||||
$(CONDA_EXE) env $(shell [ -d $(env) ] && echo update || echo create) -p $(env) --file environment.yml
|
@if [ $(PY_OK) -eq 0 ]; then \
|
||||||
$(conda_run) playwright install
|
echo "\033[0;31mRequires Python 3.$(MIN_PY3_VER).x or higher: 3.$(PY3_VER) detected.\033[0m"; \
|
||||||
$(CONDA_EXE) install -c anaconda pytest -y
|
false; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check the environment, install the dependencies.
|
||||||
|
setup: check-node check-npm check-python
|
||||||
|
cd pyscript.core && npm install && cd ..
|
||||||
|
ifeq ($(VIRTUAL_ENV),)
|
||||||
|
echo "\n\n\033[0;31mCannot install Python dependencies. Your virtualenv is not activated.\033[0m"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
|
playwright install
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Clean up generated assets.
|
||||||
clean:
|
clean:
|
||||||
find . -name \*.py[cod] -delete
|
find . -name \*.py[cod] -delete
|
||||||
|
rm -rf $(env) *.egg-info
|
||||||
rm -rf .pytest_cache .coverage coverage.xml
|
rm -rf .pytest_cache .coverage coverage.xml
|
||||||
|
|
||||||
clean-all: clean
|
# Build PyScript.
|
||||||
rm -rf $(env) *.egg-info
|
|
||||||
|
|
||||||
shell:
|
|
||||||
@export CONDA_ENV_PROMPT='<{name}>'
|
|
||||||
@echo 'conda activate $(env)'
|
|
||||||
|
|
||||||
dev:
|
|
||||||
cd pyscript.core && npm run dev
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
cd pyscript.core && npm run build
|
cd pyscript.core && npx playwright install && npm run build
|
||||||
|
|
||||||
# use the following rule to do all the checks done by precommit: in
|
# Run the precommit checks (run eslint).
|
||||||
# particular, use this if you want to run eslint.
|
|
||||||
precommit-check:
|
precommit-check:
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
|
||||||
examples:
|
# Run all integration tests sequentially.
|
||||||
mkdir -p ./examples
|
|
||||||
cp -r ../examples/* ./examples
|
|
||||||
chmod -R 755 examples
|
|
||||||
find ./examples/toga -type f -name '*.html' -exec sed $(SED_I_ARG) s+https://pyscript.net/latest/+../../build/+g {} \;
|
|
||||||
find ./examples/webgl -type f -name '*.html' -exec sed $(SED_I_ARG) s+https://pyscript.net/latest/+../../../build/+g {} \;
|
|
||||||
find ./examples -type f -name '*.html' -exec sed $(SED_I_ARG) s+https://pyscript.net/latest/+../build/+g {} \;
|
|
||||||
npm run build
|
|
||||||
rm -rf ./examples/build
|
|
||||||
mkdir -p ./examples/build
|
|
||||||
cp -R ./build/* ./examples/build
|
|
||||||
@echo "To serve examples run: $(conda_run) python -m http.server 8080 --directory examples"
|
|
||||||
|
|
||||||
# run prerequisites and serve pyscript examples at http://localhost:8000/examples/
|
|
||||||
run-examples: setup build examples
|
|
||||||
make examples
|
|
||||||
npm install
|
|
||||||
make dev
|
|
||||||
|
|
||||||
# run all integration tests *including examples* sequentially
|
|
||||||
# TODO: (fpliger) The cd pyscript.core before running the tests shouldn't be needed but for
|
|
||||||
# but for some reason it seems to bother pytest tmppaths (or test cache?). Unclear.
|
|
||||||
test-integration:
|
test-integration:
|
||||||
mkdir -p test_results
|
mkdir -p test_results
|
||||||
$(PYTEST_EXE) -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
pytest -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||||
|
|
||||||
# run all integration tests *except examples* in parallel (examples use too much memory)
|
# Run all integration tests in parallel.
|
||||||
test-integration-parallel:
|
test-integration-parallel:
|
||||||
mkdir -p test_results
|
mkdir -p test_results
|
||||||
$(PYTEST_EXE) --numprocesses auto -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
pytest --numprocesses auto -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml
|
||||||
|
|
||||||
# run integration tests on only examples sequentially (to avoid running out of memory)
|
# Format the code.
|
||||||
test-examples:
|
fmt: fmt-py
|
||||||
mkdir -p test_results
|
|
||||||
$(PYTEST_EXE) -vv $(ARGS) pyscript.core/tests/integration/ --log-cli-level=warning --junitxml=test_results/integration.xml -k 'zz_examples'
|
|
||||||
|
|
||||||
fmt: fmt-py fmt-ts
|
|
||||||
@echo "Format completed"
|
@echo "Format completed"
|
||||||
|
|
||||||
fmt-check: fmt-ts-check fmt-py-check
|
# Check the code formatting.
|
||||||
|
fmt-check: fmt-py-check
|
||||||
@echo "Format check completed"
|
@echo "Format check completed"
|
||||||
|
|
||||||
fmt-ts:
|
# Format Python code.
|
||||||
npm run format
|
|
||||||
|
|
||||||
fmt-ts-check:
|
|
||||||
npm run format:check
|
|
||||||
|
|
||||||
fmt-py:
|
fmt-py:
|
||||||
$(conda_run) black --skip-string-normalization .
|
black -l 88 --skip-string-normalization .
|
||||||
$(conda_run) isort --profile black .
|
isort --profile black .
|
||||||
|
|
||||||
|
# Check the format of Python code.
|
||||||
fmt-py-check:
|
fmt-py-check:
|
||||||
$(conda_run) black -l 88 --check .
|
black -l 88 --check .
|
||||||
|
|
||||||
.PHONY: $(MAKECMDGOALS)
|
.PHONY: $(MAKECMDGOALS)
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -20,8 +20,14 @@ To try PyScript, import the appropriate pyscript files into the `<head>` tag of
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
|
<link
|
||||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
rel="stylesheet"
|
||||||
|
href="https://pyscript.net/releases/2023.11.1/core.css"
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="https://pyscript.net/releases/2023.11.1/core.js"
|
||||||
|
></script>
|
||||||
</head>
|
</head>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -36,7 +42,7 @@ Check out the [the examples directory](examples) folder for more examples on how
|
|||||||
|
|
||||||
Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
|
Read the [contributing guide](CONTRIBUTING.md) to learn about our development process, reporting bugs and improvements, creating issues and asking questions.
|
||||||
|
|
||||||
Check out the [developing process](https://docs.pyscript.net/latest/development/developing.html) documentation for more information on how to setup your development environment.
|
Check out the [developing process](https://docs.pyscript.net/latest/contributing) documentation for more information on how to setup your development environment.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
channels:
|
|
||||||
- defaults
|
|
||||||
- conda-forge
|
|
||||||
- microsoft
|
|
||||||
dependencies:
|
|
||||||
- python=3.11.3
|
|
||||||
- pip
|
|
||||||
- pytest=7.1.2
|
|
||||||
- nodejs=16
|
|
||||||
- black
|
|
||||||
- isort
|
|
||||||
- codespell
|
|
||||||
- pre-commit
|
|
||||||
- pillow
|
|
||||||
- numpy
|
|
||||||
- markdown
|
|
||||||
- toml
|
|
||||||
- pip:
|
|
||||||
- playwright==1.33.0
|
|
||||||
- pytest-playwright==0.3.3
|
|
||||||
- pytest-xdist==3.3.0
|
|
||||||
- pexpect
|
|
||||||
# We need Pyodide and micropip so we can import them in our Python
|
|
||||||
# unit tests
|
|
||||||
- pyodide_py==0.23.2
|
|
||||||
- micropip==0.2.2
|
|
||||||
@@ -19,6 +19,7 @@ module.exports = {
|
|||||||
ecmaVersion: "latest",
|
ecmaVersion: "latest",
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
|
ignorePatterns: ["3rd-party"],
|
||||||
rules: {
|
rules: {
|
||||||
"no-implicit-globals": ["error"],
|
"no-implicit-globals": ["error"],
|
||||||
},
|
},
|
||||||
|
|||||||
31
pyscript.core/dev.cjs
Normal file
31
pyscript.core/dev.cjs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
let queue = Promise.resolve();
|
||||||
|
|
||||||
|
const { exec } = require("node:child_process");
|
||||||
|
|
||||||
|
const build = (fileName) => {
|
||||||
|
if (fileName) console.log(fileName, "changed");
|
||||||
|
else console.log("building without optimizations");
|
||||||
|
queue = queue.then(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
exec(
|
||||||
|
"npm run build:stdlib && npm run build:plugins && npm run build:core",
|
||||||
|
{ cwd: __dirname, env: { ...process.env, NO_MIN: true } },
|
||||||
|
(error) => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
else console.log(fileName || "", "build completed");
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
ignored: /\/(?:toml|plugins|pyscript)\.[mc]?js$/,
|
||||||
|
persistent: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
require("chokidar").watch("./src", options).on("change", build);
|
||||||
|
|
||||||
|
build();
|
||||||
@@ -159,7 +159,7 @@ The commonly shared utilities are:
|
|||||||
* **display** in both main and worker, refers to the good old `display` utility except:
|
* **display** in both main and worker, refers to the good old `display` utility except:
|
||||||
* in the *main* it automatically uses the current script `target` to display content
|
* in the *main* it automatically uses the current script `target` to display content
|
||||||
* in the *worker* it still needs to know *where* to display content using the `target="dom-id"` named argument, as workers don't get a default target attached
|
* in the *worker* it still needs to know *where* to display content using the `target="dom-id"` named argument, as workers don't get a default target attached
|
||||||
* in both main and worker, the `append=Flase` is the *default* behavior, which is a breaking change compared to classic PyScript, but because there is no `Element` with its `write(content)` utility, which would have used that `append=False` behind the scene, we've decided that `false` as append default is more desired, specially after porting most examples in *PyScript Next*, where `append=True` is the exception, not the norm.
|
* in both main and worker, the `append=True` is the *default* behavior, which is inherited from the classic PyScript.
|
||||||
|
|
||||||
#### Extra main-only features
|
#### Extra main-only features
|
||||||
|
|
||||||
|
|||||||
734
pyscript.core/package-lock.json
generated
734
pyscript.core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.2.7",
|
"version": "0.3.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "PyScript",
|
"description": "PyScript",
|
||||||
"module": "./index.js",
|
"module": "./index.js",
|
||||||
@@ -19,10 +19,18 @@
|
|||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "npx static-handler --cors --coep --coop --corp .",
|
"server": "npx static-handler --coi .",
|
||||||
"build": "node rollup/stdlib.cjs && node rollup/plugins.cjs && rm -rf dist && rollup --config rollup/core.config.js && eslint src/ && npm run ts",
|
"build": "npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
|
||||||
|
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
|
||||||
|
"build:plugins": "node rollup/plugins.cjs",
|
||||||
|
"build:stdlib": "node rollup/stdlib.cjs",
|
||||||
|
"build:3rd-party": "node rollup/3rd-party.cjs",
|
||||||
|
"test:mpy": "static-handler --coi . 2>/dev/null & SH_PID=$!; EXIT_CODE=0; playwright test --fully-parallel test/ || EXIT_CODE=$?; kill $SH_PID 2>/dev/null; exit $EXIT_CODE",
|
||||||
|
"dev": "node dev.cjs",
|
||||||
|
"release": "npm run build && npm run zip",
|
||||||
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do echo -e \"\\033[2m$js:\\033[0m $(cat $js | brotli | wc -c) bytes\"; done",
|
"size": "echo -e \"\\033[1mdist/*.js file size\\033[0m\"; for js in $(ls dist/*.js); do echo -e \"\\033[2m$js:\\033[0m $(cat $js | brotli | wc -c) bytes\"; done",
|
||||||
"ts": "tsc -p ."
|
"ts": "tsc -p .",
|
||||||
|
"zip": "zip -r dist.zip ./dist"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"pyscript",
|
"pyscript",
|
||||||
@@ -33,18 +41,26 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/with-resolvers": "^0.1.0",
|
"@ungap/with-resolvers": "^0.1.0",
|
||||||
"basic-devtools": "^0.1.6",
|
"basic-devtools": "^0.1.6",
|
||||||
"polyscript": "^0.4.11",
|
"polyscript": "^0.5.11",
|
||||||
|
"sticky-module": "^0.1.1",
|
||||||
|
"to-json-callback": "^0.1.1",
|
||||||
"type-checked-collections": "^0.1.7"
|
"type-checked-collections": "^0.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
"@playwright/test": "^1.39.0",
|
||||||
"@rollup/plugin-terser": "^0.4.3",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"eslint": "^8.50.0",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"rollup": "^3.29.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@webreflection/toml-j0.4": "^1.1.3",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"eslint": "^8.53.0",
|
||||||
|
"rollup": "^4.3.0",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-string": "^3.0.0",
|
"rollup-plugin-string": "^3.0.0",
|
||||||
"static-handler": "^0.4.2",
|
"static-handler": "^0.4.3",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2",
|
||||||
|
"xterm": "^5.3.0",
|
||||||
|
"xterm-readline": "^1.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
52
pyscript.core/rollup/3rd-party.cjs
Normal file
52
pyscript.core/rollup/3rd-party.cjs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const { copyFileSync, writeFileSync } = require("node:fs");
|
||||||
|
const { join } = require("node:path");
|
||||||
|
|
||||||
|
const CDN = "https://cdn.jsdelivr.net/npm";
|
||||||
|
|
||||||
|
const targets = join(__dirname, "..", "src", "3rd-party");
|
||||||
|
const node_modules = join(__dirname, "..", "node_modules");
|
||||||
|
|
||||||
|
const { devDependencies } = require(join(__dirname, "..", "package.json"));
|
||||||
|
|
||||||
|
const v = (name) => devDependencies[name].replace(/[^\d.]/g, "");
|
||||||
|
|
||||||
|
// Fetch a module via jsdelivr CDN `/+esm` orchestration
|
||||||
|
// then sanitize the resulting outcome to avoid importing
|
||||||
|
// anything via `/npm/...` through Rollup
|
||||||
|
const resolve = (name) => {
|
||||||
|
const cdn = `${CDN}/${name}@${v(name)}/+esm`;
|
||||||
|
console.debug("fetching", cdn);
|
||||||
|
return fetch(cdn)
|
||||||
|
.then((b) => b.text())
|
||||||
|
.then((text) =>
|
||||||
|
text.replace(
|
||||||
|
/("|')\/npm\/(.+)?\+esm\1/g,
|
||||||
|
// normalize `/npm/module@version/+esm` as
|
||||||
|
// just `module` so that rollup can do the rest
|
||||||
|
(_, quote, module) => {
|
||||||
|
const i = module.lastIndexOf("@");
|
||||||
|
return `${quote}${module.slice(0, i)}${quote}`;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// key/value pairs as:
|
||||||
|
// "3rd-party/file-name.js"
|
||||||
|
// string as content or
|
||||||
|
// Promise<string> as resolved content
|
||||||
|
const modules = {
|
||||||
|
"toml.js": join(node_modules, "@webreflection", "toml-j0.4", "toml.js"),
|
||||||
|
"xterm.js": resolve("xterm"),
|
||||||
|
"xterm.css": fetch(`${CDN}/xterm@${v("xterm")}/css/xterm.min.css`).then(
|
||||||
|
(b) => b.text(),
|
||||||
|
),
|
||||||
|
"xterm-readline.js": resolve("xterm-readline"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [target, source] of Object.entries(modules)) {
|
||||||
|
if (typeof source === "string") copyFileSync(source, join(targets, target));
|
||||||
|
else {
|
||||||
|
source.then((text) => writeFileSync(join(targets, target), text));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
// the default exported as npm entry.
|
// the default exported as npm entry.
|
||||||
|
|
||||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
import terser from "@rollup/plugin-terser";
|
import terser from "@rollup/plugin-terser";
|
||||||
import postcss from "rollup-plugin-postcss";
|
import postcss from "rollup-plugin-postcss";
|
||||||
|
|
||||||
@@ -11,7 +12,9 @@ export default [
|
|||||||
{
|
{
|
||||||
input: "./src/core.js",
|
input: "./src/core.js",
|
||||||
plugins: plugins.concat(
|
plugins: plugins.concat(
|
||||||
process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
process.env.NO_MIN
|
||||||
|
? [nodeResolve(), commonjs()]
|
||||||
|
: [nodeResolve(), commonjs(), terser()],
|
||||||
),
|
),
|
||||||
output: {
|
output: {
|
||||||
esModule: true,
|
esModule: true,
|
||||||
|
|||||||
7
pyscript.core/src/3rd-party/README.md
vendored
Normal file
7
pyscript.core/src/3rd-party/README.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# PyScript 3rd Party
|
||||||
|
|
||||||
|
This folder contains artifacts created via [3rd-party.cjs](../../rollup/3rd-party.cjs).
|
||||||
|
|
||||||
|
As we would like to offer a way to run PyScript offline, and we already offer a `dist` folder with all the necessary scripts, we have created a foreign dependencies resolver that allow to lazy-load CDN dependencies out of the box.
|
||||||
|
|
||||||
|
Please **note** these dependencies are **not interpreters**, because interpreters have their own mechanism, folders structure, WASM files, and whatnot, to work locally, but at least XTerm or the TOML parser, among other lazy dependencies, should be available within the dist folder.
|
||||||
@@ -89,8 +89,7 @@ for (const [TYPE] of TYPES) {
|
|||||||
} else if (toml || type === "toml") {
|
} else if (toml || type === "toml") {
|
||||||
try {
|
try {
|
||||||
const { parse } = await import(
|
const { parse } = await import(
|
||||||
/* webpackIgnore: true */
|
/* webpackIgnore: true */ "./3rd-party/toml.js"
|
||||||
"https://cdn.jsdelivr.net/npm/@webreflection/toml-j0.4/toml.js"
|
|
||||||
);
|
);
|
||||||
parsed = parse(text);
|
parsed = parse(text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -114,7 +113,7 @@ for (const [TYPE] of TYPES) {
|
|||||||
value().then(({ notify }) => notify(error.message));
|
value().then(({ notify }) => notify(error.message));
|
||||||
}
|
}
|
||||||
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
} else if (!parsed?.plugins?.includes(`!${key}`)) {
|
||||||
toBeAwaited.push(value());
|
toBeAwaited.push(value().then(({ default: p }) => p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
/*! (c) PyScript Development Team */
|
/*! (c) PyScript Development Team */
|
||||||
|
|
||||||
|
import stickyModule from "sticky-module";
|
||||||
import "@ungap/with-resolvers";
|
import "@ungap/with-resolvers";
|
||||||
|
|
||||||
// These imports can hook more than usual and help debugging possible polyscript issues
|
|
||||||
import {
|
import {
|
||||||
INVALID_CONTENT,
|
INVALID_CONTENT,
|
||||||
define,
|
Hook,
|
||||||
XWorker,
|
XWorker,
|
||||||
} from "../node_modules/polyscript/esm/index.js";
|
assign,
|
||||||
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
|
|
||||||
import {
|
|
||||||
dedent,
|
dedent,
|
||||||
|
define,
|
||||||
|
defineProperty,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
queryTarget,
|
||||||
unescape,
|
unescape,
|
||||||
} from "../node_modules/polyscript/esm/utils.js";
|
whenDefined,
|
||||||
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
|
} from "polyscript/exports";
|
||||||
|
|
||||||
import "./all-done.js";
|
import "./all-done.js";
|
||||||
import TYPES from "./types.js";
|
import TYPES from "./types.js";
|
||||||
import configs from "./config.js";
|
import configs from "./config.js";
|
||||||
import hooks from "./hooks.js";
|
|
||||||
import sync from "./sync.js";
|
import sync from "./sync.js";
|
||||||
import stdlib from "./stdlib.js";
|
import bootstrapNodeAndPlugins from "./plugins-helper.js";
|
||||||
import { ErrorCode } from "./exceptions.js";
|
import { ErrorCode } from "./exceptions.js";
|
||||||
import { robustFetch as fetch, getText } from "./fetch.js";
|
import { robustFetch as fetch, getText } from "./fetch.js";
|
||||||
|
import { hooks, main, worker, codeFor, createFunction } from "./hooks.js";
|
||||||
const { assign, defineProperty } = Object;
|
|
||||||
|
|
||||||
// allows lazy element features on code evaluation
|
// allows lazy element features on code evaluation
|
||||||
let currentElement;
|
let currentElement;
|
||||||
@@ -33,25 +32,6 @@ let currentElement;
|
|||||||
// generic helper to disambiguate between custom element and script
|
// generic helper to disambiguate between custom element and script
|
||||||
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
const isScript = ({ tagName }) => tagName === "SCRIPT";
|
||||||
|
|
||||||
// helper for all script[type="py"] out there
|
|
||||||
const before = (script) => {
|
|
||||||
defineProperty(document, "currentScript", {
|
|
||||||
configurable: true,
|
|
||||||
get: () => script,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const after = () => {
|
|
||||||
delete document.currentScript;
|
|
||||||
};
|
|
||||||
|
|
||||||
// common life-cycle handlers for any node
|
|
||||||
const bootstrapNodeAndPlugins = (wrap, element, callback, hook) => {
|
|
||||||
// make it possible to reach the current target node via Python
|
|
||||||
callback(element);
|
|
||||||
for (const fn of hooks[hook]) fn(wrap, element);
|
|
||||||
};
|
|
||||||
|
|
||||||
let shouldRegister = true;
|
let shouldRegister = true;
|
||||||
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
||||||
// automatically use the pyscript stderr (when/if defined)
|
// automatically use the pyscript stderr (when/if defined)
|
||||||
@@ -71,25 +51,38 @@ const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
|
|||||||
: currentElement.id;
|
: currentElement.id;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
interpreter.runPython(stdlib, { globals: interpreter.runPython("{}") });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const workerHooks = {
|
// avoid multiple initialization of the same library
|
||||||
codeBeforeRunWorker: () =>
|
const [
|
||||||
[stdlib, ...hooks.codeBeforeRunWorker].map(dedent).join("\n"),
|
{
|
||||||
codeBeforeRunWorkerAsync: () =>
|
PyWorker: exportedPyWorker,
|
||||||
[stdlib, ...hooks.codeBeforeRunWorkerAsync].map(dedent).join("\n"),
|
hooks: exportedHooks,
|
||||||
codeAfterRunWorker: () =>
|
config: exportedConfig,
|
||||||
[...hooks.codeAfterRunWorker].map(dedent).join("\n"),
|
whenDefined: exportedWhenDefined,
|
||||||
codeAfterRunWorkerAsync: () =>
|
},
|
||||||
[...hooks.codeAfterRunWorkerAsync].map(dedent).join("\n"),
|
alreadyLive,
|
||||||
|
] = stickyModule("@pyscript/core", {
|
||||||
|
PyWorker,
|
||||||
|
hooks,
|
||||||
|
config: {},
|
||||||
|
whenDefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
TYPES,
|
||||||
|
exportedPyWorker as PyWorker,
|
||||||
|
exportedHooks as hooks,
|
||||||
|
exportedConfig as config,
|
||||||
|
exportedWhenDefined as whenDefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportedConfig = {};
|
const hooked = new Map();
|
||||||
export { exportedConfig as config, hooks };
|
|
||||||
|
|
||||||
for (const [TYPE, interpreter] of TYPES) {
|
for (const [TYPE, interpreter] of TYPES) {
|
||||||
|
// avoid any dance if the module already landed
|
||||||
|
if (alreadyLive) break;
|
||||||
|
|
||||||
const dispatchDone = (element, isAsync, result) => {
|
const dispatchDone = (element, isAsync, result) => {
|
||||||
if (isAsync) result.then(() => dispatch(element, TYPE, "done"));
|
if (isAsync) result.then(() => dispatch(element, TYPE, "done"));
|
||||||
else dispatch(element, TYPE, "done");
|
else dispatch(element, TYPE, "done");
|
||||||
@@ -135,50 +128,11 @@ for (const [TYPE, interpreter] of TYPES) {
|
|||||||
// possible early errors sent by polyscript
|
// possible early errors sent by polyscript
|
||||||
const errors = new Map();
|
const errors = new Map();
|
||||||
|
|
||||||
define(TYPE, {
|
// specific main and worker hooks
|
||||||
config,
|
const hooks = {
|
||||||
interpreter,
|
main: {
|
||||||
env: `${TYPE}-script`,
|
...codeFor(main),
|
||||||
version: config?.interpreter,
|
async onReady(wrap, element) {
|
||||||
onerror(error, element) {
|
|
||||||
errors.set(element, error);
|
|
||||||
},
|
|
||||||
...workerHooks,
|
|
||||||
onWorkerReady(_, xworker) {
|
|
||||||
assign(xworker.sync, sync);
|
|
||||||
for (const callback of hooks.onWorkerReady)
|
|
||||||
callback(_, xworker);
|
|
||||||
},
|
|
||||||
onBeforeRun(wrap, element) {
|
|
||||||
currentElement = element;
|
|
||||||
bootstrapNodeAndPlugins(
|
|
||||||
wrap,
|
|
||||||
element,
|
|
||||||
before,
|
|
||||||
"onBeforeRun",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onBeforeRunAsync(wrap, element) {
|
|
||||||
currentElement = element;
|
|
||||||
bootstrapNodeAndPlugins(
|
|
||||||
wrap,
|
|
||||||
element,
|
|
||||||
before,
|
|
||||||
"onBeforeRunAsync",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onAfterRun(wrap, element) {
|
|
||||||
bootstrapNodeAndPlugins(wrap, element, after, "onAfterRun");
|
|
||||||
},
|
|
||||||
onAfterRunAsync(wrap, element) {
|
|
||||||
bootstrapNodeAndPlugins(
|
|
||||||
wrap,
|
|
||||||
element,
|
|
||||||
after,
|
|
||||||
"onAfterRunAsync",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async onInterpreterReady(wrap, element) {
|
|
||||||
if (shouldRegister) {
|
if (shouldRegister) {
|
||||||
shouldRegister = false;
|
shouldRegister = false;
|
||||||
registerModule(wrap);
|
registerModule(wrap);
|
||||||
@@ -186,8 +140,8 @@ for (const [TYPE, interpreter] of TYPES) {
|
|||||||
|
|
||||||
// allows plugins to do whatever they want with the element
|
// allows plugins to do whatever they want with the element
|
||||||
// before regular stuff happens in here
|
// before regular stuff happens in here
|
||||||
for (const callback of hooks.onInterpreterReady)
|
for (const callback of main("onReady"))
|
||||||
callback(wrap, element);
|
await callback(wrap, element);
|
||||||
|
|
||||||
// now that all possible plugins are configured,
|
// now that all possible plugins are configured,
|
||||||
// bail out if polyscript encountered an error
|
// bail out if polyscript encountered an error
|
||||||
@@ -236,6 +190,79 @@ for (const [TYPE, interpreter] of TYPES) {
|
|||||||
}
|
}
|
||||||
console.debug("[pyscript/main] PyScript Ready");
|
console.debug("[pyscript/main] PyScript Ready");
|
||||||
},
|
},
|
||||||
|
onWorker(_, xworker) {
|
||||||
|
assign(xworker.sync, sync);
|
||||||
|
for (const callback of main("onWorker"))
|
||||||
|
callback(_, xworker);
|
||||||
|
},
|
||||||
|
onBeforeRun(wrap, element) {
|
||||||
|
currentElement = element;
|
||||||
|
bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onBeforeRun",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onBeforeRunAsync(wrap, element) {
|
||||||
|
currentElement = element;
|
||||||
|
return bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onBeforeRunAsync",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onAfterRun(wrap, element) {
|
||||||
|
bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onAfterRun",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onAfterRunAsync(wrap, element) {
|
||||||
|
return bootstrapNodeAndPlugins(
|
||||||
|
main,
|
||||||
|
wrap,
|
||||||
|
element,
|
||||||
|
"onAfterRunAsync",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
worker: {
|
||||||
|
...codeFor(worker),
|
||||||
|
// these are lazy getters that returns a composition
|
||||||
|
// of the current hooks or undefined, if no hook is present
|
||||||
|
get onReady() {
|
||||||
|
return createFunction(this, "onReady", true);
|
||||||
|
},
|
||||||
|
get onBeforeRun() {
|
||||||
|
return createFunction(this, "onBeforeRun", false);
|
||||||
|
},
|
||||||
|
get onBeforeRunAsync() {
|
||||||
|
return createFunction(this, "onBeforeRunAsync", true);
|
||||||
|
},
|
||||||
|
get onAfterRun() {
|
||||||
|
return createFunction(this, "onAfterRun", false);
|
||||||
|
},
|
||||||
|
get onAfterRunAsync() {
|
||||||
|
return createFunction(this, "onAfterRunAsync", true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
hooked.set(TYPE, hooks);
|
||||||
|
|
||||||
|
define(TYPE, {
|
||||||
|
config,
|
||||||
|
interpreter,
|
||||||
|
hooks,
|
||||||
|
env: `${TYPE}-script`,
|
||||||
|
version: config?.interpreter,
|
||||||
|
onerror(error, element) {
|
||||||
|
errors.set(element, error);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
customElements.define(
|
customElements.define(
|
||||||
@@ -290,13 +317,14 @@ for (const [TYPE, interpreter] of TYPES) {
|
|||||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||||
*/
|
*/
|
||||||
export function PyWorker(file, options) {
|
function PyWorker(file, options) {
|
||||||
|
const hooks = hooked.get("py");
|
||||||
// this propagates pyscript worker hooks without needing a pyscript
|
// this propagates pyscript worker hooks without needing a pyscript
|
||||||
// bootstrap + it passes arguments and enforces `pyodide`
|
// bootstrap + it passes arguments and enforces `pyodide`
|
||||||
// as the interpreter to use in the worker, as all hooks assume that
|
// as the interpreter to use in the worker, as all hooks assume that
|
||||||
// and as `pyodide` is the only default interpreter that can deal with
|
// and as `pyodide` is the only default interpreter that can deal with
|
||||||
// all the features we need to deliver pyscript out there.
|
// all the features we need to deliver pyscript out there.
|
||||||
const xworker = XWorker.call(new Hook(null, workerHooks), file, {
|
const xworker = XWorker.call(new Hook(null, hooks), file, {
|
||||||
type: "pyodide",
|
type: "pyodide",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { assign } from "polyscript/exports";
|
||||||
|
|
||||||
const CLOSEBUTTON =
|
const CLOSEBUTTON =
|
||||||
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' width='12px'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>";
|
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' width='12px'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>";
|
||||||
|
|
||||||
@@ -87,13 +89,13 @@ export function _createAlertBanner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = messageType === "html" ? "innerHTML" : "textContent";
|
const content = messageType === "html" ? "innerHTML" : "textContent";
|
||||||
const banner = Object.assign(document.createElement("div"), {
|
const banner = assign(document.createElement("div"), {
|
||||||
className: `alert-banner py-${level}`,
|
className: `alert-banner py-${level}`,
|
||||||
[content]: message,
|
[content]: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (level === "warning") {
|
if (level === "warning") {
|
||||||
const closeButton = Object.assign(document.createElement("button"), {
|
const closeButton = assign(document.createElement("button"), {
|
||||||
id: "alert-close-button",
|
id: "alert-close-button",
|
||||||
innerHTML: CLOSEBUTTON,
|
innerHTML: CLOSEBUTTON,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FetchError, ErrorCode } from "./exceptions.js";
|
import { FetchError, ErrorCode } from "./exceptions.js";
|
||||||
import { getText } from "../node_modules/polyscript/esm/fetch-utils.js";
|
import { getText } from "polyscript/exports";
|
||||||
|
|
||||||
export { getText };
|
export { getText };
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,56 @@
|
|||||||
import { typedSet } from "type-checked-collections";
|
import { typedSet } from "type-checked-collections";
|
||||||
|
import { dedent } from "polyscript/exports";
|
||||||
|
import toJSONCallback from "to-json-callback";
|
||||||
|
|
||||||
|
import stdlib from "./stdlib.js";
|
||||||
|
|
||||||
|
export const main = (name) => hooks.main[name];
|
||||||
|
export const worker = (name) => hooks.worker[name];
|
||||||
|
|
||||||
|
const code = (hooks, branch, key, lib) => {
|
||||||
|
hooks[key] = () => {
|
||||||
|
const arr = lib ? [lib] : [];
|
||||||
|
arr.push(...branch(key));
|
||||||
|
return arr.map(dedent).join("\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const codeFor = (branch) => {
|
||||||
|
const hooks = {};
|
||||||
|
code(hooks, branch, `codeBeforeRun`, stdlib);
|
||||||
|
code(hooks, branch, `codeBeforeRunAsync`, stdlib);
|
||||||
|
code(hooks, branch, `codeAfterRun`);
|
||||||
|
code(hooks, branch, `codeAfterRunAsync`);
|
||||||
|
return hooks;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFunction = (self, name) => {
|
||||||
|
const cbs = [...worker(name)];
|
||||||
|
if (cbs.length) {
|
||||||
|
const cb = toJSONCallback(
|
||||||
|
self[`_${name}`] ||
|
||||||
|
(name.endsWith("Async")
|
||||||
|
? async (wrap, xworker, ...cbs) => {
|
||||||
|
for (const cb of cbs) await cb(wrap, xworker);
|
||||||
|
}
|
||||||
|
: (wrap, xworker, ...cbs) => {
|
||||||
|
for (const cb of cbs) cb(wrap, xworker);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const a = cbs.map(toJSONCallback).join(", ");
|
||||||
|
return Function(`return(w,x)=>(${cb})(w,x,...[${a}])`)();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const SetFunction = typedSet({ typeof: "function" });
|
const SetFunction = typedSet({ typeof: "function" });
|
||||||
const SetString = typedSet({ typeof: "string" });
|
const SetString = typedSet({ typeof: "string" });
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
|
main: {
|
||||||
/** @type {Set<function>} */
|
/** @type {Set<function>} */
|
||||||
onInterpreterReady: new SetFunction(),
|
onWorker: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onReady: new SetFunction(),
|
||||||
/** @type {Set<function>} */
|
/** @type {Set<function>} */
|
||||||
onBeforeRun: new SetFunction(),
|
onBeforeRun: new SetFunction(),
|
||||||
/** @type {Set<function>} */
|
/** @type {Set<function>} */
|
||||||
@@ -14,15 +59,33 @@ export default {
|
|||||||
onAfterRun: new SetFunction(),
|
onAfterRun: new SetFunction(),
|
||||||
/** @type {Set<function>} */
|
/** @type {Set<function>} */
|
||||||
onAfterRunAsync: new SetFunction(),
|
onAfterRunAsync: new SetFunction(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRun: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeBeforeRunAsync: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRun: new SetString(),
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
codeAfterRunAsync: new SetString(),
|
||||||
|
},
|
||||||
|
worker: {
|
||||||
/** @type {Set<function>} */
|
/** @type {Set<function>} */
|
||||||
onWorkerReady: new SetFunction(),
|
onReady: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRun: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onBeforeRunAsync: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRun: new SetFunction(),
|
||||||
|
/** @type {Set<function>} */
|
||||||
|
onAfterRunAsync: new SetFunction(),
|
||||||
/** @type {Set<string>} */
|
/** @type {Set<string>} */
|
||||||
codeBeforeRunWorker: new SetString(),
|
codeBeforeRun: new SetString(),
|
||||||
/** @type {Set<string>} */
|
/** @type {Set<string>} */
|
||||||
codeBeforeRunWorkerAsync: new SetString(),
|
codeBeforeRunAsync: new SetString(),
|
||||||
/** @type {Set<string>} */
|
/** @type {Set<string>} */
|
||||||
codeAfterRunWorker: new SetString(),
|
codeAfterRun: new SetString(),
|
||||||
/** @type {Set<string>} */
|
/** @type {Set<string>} */
|
||||||
codeAfterRunWorkerAsync: new SetString(),
|
codeAfterRunAsync: new SetString(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
26
pyscript.core/src/plugins-helper.js
Normal file
26
pyscript.core/src/plugins-helper.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineProperty } from "polyscript/exports";
|
||||||
|
|
||||||
|
// helper for all script[type="py"] out there
|
||||||
|
const before = (script) => {
|
||||||
|
defineProperty(document, "currentScript", {
|
||||||
|
configurable: true,
|
||||||
|
get: () => script,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const after = () => {
|
||||||
|
delete document.currentScript;
|
||||||
|
};
|
||||||
|
|
||||||
|
// common life-cycle handlers for any node
|
||||||
|
export default async (main, wrap, element, hook) => {
|
||||||
|
const isAsync = hook.endsWith("Async");
|
||||||
|
const isBefore = hook.startsWith("onBefore");
|
||||||
|
// make it possible to reach the current target node via Python
|
||||||
|
// or clean up for other scripts executing around this one
|
||||||
|
(isBefore ? before : after)(element);
|
||||||
|
for (const fn of main(hook)) {
|
||||||
|
if (isAsync) await fn(wrap, element);
|
||||||
|
else fn(wrap, element);
|
||||||
|
}
|
||||||
|
};
|
||||||
27
pyscript.core/src/plugins/deprecations-manager.js
Normal file
27
pyscript.core/src/plugins/deprecations-manager.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// PyScript Derepcations Plugin
|
||||||
|
import { hooks } from "../core.js";
|
||||||
|
import { notify } from "./error.js";
|
||||||
|
|
||||||
|
// react lazily on PyScript bootstrap
|
||||||
|
hooks.main.onReady.add(checkDeprecations);
|
||||||
|
hooks.main.onWorker.add(checkDeprecations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that there are no scripts loading from pyscript.net/latest
|
||||||
|
*/
|
||||||
|
function checkDeprecations() {
|
||||||
|
const scripts = document.querySelectorAll("script");
|
||||||
|
for (const script of scripts) checkLoadingScriptsFromLatest(script.src);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if src being loaded from pyscript.net/latest and display a notification if true
|
||||||
|
* * @param {string} src
|
||||||
|
*/
|
||||||
|
function checkLoadingScriptsFromLatest(src) {
|
||||||
|
if (/\/pyscript\.net\/latest/.test(src)) {
|
||||||
|
notify(
|
||||||
|
"Loading scripts from latest is deprecated and will be removed soon. Please use a specific version instead.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// PyScript Error Plugin
|
// PyScript Error Plugin
|
||||||
import { hooks } from "../core.js";
|
import { hooks } from "../core.js";
|
||||||
|
|
||||||
hooks.onInterpreterReady.add(function override(pyScript) {
|
hooks.main.onReady.add(function override(pyScript) {
|
||||||
// be sure this override happens only once
|
// be sure this override happens only once
|
||||||
hooks.onInterpreterReady.delete(override);
|
hooks.main.onReady.delete(override);
|
||||||
|
|
||||||
// trap generic `stderr` to propagate to it regardless
|
// trap generic `stderr` to propagate to it regardless
|
||||||
const { stderr } = pyScript.io;
|
const { stderr } = pyScript.io;
|
||||||
|
|||||||
146
pyscript.core/src/plugins/py-terminal.js
Normal file
146
pyscript.core/src/plugins/py-terminal.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// PyScript py-terminal plugin
|
||||||
|
import { TYPES, hooks } from "../core.js";
|
||||||
|
import { notify } from "./error.js";
|
||||||
|
|
||||||
|
const SELECTOR = [...TYPES.keys()]
|
||||||
|
.map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
// show the error on main and
|
||||||
|
// stops the module from keep executing
|
||||||
|
const notifyAndThrow = (message) => {
|
||||||
|
notify(message);
|
||||||
|
throw new Error(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pyTerminal = async () => {
|
||||||
|
const terminals = document.querySelectorAll(SELECTOR);
|
||||||
|
|
||||||
|
// no results will look further for runtime nodes
|
||||||
|
if (!terminals.length) return;
|
||||||
|
|
||||||
|
// if we arrived this far, let's drop the MutationObserver
|
||||||
|
// as we only support one terminal per page (right now).
|
||||||
|
mo.disconnect();
|
||||||
|
|
||||||
|
// we currently support only one terminal as in "classic"
|
||||||
|
if (terminals.length > 1) notifyAndThrow("You can use at most 1 terminal.");
|
||||||
|
|
||||||
|
const [element] = terminals;
|
||||||
|
// hopefully to be removed in the near future!
|
||||||
|
if (element.matches('script[type="mpy"],mpy-script'))
|
||||||
|
notifyAndThrow("Unsupported terminal.");
|
||||||
|
|
||||||
|
// import styles lazily
|
||||||
|
document.head.append(
|
||||||
|
Object.assign(document.createElement("link"), {
|
||||||
|
rel: "stylesheet",
|
||||||
|
href: new URL("./xterm.css", import.meta.url),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// lazy load these only when a valid terminal is found
|
||||||
|
const [{ Terminal }, { Readline }] = await Promise.all([
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
|
||||||
|
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const readline = new Readline();
|
||||||
|
|
||||||
|
// common main thread initialization for both worker
|
||||||
|
// or main case, bootstrapping the terminal on its target
|
||||||
|
const init = (options) => {
|
||||||
|
let target = element;
|
||||||
|
const selector = element.getAttribute("target");
|
||||||
|
if (selector) {
|
||||||
|
target =
|
||||||
|
document.getElementById(selector) ||
|
||||||
|
document.querySelector(selector);
|
||||||
|
if (!target) throw new Error(`Unknown target ${selector}`);
|
||||||
|
} else {
|
||||||
|
target = document.createElement("py-terminal");
|
||||||
|
target.style.display = "block";
|
||||||
|
element.after(target);
|
||||||
|
}
|
||||||
|
const terminal = new Terminal({
|
||||||
|
theme: {
|
||||||
|
background: "#191A19",
|
||||||
|
foreground: "#F5F2E7",
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
terminal.loadAddon(readline);
|
||||||
|
terminal.open(target);
|
||||||
|
terminal.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// branch logic for the worker
|
||||||
|
if (element.hasAttribute("worker")) {
|
||||||
|
// when the remote thread onReady triggers:
|
||||||
|
// setup the interpreter stdout and stderr
|
||||||
|
const workerReady = ({ interpreter }, { sync }) => {
|
||||||
|
sync.pyterminal_drop_hooks();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let data = "";
|
||||||
|
const generic = {
|
||||||
|
isatty: true,
|
||||||
|
write(buffer) {
|
||||||
|
data = decoder.decode(buffer);
|
||||||
|
sync.pyterminal_write(data);
|
||||||
|
return buffer.length;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
interpreter.setStdout(generic);
|
||||||
|
interpreter.setStderr(generic);
|
||||||
|
interpreter.setStdin({
|
||||||
|
isatty: true,
|
||||||
|
stdin: () => sync.pyterminal_read(data),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// add a hook on the main thread to setup all sync helpers
|
||||||
|
// also bootstrapping the XTerm target on main
|
||||||
|
hooks.main.onWorker.add(function worker(_, xworker) {
|
||||||
|
hooks.main.onWorker.delete(worker);
|
||||||
|
init({
|
||||||
|
disableStdin: false,
|
||||||
|
cursorBlink: true,
|
||||||
|
cursorStyle: "block",
|
||||||
|
});
|
||||||
|
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
||||||
|
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
||||||
|
// allow a worker to drop main thread hooks ASAP
|
||||||
|
xworker.sync.pyterminal_drop_hooks = () => {
|
||||||
|
hooks.worker.onReady.delete(workerReady);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup remote thread JS/Python code for whenever the
|
||||||
|
// worker is ready to become a terminal
|
||||||
|
hooks.worker.onReady.add(workerReady);
|
||||||
|
} else {
|
||||||
|
// in the main case, just bootstrap XTerm without
|
||||||
|
// allowing any input as that's not possible / awkward
|
||||||
|
hooks.main.onReady.add(function main({ io }) {
|
||||||
|
console.warn("py-terminal is read only on main thread");
|
||||||
|
hooks.main.onReady.delete(main);
|
||||||
|
init({
|
||||||
|
disableStdin: true,
|
||||||
|
cursorBlink: false,
|
||||||
|
cursorStyle: "underline",
|
||||||
|
});
|
||||||
|
io.stdout = (value) => {
|
||||||
|
readline.write(`${value}\n`);
|
||||||
|
};
|
||||||
|
io.stderr = (error) => {
|
||||||
|
readline.write(`${error.message || error}\n`);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mo = new MutationObserver(pyTerminal);
|
||||||
|
mo.observe(document, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
// try to check the current document ASAP
|
||||||
|
export default pyTerminal();
|
||||||
@@ -10,16 +10,22 @@ import pyscript from "./stdlib/pyscript.js";
|
|||||||
|
|
||||||
const { entries } = Object;
|
const { entries } = Object;
|
||||||
|
|
||||||
const python = ["from pathlib import Path as _Path"];
|
const python = [
|
||||||
|
"import os as _os",
|
||||||
|
"from pathlib import Path as _Path",
|
||||||
|
"_path = None",
|
||||||
|
];
|
||||||
|
|
||||||
const write = (base, literal) => {
|
const write = (base, literal) => {
|
||||||
for (const [key, value] of entries(literal)) {
|
for (const [key, value] of entries(literal)) {
|
||||||
const path = `_Path("${base}/${key}")`;
|
python.push(`_path = _Path("${base}/${key}")`);
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
const code = JSON.stringify(value);
|
const code = JSON.stringify(value);
|
||||||
python.push(`${path}.write_text(${code})`);
|
python.push(`_path.write_text(${code})`);
|
||||||
} else {
|
} else {
|
||||||
python.push(`${path}.mkdir(parents=True, exist_ok=True)`);
|
// @see https://github.com/pyscript/pyscript/pull/1813#issuecomment-1781502909
|
||||||
|
python.push(`if not _os.path.exists("${base}/${key}"):`);
|
||||||
|
python.push(" _path.mkdir(parents=True, exist_ok=True)");
|
||||||
write(`${base}/${key}`, value);
|
write(`${base}/${key}`, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,6 +34,8 @@ const write = (base, literal) => {
|
|||||||
write(".", pyscript);
|
write(".", pyscript);
|
||||||
|
|
||||||
python.push("del _Path");
|
python.push("del _Path");
|
||||||
|
python.push("del _path");
|
||||||
|
python.push("del _os");
|
||||||
python.push("\n");
|
python.push("\n");
|
||||||
|
|
||||||
export default python.join("\n");
|
export default python.join("\n");
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ from typing import Any
|
|||||||
from pyodide.ffi import JsProxy
|
from pyodide.ffi import JsProxy
|
||||||
from pyscript import display, document, window
|
from pyscript import display, document, window
|
||||||
|
|
||||||
# from pyscript import when as _when
|
|
||||||
|
|
||||||
alert = window.alert
|
alert = window.alert
|
||||||
|
|
||||||
|
|
||||||
@@ -131,6 +129,22 @@ class Element(BaseElement):
|
|||||||
def id(self, value):
|
def id(self, value):
|
||||||
self._js.id = value
|
self._js.id = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._js.value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
# in order to avoid confusion to the user, we don't allow setting the
|
||||||
|
# value of elements that don't have a value attribute
|
||||||
|
if not hasattr(self._js, "value"):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Element {self._js.tagName} has no value attribute. If you want to "
|
||||||
|
"force a value attribute, set it directly using the `_js.value = <value>` "
|
||||||
|
"javascript API attribute instead."
|
||||||
|
)
|
||||||
|
self._js.value = value
|
||||||
|
|
||||||
def clone(self, new_id=None):
|
def clone(self, new_id=None):
|
||||||
clone = Element(self._js.cloneNode(True))
|
clone = Element(self._js.cloneNode(True))
|
||||||
clone.id = new_id
|
clone.id = new_id
|
||||||
@@ -161,9 +175,6 @@ class Element(BaseElement):
|
|||||||
def show_me(self):
|
def show_me(self):
|
||||||
self._js.scrollIntoView()
|
self._js.scrollIntoView()
|
||||||
|
|
||||||
def when(self, event, handler):
|
|
||||||
document.when(event, selector=self)(handler)
|
|
||||||
|
|
||||||
|
|
||||||
class StyleProxy(dict):
|
class StyleProxy(dict):
|
||||||
def __init__(self, element: Element) -> None:
|
def __init__(self, element: Element) -> None:
|
||||||
@@ -264,6 +275,14 @@ class ElementCollection:
|
|||||||
def html(self, value):
|
def html(self, value):
|
||||||
self._set_attribute("html", value)
|
self._set_attribute("html", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._get_attribute("value")
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
self._set_attribute("value", value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def children(self):
|
def children(self):
|
||||||
return self._elements
|
return self._elements
|
||||||
@@ -295,8 +314,8 @@ class PyDom(BaseElement):
|
|||||||
self.body = Element(document.body)
|
self.body = Element(document.body)
|
||||||
self.head = Element(document.head)
|
self.head = Element(document.head)
|
||||||
|
|
||||||
def create(self, type_, parent=None, classes=None, html=None):
|
def create(self, type_, classes=None, html=None):
|
||||||
return super().create(type_, is_child=False)
|
return super().create(type_, is_child=False, classes=classes, html=html)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, int):
|
if isinstance(key, int):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
def on_click(event):
|
def on_click(event):
|
||||||
print(f"Hello from Python! {dt.now()}")
|
print(f"Hello from Python! {dt.now()}")
|
||||||
display(f"Hello from Python! {dt.now()}", append=False, target='eresult')
|
display(f"Hello from Python! {dt.now()}", append=False, target='result')
|
||||||
|
|
||||||
add_event_listener(element, "click", on_click)
|
add_event_listener(element, "click", on_click)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
60
pyscript.core/test/hooks.html
Normal file
60
pyscript.core/test/hooks.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PyScript Next Plugin Bug?</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module">
|
||||||
|
addEventListener('mpy:done', () => {
|
||||||
|
document.documentElement.classList.add('done');
|
||||||
|
});
|
||||||
|
|
||||||
|
import { hooks } from "../dist/core.js";
|
||||||
|
|
||||||
|
// Main
|
||||||
|
hooks.main.onReady.add((wrap, element) => {
|
||||||
|
console.log("main", "onReady");
|
||||||
|
if (location.search === '?debug') {
|
||||||
|
console.debug("main", "wrap", wrap);
|
||||||
|
console.debug("main", "element", element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hooks.main.onBeforeRun.add(() => {
|
||||||
|
console.log("main", "onBeforeRun");
|
||||||
|
});
|
||||||
|
hooks.main.codeBeforeRun.add('print("main", "codeBeforeRun")');
|
||||||
|
hooks.main.codeAfterRun.add('print("main", "codeAfterRun")');
|
||||||
|
hooks.main.onAfterRun.add(() => {
|
||||||
|
console.log("main", "onAfterRun");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Worker
|
||||||
|
hooks.worker.onReady.add((wrap, xworker) => {
|
||||||
|
console.log("worker", "onReady");
|
||||||
|
if (location.search === '?debug') {
|
||||||
|
console.debug("worker", "wrap", wrap);
|
||||||
|
console.debug("worker", "xworker", xworker);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hooks.worker.onBeforeRun.add(() => {
|
||||||
|
console.log("worker", "onBeforeRun");
|
||||||
|
});
|
||||||
|
hooks.worker.codeBeforeRun.add('print("worker", "codeBeforeRun")');
|
||||||
|
hooks.worker.codeAfterRun.add('print("worker", "codeAfterRun")');
|
||||||
|
hooks.worker.onAfterRun.add(() => {
|
||||||
|
console.log("worker", "onAfterRun");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="mpy" worker>
|
||||||
|
from pyscript import document
|
||||||
|
print("actual code in worker")
|
||||||
|
document.documentElement.classList.add('worker')
|
||||||
|
</script>
|
||||||
|
<script type="mpy">
|
||||||
|
print("actual code in main")
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>PyScript Next</title>
|
<title>PyScript Next</title>
|
||||||
<script>
|
<script>
|
||||||
addEventListener("mpy:ready", console.log);
|
addEventListener('mpy:done', () => {
|
||||||
|
document.documentElement.classList.add('done');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="../dist/core.css">
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
<script type="module" src="../dist/core.js"></script>
|
<script type="module" src="../dist/core.js"></script>
|
||||||
@@ -13,11 +15,16 @@
|
|||||||
<body>
|
<body>
|
||||||
<script type="mpy">
|
<script type="mpy">
|
||||||
from pyscript import display
|
from pyscript import display
|
||||||
display("Hello", "M-PyScript Next", append=False)
|
display("Hello", "M-PyScript Main 1", append=False)
|
||||||
</script>
|
</script>
|
||||||
<mpy-script worker>
|
<mpy-script>
|
||||||
from pyscript import display
|
from pyscript import display
|
||||||
display("Hello", "M-PyScript Next Worker", append=False)
|
display("Hello", "M-PyScript Main 2", append=False)
|
||||||
|
</mpy-script>
|
||||||
|
<mpy-script worker>
|
||||||
|
from pyscript import display, document
|
||||||
|
display("Hello", "M-PyScript Worker", append=False)
|
||||||
|
document.documentElement.classList.add('worker')
|
||||||
</mpy-script>
|
</mpy-script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
37
pyscript.core/test/mpy.spec.js
Normal file
37
pyscript.core/test/mpy.spec.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('MicroPython display', async ({ page }) => {
|
||||||
|
await page.goto('http://localhost:8080/test/mpy.html');
|
||||||
|
await page.waitForSelector('html.done.worker');
|
||||||
|
const body = await page.evaluate(() => document.body.innerText);
|
||||||
|
await expect(body.trim()).toBe([
|
||||||
|
'M-PyScript Main 1',
|
||||||
|
'M-PyScript Main 2',
|
||||||
|
'M-PyScript Worker',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('MicroPython hooks', async ({ page }) => {
|
||||||
|
const logs = [];
|
||||||
|
page.on('console', msg => {
|
||||||
|
const text = msg.text();
|
||||||
|
if (!text.startsWith('['))
|
||||||
|
logs.push(text);
|
||||||
|
});
|
||||||
|
await page.goto('http://localhost:8080/test/hooks.html');
|
||||||
|
await page.waitForSelector('html.done.worker');
|
||||||
|
await expect(logs.join('\n')).toBe([
|
||||||
|
'main onReady',
|
||||||
|
'main onBeforeRun',
|
||||||
|
'main codeBeforeRun',
|
||||||
|
'actual code in main',
|
||||||
|
'main codeAfterRun',
|
||||||
|
'main onAfterRun',
|
||||||
|
'worker onReady',
|
||||||
|
'worker onBeforeRun',
|
||||||
|
'worker codeBeforeRun',
|
||||||
|
'actual code in worker',
|
||||||
|
'worker codeAfterRun',
|
||||||
|
'worker onAfterRun',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
78
pyscript.core/test/panel/panel.py
Normal file
78
pyscript.core/test/panel/panel.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import js
|
||||||
|
import panel as pn
|
||||||
|
from js import JSON
|
||||||
|
from panel.io.pyodide import init_doc, write_doc
|
||||||
|
from polyscript import xworker
|
||||||
|
from pyodide.ffi import create_once_callable, create_proxy, to_js
|
||||||
|
from pyscript import document, window
|
||||||
|
|
||||||
|
js.document = document
|
||||||
|
init_doc()
|
||||||
|
|
||||||
|
print("Hello from panel.py")
|
||||||
|
|
||||||
|
slider = pn.widgets.FloatSlider(start=0, end=10, name="Amplitude")
|
||||||
|
|
||||||
|
|
||||||
|
def callback(new):
|
||||||
|
print(f"Amplitude is: {new}")
|
||||||
|
return f"Amplitude is: {new}"
|
||||||
|
|
||||||
|
|
||||||
|
print("made this far...")
|
||||||
|
pn.Row(slider, pn.bind(callback, slider)).servable(target="simple_app")
|
||||||
|
|
||||||
|
# ------ END OF PANEL CODE ------
|
||||||
|
|
||||||
|
docs_json_str, render_items_str, root_ids_str = await write_doc()
|
||||||
|
docs_json = JSON.parse(docs_json_str) # .as_object_map()
|
||||||
|
# render_items = to_js(JSON.parse(render_items_str), depth=-1, pyproxies=None, create_pyproxies=False, dict_converter=js.Object.fromEntries)#.as_object_map()
|
||||||
|
root_ids = JSON.parse(root_ids_str) # .as_object_map()
|
||||||
|
|
||||||
|
# docs_json = json.loads(docs_json_str)
|
||||||
|
render_items = json.loads(render_items_str)
|
||||||
|
# root_ids = json.loads(root_ids_str)
|
||||||
|
|
||||||
|
print(type(render_items))
|
||||||
|
root_elements = document.querySelectorAll("[data-root-id]")
|
||||||
|
data_roots = []
|
||||||
|
for el in root_elements:
|
||||||
|
el.innerHTML = ""
|
||||||
|
data_roots.append([el.getAttribute("data-root-id"), el.id])
|
||||||
|
|
||||||
|
roots = {root_ids[i]: root for i, root in enumerate(data_roots)}
|
||||||
|
|
||||||
|
print("Quick check")
|
||||||
|
print(roots)
|
||||||
|
print(root_ids)
|
||||||
|
print(render_items)
|
||||||
|
# render_items[0]['roots'] = roots
|
||||||
|
# render_items[0]['root_ids'] = root_ids
|
||||||
|
# render_items[0].roots = to_js(roots)
|
||||||
|
# render_items[0].root_ids = to_js(root_ids)
|
||||||
|
|
||||||
|
|
||||||
|
# print(roots)
|
||||||
|
# print(data_roots)
|
||||||
|
print(docs_json)
|
||||||
|
print(render_items)
|
||||||
|
print("here....")
|
||||||
|
|
||||||
|
# views = await window.Bokeh.embed.embed_items(to_js(docs_json), to_js(render_items))
|
||||||
|
views = await window.Bokeh.embed.embed_items(
|
||||||
|
docs_json, # to_js(docs_json, depth=-1, pyproxies=None, create_pyproxies=False, dict_converter=js.Object.fromEntries),
|
||||||
|
to_js(
|
||||||
|
render_items,
|
||||||
|
depth=-1,
|
||||||
|
pyproxies=None,
|
||||||
|
create_pyproxies=False,
|
||||||
|
dict_converter=js.Object.fromEntries,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Experiments back to main thread
|
||||||
|
# await xworker.sync.render_full(docs_json_str, render_items_str, root_ids_str)
|
||||||
|
print("made it to the end")
|
||||||
|
print(docs_json)
|
||||||
5
pyscript.core/test/panel/panel.toml
Normal file
5
pyscript.core/test/panel/panel.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
packages = [
|
||||||
|
"https://cdn.holoviz.org/panel/0.14.3/dist/wheels/bokeh-2.4.3-py3-none-any.whl",
|
||||||
|
"numpy",
|
||||||
|
"panel==0.14.1"
|
||||||
|
]
|
||||||
71
pyscript.core/test/panel/panel_worker.html
Normal file
71
pyscript.core/test/panel/panel_worker.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Panel Example</title>
|
||||||
|
<meta charset="iso-8859-1" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="./favicon.png" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="pyscript">
|
||||||
|
<div id="simple_app"></div>
|
||||||
|
|
||||||
|
<py-tutor>
|
||||||
|
<script defer src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.js"></script>
|
||||||
|
<script defer src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js"></script>
|
||||||
|
<script defer src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/@holoviz/panel@0.14.1/dist/panel.min.js"></script>
|
||||||
|
|
||||||
|
<script type="module" src="../../core.js"></script>
|
||||||
|
<!-- <script
|
||||||
|
type="module"
|
||||||
|
src="https://esm.sh/@pyscript/core@latest/core.js"
|
||||||
|
></script> -->
|
||||||
|
|
||||||
|
<script type="py" worker="./panel.py" config="./panel.toml" async></script>
|
||||||
|
|
||||||
|
<!-- Code below borrowed for reference from example working on polyscript directly -->
|
||||||
|
<!-- <script type="micropython">
|
||||||
|
from pyscript import PyWorker
|
||||||
|
import json
|
||||||
|
import js
|
||||||
|
|
||||||
|
w = PyWorker('./panel.py', **{'type': 'pyodide', 'async': True, 'config': './panel.toml'})
|
||||||
|
|
||||||
|
# xworker.window made the following completely unnecessary
|
||||||
|
document = js.document
|
||||||
|
|
||||||
|
async def render_full(docs_json_str, render_items_str, root_ids_str):
|
||||||
|
docs_json = json.loads(docs_json_str)
|
||||||
|
render_items = json.loads(render_items_str)
|
||||||
|
root_ids = json.loads(root_ids_str)
|
||||||
|
|
||||||
|
print(type(render_items))
|
||||||
|
root_elements = document.querySelectorAll('[data-root-id]')
|
||||||
|
data_roots = []
|
||||||
|
for el in root_elements:
|
||||||
|
el.innerHTML = ''
|
||||||
|
data_roots.append([el.getAttribute('data-root-id'), el.id])
|
||||||
|
|
||||||
|
roots = {root_ids[i]: root for i, root in enumerate(data_roots)}
|
||||||
|
|
||||||
|
print("Quick check")
|
||||||
|
render_items[0]['roots'] = roots
|
||||||
|
render_items[0]['root_ids'] = root_ids
|
||||||
|
print("here....")
|
||||||
|
views = await js.Bokeh.embed.embed_items(docs_json, render_items)
|
||||||
|
|
||||||
|
|
||||||
|
def render(docs_json, render_items):
|
||||||
|
print(f"GOT DATA: {docs_json}")
|
||||||
|
print(f"GOT ITEMS: {render_items}")
|
||||||
|
|
||||||
|
await js.Bokeh.embed.embed_items(json.loads(docs_json), json.loads(render_items))
|
||||||
|
# views = await xworker.window.Bokeh.embed.embed_items(create_proxy(docs_json), create_proxy(render_items))
|
||||||
|
|
||||||
|
w.sync.render = render
|
||||||
|
w.sync.render_full = render_full
|
||||||
|
|
||||||
|
</script> -->
|
||||||
|
</py-tutor>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
pyscript.core/test/py-terminal.html
Normal file
29
pyscript.core/test/py-terminal.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>PyTerminal</title>
|
||||||
|
<link rel="stylesheet" href="../dist/core.css">
|
||||||
|
<script type="module" src="../dist/core.js"></script>
|
||||||
|
<style>.xterm { padding: .5rem; }</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py">
|
||||||
|
def greetings(event):
|
||||||
|
print('hello world')
|
||||||
|
</script>
|
||||||
|
<py-script worker terminal>
|
||||||
|
import sys
|
||||||
|
from pyscript import display, document
|
||||||
|
display("Hello", "PyScript Next - PyTerminal", append=False)
|
||||||
|
print("this should go to the terminal")
|
||||||
|
print("another line")
|
||||||
|
|
||||||
|
# this works as expected
|
||||||
|
print("this goes to stderr", file=sys.stderr)
|
||||||
|
document.addEventListener('click', lambda event: print(event.type));
|
||||||
|
</py-script>
|
||||||
|
<button id="my-button" py-click="greetings">Click me</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
<h2 id="multi-elem-h2" class="multi-elems">Content multi-elem-h2</h2>
|
<h2 id="multi-elem-h2" class="multi-elems">Content multi-elem-h2</h2>
|
||||||
|
|
||||||
<form>
|
<form>
|
||||||
<input id="test_rr_input_txt" type="text" value="Content test_rr_input_txt">
|
<input id="test_rr_input_text" type="text" value="Content test_rr_input_text">
|
||||||
<input id="test_rr_input_btn" type="button" value="Content test_rr_input_btn">
|
<input id="test_rr_input_button" type="button" value="Content test_rr_input_button">
|
||||||
<input id="test_rr_input_email" type="email" value="Content test_rr_input_email">
|
<input id="test_rr_input_email" type="email" value="Content test_rr_input_email">
|
||||||
<input id="test_rr_input_password" type="password" value="Content test_rr_input_password">
|
<input id="test_rr_input_password" type="password" value="Content test_rr_input_password">
|
||||||
</form>
|
</form>
|
||||||
@@ -89,7 +89,6 @@
|
|||||||
const log = console.log.bind(console)
|
const log = console.log.bind(console)
|
||||||
let testsStarted = false;
|
let testsStarted = false;
|
||||||
console.log = (...args) => {
|
console.log = (...args) => {
|
||||||
log("---IN---");
|
|
||||||
let txt = args.join(" ");
|
let txt = args.join(" ");
|
||||||
let token = "<br>";
|
let token = "<br>";
|
||||||
if (txt.endsWith("FAILED"))
|
if (txt.endsWith("FAILED"))
|
||||||
|
|||||||
@@ -244,3 +244,51 @@ class TestCreation:
|
|||||||
assert new_el.parent == parent_div
|
assert new_el.parent == parent_div
|
||||||
|
|
||||||
assert pydom[selector][0].children[0] == new_el
|
assert pydom[selector][0].children[0] == new_el
|
||||||
|
|
||||||
|
|
||||||
|
class TestInput:
|
||||||
|
input_ids = [
|
||||||
|
"test_rr_input_text",
|
||||||
|
"test_rr_input_button",
|
||||||
|
"test_rr_input_email",
|
||||||
|
"test_rr_input_password",
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_value(self):
|
||||||
|
for id_ in self.input_ids:
|
||||||
|
expected_type = id_.split("_")[-1]
|
||||||
|
result = pydom[f"#{id_}"]
|
||||||
|
input_el = result[0]
|
||||||
|
assert input_el._js.type == expected_type
|
||||||
|
assert input_el.value == f"Content {id_}" == input_el._js.value
|
||||||
|
|
||||||
|
# Check that we can set the value
|
||||||
|
new_value = f"New Value {expected_type}"
|
||||||
|
input_el.value = new_value
|
||||||
|
assert input_el.value == new_value
|
||||||
|
|
||||||
|
# Check that we can set the value back to the original using
|
||||||
|
# the collection
|
||||||
|
new_value = f"Content {id_}"
|
||||||
|
result.value = new_value
|
||||||
|
assert input_el.value == new_value
|
||||||
|
|
||||||
|
def test_set_value_collection(self):
|
||||||
|
for id_ in self.input_ids:
|
||||||
|
input_el = pydom[f"#{id_}"]
|
||||||
|
|
||||||
|
assert input_el.value[0] == f"Content {id_}" == input_el[0].value
|
||||||
|
|
||||||
|
new_value = f"New Value {id_}"
|
||||||
|
input_el.value = new_value
|
||||||
|
assert input_el.value[0] == new_value == input_el[0].value
|
||||||
|
|
||||||
|
def test_element_without_value(self):
|
||||||
|
result = pydom[f"#tests-terminal"][0]
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
result.value = "some value"
|
||||||
|
|
||||||
|
def test_element_without_collection(self):
|
||||||
|
result = pydom[f"#tests-terminal"]
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
result.value = "some value"
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>PyScript Next - Terminal</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@latest/css/xterm.min.css">
|
|
||||||
<script type="module">
|
|
||||||
import { Readline } from "https://cdn.jsdelivr.net/npm/xterm-readline@latest/+esm";
|
|
||||||
const rl = new Readline;
|
|
||||||
rl.setCheckHandler(text => !text.trimEnd().endsWith("&&"));
|
|
||||||
|
|
||||||
import { Terminal } from "https://cdn.jsdelivr.net/npm/xterm@latest/+esm";
|
|
||||||
const term = new Terminal({
|
|
||||||
theme: {
|
|
||||||
background: "#191A19",
|
|
||||||
foreground: "#F5F2E7",
|
|
||||||
},
|
|
||||||
cursorBlink: true,
|
|
||||||
cursorStyle: "block",
|
|
||||||
});
|
|
||||||
term.loadAddon(rl);
|
|
||||||
term.open(terminal);
|
|
||||||
term.focus();
|
|
||||||
|
|
||||||
import { PyWorker } from "../dist/core.js";
|
|
||||||
const { sync } = new PyWorker("terminal.py");
|
|
||||||
Object.assign(sync, {
|
|
||||||
readline: prompt => rl.read(prompt),
|
|
||||||
write: line => term.write(line),
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="terminal"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
###### magic monkey patching ######
|
|
||||||
import builtins
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from pyodide.code import eval_code
|
|
||||||
from pyscript import sync
|
|
||||||
|
|
||||||
sys.stdout = sync
|
|
||||||
builtins.input = sync.readline
|
|
||||||
|
|
||||||
####### main code ######
|
|
||||||
import code
|
|
||||||
|
|
||||||
code.interact()
|
|
||||||
@@ -8,8 +8,10 @@
|
|||||||
|
|
||||||
<!-- the PyWorker approach -->
|
<!-- the PyWorker approach -->
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { PyWorker } from '../dist/core.js';
|
import { PyWorker, whenDefined } from '../dist/core.js';
|
||||||
|
whenDefined('py').then(() => {
|
||||||
PyWorker('./worker.py', {config: {fetch: [{files: ['./a.py']}]}});
|
PyWorker('./worker.py', {config: {fetch: [{files: ['./a.py']}]}});
|
||||||
|
});
|
||||||
// the type is overwritten as "pyodide" in PyScript as the module
|
// the type is overwritten as "pyodide" in PyScript as the module
|
||||||
// lives in that env too
|
// lives in that env too
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -118,6 +118,20 @@ def only_main(fn):
|
|||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
def only_worker(fn):
|
||||||
|
"""
|
||||||
|
Decorator to mark a test which make sense only in the worker thread
|
||||||
|
"""
|
||||||
|
|
||||||
|
@functools.wraps(fn)
|
||||||
|
def decorated(self, *args):
|
||||||
|
if self.execution_thread != "worker":
|
||||||
|
return
|
||||||
|
return fn(self, *args)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
def filter_inner_text(text, exclude=None):
|
def filter_inner_text(text, exclude=None):
|
||||||
return "\n".join(filter_page_content(text.splitlines(), exclude=exclude))
|
return "\n".join(filter_page_content(text.splitlines(), exclude=exclude))
|
||||||
|
|
||||||
@@ -531,7 +545,9 @@ class PyScriptTest:
|
|||||||
- wait until pyscript has been fully loaded
|
- wait until pyscript has been fully loaded
|
||||||
"""
|
"""
|
||||||
doc = self._pyscript_format(
|
doc = self._pyscript_format(
|
||||||
snippet, execution_thread=self.execution_thread, extra_head=extra_head
|
snippet,
|
||||||
|
execution_thread=self.execution_thread,
|
||||||
|
extra_head=extra_head,
|
||||||
)
|
)
|
||||||
if not wait_for_pyscript and timeout is not None:
|
if not wait_for_pyscript and timeout is not None:
|
||||||
raise ValueError("Cannot set a timeout if wait_for_pyscript=False")
|
raise ValueError("Cannot set a timeout if wait_for_pyscript=False")
|
||||||
@@ -652,7 +668,7 @@ TEST_ITERATIONS = math.ceil(
|
|||||||
) # 120 iters of 1/4 second
|
) # 120 iters of 1/4 second
|
||||||
|
|
||||||
|
|
||||||
def wait_for_render(page, selector, pattern, timeout_seconds: int | None = None):
|
def wait_for_render(page, selector, pattern, timeout_seconds=None):
|
||||||
"""
|
"""
|
||||||
Assert that rendering inserts data into the page as expected: search the
|
Assert that rendering inserts data into the page as expected: search the
|
||||||
DOM from within the timing loop for a string that is not present in the
|
DOM from within the timing loop for a string that is not present in the
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class TestBasic(PyScriptTest):
|
|||||||
'"Cross-Origin-Opener-Policy":"same-origin"}. '
|
'"Cross-Origin-Opener-Policy":"same-origin"}. '
|
||||||
"The problem may be that one or both of these are missing."
|
"The problem may be that one or both of these are missing."
|
||||||
)
|
)
|
||||||
alert_banner = self.page.wait_for_selector(".alert-banner")
|
alert_banner = self.page.wait_for_selector(".py-error")
|
||||||
assert expected_alert_banner_msg in alert_banner.inner_text()
|
assert expected_alert_banner_msg in alert_banner.inner_text()
|
||||||
|
|
||||||
def test_print(self):
|
def test_print(self):
|
||||||
|
|||||||
@@ -3,15 +3,62 @@ import time
|
|||||||
import pytest
|
import pytest
|
||||||
from playwright.sync_api import expect
|
from playwright.sync_api import expect
|
||||||
|
|
||||||
from .support import PyScriptTest, skip_worker
|
from .support import PageErrors, PyScriptTest, only_worker, skip_worker
|
||||||
|
|
||||||
pytest.skip(
|
|
||||||
reason="FIX LATER: pyscript NEXT doesn't support the Terminal yet",
|
|
||||||
allow_module_level=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPyTerminal(PyScriptTest):
|
class TestPyTerminal(PyScriptTest):
|
||||||
|
def test_multiple_terminals(self):
|
||||||
|
"""
|
||||||
|
Multiple terminals are not currently supported
|
||||||
|
"""
|
||||||
|
self.pyscript_run(
|
||||||
|
"""
|
||||||
|
<script type="py" terminal></script>
|
||||||
|
<script type="py" terminal></script>
|
||||||
|
""",
|
||||||
|
wait_for_pyscript=False,
|
||||||
|
check_js_errors=False,
|
||||||
|
)
|
||||||
|
assert self.assert_banner_message("You can use at most 1 terminal")
|
||||||
|
|
||||||
|
with pytest.raises(PageErrors, match="You can use at most 1 terminal"):
|
||||||
|
self.check_js_errors()
|
||||||
|
|
||||||
|
# TODO: interactive shell still unclear
|
||||||
|
# @only_worker
|
||||||
|
# def test_py_terminal_input(self):
|
||||||
|
# """
|
||||||
|
# Only worker py-terminal accepts an input
|
||||||
|
# """
|
||||||
|
# self.pyscript_run(
|
||||||
|
# """
|
||||||
|
# <script type="py" terminal></script>
|
||||||
|
# """,
|
||||||
|
# wait_for_pyscript=False,
|
||||||
|
# )
|
||||||
|
# self.page.get_by_text(">>> ", exact=True).wait_for()
|
||||||
|
# self.page.keyboard.type("'the answer is ' + str(6 * 7)")
|
||||||
|
# self.page.keyboard.press("Enter")
|
||||||
|
# self.page.get_by_text("the answer is 42").wait_for()
|
||||||
|
|
||||||
|
@only_worker
|
||||||
|
def test_py_terminal_os_write(self):
|
||||||
|
"""
|
||||||
|
An `os.write("text")` should land in the terminal
|
||||||
|
"""
|
||||||
|
self.pyscript_run(
|
||||||
|
"""
|
||||||
|
<script type="py" terminal>
|
||||||
|
import os
|
||||||
|
os.write(1, str.encode("hello\\n"))
|
||||||
|
os.write(2, str.encode("world\\n"))
|
||||||
|
</script>
|
||||||
|
""",
|
||||||
|
wait_for_pyscript=False,
|
||||||
|
)
|
||||||
|
self.page.get_by_text("hello\n").wait_for()
|
||||||
|
self.page.get_by_text("world\n").wait_for()
|
||||||
|
|
||||||
def test_py_terminal(self):
|
def test_py_terminal(self):
|
||||||
"""
|
"""
|
||||||
1. <py-terminal> should redirect stdout and stderr to the DOM
|
1. <py-terminal> should redirect stdout and stderr to the DOM
|
||||||
@@ -20,138 +67,44 @@ class TestPyTerminal(PyScriptTest):
|
|||||||
"""
|
"""
|
||||||
self.pyscript_run(
|
self.pyscript_run(
|
||||||
"""
|
"""
|
||||||
<py-terminal></py-terminal>
|
<script type="py" terminal>
|
||||||
|
|
||||||
<script type="py">
|
|
||||||
import sys
|
import sys
|
||||||
print('hello world')
|
print('hello world')
|
||||||
print('this goes to stderr', file=sys.stderr)
|
print('this goes to stderr', file=sys.stderr)
|
||||||
print('this goes to stdout')
|
print('this goes to stdout')
|
||||||
</script>
|
</script>
|
||||||
"""
|
""",
|
||||||
|
wait_for_pyscript=False,
|
||||||
)
|
)
|
||||||
|
self.page.get_by_text("hello world").wait_for()
|
||||||
term = self.page.locator("py-terminal")
|
term = self.page.locator("py-terminal")
|
||||||
term_lines = term.inner_text().splitlines()
|
term_lines = term.inner_text().splitlines()
|
||||||
assert term_lines == [
|
assert term_lines[0:3] == [
|
||||||
"hello world",
|
|
||||||
"this goes to stderr",
|
|
||||||
"this goes to stdout",
|
|
||||||
]
|
|
||||||
assert self.console.log.lines[-3:] == [
|
|
||||||
"hello world",
|
"hello world",
|
||||||
"this goes to stderr",
|
"this goes to stderr",
|
||||||
"this goes to stdout",
|
"this goes to stdout",
|
||||||
]
|
]
|
||||||
|
|
||||||
@skip_worker("FIXME: js.document")
|
@skip_worker(
|
||||||
def test_two_terminals(self):
|
"Workers don't have events + two different workers don't share the same I/O"
|
||||||
"""
|
)
|
||||||
Multiple <py-terminal>s can cohexist.
|
def test_button_action(self):
|
||||||
A <py-terminal> receives only output from the moment it is added to
|
|
||||||
the DOM.
|
|
||||||
"""
|
|
||||||
self.pyscript_run(
|
self.pyscript_run(
|
||||||
"""
|
"""
|
||||||
<py-terminal id="term1"></py-terminal>
|
|
||||||
|
|
||||||
<script type="py">
|
|
||||||
import js
|
|
||||||
print('one')
|
|
||||||
term2 = js.document.createElement('py-terminal')
|
|
||||||
term2.id = 'term2'
|
|
||||||
js.document.body.append(term2)
|
|
||||||
|
|
||||||
print('two')
|
|
||||||
print('three')
|
|
||||||
</script>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
term1 = self.page.locator("#term1")
|
|
||||||
term2 = self.page.locator("#term2")
|
|
||||||
term1_lines = term1.inner_text().splitlines()
|
|
||||||
term2_lines = term2.inner_text().splitlines()
|
|
||||||
assert term1_lines == ["one", "two", "three"]
|
|
||||||
assert term2_lines == ["two", "three"]
|
|
||||||
|
|
||||||
def test_auto_attribute(self):
|
|
||||||
self.pyscript_run(
|
|
||||||
"""
|
|
||||||
<py-terminal auto></py-terminal>
|
|
||||||
|
|
||||||
<button id="my-button" py-click="print('hello world')">Click me</button>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
term = self.page.locator("py-terminal")
|
|
||||||
expect(term).to_be_hidden()
|
|
||||||
self.page.locator("button").click()
|
|
||||||
expect(term).to_be_visible()
|
|
||||||
assert term.inner_text() == "hello world\n"
|
|
||||||
|
|
||||||
def test_config_auto(self):
|
|
||||||
"""
|
|
||||||
config.terminal == "auto" is the default: a <py-terminal auto> is
|
|
||||||
automatically added to the page
|
|
||||||
"""
|
|
||||||
self.pyscript_run(
|
|
||||||
"""
|
|
||||||
<button id="my-button" py-click="print('hello world')">Click me</button>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
term = self.page.locator("py-terminal")
|
|
||||||
expect(term).to_be_hidden()
|
|
||||||
assert "No <py-terminal> found, adding one" in self.console.info.text
|
|
||||||
#
|
|
||||||
self.page.locator("button").click()
|
|
||||||
expect(term).to_be_visible()
|
|
||||||
assert term.inner_text() == "hello world\n"
|
|
||||||
|
|
||||||
def test_config_true(self):
|
|
||||||
"""
|
|
||||||
If we set config.terminal == true, a <py-terminal> is automatically added
|
|
||||||
"""
|
|
||||||
self.pyscript_run(
|
|
||||||
"""
|
|
||||||
<py-config>
|
|
||||||
terminal = true
|
|
||||||
</py-config>
|
|
||||||
|
|
||||||
<script type="py">
|
<script type="py">
|
||||||
|
def greetings(event):
|
||||||
print('hello world')
|
print('hello world')
|
||||||
</script>
|
</script>
|
||||||
"""
|
<script type="py" terminal></script>
|
||||||
)
|
|
||||||
term = self.page.locator("py-terminal")
|
|
||||||
expect(term).to_be_visible()
|
|
||||||
assert term.inner_text() == "hello world\n"
|
|
||||||
|
|
||||||
def test_config_false(self):
|
<button id="my-button" py-click="greetings">Click me</button>
|
||||||
"""
|
|
||||||
If we set config.terminal == false, no <py-terminal> is added
|
|
||||||
"""
|
|
||||||
self.pyscript_run(
|
|
||||||
"""
|
|
||||||
<py-config>
|
|
||||||
terminal = false
|
|
||||||
</py-config>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
term = self.page.locator("py-terminal")
|
|
||||||
assert term.count() == 0
|
|
||||||
|
|
||||||
def test_config_docked(self):
|
|
||||||
"""
|
|
||||||
config.docked == "docked" is also the default: a <py-terminal auto docked> is
|
|
||||||
automatically added to the page
|
|
||||||
"""
|
|
||||||
self.pyscript_run(
|
|
||||||
"""
|
|
||||||
<button id="my-button" py-click="print('hello world')">Click me</button>
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
term = self.page.locator("py-terminal")
|
term = self.page.locator("py-terminal")
|
||||||
self.page.locator("button").click()
|
self.page.locator("button").click()
|
||||||
expect(term).to_be_visible()
|
last_line = self.page.get_by_text("hello world")
|
||||||
assert term.get_attribute("docked") == ""
|
last_line.wait_for()
|
||||||
|
assert term.inner_text().rstrip() == "hello world"
|
||||||
|
|
||||||
def test_xterm_function(self):
|
def test_xterm_function(self):
|
||||||
"""Test a few basic behaviors of the xtermjs terminal.
|
"""Test a few basic behaviors of the xtermjs terminal.
|
||||||
@@ -164,17 +117,15 @@ class TestPyTerminal(PyScriptTest):
|
|||||||
"""
|
"""
|
||||||
self.pyscript_run(
|
self.pyscript_run(
|
||||||
"""
|
"""
|
||||||
<py-config>
|
<script type="py" terminal>
|
||||||
xterm = true
|
|
||||||
</py-config>
|
|
||||||
<script type="py">
|
|
||||||
print("\x1b[33mYellow\x1b[0m")
|
print("\x1b[33mYellow\x1b[0m")
|
||||||
print("\x1b[4mUnderline\x1b[24m")
|
print("\x1b[4mUnderline\x1b[24m")
|
||||||
print("\x1b[1mBold\x1b[22m")
|
print("\x1b[1mBold\x1b[22m")
|
||||||
print("\x1b[3mItalic\x1b[23m")
|
print("\x1b[3mItalic\x1b[23m")
|
||||||
print("done")
|
print("done")
|
||||||
</script>
|
</script>
|
||||||
"""
|
""",
|
||||||
|
wait_for_pyscript=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wait for "done" to actually appear in the xterm; may be delayed,
|
# Wait for "done" to actually appear in the xterm; may be delayed,
|
||||||
@@ -234,37 +185,3 @@ class TestPyTerminal(PyScriptTest):
|
|||||||
"(element) => getComputedStyle(element).getPropertyValue('font-style')"
|
"(element) => getComputedStyle(element).getPropertyValue('font-style')"
|
||||||
)
|
)
|
||||||
assert font_style == "italic"
|
assert font_style == "italic"
|
||||||
|
|
||||||
def test_xterm_multiple(self):
|
|
||||||
"""Test whether multiple x-terms on the page all function"""
|
|
||||||
self.pyscript_run(
|
|
||||||
"""
|
|
||||||
<py-config>
|
|
||||||
xterm = true
|
|
||||||
</py-config>
|
|
||||||
<script type="py">
|
|
||||||
print("\x1b[33mYellow\x1b[0m")
|
|
||||||
print("done")
|
|
||||||
</script>
|
|
||||||
<py-terminal id="a"></py-terminal>
|
|
||||||
<py-terminal id="b" data-testid="b"></py-terminal>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wait for "done" to actually appear in the xterm; may be delayed,
|
|
||||||
# since xtermjs processes its input buffer in chunks
|
|
||||||
last_line = self.page.get_by_test_id("b").get_by_text("done")
|
|
||||||
last_line.wait_for()
|
|
||||||
|
|
||||||
# Yes, this is not ideal. See note in `test_xterm_function`
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
rows = self.page.locator("#a .xterm-rows")
|
|
||||||
|
|
||||||
# First line should be yellow
|
|
||||||
first_line = rows.locator("div").nth(0)
|
|
||||||
first_char = first_line.locator("span").nth(0)
|
|
||||||
color = first_char.evaluate(
|
|
||||||
"(element) => getComputedStyle(element).getPropertyValue('color')"
|
|
||||||
)
|
|
||||||
assert color == "rgb(196, 160, 0)"
|
|
||||||
|
|||||||
@@ -1,13 +1,35 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .support import PyScriptTest
|
from .support import PyScriptTest, skip_worker
|
||||||
|
|
||||||
pytest.skip(reason="NEXT: Restore the banner", allow_module_level=True)
|
|
||||||
|
|
||||||
|
|
||||||
class TestWarningsAndBanners(PyScriptTest):
|
class TestWarningsAndBanners(PyScriptTest):
|
||||||
# Test the behavior of generated warning banners
|
# Test the behavior of generated warning banners
|
||||||
|
|
||||||
|
def test_deprecate_loading_scripts_from_latest(self):
|
||||||
|
# Use a script tag with an invalid output attribute to generate a warning, but only one
|
||||||
|
self.pyscript_run(
|
||||||
|
"""
|
||||||
|
<script type="py">
|
||||||
|
print("whatever..")
|
||||||
|
</script>
|
||||||
|
""",
|
||||||
|
extra_head='<script type="ignore-me" src="https://pyscript.net/latest/any-path-triggers-the-warning-anyway.js"></script>',
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the banner to appear (we could have a page.locater call but for some reason
|
||||||
|
# the worker takes to long to render on CI, since it's a test we can afford 2 calls)
|
||||||
|
loc = self.page.wait_for_selector(".py-error")
|
||||||
|
assert (
|
||||||
|
loc.inner_text()
|
||||||
|
== "Loading scripts from latest is deprecated and will be removed soon. Please use a specific version instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only one banner should appear
|
||||||
|
loc = self.page.locator(".py-error")
|
||||||
|
assert loc.count() == 1
|
||||||
|
|
||||||
|
@pytest.mark.skip("NEXT: To check if behaviour is consistent with classic")
|
||||||
def test_create_singular_warning(self):
|
def test_create_singular_warning(self):
|
||||||
# Use a script tag with an invalid output attribute to generate a warning, but only one
|
# Use a script tag with an invalid output attribute to generate a warning, but only one
|
||||||
self.pyscript_run(
|
self.pyscript_run(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "ES2022",
|
"module": "NodeNext",
|
||||||
"target": "ES2022",
|
"target": "esnext",
|
||||||
"moduleResolution": "Classic",
|
"moduleResolution": "nodenext",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
|
|||||||
12
pyscript.core/types/3rd-party/toml.d.ts
vendored
Normal file
12
pyscript.core/types/3rd-party/toml.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*! (c) Jak Wings - MIT */ declare class e extends SyntaxError {
|
||||||
|
constructor(r: any, { offset: t, line: e, column: n }: {
|
||||||
|
offset: any;
|
||||||
|
line: any;
|
||||||
|
column: any;
|
||||||
|
});
|
||||||
|
offset: any;
|
||||||
|
line: any;
|
||||||
|
column: any;
|
||||||
|
}
|
||||||
|
declare function n(n: any): any;
|
||||||
|
export { e as SyntaxError, n as parse };
|
||||||
138
pyscript.core/types/3rd-party/xterm-readline.d.ts
vendored
Normal file
138
pyscript.core/types/3rd-party/xterm-readline.d.ts
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
declare var b: any;
|
||||||
|
declare var I: boolean;
|
||||||
|
declare namespace r {
|
||||||
|
export let __esModule: boolean;
|
||||||
|
export { Readline };
|
||||||
|
}
|
||||||
|
declare class Readline {
|
||||||
|
highlighter: {
|
||||||
|
highlight(t: any, e: any): any;
|
||||||
|
highlightPrompt(t: any): any;
|
||||||
|
highlightChar(t: any, e: any): boolean;
|
||||||
|
};
|
||||||
|
history: {
|
||||||
|
entries: any[];
|
||||||
|
cursor: number;
|
||||||
|
maxEntries: any;
|
||||||
|
saveToLocalStorage(): void;
|
||||||
|
restoreFromLocalStorage(): void;
|
||||||
|
append(t: any): void;
|
||||||
|
resetCursor(): void;
|
||||||
|
next(): any;
|
||||||
|
prev(): any;
|
||||||
|
};
|
||||||
|
disposables: any[];
|
||||||
|
watermark: number;
|
||||||
|
highWatermark: number;
|
||||||
|
lowWatermark: number;
|
||||||
|
highWater: boolean;
|
||||||
|
state: {
|
||||||
|
line: {
|
||||||
|
buf: string;
|
||||||
|
pos: number;
|
||||||
|
buffer(): string;
|
||||||
|
pos_buffer(): string;
|
||||||
|
length(): number;
|
||||||
|
char_length(): number;
|
||||||
|
update(t: any, e: any): void;
|
||||||
|
insert(t: any): boolean;
|
||||||
|
moveBack(t: any): boolean;
|
||||||
|
moveForward(t: any): boolean;
|
||||||
|
moveHome(): boolean;
|
||||||
|
moveEnd(): boolean;
|
||||||
|
startOfLine(): number;
|
||||||
|
endOfLine(): number;
|
||||||
|
moveLineUp(t: any): boolean;
|
||||||
|
moveLineDown(t: any): boolean;
|
||||||
|
set_pos(t: any): void;
|
||||||
|
prevPos(t: any): number;
|
||||||
|
nextPos(t: any): number;
|
||||||
|
backspace(t: any): boolean;
|
||||||
|
delete(t: any): boolean;
|
||||||
|
deleteEndOfLine(): boolean;
|
||||||
|
};
|
||||||
|
highlighting: boolean;
|
||||||
|
prompt: any;
|
||||||
|
tty: any;
|
||||||
|
highlighter: any;
|
||||||
|
history: any;
|
||||||
|
promptSize: any;
|
||||||
|
layout: p;
|
||||||
|
buffer(): string;
|
||||||
|
shouldHighlight(): boolean;
|
||||||
|
clearScreen(): void;
|
||||||
|
editInsert(t: any): void;
|
||||||
|
update(t: any): void;
|
||||||
|
editBackspace(t: any): void;
|
||||||
|
editDelete(t: any): void;
|
||||||
|
editDeleteEndOfLine(): void;
|
||||||
|
refresh(): void;
|
||||||
|
moveCursorBack(t: any): void;
|
||||||
|
moveCursorForward(t: any): void;
|
||||||
|
moveCursorUp(t: any): void;
|
||||||
|
moveCursorDown(t: any): void;
|
||||||
|
moveCursorHome(): void;
|
||||||
|
moveCursorEnd(): void;
|
||||||
|
moveCursorToEnd(): void;
|
||||||
|
previousHistory(): void;
|
||||||
|
nextHistory(): void;
|
||||||
|
moveCursor(): void;
|
||||||
|
};
|
||||||
|
checkHandler: () => boolean;
|
||||||
|
ctrlCHandler: () => void;
|
||||||
|
pauseHandler: (t: any) => void;
|
||||||
|
activate(t: any): void;
|
||||||
|
term: any;
|
||||||
|
dispose(): void;
|
||||||
|
appendHistory(t: any): void;
|
||||||
|
setHighlighter(t: any): void;
|
||||||
|
setCheckHandler(t: any): void;
|
||||||
|
setCtrlCHandler(t: any): void;
|
||||||
|
setPauseHandler(t: any): void;
|
||||||
|
writeReady(): boolean;
|
||||||
|
write(t: any): void;
|
||||||
|
print(t: any): void;
|
||||||
|
println(t: any): void;
|
||||||
|
output(): this;
|
||||||
|
tty(): {
|
||||||
|
tabWidth: any;
|
||||||
|
col: any;
|
||||||
|
row: any;
|
||||||
|
out: any;
|
||||||
|
write(t: any): any;
|
||||||
|
print(t: any): any;
|
||||||
|
println(t: any): any;
|
||||||
|
clearScreen(): void;
|
||||||
|
calculatePosition(t: any, e: any): any;
|
||||||
|
computeLayout(t: any, e: any): {
|
||||||
|
promptSize: any;
|
||||||
|
cursor: any;
|
||||||
|
end: any;
|
||||||
|
};
|
||||||
|
refreshLine(t: any, e: any, s: any, i: any, r: any): void;
|
||||||
|
clearOldRows(t: any): void;
|
||||||
|
moveCursor(t: any, e: any): void;
|
||||||
|
};
|
||||||
|
read(t: any): Promise<any>;
|
||||||
|
activeRead: {
|
||||||
|
prompt: any;
|
||||||
|
resolve: (value: any) => void;
|
||||||
|
reject: (reason?: any) => void;
|
||||||
|
};
|
||||||
|
handleKeyEvent(t: any): boolean;
|
||||||
|
readData(t: any): void;
|
||||||
|
readPaste(t: any): void;
|
||||||
|
readKey(t: any): void;
|
||||||
|
}
|
||||||
|
declare class p {
|
||||||
|
constructor(t: any);
|
||||||
|
promptSize: any;
|
||||||
|
cursor: c;
|
||||||
|
end: c;
|
||||||
|
}
|
||||||
|
declare class c {
|
||||||
|
constructor(t: any, e: any);
|
||||||
|
row: any;
|
||||||
|
col: any;
|
||||||
|
}
|
||||||
|
export { b as Readline, I as __esModule, r as default };
|
||||||
4
pyscript.core/types/3rd-party/xterm.d.ts
vendored
Normal file
4
pyscript.core/types/3rd-party/xterm.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare var i: any;
|
||||||
|
declare var s: any;
|
||||||
|
declare var t: {};
|
||||||
|
export { i as Terminal, s as __esModule, t as default };
|
||||||
34
pyscript.core/types/core.d.ts
vendored
34
pyscript.core/types/core.d.ts
vendored
@@ -1,16 +1,42 @@
|
|||||||
|
import TYPES from "./types.js";
|
||||||
/**
|
/**
|
||||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||||
* @param {string} file the python file to run ina worker.
|
* @param {string} file the python file to run ina worker.
|
||||||
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
* @param {{config?: string | object, async?: boolean}} [options] optional configuration for the worker.
|
||||||
* @returns {Worker & {sync: ProxyHandler<object>}}
|
* @returns {Worker & {sync: ProxyHandler<object>}}
|
||||||
*/
|
*/
|
||||||
export function PyWorker(file: string, options?: {
|
declare function exportedPyWorker(file: string, options?: {
|
||||||
config?: string | object;
|
config?: string | object;
|
||||||
async?: boolean;
|
async?: boolean;
|
||||||
}): Worker & {
|
}): Worker & {
|
||||||
sync: ProxyHandler<object>;
|
sync: ProxyHandler<object>;
|
||||||
};
|
};
|
||||||
import sync from "./sync.js";
|
declare const exportedHooks: {
|
||||||
|
main: {
|
||||||
|
onWorker: Set<Function>;
|
||||||
|
onReady: Set<Function>;
|
||||||
|
onBeforeRun: Set<Function>;
|
||||||
|
onBeforeRunAsync: Set<Function>;
|
||||||
|
onAfterRun: Set<Function>;
|
||||||
|
onAfterRunAsync: Set<Function>;
|
||||||
|
codeBeforeRun: Set<string>;
|
||||||
|
codeBeforeRunAsync: Set<string>;
|
||||||
|
codeAfterRun: Set<string>;
|
||||||
|
codeAfterRunAsync: Set<string>;
|
||||||
|
};
|
||||||
|
worker: {
|
||||||
|
onReady: Set<Function>;
|
||||||
|
onBeforeRun: Set<Function>;
|
||||||
|
onBeforeRunAsync: Set<Function>;
|
||||||
|
onAfterRun: Set<Function>;
|
||||||
|
onAfterRunAsync: Set<Function>;
|
||||||
|
codeBeforeRun: Set<string>;
|
||||||
|
codeBeforeRunAsync: Set<string>;
|
||||||
|
codeAfterRun: Set<string>;
|
||||||
|
codeAfterRunAsync: Set<string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
declare const exportedConfig: {};
|
declare const exportedConfig: {};
|
||||||
import hooks from "./hooks.js";
|
declare const exportedWhenDefined: (type: string) => Promise<any>;
|
||||||
export { exportedConfig as config, hooks };
|
import sync from "./sync.js";
|
||||||
|
export { TYPES, exportedPyWorker as PyWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
||||||
|
|||||||
1
pyscript.core/types/fetch.d.ts
vendored
1
pyscript.core/types/fetch.d.ts
vendored
@@ -9,3 +9,4 @@
|
|||||||
*/
|
*/
|
||||||
export function robustFetch(url: string, options?: Request): Promise<Response>;
|
export function robustFetch(url: string, options?: Request): Promise<Response>;
|
||||||
export { getText };
|
export { getText };
|
||||||
|
import { getText } from "polyscript/exports";
|
||||||
|
|||||||
41
pyscript.core/types/hooks.d.ts
vendored
41
pyscript.core/types/hooks.d.ts
vendored
@@ -1,13 +1,38 @@
|
|||||||
declare namespace _default {
|
export function main(name: any): any;
|
||||||
let onInterpreterReady: Set<Function>;
|
export function worker(name: any): any;
|
||||||
|
export function codeFor(branch: any): {};
|
||||||
|
export function createFunction(self: any, name: any): any;
|
||||||
|
export namespace hooks {
|
||||||
|
namespace main {
|
||||||
|
let onWorker: Set<Function>;
|
||||||
|
let onReady: Set<Function>;
|
||||||
let onBeforeRun: Set<Function>;
|
let onBeforeRun: Set<Function>;
|
||||||
let onBeforeRunAsync: Set<Function>;
|
let onBeforeRunAsync: Set<Function>;
|
||||||
let onAfterRun: Set<Function>;
|
let onAfterRun: Set<Function>;
|
||||||
let onAfterRunAsync: Set<Function>;
|
let onAfterRunAsync: Set<Function>;
|
||||||
let onWorkerReady: Set<Function>;
|
let codeBeforeRun: Set<string>;
|
||||||
let codeBeforeRunWorker: Set<string>;
|
let codeBeforeRunAsync: Set<string>;
|
||||||
let codeBeforeRunWorkerAsync: Set<string>;
|
let codeAfterRun: Set<string>;
|
||||||
let codeAfterRunWorker: Set<string>;
|
let codeAfterRunAsync: Set<string>;
|
||||||
let codeAfterRunWorkerAsync: Set<string>;
|
}
|
||||||
|
namespace worker {
|
||||||
|
let onReady_1: Set<Function>;
|
||||||
|
export { onReady_1 as onReady };
|
||||||
|
let onBeforeRun_1: Set<Function>;
|
||||||
|
export { onBeforeRun_1 as onBeforeRun };
|
||||||
|
let onBeforeRunAsync_1: Set<Function>;
|
||||||
|
export { onBeforeRunAsync_1 as onBeforeRunAsync };
|
||||||
|
let onAfterRun_1: Set<Function>;
|
||||||
|
export { onAfterRun_1 as onAfterRun };
|
||||||
|
let onAfterRunAsync_1: Set<Function>;
|
||||||
|
export { onAfterRunAsync_1 as onAfterRunAsync };
|
||||||
|
let codeBeforeRun_1: Set<string>;
|
||||||
|
export { codeBeforeRun_1 as codeBeforeRun };
|
||||||
|
let codeBeforeRunAsync_1: Set<string>;
|
||||||
|
export { codeBeforeRunAsync_1 as codeBeforeRunAsync };
|
||||||
|
let codeAfterRun_1: Set<string>;
|
||||||
|
export { codeAfterRun_1 as codeAfterRun };
|
||||||
|
let codeAfterRunAsync_1: Set<string>;
|
||||||
|
export { codeAfterRunAsync_1 as codeAfterRunAsync };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default _default;
|
|
||||||
|
|||||||
2
pyscript.core/types/plugins-helper.d.ts
vendored
Normal file
2
pyscript.core/types/plugins-helper.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
declare function _default(main: any, wrap: any, element: any, hook: any): Promise<void>;
|
||||||
|
export default _default;
|
||||||
8
pyscript.core/types/plugins.d.ts
vendored
8
pyscript.core/types/plugins.d.ts
vendored
@@ -1,4 +1,6 @@
|
|||||||
declare namespace _default {
|
declare const _default: {
|
||||||
function error(): Promise<typeof import("./plugins/error.js")>;
|
"deprecations-manager": () => Promise<typeof import("./plugins/deprecations-manager.js")>;
|
||||||
}
|
error: () => Promise<typeof import("./plugins/error.js")>;
|
||||||
|
"py-terminal": () => Promise<typeof import("./plugins/py-terminal.js")>;
|
||||||
|
};
|
||||||
export default _default;
|
export default _default;
|
||||||
|
|||||||
1
pyscript.core/types/plugins/deprecations-manager.d.ts
vendored
Normal file
1
pyscript.core/types/plugins/deprecations-manager.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
2
pyscript.core/types/plugins/py-terminal.d.ts
vendored
Normal file
2
pyscript.core/types/plugins/py-terminal.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
declare const _default: Promise<void>;
|
||||||
|
export default _default;
|
||||||
12
pyscript.core/types/toml.d.ts
vendored
Normal file
12
pyscript.core/types/toml.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*! (c) Jak Wings - MIT */ declare class e extends SyntaxError {
|
||||||
|
constructor(r: any, { offset: t, line: e, column: n }: {
|
||||||
|
offset: any;
|
||||||
|
line: any;
|
||||||
|
column: any;
|
||||||
|
});
|
||||||
|
offset: any;
|
||||||
|
line: any;
|
||||||
|
column: any;
|
||||||
|
}
|
||||||
|
declare function n(n: any): any;
|
||||||
|
export { e as SyntaxError, n as parse };
|
||||||
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
black
|
||||||
|
isort
|
||||||
|
pytest==7.1.2
|
||||||
|
pre-commit
|
||||||
|
playwright==1.33.0
|
||||||
|
pytest-playwright==0.3.3
|
||||||
|
pytest-xdist==3.3.0
|
||||||
|
pexpect
|
||||||
|
pyodide_py==0.24.1
|
||||||
|
micropip
|
||||||
|
toml
|
||||||
|
numpy
|
||||||
|
pillow
|
||||||
Reference in New Issue
Block a user