diff --git a/.github/workflows/test-next.yml b/.github/workflows/test-next.yml index 0e325622..3764459f 100644 --- a/.github/workflows/test-next.yml +++ b/.github/workflows/test-next.yml @@ -44,17 +44,18 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - - name: install next deps - working-directory: pyscript.core - run: npm i; npx playwright install + # TODO: this will likely change soon to pyscript.next + # - name: install next deps + # working-directory: pyscript.core + # run: npm i; npx playwright install - - name: build next - working-directory: pyscript.core - run: npm run build + # - name: build next + # working-directory: pyscript.core + # run: npm run build - - name: Run next tests - working-directory: pyscript.core - run: npm run test + # - name: Run next tests + # working-directory: pyscript.core + # run: npm run test # TODO: DO we want to upload next yet? # - uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index 71e28403..cc7325e3 100644 --- a/.gitignore +++ b/.gitignore @@ -141,12 +141,3 @@ coverage/ # junit xml for test results test_results - -# pyscript.core -pyscript.core/coverage/ -pyscript.core/node_modules/ -pyscript.core/cjs/ -!pyscript.core/cjs/package.json -pyscript.core/min.js -pyscript.core/esm/worker/xworker.js -pyscript.core/types/ diff --git a/.prettierignore b/.prettierignore index 24675894..bff93a89 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,10 +3,3 @@ ISSUE_TEMPLATE package-lock.json docs examples/panel.html -pyscript.core/micropython/ -pyscript.core/pyscript/ -pyscript.core/types/ -pyscript.core/esm/worker/xworker.js -pyscript.core/cjs/package.json -pyscript.core/min.js -pyscript.core/pyscript.js diff --git a/pyscript.core/.eslintrc.json b/pyscript.core/.eslintrc.json deleted file mode 100644 index 66c656ce..00000000 --- a/pyscript.core/.eslintrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "env": { - "browser": true, - "es2022": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module" - }, - "ignorePatterns": ["__template.js"], - "rules": {} -} diff --git a/pyscript.core/.github/workflows/node.js.yml b/pyscript.core/.github/workflows/node.js.yml deleted file mode 100644 index c8046464..00000000 --- a/pyscript.core/.github/workflows/node.js.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - -name: build - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [20] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - cache: "npm" - - run: npm ci - - run: npm run build --if-present - - run: npm test - - run: npm run coverage --if-present - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyscript.core/.gitignore b/pyscript.core/.gitignore deleted file mode 100644 index 51c31f4d..00000000 --- a/pyscript.core/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -coverage/ -node_modules/ -cjs/ -!cjs/package.json -core.js -pyscript.js -esm/worker/xworker.js -esm/worker/__template.js -types/ diff --git a/pyscript.core/.npmignore b/pyscript.core/.npmignore deleted file mode 100644 index c5cc42aa..00000000 --- a/pyscript.core/.npmignore +++ /dev/null @@ -1,21 +0,0 @@ -.DS_Store -.nyc_output -.eslintrc.json -.github/ -.travis.yml -.eslintrc.json -*.log -coverage/ -micropython/ -node_modules/ -pyscript/ -rollup/ -test/ -index.html -node.importmap -sw.js -tsconfig.json -cjs/worker/_template.js -cjs/worker/__template.js -esm/worker/_template.js -esm/worker/__template.js diff --git a/pyscript.core/.npmrc b/pyscript.core/.npmrc deleted file mode 100644 index cafe685a..00000000 --- a/pyscript.core/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=true diff --git a/pyscript.core/README.md b/pyscript.core/README.md index 43291320..dec5f522 100644 --- a/pyscript.core/README.md +++ b/pyscript.core/README.md @@ -1,92 +1,5 @@ -# @pyscript/core +# @pyscript/core - deprecated -[![build](https://github.com/WebReflection/python/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/python/actions/workflows/node.js.yml) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/python/badge.svg?branch=api&t=1RBdLX)](https://coveralls.io/github/WebReflection/python?branch=api) +After various discussions around this topic, we decided to avoid any confusion around this folder which never really belonged in here, as _core_ as we meant it was meant to be a _PyScript Next_ dependency, not the _PyScript Next_ itself. ---- - -## Documentation - -Please read [the documentation page](./docs/README.md) to know all the user-facing details around this module. - -## Development - -The working folder (source code of truth) is the `./esm` one, while the `./cjs` is populated as a dual module and to test (but it's 1:1 code, no transpilation except for imports/exports). - -```sh -# install all dependencies needed by core -npm i -``` - -### Build / Artifacts - -This project requires some automatic artifact creation to: - - * create a _Worker_ as a _Blob_ based on the same code used by this repo - * create automatically the list of runtimes available via the module - * create the `core.js` or the `pyscript.js` file used by most integration tests - * create a sha256 version of the Blob content for CSP cases - -Accordingly, to build latest project: - -```sh -# create all artifacts needed to test core -npm run build - -# optionally spin a server with CORS, COOP, and COEP enabled -npm run server -``` - -If **no minification** is desired or helpful while debugging potential issues, please use `NO_MIN=1` in front of the _build_ step: - -```sh -NO_MIN=1 npm run build - -npm run server -``` - -### Dev Build - -Besides spinning the _localhost_ server via `npm run server`, the `npm run dev` will watch changes in the `./esm` folder and it will build automatically non optimized artifacts out of the box. - -## Integration Tests - -To keep it simple, and due to technical differences between what was in PyScript before and what we actually need for core (special headers, multiple interpreters, different bootstrap logic), core integration tests can be performed simply by running: - -```sh -npm run test:integration -``` - -The package's entry takes care of eventually bootstrapping localhost, starting in parallel all tests, and shutting down the server after, if any was bootstrapped. - -The tool to test integration is still _playwright_ but moves things a bit faster (from my side) tests are written in JS. - -#### Integration Tests Structure - -``` -integration - ├ interpreter - │ ├ micropython - │ ├ pyodide - │ ├ ruby-wasm-wasi - │ ├ wasmoon - │ ├ xxx.yy - │ ├ xxx.toml - │ └ utils.js - ├ _shared.js - ├ micropython.js - ├ pyodide.js - ├ ruby-wasm-wasi.js - └ wasmoon.js -``` - -- **interpreter** this folder contains, per each interpreter, a dedicated folder with the interpreter's name. Each of these sub-folders will contain all `.html` and other files to test every specific behavior. In this folder, it's possible to share files, config, or anything else that makes sense for one or more interpreters. -- **\_shared.js** contains some utility used across all tests. Any file prefixed with `_` (underscore) will be ignored for tests purposes but it can be used by the code itself. -- **micropython.js** and all others contain the actual test per each interpreter. If a test is the same across multiple interpreters it can be exported via the `_shared.js` file as it is for most _Pyodide_ and _MicroPython_ cases. - -The [test/integration.spec.js](./test/integration.spec.js) file simply loops over folders that match interpreters _by name_ and execute in parallel all tests. - -#### Manual Test - -To **test manually** an integration test, simply `npm run server` and reach the _html_ file created for that particular test. - -As example, reaching http://localhost:8080/test/integration/interpreter/micropython/fetch.html would log in the console and show expectations on the page and this can be easily tested via multiple browsers by simply reaching the very same integration test. +We have hence moved and renamed _core_ as [polyscript](https://github.com/pyscript/polyscript/#readme) which is the base module to build up a better _PyScript Next_ without confusing our users or ourselves while talking about these two distinct projects. diff --git a/pyscript.core/cjs/package.json b/pyscript.core/cjs/package.json deleted file mode 100644 index 729ac4d9..00000000 --- a/pyscript.core/cjs/package.json +++ /dev/null @@ -1 +0,0 @@ -{"type":"commonjs"} diff --git a/pyscript.core/dev.cjs b/pyscript.core/dev.cjs deleted file mode 100644 index 04fd1919..00000000 --- a/pyscript.core/dev.cjs +++ /dev/null @@ -1,33 +0,0 @@ -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, reject) => { - exec( - "npm run rollup:xworker && npm run rollup:core && npm run rollup:pyscript", - { cwd: __dirname, env: { ...process.env, NO_MIN: true } }, - (error) => { - if (error) reject(error); - else - resolve( - console.log(fileName || "", "build completed"), - ); - }, - ); - }), - ); -}; - -const options = { - ignored: /\/(?:__template|interpreters|xworker)\.[mc]?js$/, - persistent: true, -}; - -require("chokidar").watch("./esm", options).on("change", build); - -build(); diff --git a/pyscript.core/docs/README.md b/pyscript.core/docs/README.md deleted file mode 100644 index 5dac0182..00000000 --- a/pyscript.core/docs/README.md +++ /dev/null @@ -1,475 +0,0 @@ -# PyScript Core Documentation - - * [Terminology](#terminology) - what we mean by "_term_" in this document - * [Bootstrapping core](#bootstrapping-core) - how to enable PyScript Next in your page - * [How Scripts Work](#how-scripts-work) - how ` - - - - - - -``` - -ℹ️ - Please note we decided on purpose to not use the generic programming language name instead of its interpreter project name to avoid being too exclusive for alternative projects that would like to target that very same Programming Language (i.e. note *pyodide* & *micropython* not using *python* indeed as interpreter name). - -Custom values for the `type` attribute can also be created which alias (and potential build on top of) existing interpreter types. We include ` -
- -
-``` - -When it comes to the `property` or `field` attached to a ` -
- -
-``` - -ℹ️ - Please note that if no `target` attribute is specified, the *script* will automatically create a "_companion element_" when the `target` property/field is accessed for the very first time: - -```html - - -``` - - - - -
- Env -
- -ℹ️ - This is an **advanced feature** that is worth describing but usually it is not needed for most common use cases. - -Mostly due its terseness that plays nicely as attribute's suffix, among its commonly understood meaning, we consider an *env* an identifier that guarantee the used *interpreter* would always be the same and no other interpreters, even if they point at very same project, could interfere with globals, behavior, or what's not. - -In few words, every single *env* would spawn a new interpreter dedicated to such env, and global variables defined elsewhere will not affect this "_environment_" and vice-versa, an *env* cannot dictate what will happen to other interpreters. - -```html - - - - - - -``` - -ℹ️ - Please note if the interpreter takes 1 second to bootstrap, multiple *environments* will take *that* second multiplied by the number of different environments, which is why this feature is considered for **advanced** use cases only and it should be discouraged as generic practice. - -
-
- - -## Bootstrapping core - -In order to have anything working at all in our pages, we need to at least bootstrap *@pyscript/core* functionalities, otherwise all examples and scripts mentioned in this document would just sit there ... sadly ignored by every browser: - -```html - - - - - - - - - - -``` - -As *core* exposes some utility/API, using the following method would also work: - -```html - -``` - -Please keep reading this document to understand how to use those utilities or how to have other *Pogramming Languages* enabled in your page via ` -``` - -Not only this is helpful to crawl the surrounding *DOM* or *HTML*, every script will also have a `target` property that will point either to the element reachable through the `target` attribute, or it lazily creates once a companion element that will be appended right after the currently executing *script*. - -Please read the [Terminology](#terminology) **target** dedicated details to know more. - - - - -
- XWorker -
- -With or without access to the `document`, every (*non experimental*) interpreter will have defined, either at the global level or after an import (i.e.`from xworker import XWorker` in *Python* case), a reference to the `XWorker` "_class_" (it's just a *function*!), which goal is to enable off-loading heavy operations on a worker, without blocking the main / UI thread (the current page) and allowing such worker to even reach the `document` or anything else available on the very same main / UI thread. - -```html - -``` - -Please read the [XWorker](#xworker) dedicated section to know more. - -
-
- - -## How Events Work - -The event should contain the *interpreter* or *custom type* prefix, followed by the *event* type it'd like to handle. - -```html - - -``` - -Differently from *Web* inline events, there's no code evaluation at all within the attribute: it's just a globally available name that will receive the current event and nothing else. - -#### The type-env attribute - -Just as the `env` attribute on a ` - - - - -``` - -As mentioned before, this will work with `py-env` too, or any custom type defined out there. - - -## XWorker - -Whenever computing relatively expensive stuff, such as a *matplot* image, or literally anything else that would take more than let's say 100ms to answer, running your *interpreter* of choice within a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is likely desirable, so that the main / UI thread won't block users' actions, listeners, or any other computation going on in these days highly dynamic pages. - -`@pyscript/core` adds a functionality called `XWorker` to all of the interpreters it offers, which works in each language the way `Worker` does in JavaScript. - -In each Interpreter, `XWorker` is either global reference or an import (i.e.`from xworker import XWorker` in *Python* case) module's utility, with a counter `xworker` (lower case) global reference, or an import (i.e.`from xworker import xworker` in *Python* case) module's utility, within the worker code. - -In short, the `XWorker` utility is to help, without much thinking, to run any desired interpreter out of a *Worker*, enabling extra features on the *worker*'s code side. - - -### Enabling XWorker - -We use the latest Web technologies to allow fast, non-blocking, yet synchronous like, operations from any non-experimental interpreter's worker, and the standard requires some special header to enable such technologies and, most importantly, the [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer). - -There is an exhaustive [section](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements) around this topic but the *TL;DR* version is: - - * to protect your page from undesired attacks, the `Cross-Origin-Opener-Policy` header should be present with the `same-origin` value - * to protect other sites from your pages' code, the `Cross-Origin-Embedder-Policy` header should be present with either the `credentialless` value (Chrome and Firefox browsers) or the `require-corp` one (Safari + other browsers) - * when the `Cross-Origin-Embedder-Policy` header is set with the `require-corp` value, the `Cross-Origin-Resource-Policy` header should also be available with [one of these options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy): `same-site`, `same-origin` or `cross-origin` - -There are **alternative ways** to enable these headers for your site or local host, and [this script](https://github.com/gzuidhof/coi-serviceworker#readme) is just one of these, one that works with most free-hosting websites too. - - -### XWorker options - -Before showing any example, it's important to understand how the offered API differs from Web standard *workers*: - -| name | example | behavior | -| :-------- | :------------------------------------------------------- | :--------| -| async | `XWorker('./file.py', async=True)` | The worker code is evaluated via `runAsync` utility where, if the *interpreter* allows it, top level *await* would be possible, among other *PL* specific asynchronous features. | -| config | `XWorker('./file.py', config='./cfg.toml')` | The worker will load and parse the *JSON* or *TOML* file to configure itself. Please see [currently supported config values](https://docs.pyscript.net/latest/reference/elements/py-config.html#supported-configuration-values) as this is currently based on `` features. | -| type | `XWorker('./file.py', type='pyodide')` | Define the *interpreter* to use with this worker which is, by default, the same one used within the running code. Please read the [Terminology](#terminology) **interpreter** dedicated details to know more. | -| version | `XWorker('./file.py', type='pyodide', version='0.23.2')` | Allow the usage of a specific version where, if numeric, must be available through the project *CDN* used by *core* but if specified as fully qualified *URL*, allows usage of any interpreter's version: `",\n}\n\n\n# these are set by _set_version_info\n__version__ = None\nversion_info = None\n\n\ndef _set_version_info(version_from_interpreter: str):\n """Sets the __version__ and version_info properties from provided JSON data\n Args:\n version_from_interpreter (str): A "dotted" representation of the version:\n YYYY.MM.m(m).releaselevel\n Year, Month, and Minor should be integers; releaselevel can be any string\n """\n global __version__\n global version_info\n\n __version__ = version_from_interpreter\n\n version_parts = version_from_interpreter.split(".")\n year = int(version_parts[0])\n month = int(version_parts[1])\n minor = int(version_parts[2])\n if len(version_parts) > 3:\n releaselevel = version_parts[3]\n else:\n releaselevel = ""\n\n VersionInfo = namedtuple("version_info", ("year", "month", "minor", "releaselevel"))\n version_info = VersionInfo(year, month, minor, releaselevel)\n\n # we ALSO set PyScript.__version__ and version_info for backwards\n # compatibility. Should be killed eventually.\n PyScript.__version__ = __version__\n PyScript.version_info = version_info\n\n\nclass HTML:\n """\n Wrap a string so that display() can render it as plain HTML\n """\n\n def __init__(self, html):\n self._html = html\n\n def _repr_html_(self):\n return self._html\n\n\ndef eval_formatter(obj, print_method):\n """\n Evaluates a formatter method.\n """\n if print_method == "__repr__":\n return repr(obj)\n elif hasattr(obj, print_method):\n if print_method == "savefig":\n buf = io.BytesIO()\n obj.savefig(buf, format="png")\n buf.seek(0)\n return base64.b64encode(buf.read()).decode("utf-8")\n return getattr(obj, print_method)()\n elif print_method == "_repr_mimebundle_":\n return {}, {}\n return None\n\n\ndef format_mime(obj):\n """\n Formats object using _repr_x_ methods.\n """\n if isinstance(obj, str):\n return html.escape(obj), "text/plain"\n\n mimebundle = eval_formatter(obj, "_repr_mimebundle_")\n if isinstance(mimebundle, tuple):\n format_dict, _ = mimebundle\n else:\n format_dict = mimebundle\n\n output, not_available = None, []\n for method, mime_type in reversed(MIME_METHODS.items()):\n if mime_type in format_dict:\n output = format_dict[mime_type]\n else:\n output = eval_formatter(obj, method)\n\n if output is None:\n continue\n elif mime_type not in MIME_RENDERERS:\n not_available.append(mime_type)\n continue\n break\n if output is None:\n if not_available:\n js.console.warn(\n f"Rendered object requested unavailable MIME renderers: {not_available}"\n )\n output = repr(output)\n mime_type = "text/plain"\n elif isinstance(output, tuple):\n output, meta = output\n else:\n meta = {}\n return MIME_RENDERERS[mime_type](output, meta), mime_type\n\n\ndef run_until_complete(f):\n _ = loop.run_until_complete(f)\n\n\ndef write(element_id, value, append=False, exec_id=0):\n """Writes value to the element with id "element_id"""\n Element(element_id).write(value=value, append=append)\n js.console.warn(\n dedent(\n """PyScript Deprecation Warning: PyScript.write is\n marked as deprecated and will be removed sometime soon. Please, use\n Element().write instead."""\n )\n )\n\n\ndef set_current_display_target(target_id):\n get_current_display_target._id = target_id\n\n\ndef get_current_display_target():\n return get_current_display_target._id\n\n\nget_current_display_target._id = None\n\n\ndef display(*values, target=None, append=True):\n default_target = get_current_display_target()\n if default_target is None and target is None:\n raise Exception(\n "Implicit target not allowed here. Please use display(..., target=...)"\n )\n if target is not None:\n for v in values:\n Element(target).write(v, append=append)\n else:\n for v in values:\n Element(default_target).write(v, append=append)\n\n\nclass Element:\n def __init__(self, element_id, element=None):\n self._id = element_id\n self._element = element\n\n @property\n def id(self):\n return self._id\n\n @property\n def element(self):\n """Return the dom element"""\n if not self._element:\n self._element = js.document.querySelector(f"#{self._id}")\n return self._element\n\n @property\n def value(self):\n return self.element.value\n\n @property\n def innerHtml(self):\n return self.element.innerHTML\n\n def write(self, value, append=False):\n html, mime_type = format_mime(value)\n if html == "\\n":\n return\n\n if append:\n child = js.document.createElement("div")\n self.element.appendChild(child)\n\n if append and self.element.children:\n out_element = self.element.children[-1]\n else:\n out_element = self.element\n\n if mime_type in ("application/javascript", "text/html"):\n script_element = js.document.createRange().createContextualFragment(html)\n out_element.appendChild(script_element)\n else:\n out_element.innerHTML = html\n\n def clear(self):\n if hasattr(self.element, "value"):\n self.element.value = ""\n else:\n self.write("", append=False)\n\n def select(self, query, from_content=False):\n el = self.element\n\n if from_content:\n el = el.content\n\n _el = el.querySelector(query)\n if _el:\n return Element(_el.id, _el)\n else:\n js.console.warn(f"WARNING: can\'t find element matching query {query}")\n\n def clone(self, new_id=None, to=None):\n if new_id is None:\n new_id = self.element.id\n\n clone = self.element.cloneNode(True)\n clone.id = new_id\n\n if to:\n to.element.appendChild(clone)\n # Inject it into the DOM\n to.element.after(clone)\n else:\n # Inject it into the DOM\n self.element.after(clone)\n\n return Element(clone.id, clone)\n\n def remove_class(self, classname):\n if isinstance(classname, list):\n for cl in classname:\n self.remove_class(cl)\n else:\n self.element.classList.remove(classname)\n\n def add_class(self, classname):\n if isinstance(classname, list):\n for cl in classname:\n self.element.classList.add(cl)\n else:\n self.element.classList.add(classname)\n\n\ndef add_classes(element, class_list):\n for klass in class_list.split(" "):\n element.classList.add(klass)\n\n\ndef create(what, id_=None, classes=""):\n element = js.document.createElement(what)\n if id_:\n element.id = id_\n add_classes(element, classes)\n return Element(id_, element)\n\n\nclass PyWidgetTheme:\n def __init__(self, main_style_classes):\n self.main_style_classes = main_style_classes\n\n def theme_it(self, widget):\n for klass in self.main_style_classes.split(" "):\n widget.classList.add(klass)\n\n\nclass PyItemTemplate(Element):\n label_fields = None\n\n def __init__(self, data, labels=None, state_key=None, parent=None):\n self.data = data\n\n self.register_parent(parent)\n\n if not labels:\n labels = list(self.data.keys())\n self.labels = labels\n\n self.state_key = state_key\n\n super().__init__(self._id)\n\n def register_parent(self, parent):\n self._parent = parent\n if parent:\n self._id = f"{self._parent._id}-c-{len(self._parent._children)}"\n self.data["id"] = self._id\n else:\n self._id = None\n\n def create(self):\n new_child = create("div", self._id, "py-li-element")\n new_child._element.innerHTML = dedent(\n f"""\n \n """\n )\n return new_child\n\n def on_click(self, evt):\n pass\n\n def pre_append(self):\n pass\n\n def post_append(self):\n self.element.click = self.on_click\n self.element.onclick = self.on_click\n\n self._post_append()\n\n def _post_append(self):\n pass\n\n def strike(self, value, extra=None):\n if value:\n self.add_class("line-through")\n else:\n self.remove_class("line-through")\n\n def render_content(self):\n return " - ".join([self.data[f] for f in self.labels])\n\n\nclass PyListTemplate:\n theme = PyWidgetTheme("py-li-element")\n item_class = PyItemTemplate\n\n def __init__(self, parent):\n self.parent = parent\n self._children = []\n self._id = self.parent.id\n\n @property\n def children(self):\n return self._children\n\n @property\n def data(self):\n return [c.data for c in self._children]\n\n def render_children(self):\n binds = {}\n for i, c in enumerate(self._children):\n txt = c.element.innerHTML\n rnd = str(time.time()).replace(".", "")[-5:]\n new_id = f"{c.element.id}-{i}-{rnd}"\n binds[new_id] = c.element.id\n txt = txt.replace(">", f" id=\'{new_id}\'>")\n print(txt)\n\n def foo(evt):\n evtEl = evt.srcElement\n srcEl = Element(binds[evtEl.id])\n srcEl.element.onclick()\n evtEl.classList = srcEl.element.classList\n\n for new_id in binds:\n Element(new_id).element.onclick = foo\n\n def connect(self):\n self.md = main_div = js.document.createElement("div")\n main_div.id = self._id + "-list-tasks-container"\n\n if self.theme:\n self.theme.theme_it(main_div)\n\n self.parent.appendChild(main_div)\n\n def add(self, *args, **kws):\n if not isinstance(args[0], self.item_class):\n child = self.item_class(*args, **kws)\n else:\n child = args[0]\n child.register_parent(self)\n return self._add(child)\n\n def _add(self, child_elem):\n self.pre_child_append(child_elem)\n child_elem.pre_append()\n self._children.append(child_elem)\n self.md.appendChild(child_elem.create().element)\n child_elem.post_append()\n self.child_appended(child_elem)\n return child_elem\n\n def pre_child_append(self, child):\n pass\n\n def child_appended(self, child):\n """Overwrite me to define logic"""\n pass\n\n\nclass TopLevelAsyncFinder(ast.NodeVisitor):\n def is_source_top_level_await(self, source):\n self.async_found = False\n node = ast.parse(source)\n self.generic_visit(node)\n return self.async_found\n\n def visit_Await(self, node):\n self.async_found = True\n\n def visit_AsyncFor(self, node):\n self.async_found = True\n\n def visit_AsyncWith(self, node):\n self.async_found = True\n\n def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):\n pass # Do not visit children of async function defs\n\n\ndef uses_top_level_await(source: str) -> bool:\n return TopLevelAsyncFinder().is_source_top_level_await(source)\n\n\nclass Plugin:\n def __init__(self, name=None):\n if not name:\n name = self.__class__.__name__\n\n self.name = name\n self._custom_elements = []\n self.app = None\n\n def init(self, app):\n self.app = app\n\n def register_custom_element(self, tag):\n """\n Decorator to register a new custom element as part of a Plugin and associate\n tag to it. Internally, it delegates the registration to the PyScript internal\n [JS] plugin manager, who actually creates the JS custom element that can be\n attached to the page and instantiate an instance of the class passing the custom\n element to the plugin constructor.\n\n Exammple:\n >> plugin = Plugin("PyTutorial")\n >> @plugin.register_custom_element("py-tutor")\n >> class PyTutor:\n >> def __init__(self, element):\n >> self.element = element\n """\n # TODO: Ideally would be better to use the logger.\n js.console.info(f"Defining new custom element {tag}")\n\n def wrapper(class_):\n # TODO: this is very pyodide specific but will have to do\n # until we have JS interface that works across interpreters\n define_custom_element(tag, create_proxy(class_)) # noqa: F821\n\n self._custom_elements.append(tag)\n return create_proxy(wrapper)\n\n\nclass DeprecatedGlobal:\n """\n Proxy for globals which are deprecated.\n\n The intendend usage is as follows:\n\n # in the global namespace\n Element = pyscript.DeprecatedGlobal(\'Element\', pyscript.Element, "...")\n console = pyscript.DeprecatedGlobal(\'console\', js.console, "...")\n ...\n\n The proxy forwards __getattr__ and __call__ to the underlying object, and\n emit a warning on the first usage.\n\n This way users see a warning only if they actually access the top-level\n name.\n """\n\n def __init__(self, name, obj, message):\n self.__name = name\n self.__obj = obj\n self.__message = message\n self.__warning_already_shown = False\n\n def __repr__(self):\n return f""\n\n def _show_warning(self, message):\n """\n NOTE: this is overridden by unit tests\n """\n # this showWarning is implemented in js and injected into this\n # namespace by main.ts\n showWarning(message, "html") # noqa: F821\n\n def _show_warning_maybe(self):\n if self.__warning_already_shown:\n return\n self._show_warning(self.__message)\n self.__warning_already_shown = True\n\n def __getattr__(self, attr):\n self._show_warning_maybe()\n return getattr(self.__obj, attr)\n\n def __call__(self, *args, **kwargs):\n self._show_warning_maybe()\n return self.__obj(*args, **kwargs)\n\n def __iter__(self):\n self._show_warning_maybe()\n return iter(self.__obj)\n\n def __getitem__(self, key):\n self._show_warning_maybe()\n return self.__obj[key]\n\n def __setitem__(self, key, value):\n self._show_warning_maybe()\n self.__obj[key] = value\n\n\nclass PyScript:\n """\n This class is deprecated since 2022.12.1.\n\n All its old functionalities are available as module-level functions. This\n class should be killed eventually.\n """\n\n loop = loop\n\n @staticmethod\n def run_until_complete(f):\n run_until_complete(f)\n\n @staticmethod\n def write(element_id, value, append=False, exec_id=0):\n write(element_id, value, append, exec_id)\n\n\ndef _install_deprecated_globals_2022_12_1(ns):\n """\n Install into the given namespace all the globals which have been\n deprecated since the 2022.12.1 release. Eventually they should be killed.\n """\n\n def deprecate(name, obj, instead):\n message = f"Direct usage of {name} is deprecated. " + instead\n ns[name] = DeprecatedGlobal(name, obj, message)\n\n # function/classes defined in pyscript.py ===> pyscript.XXX\n pyscript_names = [\n "PyItemTemplate",\n "PyListTemplate",\n "PyWidgetTheme",\n "add_classes",\n "create",\n "loop",\n ]\n for name in pyscript_names:\n deprecate(\n name, globals()[name], f"Please use pyscript.{name} instead."\n )\n\n # stdlib modules ===> import XXX\n stdlib_names = [\n "asyncio",\n "base64",\n "io",\n "sys",\n "time",\n "datetime",\n "pyodide",\n "micropip",\n ]\n for name in stdlib_names:\n obj = __import__(name)\n deprecate(name, obj, f"Please use import {name} instead.")\n\n # special case\n deprecate(\n "dedent", dedent, "Please use from textwrap import dedent instead."\n )\n\n # these are names that used to leak in the globals but they are just\n # implementation details. People should not use them.\n private_names = [\n "eval_formatter",\n "format_mime",\n "identity",\n "render_image",\n "MIME_RENDERERS",\n "MIME_METHODS",\n ]\n for name in private_names:\n obj = globals()[name]\n message = (\n f"{name} is deprecated. "\n "This is a private implementation detail of pyscript. "\n "You should not use it."\n )\n ns[name] = DeprecatedGlobal(name, obj, message)\n\n # these names are available as js.XXX\n for name in ["document", "console"]:\n obj = getattr(js, name)\n deprecate(name, obj, f"Please use js.{name} instead.")\n\n # PyScript is special, use a different message\n message = (\n "The PyScript object is deprecated. "\n "Please use pyscript instead."\n )\n ns["PyScript"] = DeprecatedGlobal("PyScript", PyScript, message)\n'; - - const logger = getLogger("pyscript/main"); - /* High-level overview of the lifecycle of a PyScript App: - - 1. pyscript.js is loaded by the browser. PyScriptApp().main() is called - - 2. loadConfig(): search for py-config and compute the config for the app - - 3. (it used to be "show the splashscreen", but now it's a plugin) - - 4. loadInterpreter(): start downloading the actual interpreter (e.g. pyodide.js) - - --- wait until (4) has finished --- - - 5. now the pyodide src is available. Initialize the engine - - 6. setup the environment, install packages - - 6.5: call the Plugin.afterSetup() hook - - 7. connect the py-script web component. This causes the execution of all the - user scripts - - 8. initialize the rest of web components such as py-button, py-repl, etc. - - More concretely: - - - Points 1-4 are implemented sequentially in PyScriptApp.main(). - - - PyScriptApp.loadInterpreter adds a - - - - -

Open the console.

- - - - diff --git a/pyscript.core/test/async-events/main.py b/pyscript.core/test/async-events/main.py deleted file mode 100644 index 794934a0..00000000 --- a/pyscript.core/test/async-events/main.py +++ /dev/null @@ -1,5 +0,0 @@ -import js - - -async def click_handler(e): - js.console.log(e) diff --git a/pyscript.core/test/async.html b/pyscript.core/test/async.html deleted file mode 100644 index 8cf19535..00000000 --- a/pyscript.core/test/async.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - python - - - - - - - diff --git a/pyscript.core/test/b.py b/pyscript.core/test/b.py deleted file mode 100644 index 188f852c..00000000 --- a/pyscript.core/test/b.py +++ /dev/null @@ -1 +0,0 @@ -x = "hello from B" diff --git a/pyscript.core/test/config.json b/pyscript.core/test/config.json deleted file mode 100644 index f0941831..00000000 --- a/pyscript.core/test/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "packages": ["matplotlib"] -} diff --git a/pyscript.core/test/config.toml b/pyscript.core/test/config.toml deleted file mode 100644 index 70403d5b..00000000 --- a/pyscript.core/test/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -packages = [ - "matplotlib" -] diff --git a/pyscript.core/test/counter.js b/pyscript.core/test/counter.js deleted file mode 100644 index f1dcf646..00000000 --- a/pyscript.core/test/counter.js +++ /dev/null @@ -1,8 +0,0 @@ -const div = document.body.appendChild(document.createElement("div")); -div.style.cssText = "position:fixed;top:0;left:0"; - -let i = 0; -(function counter() { - div.textContent = ++i; - requestAnimationFrame(counter); -})(); diff --git a/pyscript.core/test/doc-intro.html b/pyscript.core/test/doc-intro.html deleted file mode 100644 index 9411f723..00000000 --- a/pyscript.core/test/doc-intro.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/pyscript.core/test/env.html b/pyscript.core/test/env.html deleted file mode 100644 index ac941c51..00000000 --- a/pyscript.core/test/env.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - python - - - - - -
- -
- -
- -
- - diff --git a/pyscript.core/test/error.html b/pyscript.core/test/error.html deleted file mode 100644 index f9e41b3e..00000000 --- a/pyscript.core/test/error.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - -
See console ➡️
- - - - diff --git a/pyscript.core/test/error.py b/pyscript.core/test/error.py deleted file mode 100644 index 389df6b3..00000000 --- a/pyscript.core/test/error.py +++ /dev/null @@ -1,3 +0,0 @@ -# from xworker import xworker - -xworker.postMessage("error") diff --git a/pyscript.core/test/fetch.html b/pyscript.core/test/fetch.html deleted file mode 100644 index bb9623dd..00000000 --- a/pyscript.core/test/fetch.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - python - - - - - - diff --git a/pyscript.core/test/fetch.toml b/pyscript.core/test/fetch.toml deleted file mode 100644 index 7cbcde7f..00000000 --- a/pyscript.core/test/fetch.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[fetch]] -files = ["./a.py", "./b.py"] diff --git a/pyscript.core/test/icon.svg b/pyscript.core/test/icon.svg deleted file mode 100644 index 11c0e4dc..00000000 --- a/pyscript.core/test/icon.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/pyscript.core/test/index.html b/pyscript.core/test/index.html deleted file mode 100644 index 9ef45e5d..00000000 --- a/pyscript.core/test/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - python - - - - - - - diff --git a/pyscript.core/test/index.js b/pyscript.core/test/index.js deleted file mode 100644 index 0511fc30..00000000 --- a/pyscript.core/test/index.js +++ /dev/null @@ -1,209 +0,0 @@ -const assert = require("./assert.js"); -require("./_utils.js"); - -const { fetch } = globalThis; - -const tick = (ms = 10) => new Promise(($) => setTimeout($, ms)); - -const clear = (python) => { - for (const [key, value] of Object.entries(python)) { - if (typeof value === "object") python[key] = null; - else python[key] = ""; - } -}; - -const patchFetch = (callback) => { - Object.defineProperty(globalThis, "fetch", { - configurable: true, - get() { - try { - return callback; - } finally { - globalThis.fetch = fetch; - } - }, - }); -}; - -const { parseHTML } = require("linkedom"); -const { document, window } = parseHTML("..."); - -globalThis.document = document; -globalThis.Element = window.Element; -globalThis.MutationObserver = window.MutationObserver; -globalThis.XPathResult = {}; -globalThis.XPathEvaluator = - window.XPathEvaluator || - class XPathEvaluator { - createExpression() { - return { evaluate: () => [] }; - } - }; - -require("../cjs"); - -(async () => { - // shared 3rd party mocks - const { - python: pyodide, - setTarget, - loadPyodide, - } = await import("./mocked/pyodide.mjs"); - const { python: micropython } = await import("./mocked/micropython.mjs"); - - // shared helpers - const div = document.createElement("div"); - const shadowRoot = div.attachShadow({ mode: "open" }); - const content = ` - import sys - import js - js.document.currentScript.target.textContent = sys.version - `; - - const { URL } = globalThis; - globalThis.URL = function (href) { - return { href }; - }; - globalThis.location = { href: "" }; - - // all tests - for (const test of [ - async function versionedRuntime() { - document.head.innerHTML = ``; - await tick(); - assert(pyodide.content, content); - assert(pyodide.target.tagName, "PYODIDE-SCRIPT"); - }, - - async function basicExpectations() { - document.head.innerHTML = ``; - await tick(); - assert(pyodide.content, content); - assert(pyodide.target.tagName, "PYODIDE-SCRIPT"); - }, - - async function foreignRuntime() { - document.head.innerHTML = ``; - await tick(); - assert(pyodide.content, content); - assert(pyodide.target.tagName, "PYODIDE-SCRIPT"); - }, - - async function basicMicroPython() { - document.head.innerHTML = ``; - await tick(); - assert(micropython.content, content); - assert(micropython.target.tagName, "MICROPYTHON-SCRIPT"); - const script = document.head.firstElementChild; - document.body.appendChild(script); - await tick(); - assert(script.nextSibling, micropython.target); - micropython.target = null; - }, - - async function exernalResourceInShadowRoot() { - patchFetch(() => - Promise.resolve({ text: () => Promise.resolve("OK") }), - ); - shadowRoot.innerHTML = ` - - - `.trim(); - await tick(); - assert(pyodide.content, "OK"); - assert(pyodide.target.tagName, "MY-PLUGIN"); - }, - - async function explicitTargetNode() { - setTarget(div); - shadowRoot.innerHTML = ` - - - `.trim(); - await tick(); - assert(pyodide.target, div); - }, - - async function explicitTargetAsString() { - setTarget("my-plugin"); - shadowRoot.innerHTML = ` - - - `.trim(); - await tick(); - assert(pyodide.target.tagName, "MY-PLUGIN"); - }, - - async function jsonConfig() { - const packages = {}; - patchFetch(() => Promise.resolve({ json: () => ({ packages }) })); - shadowRoot.innerHTML = ``; - await tick(); - assert(pyodide.packages, packages); - }, - - async function tomlConfig() { - const jsonPackages = JSON.stringify({ - packages: { a: Math.random() }, - }); - patchFetch(() => - Promise.resolve({ text: () => Promise.resolve(jsonPackages) }), - ); - shadowRoot.innerHTML = ``; - // there are more promises in here let's increase the tick delay to avoid flaky tests - await tick(20); - assert( - JSON.stringify({ packages: pyodide.packages }), - jsonPackages, - ); - }, - - async function fetchConfig() { - const jsonPackages = JSON.stringify({ - fetch: [ - { files: ["./a.py", "./b.py"] }, - { from: "utils" }, - { from: "/utils", files: ["c.py"] }, - ], - }); - patchFetch(() => - Promise.resolve({ - arrayBuffer: () => Promise.resolve([]), - text: () => Promise.resolve(jsonPackages), - }), - ); - shadowRoot.innerHTML = ` - - `; - await tick(10); - }, - - async function testDefaultRuntime() { - const pyodide = await pyscript.env.pyodide; - const keys = Object.keys(loadPyodide()).join(","); - assert(Object.keys(pyodide).join(","), keys); - - const unique = await pyscript.env.unique; - assert(Object.keys(unique).join(","), keys); - }, - - async function pyEvents() { - shadowRoot.innerHTML = ` - - - `; - await tick(20); - }, - ]) { - await test(); - clear(pyodide); - clear(micropython); - } - - globalThis.URL = URL; -})(); diff --git a/pyscript.core/test/integration.spec.js b/pyscript.core/test/integration.spec.js deleted file mode 100644 index 51a06e2a..00000000 --- a/pyscript.core/test/integration.spec.js +++ /dev/null @@ -1,19 +0,0 @@ -const { existsSync, readdirSync } = require('node:fs'); -const { join } = require('node:path'); -const playwright = require('@playwright/test'); - -// integration tests for interpreters -const TEST_INTERPRETER = join(__dirname, 'integration'); - -// source of truth for interpreters -const CORE_INTERPRETER = join(__dirname, '..', 'esm', 'interpreter'); - -for (const file of readdirSync(TEST_INTERPRETER)) { - // filter only JS files that match their counter-part interpreter - if (/\.js$/.test(file) && existsSync(join(CORE_INTERPRETER, file))) { - require(join(TEST_INTERPRETER, file))( - playwright, - `http://localhost:8080/test/integration/interpreter/${file.slice(0, -3)}` - ); - } -} diff --git a/pyscript.core/test/integration/_shared.js b/pyscript.core/test/integration/_shared.js deleted file mode 100644 index c34a4a43..00000000 --- a/pyscript.core/test/integration/_shared.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -exports.shared = { - bootstrap: ({ expect }, baseURL) => async ({ page }) => { - await page.goto(`${baseURL}/bootstrap.html`); - await page.waitForSelector('html.ready'); - await page.getByRole('button').click(); - const result = await page.evaluate(() => document.body.innerText); - await expect(result.trim()).toBe('OK'); - }, - - worker: ({ expect }, url) => async ({ page }) => { - const logs = []; - page.on('console', msg => logs.push(msg.text())); - await page.goto(url); - await page.waitForSelector('html.worker.ready'); - await expect(logs.join(',')).toBe('main,thread'); - }, - - workerWindow: ({ expect }, baseURL) => async ({ page }) => { - await page.goto(`${baseURL}/worker-window.html`); - await page.waitForSelector('html.worker.ready'); - const result = await page.evaluate(() => document.body.innerText); - await expect(result.trim()).toBe('OK'); - }, -}; - -exports.python = { - bootstrap: ({ expect }, baseURL) => async ({ page }) => { - await page.goto(`${baseURL}/bootstrap.html`); - await page.waitForSelector('html.ready'); - const result = await page.evaluate(() => document.body.innerText); - await expect(result.trim()).toBe('OK'); - }, - - fetch: ({ expect }, url) => async ({ page }) => { - const logs = []; - page.on('console', msg => logs.push(msg.text())); - await page.goto(url); - await page.waitForSelector('html.ready'); - await expect(logs.length).toBe(1); - await expect(logs[0]).toBe('hello from A'); - const body = await page.evaluate(() => document.body.innerText); - await expect(body.trim()).toBe('hello from A, hello from B'); - }, -}; diff --git a/pyscript.core/test/integration/interpreter/a.py b/pyscript.core/test/integration/interpreter/a.py deleted file mode 100644 index 2bf1368b..00000000 --- a/pyscript.core/test/integration/interpreter/a.py +++ /dev/null @@ -1,2 +0,0 @@ -x = "hello from A" -print(x) diff --git a/pyscript.core/test/integration/interpreter/b.py b/pyscript.core/test/integration/interpreter/b.py deleted file mode 100644 index 188f852c..00000000 --- a/pyscript.core/test/integration/interpreter/b.py +++ /dev/null @@ -1 +0,0 @@ -x = "hello from B" diff --git a/pyscript.core/test/integration/interpreter/fetch.toml b/pyscript.core/test/integration/interpreter/fetch.toml deleted file mode 100644 index 7cbcde7f..00000000 --- a/pyscript.core/test/integration/interpreter/fetch.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[fetch]] -files = ["./a.py", "./b.py"] diff --git a/pyscript.core/test/integration/interpreter/micropython/bootstrap.html b/pyscript.core/test/integration/interpreter/micropython/bootstrap.html deleted file mode 100644 index 7102fea4..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/bootstrap.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/micropython/fetch.html b/pyscript.core/test/integration/interpreter/micropython/fetch.html deleted file mode 100644 index 7f009b44..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/fetch.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/micropython/worker-lua.html b/pyscript.core/test/integration/interpreter/micropython/worker-lua.html deleted file mode 100644 index 61718a8c..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/worker-lua.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/micropython/worker-window.html b/pyscript.core/test/integration/interpreter/micropython/worker-window.html deleted file mode 100644 index 369e083b..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/worker-window.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/micropython/worker-window.py b/pyscript.core/test/integration/interpreter/micropython/worker-window.py deleted file mode 100644 index 6ae014a1..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/worker-window.py +++ /dev/null @@ -1,9 +0,0 @@ -from xworker import xworker - -document = xworker.window.document - -document.body.textContent = "OK" - -# be sure the page knows the worker has done parsing to avoid -# unnecessary random timeouts all over the tests -document.documentElement.className += " worker" diff --git a/pyscript.core/test/integration/interpreter/micropython/worker.html b/pyscript.core/test/integration/interpreter/micropython/worker.html deleted file mode 100644 index ab45b66a..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/worker.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/micropython/worker.py b/pyscript.core/test/integration/interpreter/micropython/worker.py deleted file mode 100644 index f5439a56..00000000 --- a/pyscript.core/test/integration/interpreter/micropython/worker.py +++ /dev/null @@ -1,15 +0,0 @@ -from xworker import xworker - -document = xworker.window.document - - -def on_message(event): - print(event.data) - xworker.postMessage("thread") - - -xworker.onmessage = on_message - -# be sure the page knows the worker has done parsing to avoid -# unnecessary random timeouts all over the tests -document.documentElement.className += " worker" diff --git a/pyscript.core/test/integration/interpreter/pyodide/bootstrap.html b/pyscript.core/test/integration/interpreter/pyodide/bootstrap.html deleted file mode 100644 index 0e307e1b..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/bootstrap.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/pyodide/fetch.html b/pyscript.core/test/integration/interpreter/pyodide/fetch.html deleted file mode 100644 index 2469b38c..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/fetch.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/pyodide/pyodide.js b/pyscript.core/test/integration/interpreter/pyodide/pyodide.js deleted file mode 100644 index dd650e98..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/pyodide.js +++ /dev/null @@ -1,3 +0,0 @@ -import '/core.js'; -await pyscript.env.pyodide; -document.documentElement.classList.add('ready'); diff --git a/pyscript.core/test/integration/interpreter/pyodide/sync.html b/pyscript.core/test/integration/interpreter/pyodide/sync.html deleted file mode 100644 index 0f42b484..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/sync.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/pyodide/sync.py b/pyscript.core/test/integration/interpreter/pyodide/sync.py deleted file mode 100644 index 8ba5a0f7..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/sync.py +++ /dev/null @@ -1,12 +0,0 @@ -import time -from xworker import xworker - -time.sleep = xworker.sync.sleep - -print("before") -time.sleep(1) -print("after") - -# be sure the page knows the worker has done parsing to avoid -# unnecessary random timeouts all over the tests -xworker.window.document.documentElement.classList.add("worker") diff --git a/pyscript.core/test/integration/interpreter/pyodide/worker.html b/pyscript.core/test/integration/interpreter/pyodide/worker.html deleted file mode 100644 index e7801082..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/worker.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/pyodide/worker.py b/pyscript.core/test/integration/interpreter/pyodide/worker.py deleted file mode 100644 index 6692197a..00000000 --- a/pyscript.core/test/integration/interpreter/pyodide/worker.py +++ /dev/null @@ -1,13 +0,0 @@ -from xworker import xworker - - -def on_message(event): - print(event.data) - xworker.postMessage("thread") - - -xworker.onmessage = on_message - -# be sure the page knows the worker has done parsing to avoid -# unnecessary random timeouts all over the tests -xworker.window.document.documentElement.classList.add("worker") diff --git a/pyscript.core/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html b/pyscript.core/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html deleted file mode 100644 index cb483e31..00000000 --- a/pyscript.core/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/ruby-wasm-wasi/ruby-wasm-wasi.js b/pyscript.core/test/integration/interpreter/ruby-wasm-wasi/ruby-wasm-wasi.js deleted file mode 100644 index 113df31d..00000000 --- a/pyscript.core/test/integration/interpreter/ruby-wasm-wasi/ruby-wasm-wasi.js +++ /dev/null @@ -1,3 +0,0 @@ -import '/core.js'; -await pyscript.env['ruby-wasm-wasi']; -document.documentElement.classList.add('ready'); diff --git a/pyscript.core/test/integration/interpreter/utils.js b/pyscript.core/test/integration/interpreter/utils.js deleted file mode 100644 index b760d029..00000000 --- a/pyscript.core/test/integration/interpreter/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -import '/core.js'; - -export const init = name => pyscript.env[name].then(() => { - document.documentElement.classList.add('ready'); -}); diff --git a/pyscript.core/test/integration/interpreter/wasmoon/bootstrap.html b/pyscript.core/test/integration/interpreter/wasmoon/bootstrap.html deleted file mode 100644 index d606f717..00000000 --- a/pyscript.core/test/integration/interpreter/wasmoon/bootstrap.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/wasmoon/wasmoon.js b/pyscript.core/test/integration/interpreter/wasmoon/wasmoon.js deleted file mode 100644 index df4b1f32..00000000 --- a/pyscript.core/test/integration/interpreter/wasmoon/wasmoon.js +++ /dev/null @@ -1,3 +0,0 @@ -import '/core.js'; -await pyscript.env.wasmoon; -document.documentElement.classList.add('ready'); diff --git a/pyscript.core/test/integration/interpreter/wasmoon/worker.html b/pyscript.core/test/integration/interpreter/wasmoon/worker.html deleted file mode 100644 index d6c3f8a1..00000000 --- a/pyscript.core/test/integration/interpreter/wasmoon/worker.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - diff --git a/pyscript.core/test/integration/interpreter/wasmoon/worker.lua b/pyscript.core/test/integration/interpreter/wasmoon/worker.lua deleted file mode 100644 index 65976bc6..00000000 --- a/pyscript.core/test/integration/interpreter/wasmoon/worker.lua +++ /dev/null @@ -1,10 +0,0 @@ -function on_message(event) - print(event.data) - xworker.postMessage('thread') -end - -xworker.onmessage = on_message - --- be sure the page knows the worker has done parsing to avoid --- unnecessary random timeouts all over the tests -xworker.window.document.documentElement.classList.add("worker") diff --git a/pyscript.core/test/integration/micropython.js b/pyscript.core/test/integration/micropython.js deleted file mode 100644 index 087f8228..00000000 --- a/pyscript.core/test/integration/micropython.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const { shared, python } = require('./_shared.js'); - -module.exports = (playwright, baseURL) => { - const { test } = playwright; - - test('MicroPython bootstrap', python.bootstrap(playwright, baseURL)); - - test('MicroPython fetch', python.fetch(playwright, `${baseURL}/fetch.html`)); - - test('MicroPython to MicroPython Worker', shared.worker(playwright, `${baseURL}/worker.html`)); - - test('MicroPython Worker window', shared.workerWindow(playwright, baseURL)); - - test('MicroPython to Wasmoon Worker', shared.worker(playwright, `${baseURL}/worker-lua.html`)); -}; diff --git a/pyscript.core/test/integration/pyodide.js b/pyscript.core/test/integration/pyodide.js deleted file mode 100644 index c8633ff8..00000000 --- a/pyscript.core/test/integration/pyodide.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const { shared, python } = require('./_shared.js'); - -module.exports = (playwright, baseURL) => { - const { expect, test } = playwright; - - test('Pyodide bootstrap', python.bootstrap(playwright, baseURL)); - - test('Pyodide fetch', python.fetch(playwright, `${baseURL}/fetch.html`)); - - test('Pyodide to Pyodide Worker', shared.worker(playwright, `${baseURL}/worker.html`)); - - test('Pyodide sync (time)', async ({ page }) => { - const logs = []; - page.on('console', msg => logs.push({text: msg.text(), time: new Date})); - await page.goto(`${baseURL}/sync.html`); - await page.waitForSelector('html.worker.ready'); - await expect(logs.length).toBe(2); - const [ - {text: text1, time: time1}, - {text: text2, time: time2} - ] = logs; - await expect(text1).toBe('before'); - await expect(text2).toBe('after'); - await expect((time2 - time1) >= 1000).toBe(true); - }); -}; diff --git a/pyscript.core/test/integration/ruby-wasm-wasi.js b/pyscript.core/test/integration/ruby-wasm-wasi.js deleted file mode 100644 index a4e8d20c..00000000 --- a/pyscript.core/test/integration/ruby-wasm-wasi.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const { shared } = require('./_shared.js'); - -module.exports = (playwright, baseURL) => { - const { test } = playwright; - - test('Ruby WASM WASI bootstrap', shared.bootstrap(playwright, baseURL)); -}; diff --git a/pyscript.core/test/integration/wasmoon.js b/pyscript.core/test/integration/wasmoon.js deleted file mode 100644 index be5a7526..00000000 --- a/pyscript.core/test/integration/wasmoon.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const { shared } = require('./_shared.js'); - -module.exports = (playwright, baseURL) => { - const { test } = playwright; - - test('Wasmoon bootstrap', shared.bootstrap(playwright, baseURL)); - - test('Wasmoon to Wasmoon Worker', shared.worker(playwright, `${baseURL}/worker.html`)); -}; diff --git a/pyscript.core/test/isolated.html b/pyscript.core/test/isolated.html deleted file mode 100644 index f52b9e2d..00000000 --- a/pyscript.core/test/isolated.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - python - - - - - - - - diff --git a/pyscript.core/test/manifest.json b/pyscript.core/test/manifest.json deleted file mode 100644 index f2112c1a..00000000 --- a/pyscript.core/test/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "background_color": "#ffffff", - "description": "PythonScript", - "display": "standalone", - "name": "PythonScript", - "orientation": "any", - "short_name": "PythonScript", - "start_url": "./", - "theme_color": "#ffffff", - "icons": [ - { - "src": "icon.svg", - "sizes": "144x144", - "type": "image/svg+xml" - } - ] -} diff --git a/pyscript.core/test/many.html b/pyscript.core/test/many.html deleted file mode 100644 index 103a49e7..00000000 --- a/pyscript.core/test/many.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - python - - - - - - - - - - diff --git a/pyscript.core/test/matplot.html b/pyscript.core/test/matplot.html deleted file mode 100644 index 4c6a87bd..00000000 --- a/pyscript.core/test/matplot.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - python - - - - - - - - diff --git a/pyscript.core/test/matplot.json.html b/pyscript.core/test/matplot.json.html deleted file mode 100644 index 500e54f7..00000000 --- a/pyscript.core/test/matplot.json.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - python - - - - - - - - diff --git a/pyscript.core/test/matplot.py b/pyscript.core/test/matplot.py deleted file mode 100644 index d7fb8bea..00000000 --- a/pyscript.core/test/matplot.py +++ /dev/null @@ -1,61 +0,0 @@ -import js -import matplotlib -from xworker import xworker - -try: - js.document -except AttributeError: - matplotlib.use("agg") - -import base64 -import io - -import matplotlib.pyplot as plt -import matplotlib.tri as tri -import numpy as np - -# First create the x and y coordinates of the points. -n_angles = 36 -n_radii = 8 -min_radius = 0.25 -radii = np.linspace(min_radius, 0.95, n_radii) - -angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False) -angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1) -angles[:, 1::2] += np.pi / n_angles - -x = (radii * np.cos(angles)).flatten() -y = (radii * np.sin(angles)).flatten() -z = (np.cos(radii) * np.cos(3 * angles)).flatten() - -# Create the Triangulation; no triangles so Delaunay triangulation created. -triang = tri.Triangulation(x, y) - -# Mask off unwanted triangles. -triang.set_mask( - np.hypot(x[triang.triangles].mean(axis=1), y[triang.triangles].mean(axis=1)) - < min_radius -) - -fig1, ax1 = plt.subplots() -ax1.set_aspect("equal") -tpc = ax1.tripcolor(triang, z, shading="flat") -fig1.colorbar(tpc) -ax1.set_title("tripcolor of Delaunay triangulation, flat shading") - -buf = io.BytesIO() -plt.savefig(buf, format="png") -buf.seek(0) - -# how it was (including main thread counter part) -# js.xworker.postMessage( -# "data:image/png;base64," + base64.b64encode(buf.read()).decode("UTF-8") -# ) - -# how it is now via structured coincident/window -document = xworker.window.document -img = document.createElement("img") -img.style.transform = "scale(.5)" -img.src = "data:image/png;base64," + base64.b64encode(buf.read()).decode("UTF-8") - -document.body.append(img) diff --git a/pyscript.core/test/matplot.worker.html b/pyscript.core/test/matplot.worker.html deleted file mode 100644 index 5fc580fc..00000000 --- a/pyscript.core/test/matplot.worker.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - python - - - - - - -
- - diff --git a/pyscript.core/test/micropython.html b/pyscript.core/test/micropython.html deleted file mode 100644 index 205001f3..00000000 --- a/pyscript.core/test/micropython.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - python - - - - - - - - diff --git a/pyscript.core/test/mocked/micropython.mjs b/pyscript.core/test/mocked/micropython.mjs deleted file mode 100644 index 669cb95a..00000000 --- a/pyscript.core/test/mocked/micropython.mjs +++ /dev/null @@ -1,20 +0,0 @@ -export const python = { content: "", target: null }; -export const loadMicroPython = () => ({ - registerJsModule() { - - }, - runPython(content) { - if (document.currentScript?.target) { - python.content = content; - python.target = document.currentScript.target; - } - }, - globals: { - set(name, value) { - globalThis[name] = value; - }, - delete(name) { - delete globalThis[name]; - }, - }, -}); diff --git a/pyscript.core/test/mocked/pyodide.mjs b/pyscript.core/test/mocked/pyodide.mjs deleted file mode 100644 index 451a2e60..00000000 --- a/pyscript.core/test/mocked/pyodide.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import { basename, dirname } from "node:path"; - -let target; -export const setTarget = (value) => { - target = value; -}; -export const python = { content: "", target: null, packages: null }; -export const loadPyodide = () => ({ - loadPackage() {}, - registerJsModule() { - - }, - pyimport() { - return { - install(packages) { - python.packages = packages; - }, - destroy() {}, - }; - }, - runPython(content) { - python.content = content; - if (target) { - document.currentScript.target = target; - target = void 0; - } - python.target = document.currentScript.target; - }, - globals: { - set(name, value) { - globalThis[name] = value; - }, - delete(name) { - delete globalThis[name]; - }, - }, - FS: { - mkdirTree() {}, - writeFile() {}, - }, - PATH: { dirname }, - _module: { - PATH_FS: { - resolve: (path) => path, - }, - }, -}); diff --git a/pyscript.core/test/mocked/toml.mjs b/pyscript.core/test/mocked/toml.mjs deleted file mode 100644 index c2904a00..00000000 --- a/pyscript.core/test/mocked/toml.mjs +++ /dev/null @@ -1 +0,0 @@ -export const parse = (text) => JSON.parse(text); diff --git a/pyscript.core/test/multi-turtle/index.html b/pyscript.core/test/multi-turtle/index.html deleted file mode 100644 index 9e93facb..00000000 --- a/pyscript.core/test/multi-turtle/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - PyScript Turtles all the way down - - - - - - - - diff --git a/pyscript.core/test/multi-turtle/main.py b/pyscript.core/test/multi-turtle/main.py deleted file mode 100644 index cbccd4cb..00000000 --- a/pyscript.core/test/multi-turtle/main.py +++ /dev/null @@ -1,4 +0,0 @@ -from xworker import XWorker - -for i in range(4): - sync = XWorker("pompom.py", config="turtle.toml") diff --git a/pyscript.core/test/multi-turtle/pompom.py b/pyscript.core/test/multi-turtle/pompom.py deleted file mode 100644 index 87d5e397..00000000 --- a/pyscript.core/test/multi-turtle/pompom.py +++ /dev/null @@ -1,38 +0,0 @@ -import turtle -import random - -turtle.set_defaults(canvwidth=300, canvheight=240) - -colours = [ - "red", - "green", - "blue", - "yellow", - "orange", - "brown", - "gold", - "purple", - "black", -] - -turtle.speed(8) -turtle.pensize(12) - -for i in range(100): - turtle.penup() - turtle.setpos(0, 0) - turtle.left(random.randint(1, 360)) - turtle.pendown() - turtle.color(random.choice(colours)) - turtle.forward(random.randint(20, 90)) - -turtle.Screen().show_scene() -result = turtle.svg() - -from xworker import xworker - -document = xworker.window.document - -container = document.createElement("span") -container.innerHTML = result -document.body.appendChild(container) diff --git a/pyscript.core/test/multi-turtle/svg.py b/pyscript.core/test/multi-turtle/svg.py deleted file mode 100644 index 51f5fcc6..00000000 --- a/pyscript.core/test/multi-turtle/svg.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -A rewrite of Brython's SVG module, to remove JavaScript / document related -interactions (so this can be used within a web worker, where document is not -conveniently available). - -Author: Nicholas H.Tollervey (ntollervey@anaconda.com) -Based on original work by: Romain Casati - -License: GPL v3 or higher. -""" - - -class Node: - """ - Represents a node in the DOM. - """ - - def __init__(self, **kwargs): - self._node = kwargs - self.parent = kwargs.get("parent") - - @property - def outerHTML(self): - """ - Get a string representation of the element's outer HTML. - """ - return NotImplemented - - -class ElementNode(Node): - """ - An element defined by a tag, may have attributes and children. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.tagName = kwargs["tagName"] - self.attributes = kwargs.get("attributes", {}) - self.value = kwargs.get("value") - self.childNodes = [] - - def appendChild(self, child): - """ - Add a child node to the children of this node. Using DOM API naming - conventions. - """ - child.parent = self - self.childNodes.append(child) - - def setAttribute(self, key, value): - """ - Sets an attribute on the node. - """ - self.attributes[key] = value - - @property - def outerHTML(self): - """ - Get a string representation of the element's outer HTML. Using DOM API - naming conventions. - """ - result = "<" + self.tagName - for attr, val in self.attributes.items(): - result += " " + attr + '="' + str(val) + '"' - result += ">" - result += self.innerHTML - result += "" - return result - - @property - def innerHTML(self): - """ - Get a string representation of the element's inner HTML. Using DOM API - naming conventions. - """ - result = "" - for child in self.childNodes: - result += child.outerHTML - return result - - -class TextNode(Node): - """ - Textual content inside an ElementNode. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.nodeValue = kwargs.get("nodeValue") - - @property - def outerHTML(self): - """ - Get a string representation of the element's outer HTML. - """ - return self.nodeValue - - -_svg_ns = "http://www.w3.org/2000/svg" -_xlink_ns = "http://www.w3.org/1999/xlink" - - -def _tag_func(tag): - def func(*args, **kwargs): - node = ElementNode(tagName=tag) - # this is mandatory to display svg properly - if tag == "svg": - node.setAttribute("xmlns", _svg_ns) - for arg in args: - if isinstance(arg, (str, int, float)): - arg = TextNode(nodeValue=str(arg)) - node.appendChild(arg) - for key, value in kwargs.items(): - key = key.lower() - if key[0:2] == "on": - # Ignore event handlers within the SVG. This shouldn't happen. - pass - elif key == "style": - node.setAttribute( - "style", ";".join(f"{k}: {v}" for k, v in value.items()) - ) - elif value is not False: - node.setAttribute(key.replace("_", "-"), str(value)) - return node - - return func - - -a = _tag_func("a") -altGlyph = _tag_func("altGlyph") -altGlyphDef = _tag_func("altGlyphDef") -altGlyphItem = _tag_func("altGlyphItem") -animate = _tag_func("animate") -animateColor = _tag_func("animateColor") -animateMotion = _tag_func("animateMotion") -animateTransform = _tag_func("animateTransform") -circle = _tag_func("circle") -clipPath = _tag_func("clipPath") -color_profile = _tag_func("color_profile") -cursor = _tag_func("cursor") -defs = _tag_func("defs") -desc = _tag_func("desc") -ellipse = _tag_func("ellipse") -feBlend = _tag_func("feBlend") -foreignObject = _tag_func("foreignObject") -g = _tag_func("g") -image = _tag_func("image") -line = _tag_func("line") -linearGradient = _tag_func("linearGradient") -marker = _tag_func("marker") -mask = _tag_func("mask") -path = _tag_func("path") -pattern = _tag_func("pattern") -polygon = _tag_func("polygon") -polyline = _tag_func("polyline") -radialGradient = _tag_func("radialGradient") -rect = _tag_func("rect") -set = _tag_func("set") -stop = _tag_func("stop") -svg = _tag_func("svg") -text = _tag_func("text") -tref = _tag_func("tref") -tspan = _tag_func("tspan") -use = _tag_func("use") diff --git a/pyscript.core/test/multi-turtle/turtle.py b/pyscript.core/test/multi-turtle/turtle.py deleted file mode 100644 index ce817d99..00000000 --- a/pyscript.core/test/multi-turtle/turtle.py +++ /dev/null @@ -1,2486 +0,0 @@ -""" -A port of Basthon's Turtle module for PyScript (it works with both Pyodide -and MicroPython interpreters), by Nicholas H.Tollervey under the GPLv3 -License. ntollervey@anaconda.com - -A port of the Brython's Turtle module to Basthon by Romain Casati -under the GPLv3 License. - -A revised version of CPython's turtle module written for Brython - -Note: This version is not intended to be used in interactive mode, -nor use help() to look up methods/functions definitions. The docstrings -have thus been shortened considerably as compared with the CPython's version. - -All public methods/functions of the CPython version should exist, if only -to print out a warning that they are not implemented. The intent is to make -it easier to "port" any existing turtle program from CPython to the browser. - -IMPORTANT: We use SVG for drawing turtles. If we have a turtle at an angle -of 350 degrees and we rotate it by an additional 20 degrees, we will have -a turtle at an angle of 370 degrees. For turtles drawn periodically on -a screen (like typical animations, including the CPython turtle module), -drawing a turtle with a rotation of 370 degrees is the same as a rotation of -10 degrees. However, using SVG, if we "slowly" animate an object, -rotating it from 350 to 370 degrees, the result will not be the same -as rotating it from 350 to 10 degree. -""" -import math -import sys -import random -from math import cos, sin -import svg as SVG - - -def appendTo(root, node): - root.appendChild(node) - - -def generate_id(): - chars = "abcdefghijklmnopqrstuvwxyz1234567890" - return "".join([random.choice(chars) for i in range(16)]) - - -# Even though it is a private object, use the same name for the configuration -# dict as the CPython's module. - - -# Commented out configuration items are those found on the CPython version -def _default_cfg(): - return { - # "width" : 0.5, # Screen - # "height" : 0.75, - "canvwidth": 640, - "canvheight": 480, - # "leftright": None, - # "topbottom": None, - "mode": "standard", - # "colormode": 1.0, - # "delay": 10, - # "undobuffersize": 1000, - "shape": "turtle", - "pencolor": "black", - "fillcolor": "black", - # "resizemode" : "noresize", - "visible": True, - # "language": "english", # docstrings - # "exampleturtle": "turtle", - # "examplescreen": "screen", - # "title": "Python Turtle Graphics", - # "using_IDLE": False - # Below are configuration items specific to this version - "min_duration": "1ms", - } - - -_CFG = _default_cfg() - - -def set_defaults(**params): - """Allows to override defaults.""" - _CFG.update(**params) - Screen().reset() - - -class Vec2D(tuple): - """ - Used to give a nicer representation of the position. - - Because we're using SVG, if we "slowly" animate an object, rotating it - from 350 to 370 degrees, the result will not be the same as rotating it - from 350 to 10 degrees. For this reason, we did not use the Vec2D class - from the CPython module and handle the rotations quite differently. - - This version of Vec2D is implemented for completeness. - - Provides (for a, b vectors, k number): - a+b vector addition - a-b vector subtraction - a*b inner product - k*a and a*k multiplication with scalar - |a| absolute value of a - - As mentioned above, does not provide a.rotate(ange) rotation because of - SVG reasons. - """ - - def __add__(self, other): - return Vec2D(self[0] + other[0], self[1] + other[1]) - - def __mul__(self, other): - if isinstance(other, Vec2D): - return self[0] * other[0] + self[1] * other[1] - return Vec2D(self[0] * other, self[1] * other) - - def __rmul__(self, other): - if isinstance(other, int) or isinstance(other, float): - return Vec2D(self[0] * other, self[1] * other) - return NotImplemented - - def __sub__(self, other): - return Vec2D(self[0] - other[0], self[1] - other[1]) - - def __neg__(self): - return Vec2D(-self[0], -self[1]) - - def __abs__(self): - return math.sqrt(sum(i**2 for i in self)) - - def __getnewargs__(self): - return (self[0], self[1]) - - def __repr__(self): - return "(%.2f, %.2f)" % self - - -def create_circle(r): - """Creates a circle of radius r centered at the origin""" - circle = SVG.circle(x=0, y=0, r=r, stroke="black", fill="black") - circle.setAttribute("stroke-width", 1) - return circle - - -def create_polygon(points): - """Creates a polygon using the points provided""" - points = " ".join(",".join(map(str, p)) for p in points) - polygon = SVG.polygon(points=points, stroke="black", fill="black") - polygon.setAttribute("stroke-width", 1) - return polygon - - -def create_rectangle(width=2, height=2, rx=None, ry=None): - """Creates a rectangle centered at the origin. rx and ry can be - used to have rounded corners""" - rectangle = SVG.rect( - x=-width / 2, - y=-height / 2, - width=width, - height=height, - stroke="black", - fill="black", - ) - rectangle.setAttribute("stroke-width", 1) - if rx is not None: - rectangle.setAttribute("rx", rx) - if ry is not None: - rectangle.setAttribute("ry", ry) - return rectangle - - -def create_square(size=2, r=None): - """Creates a square centered at the origin. rx and ry can be - used to have rounded corners""" - return create_rectangle(width=size, height=size, rx=r, ry=r) - - -class TurtleGraphicsError(Exception): - """Some TurtleGraphics Error""" - - pass - - -class Screen: - _instance = None - _initialised = False - - def __new__(cls, *args, **kwargs): - if not cls._instance: - cls._instance = object.__new__(cls, *args, **kwargs) - return cls._instance - - def __init__(self): - if not self._initialised: - self.shapes = { - "arrow": (create_polygon, ((-10, 0), (10, 0), (0, 10))), - "turtle": ( - create_polygon, - ( - (0, 16), - (-2, 14), - (-1, 10), - (-4, 7), - (-7, 9), - (-9, 8), - (-6, 5), - (-7, 1), - (-5, -3), - (-8, -6), - (-6, -8), - (-4, -5), - (0, -7), - (4, -5), - (6, -8), - (8, -6), - (5, -3), - (7, 1), - (6, 5), - (9, 8), - (7, 9), - (4, 7), - (1, 10), - (2, 14), - ), - ), - "classic": ( - create_polygon, - ((0, 0), (-5, -9), (0, -7), (5, -9)), - ), - "triangle": ( - create_polygon, - ((10, -5.77), (0, 11.55), (-10, -5.77)), - ), - "square": (create_square, 20), - "circle": (create_circle, 10), - } - self._animate = True - self._old_svg_scene = None - self.reset() - self._initialised = True - - def animation(self, onoff): - onoff = onoff.lower() - if onoff == "on": - self._animate = True - elif onoff == "off": - self._animate = False - else: - raise ValueError("Supported values are only 'on' and 'off'.") - - def _repr_svg_(self): - return self._old_svg_scene.outerHTML - - def svg(self): - return self._repr_svg_() - - def save(self, file): - """Save SVG to a file. - file can be a file descriptor or a filename. - If file is a file descriptor, it should be open in text mode. - """ - if self._old_svg_scene is None: - raise RuntimeError( - "No turtle scene ended! " "You should call 'done' first." - ) - html = self._old_svg_scene.outerHTML - if isinstance(file, str): - with open(file, "w") as f: - f.write(html) - else: - # file should be a file descriptor - file.write(html) - - def animation_frame_id(self, index): - return "af_{}_{}".format(self.svg_id, index) - - def bgcolor(self, color=None): - """sets the background with the given color if color is not None, - else return current background color. - """ - if color is None: - return self.background_color - self.background_color = color - width = _CFG["canvwidth"] - height = _CFG["canvheight"] - if self.mode() in ["logo", "standard"]: - x = -width // 2 - y = -height // 2 - else: - x = 0 - y = -height - - self.frame_index += 1 - rect = SVG.rect(x=x, y=y, width=width, height=height, fill=color) - if self._animate: - rect.setAttribute("style", "display: none;") - an = SVG.animate( - Id=self.animation_frame_id(self.frame_index), - attributeName="display", - attributeType="CSS", - From="block", - to="block", - dur=_CFG["min_duration"], - fill="freeze", - ) - an.setAttribute( - "begin", self.animation_frame_id(self.frame_index - 1) + ".end" - ) - appendTo(rect, an) - - appendTo(self.background_canvas, rect) - - def _convert_coordinates(self, x, y): - """In the browser, the increasing y-coordinate is towards the - bottom of the screen; this is the opposite of what is assumed - normally for the methods in the CPython turtle module. - - This method makes the necessary orientation. It should be called - just prior to creating any SVG element. - """ - return x * self.yscale, self.y_points_down * y * self.yscale - - def create_svg_turtle(self, _turtle, name): - if name in self.shapes: - fn, arg = self.shapes[name] - else: - print("Unknown turtle '%s'; the default turtle will be used") - fn, arg = self.shapes[_CFG["shape"]] - shape = fn(arg) - if self._mode == "standard" or self._mode == "world": - rotation = -90 - else: - rotation = 0 - return shape, rotation - - def _dot(self, pos, size, color): - """Draws a filled circle of specified size and color""" - if color is None: - color = "black" - if size is None or size < 1: - size = 1 - self.frame_index += 1 - - # `size` represents the diameter, svg needs the radius - radius = size / 2 - - x, y = self._convert_coordinates(pos[0], pos[1]) - - circle = SVG.circle(cx=x, cy=y, r=radius, fill=color) - if self._animate: - circle.setAttribute("style", "display: none;") - an = SVG.animate( - Id=self.animation_frame_id(self.frame_index), - attributeName="display", - attributeType="CSS", - From="block", - to="block", - dur=_CFG["min_duration"], - fill="freeze", - ) - an.setAttribute( - "begin", self.animation_frame_id(self.frame_index - 1) + ".end" - ) - appendTo(circle, an) - appendTo(self.canvas, circle) - - def _drawline(self, _turtle, coordlist=None, color=None, width=1, speed=None): - """Draws an animated line with a turtle - - coordlist is the egin and end coordinates of the line - - color should include the current outline and fill colors; - - width is width of line to be drawn. - - speed is the animation speed - """ - - outline = color[0] - fill = color[1] - - x0, y0 = coordlist[0] - x1, y1 = coordlist[1] - - x0, y0 = self._convert_coordinates(x0, y0) - x1, y1 = self._convert_coordinates(x1, y1) - - # The speed scale does not correspond exactly to the CPython one... - if speed == 0: - duration = _CFG["min_duration"] - else: - dist = _turtle._distance - if speed is None or speed == 1: - duration = 0.02 * dist - else: - duration = 0.02 * dist / speed**1.2 - if duration < 0.001: - duration = _CFG["min_duration"] - else: - duration = "%6.3fs" % duration - - drawing = _turtle._drawing - - style = {"stroke": outline, "stroke-width": width} - if self._animate: - _line = SVG.line(x1=x0, y1=y0, x2=x0, y2=y0, style=style) - else: - _line = SVG.line(x1=x0, y1=y0, x2=x1, y2=y1, style=style) - - if not drawing: - _line.setAttribute("opacity", 0) - - # always create one animation for timing purpose - begin = self.animation_frame_id(self.frame_index) + ".end" - self.frame_index += 1 - if self._animate: - _an1 = SVG.animate( - Id=self.animation_frame_id(self.frame_index), - attributeName="x2", - attributeType="XML", - From=x0, - to=x1, - dur=duration, - fill="freeze", - begin=begin, - ) - appendTo(_line, _an1) - - # But, do not bother adding animations that will not be shown. - if drawing: - if self._animate: - _an2 = SVG.animate( - attributeName="y2", - attributeType="XML", - begin=begin, - From=y0, - to=y1, - dur=duration, - fill="freeze", - ) - appendTo(_line, _an2) - - if width > 2: - if self._animate: - _line_cap = SVG.set( - attributeName="stroke-linecap", - begin=begin, - attributeType="xml", - to="round", - dur=duration, - fill="freeze", - ) - appendTo(_line, _line_cap) - else: - _line.setAttribute("stroke-linecap", "round") - - appendTo(self.canvas, _line) - return begin, duration, (x0, y0), (x1, y1) - - def _drawpoly(self, coordlist, outline=None, fill=None, width=None): - """Draws a path according to provided arguments: - - coordlist is sequence of coordinates - - fill is filling color - - outline is outline color - - width is the outline width - """ - self.frame_index += 1 - - if self._animate: - style = {"display": "none"} - else: - style = {"display": "block"} - - if fill is not None: - style["fill"] = fill - if outline is not None: - style["stroke"] = outline - if width is not None: - style["stroke-width"] = width - else: - style["stroke-width"] = 1 - - points = " ".join( - ",".join(map(str, self._convert_coordinates(*p))) for p in coordlist - ) - polygon = SVG.polygon(points=points, style=style) - - if self._animate: - an = SVG.animate( - Id=self.animation_frame_id(self.frame_index), - attributeName="display", - attributeType="CSS", - From="block", - to="block", - dur=_CFG["min_duration"], - fill="freeze", - ) - - an.setAttribute( - "begin", self.animation_frame_id(self.frame_index - 1) + ".end" - ) - appendTo(polygon, an) - - appendTo(self.canvas, polygon) - - def _new_frame(self): - """returns a new animation frame index and update the current index""" - - previous_end = self.animation_frame_id(self.frame_index) + ".end" - self.frame_index += 1 - new_frame_id = self.animation_frame_id(self.frame_index) - return previous_end, new_frame_id - - def mode(self, _mode=None): - if _mode is None: - return self._mode - _CFG["mode"] = _mode - self.reset() - - def reset(self): - self._turtles = [] - self.frame_index = 0 - self.background_color = "white" - self._scene_finished = False - self._set_geometry() - - def restart(self): - _CFG.update(_default_cfg()) - self.reset() - Turtle._pen = None - - def _set_geometry(self): - self.width = _CFG["canvwidth"] - self.height = _CFG["canvheight"] - self.x_offset = self.y_offset = 0 - self.xscale = self.yscale = 1 - - self.y_points_down = -1 - self._mode = _CFG["mode"].lower() - if self._mode in ["logo", "standard"]: - self.translate_canvas = (self.width // 2, self.height // 2) - elif self._mode == "world": - self.translate_canvas = (0, self.height) - self._setup_canvas() - - def _setup_canvas(self): - self.svg_id = generate_id() - self.svg_scene = SVG.svg( - width=self.width, - height=self.height, - preserveAspectRatio="xMidYMid meet", - viewBox="0 0 {} {}".format(self.width, self.height), - ) - translate = "translate(%d %d)" % self.translate_canvas - - # always create one animation for timing purpose - # if _animate is False, we remove it in end_scene() - self._timing_anim = SVG.animate( - Id=self.animation_frame_id(self.frame_index), - attributeName="opacity", - attributeType="CSS", - From=1, - to=1, - begin="0s", - dur=_CFG["min_duration"], - fill="freeze", - ) - appendTo(self.svg_scene, self._timing_anim) - - # Unlike html elements, svg elements have no concept of a z-index: each - # new element is drawn on top of each other. - # Having separate canvas keeps the ordering - self.background_canvas = SVG.g(transform=translate) - self.canvas = SVG.g(transform=translate) - self.writing_canvas = SVG.g(transform=translate) - self.turtle_canvas = SVG.g(transform=translate) - - appendTo(self.svg_scene, self.background_canvas) - appendTo(self.svg_scene, self.canvas) - appendTo(self.svg_scene, self.writing_canvas) - appendTo(self.svg_scene, self.turtle_canvas) - - def setworldcoordinates(self, llx, lly, urx, ury): - """Set up a user defined coordinate-system. - - Arguments: - llx -- a number, x-coordinate of lower left corner of canvas - lly -- a number, y-coordinate of lower left corner of canvas - urx -- a number, x-coordinate of upper right corner of canvas - ury -- a number, y-coordinate of upper right corner of canvas - - Note: llx must be less than urx in this version. - - Warning: in user-defined coordinate systems angles may appear distorted. - """ - self._mode = "world" - - if urx < llx: - sys.stderr.write( - "Warning: urx must be greater than llx; your choice will be reversed" - ) - urx, llx = llx, urx - xspan = urx - llx - yspan = abs(ury - lly) - - self.xscale = int(self.width) / xspan - self.yscale = int(self.height) / yspan - self.x_offset = -llx * self.xscale - if ury < lly: - self.y_points_down = 1 # standard orientation in the browser - else: - self.y_points_down = -1 - self.y_offset = self.y_points_down * lly * self.yscale - self.translate_canvas = (self.x_offset, self.height - self.y_offset) - self._setup_canvas() - - def end_scene(self): - """Ends the creation of a "scene" and has it displayed""" - # removing animation used for timing purpose - if not self._animate and self._timing_anim is not None: - self.svg_scene.removeChild(self._timing_anim) - self._timing_anim = None - if not self._scene_finished: - for t in self._turtles: - appendTo(self.turtle_canvas, t.svg) - self._scene_finished = True - self._old_svg_scene = self.svg_scene - return self.svg_scene - - def show_scene(self): - """Ends the creation of a "scene" and has it displayed""" - return self.end_scene() - - def turtles(self): - """Return the list of turtles on the screen.""" - return self._turtles - - def _write(self, pos, txt, align, font, color): - """Write txt at pos in canvas with specified font - and color.""" - if isinstance(color, tuple): - stroke = color[0] - fill = color[1] - else: - fill = color - stroke = None - x, y = self._convert_coordinates(pos[0], pos[1]) - text = SVG.text( - txt, - x=x, - y=y, - fill=fill, - style={ - "display": "none" if self._animate else "block", - "font-family": font[0], - "font-size": font[1], - "font-style": font[2], - }, - ) - - if stroke is not None: - text.setAttribute("stroke", stroke) - if align == "left": - text.setAttribute("text-anchor", "start") - elif align == "center" or align == "centre": - text.setAttribute("text-anchor", "middle") - elif align == "right": - text.setAttribute("text-anchor", "end") - - self.frame_index += 1 - if self._animate: - an = SVG.animate( - Id=self.animation_frame_id(self.frame_index), - attributeName="display", - attributeType="CSS", - From="block", - to="block", - dur=_CFG["min_duration"], - fill="freeze", - ) - an.setAttribute( - "begin", self.animation_frame_id(self.frame_index - 1) + ".end" - ) - appendTo(text, an) - - appendTo(self.writing_canvas, text) - - def addshape(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.addshape() is not implemented.\n") - - def bgpic(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.bgpic() is not implemented.\n") - - def bye(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.bye() is not implemented.\n") - - def clearscreen(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.clearscreen() is not implemented.\n") - - def colormode(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.colormode() is not implemented.\n") - - def delay(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.delay() is not implemented.\n") - - def exitonclick(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.exitonclick() is not implemented.\n") - - def getcanvas(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.getcanvas() is not implemented.\n") - - def getshapes(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.getshapes() is not implemented.\n") - - def listen(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.listen() is not implemented.\n") - - def numinput(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.numinput() is not implemented.\n") - - def onkey(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.onkey() is not implemented.\n") - - def onkeypress(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.onkeypress() is not implemented.\n") - - def onkeyrelease(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.onkeyrelease() is not implemented.\n") - - def onscreenclick(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.onscreenclick() is not implemented.\n") - - def ontimer(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.ontimer() is not implemented.\n") - - def register_shape(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.register_shape() is not implemented.\n") - - def resetscreen(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.resetscreen() is not implemented.\n") - - def screensize(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.screensize() is not implemented.\n") - - def setup(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.setup() is not implemented.\n") - - def textinput(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.textinput() is not implemented.\n") - - def title(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.title() is not implemented.\n") - - def tracer(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.tracer() is not implemented.\n") - - def update(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.update() is not implemented.\n") - - def window_height(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.window_height() is not implemented.\n") - - def window_width(self, *args, **kwargs): - sys.stderr.write("Warning: Screen.window_width() is not implemented.\n") - - -class TNavigator: - """Navigation part of the Turtle. - Implements methods for turtle movement. - """ - - # START_ORIENTATION = { - # "standard": Vec2D(1.0, 0.0), - # "world": Vec2D(1.0, 0.0), - # "logo": Vec2D(0.0, 1.0)} - DEFAULT_MODE = "standard" - DEFAULT_ANGLEOFFSET = 0 - DEFAULT_ANGLEORIENT = 1 - - def __init__(self, mode=DEFAULT_MODE): - self._angleOffset = self.DEFAULT_ANGLEOFFSET - self._angleOrient = self.DEFAULT_ANGLEORIENT - self._mode = mode - self.degree_to_radians = math.pi / 180 - self.degrees() - self._mode = _CFG["mode"] - self._setmode(mode) - TNavigator.reset(self) - - def reset(self): - """reset turtle navigation to its initial values - - The derived class, which will call it directly and add its own - """ - self._position = (0.0, 0.0) - self._x = 0 - self._y = 0 - self._angle = 0 - self._old_heading = 0 - - def _setmode(self, mode=None): - """Set turtle-mode to 'standard', 'world' or 'logo'.""" - if mode is None: - return self._mode - if mode not in ["standard", "logo", "world"]: - print(mode, "is an unknown mode; it will be ignored.") - return - self._mode = mode - if mode in ["standard", "world"]: - self._angleOffset = 0 - self._angleOrient = 1 - else: # mode == "logo": - self._angleOffset = -self._fullcircle / 4.0 - self._angleOrient = 1 - - def _setDegreesPerAU(self, fullcircle): - """Helper function for degrees() and radians()""" - self._fullcircle = fullcircle - self._degreesPerAU = 360 / fullcircle - - def degrees(self, fullcircle=360.0): - """Set angle measurement units to degrees, or possibly other system.""" - self._setDegreesPerAU(fullcircle) - - def radians(self): - """Set the angle measurement units to radians.""" - self._setDegreesPerAU(2 * math.pi) - - def _rotate(self, angle): - """Turn turtle counterclockwise by specified angle if angle > 0.""" - pass - - def _goto(self, x, y): - pass # implemented by derived class - - def forward(self, distance): - """Move the turtle forward by the specified distance.""" - x1 = distance * cos(self._angle * self.degree_to_radians) - y1 = distance * sin(self._angle * self.degree_to_radians) - self._distance = distance - self._goto(self._x + x1, self._y + y1) - - fd = forward - - def back(self, distance): - """Move the turtle backward by distance.""" - x1 = -distance * cos(self._angle * self.degree_to_radians) - y1 = -distance * sin(self._angle * self.degree_to_radians) - self._distance = distance - self._goto(self._x + x1, self._y + y1) - - backward = back - bk = back - - def right(self, angle): - """Turn turtle right by angle units.""" - angle *= self._degreesPerAU - self._angle += self.screen.y_points_down * angle - self._rotate_image(-angle) - - rt = right - - def left(self, angle): - """Turn turtle left by angle units.""" - angle *= self._degreesPerAU - self._angle += -self.screen.y_points_down * angle - self._rotate_image(angle) - - lt = left - - def pos(self): - """Return the turtle's current location (x,y), as a formatted tuple""" - return Vec2D((self._x, self._y)) - - position = pos - - def xcor(self): - """Return the turtle's x coordinate.""" - return self._x - - def ycor(self): - """Return the turtle's y coordinate""" - return self._y - - def goto(self, x, y=None): - """Move turtle to an absolute position.""" - if y is None: - x, y = x[0], x[1] # "*x" here raises SyntaxError - # distance only needed to calculate the duration of - # the animation which is based on "distance" and "speed" as well. - # We use the Manhattan distance here as it is *much* faster on Chrome, - # than using the proper distance with calls to math.sqrt, while - # giving acceptable results - # - # forward, backward, etc., call _goto directly with the distance - # given by the user - self._distance = abs(self._x - x) + abs(self._y - y) - self._goto(x, y) - - setpos = goto - setposition = goto - - def home(self): - """Move turtle to the origin - coordinates (0,0), facing in the - default orientation - """ - self.goto(0, 0) - self.setheading(0) - - def setx(self, x): - """Set the turtle's first coordinate to x""" - self._distance = abs(x - self._x) - self._goto(x, self._y) - - def sety(self, y): - """Set the turtle's second coordinate to y""" - self._distance = abs(y - self._y) - self._goto(self._x, y) - - def distance(self, x, y=None): - """ - Return the distance from the turtle to (x,y) in turtle step units. - """ - if y is None: - assert isinstance(x, tuple) - x, y = x - return math.sqrt((self._x - x) ** 2 + (self._y - y) ** 2) - - def towards(self, x, y=None): - """ - Return the angle of the line from the turtle's position to (x, y). - """ - if y is None: - assert isinstance(x, tuple) - x, y = x - x, y = x - self._x, y - self._y - result = round(math.atan2(y, x) * 180.0 / math.pi, 10) % 360.0 - result /= self._degreesPerAU - return (self._angleOffset + self._angleOrient * result) % self._fullcircle - - def heading(self): - """Return the turtle's current heading.""" - angle = self._angle / self._degreesPerAU - return (self._angleOffset + self._angleOrient * angle) % self._fullcircle - - def setheading(self, to_angle): - """Set the orientation of the turtle to to_angle.""" - rot = min((to_angle + i * 360 - self._angle for i in range(-2, 3)), key=abs) - self._rotate(rot) - - seth = setheading - - def circle(self, radius, extent=None, steps=None): - """ - Draw an approximate (arc) circle with given radius, using straight - line segments. - - Arguments: - radius -- a number - extent (optional) -- a number - steps (optional) -- an integer - - Draw a circle with given radius. The center is radius units left - of the turtle; extent - an angle - determines which part of the - circle is drawn. If extent is not given, draw the entire circle. - If extent is not a full circle, one endpoint of the arc is the - current pen position. Draw the arc in counterclockwise direction - if radius is positive, otherwise in clockwise direction. Finally - the direction of the turtle is changed by the amount of extent. - - As the circle is approximated by an inscribed regular polygon, - steps determines the number of steps to use. If not given, - it will be calculated automatically. Maybe used to draw regular - polygons. - """ - speed = self.speed() - if extent is None: - extent = self._fullcircle - if steps is None: - frac = abs(extent) / self._fullcircle - steps = 1 + int(min(11 + abs(radius) / 6.0, 59.0) * frac) - w = 1.0 * extent / steps - w2 = 0.5 * w - l = 2.0 * radius * math.sin(w2 * math.pi / 180.0 * self._degreesPerAU) - if radius < 0: - l, w, w2 = -l, -w, -w2 - self._rotate(w2) - for i in range(steps): - self.speed(speed) - self.forward(l) - self.speed(0) - self._rotate(w) - self._rotate(-w2) - self.speed(speed) - - -class TPen: - """Drawing part of the Turtle.""" - - def __init__(self): - self.screen = Screen() - self._reset() - - def _reset(self, pencolor=_CFG["pencolor"], fillcolor=_CFG["fillcolor"]): - self._pensize = 1 - self._shown = True - self._drawing = True - self._pencolor = "black" - self._fillcolor = "black" - self._speed = 3 - self._stretchfactor = (1.0, 1.0) - - def resizemode(self, rmode=None): - sys.stderr.write("Warning: TPen.resizemode() is not implemented.\n") - - def pensize(self, width=None): - """Set or return the line thickness.""" - if width is None: - return self._pensize - self.pen(pensize=width) - - width = pensize - - def pendown(self): - """Pull the pen down -- drawing when moving.""" - if self._drawing: - return - self.pen(pendown=True) - - pd = pendown - down = pendown - - def penup(self): - """Pull the pen up -- no drawing when moving.""" - if not self._drawing: - return - self.pen(pendown=False) - - pu = penup - up = penup - - def isdown(self): - """Return True if pen is down, False if it's up.""" - return self._drawing - - def speed(self, speed=None): - """Return or set the turtle's speed. - - Optional argument: - speed -- an integer in the range 0..10 or a speedstring (see below) - - Set the turtle's speed to an integer value in the range 0 .. 10. - If no argument is given: return current speed. - - If input is a number greater than 10 or smaller than 0.5, - speed is set to 0. - Speedstrings are mapped to speedvalues in the following way: - 'fastest' : 0 - 'fast' : 10 - 'normal' : 6 - 'slow' : 3 - 'slowest' : 1 - speeds from 1 to 10 enforce increasingly faster animation of - line drawing and turtle turning. - - Attention: - speed = 0 : *no* animation takes place. forward/back makes turtle jump - and likewise left/right make the turtle turn instantly. - """ - speeds = { - "fastest": 0, - "fast": 10, - "normal": 6, - "slow": 3, - "slowest": 1, - } - if speed is None: - return self._speed - if speed in speeds: - speed = speeds[speed] - elif 0.5 < speed < 10.5: - speed = int(round(speed)) - else: - speed = 0 - self.pen(speed=speed) - - def color(self, *args): - """Return or set the pencolor and fillcolor. - - IMPORTANT: this is very different than the CPython's version. - - Colors are using strings in any format recognized by a browser - (named color, rgb, rgba, hex, hsl, etc.) - - Acceptable arguments: - - no argument: returns (pencolor, fillcolor) - single string -> sets both pencolor and fillcolor to that value - two string arguments -> taken to be pencolor, fillcolor - tuple of two strings -> taken to be (pencolor, fillcolor) - """ - if args: - pencolor, fillcolor = None, None - l = len(args) - if l == 1: - if isinstance(args[0], tuple): - pencolor = args[0][0] - fillcolor = args[0][1] - else: - pencolor = fillcolor = args[0] - elif l == 2: - pencolor, fillcolor = args - - if not isinstance(pencolor, str) or not isinstance(fillcolor, str): - raise TurtleGraphicsError("bad color arguments: %s" % str(args)) - - self.pen(pencolor=pencolor, fillcolor=fillcolor) - else: - return self._pencolor, self._fillcolor - - def pencolor(self, color=None): - """Return or set the pencolor. - - IMPORTANT: this is very different than the CPython's version. - - Colors are using strings in any format recognized by a browser - (named color, rgb, rgba, hex, hsl, etc.) - """ - if color is not None: - if not isinstance(color, str): - raise TurtleGraphicsError("bad color arguments: %s" % str(color)) - if color == self._pencolor: - return - self.pen(pencolor=color) - else: - return self._pencolor - - def fillcolor(self, color=None): - """Return or set the fillcolor. - - IMPORTANT: this is very different than the CPython's version. - - Colors are using strings in any format recognized by a browser - (named color, rgb, rgba, hex, hsl, etc.) - """ - if color is not None: - if not isinstance(color, str): - raise TurtleGraphicsError("bad color arguments: %s" % str(color)) - if color == self._fillcolor: - return - self.pen(fillcolor=color) - else: - return self._pencolor - - def showturtle(self): - """Makes the turtle visible.""" - if self._shown: - return - self.pen(shown=True) - self.left(0) # this will update the display to the correct rotation - - st = showturtle - - def hideturtle(self): - """Makes the turtle invisible.""" - if self._shown: - self.pen(shown=False) - - ht = hideturtle - - def isvisible(self): - """Return True if the Turtle is shown, False if it's hidden.""" - return self._shown - - def pen(self, pen=None, **pendict): - """Return or set the pen's attributes. - - Arguments: - pen -- a dictionary with some or all of the below listed keys. - **pendict -- one or more keyword-arguments with the below - listed keys as keywords. - - Return or set the pen's attributes in a 'pen-dictionary' - with the following key/value pairs: - "shown" : True/False - "pendown" : True/False - "pencolor" : color-string or color-tuple - "fillcolor" : color-string or color-tuple - "pensize" : positive number - "speed" : number in range 0..10 - """ - _pd = { - "shown": self._shown, - "pendown": self._drawing, - "pencolor": self._pencolor, - "fillcolor": self._fillcolor, - "pensize": self._pensize, - "speed": self._speed, - } - - if not (pen or pendict): - return _pd - - if isinstance(pen, dict): - p = pen - else: - p = {} - p.update(pendict) - - _p_buf = {} - for key in p: - _p_buf[key] = _pd[key] - if "pendown" in p: - self._drawing = p["pendown"] - if "pencolor" in p: - old_color = self._pencolor - self._pencolor = p["pencolor"] - previous_end, new_frame_id = self.screen._new_frame() - if self.screen._animate: - anim = SVG.animate( - Id=new_frame_id, - begin=previous_end, - dur=_CFG["min_duration"], - fill="freeze", - attributeName="stroke", - attributeType="XML", - From=old_color, - to=self._pencolor, - ) - appendTo(self.svg, anim) - else: - self.svg.setAttribute("stroke", self._pencolor) - if "pensize" in p: - self._pensize = p["pensize"] - if "fillcolor" in p: - old_color = self._fillcolor - self._fillcolor = p["fillcolor"] - previous_end, new_frame_id = self.screen._new_frame() - if self.screen._animate: - anim = SVG.animate( - Id=new_frame_id, - begin=previous_end, - dur=_CFG["min_duration"], - fill="freeze", - attributeName="fill", - attributeType="XML", - From=old_color, - to=self._fillcolor, - ) - appendTo(self.svg, anim) - else: - self.svg.setAttribute("fill", self._fillcolor) - if "speed" in p: - self._speed = p["speed"] - if "shown" in p: - old_shown = self._shown - if old_shown: - opacity = 0 - old_opacity = 1 - else: - opacity = 1 - old_opacity = 0 - previous_end, new_frame_id = self.screen._new_frame() - if self.screen._animate: - anim = SVG.animate( - Id=new_frame_id, - begin=previous_end, - dur=_CFG["min_duration"], - fill="freeze", - attributeName="opacity", - attributeType="XML", - From=old_opacity, - to=opacity, - ) - appendTo(self.svg, anim) - else: - self.svg.setAttribute("opacity", opacity) - self.forward(0) # updates the turtle visibility on screen - self._shown = p["shown"] - - -# No RawTurtle/RawPen for this version, unlike CPython's; only Turtle/Pen -class Turtle(TPen, TNavigator): - """Animation part of the Turtle. - Puts Turtle upon a TurtleScreen and provides tools for - its animation. - """ - - _pen = None - screen = None - - def __init__(self, shape=_CFG["shape"], visible=_CFG["visible"]): - self.screen = Screen() - TPen.__init__(self) - TNavigator.__init__(self, self.screen.mode()) - self._poly = None - self._creatingPoly = False - self._fillitem = self._fillpath = None - - self.name = shape - self.svg, rotation = self.screen.create_svg_turtle(self, name=shape) - self.svg.setAttribute("opacity", 0) - self._shown = False - if visible: - self.showturtle() # will ensure that turtle become visible at appropriate time - self.screen._turtles.append(self) - self.rotation_correction = rotation - # apply correction to image orientation - self._old_heading = self.heading() + self.rotation_correction - speed = self.speed() - self.speed(0) - self.left( - -self._angleOffset - ) # this will update the display to include the correction - self.speed(speed) - - def reset(self): - """Delete the turtle's drawings and restore its default values.""" - # TODO: review this and most likely revise docstring. - TNavigator.reset(self) - TPen._reset(self) - self._old_heading = self.heading() + self.rotation_correction - self.home() - self.color(_CFG["pencolor"], _CFG["fillcolor"]) - - def clear(self): - sys.stderr.write("Warning: Turtle.clear() is not implemented.\n") - - def shape(self, name=None): - """Set turtle shape to shape with given name - / return current shapename if no name is provided - """ - if name is None: - return self.name - _turtle = self._make_copy(name=name) - - visible = self.isvisible() - if visible: - self.hideturtle() - appendTo(self.screen.turtle_canvas, self.svg) - self.svg = _turtle - self.screen._turtles.append(self) - if visible: - self.showturtle() - - def clearstamp(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.clearstamp() is not implemented.\n") - - def clearstamps(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.clearstamps() is not implemented.\n") - - def onclick(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.onclick() is not implemented.\n") - - def ondrag(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.ondrag() is not implemented.\n") - - def onrelease(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.onrelease() is not implemented.\n") - - def undo(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.undo() is not implemented.\n") - - def setundobuffer(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.setundobuffer() is not implemented.\n") - - def undobufferentries(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.undobufferentries() is not implemented.\n") - - def shapesize(self, *args, **kwargs): - sys.stderr.write("Warning: Turtle.shapesize() is not implemented.\n") - - turtlesize = shapesize - - def shearfactor(self, shear=None): - sys.stderr.write("Warning: Turtle.shearfactor() is not implemented.\n") - - def settiltangle(self, angle): - sys.stderr.write("Warning: Turtle.settiltangle() is not implemented.\n") - - def tiltangle(self, angle=None): - sys.stderr.write("Warning: Turtle.tiltangle() is not implemented.\n") - - def tilt(self, angle): - sys.stderr.write("Warning: Turtle.tilt() is not implemented.\n") - - def shapetransform(self, t11=None, t12=None, t21=None, t22=None): - sys.stderr.write("Warning: Turtle.shapetransform() is not implemented.\n") - - def get_shapepoly(self): - sys.stderr.write("Warning: Turtle.get_shapepoly() is not implemented.\n") - - def _goto(self, x, y): - """Move the pen to the point end, thereby drawing a line - if pen is down. All other methods for turtle movement depend - on this one. - """ - - begin, duration, _from, _to = self.screen._drawline( - self, - ((self._x, self._y), (x, y)), - (self._pencolor, self._fillcolor), - self._pensize, - self._speed, - ) - if self._shown: - if self.screen._animate: - appendTo( - self.svg, - SVG.animateMotion( - begin=begin, dur=_CFG["min_duration"], fill="remove" - ), - ) - - appendTo( - self.svg, - SVG.animateMotion( - From="%s,%s" % _from, - to="%s,%s" % _to, - dur=duration, - begin=begin, - fill="freeze", - ), - ) - else: - self.svg.setAttribute( - "transform", - f"translate({_to[0]}, {_to[1]}) rotate({self._old_heading}, 0, 0)", - ) - - if self._fillpath is not None: - self._fillpath.append((x, y)) - self._position = (x, y) - self._x = x - self._y = y - - def _rotate(self, angle): - """Turns pen clockwise by angle.""" - angle *= self._degreesPerAU - self._angle += -self.screen.y_points_down * angle - self._rotate_image(angle) - - def _rotate_image(self, angle): - new_heading = self._old_heading - angle - - if self.isvisible(): - previous_end, new_frame_id = self.screen._new_frame() - if self._speed == 0: - duration = _CFG["min_duration"] - else: - duration = abs(angle) / (self._speed * 360) - if duration < 0.001: - duration = _CFG["min_duration"] - else: - duration = "%6.3fs" % duration - - if self.screen._animate: - appendTo( - self.svg, - SVG.animateMotion( - begin=previous_end, - dur=_CFG["min_duration"], - fill="remove", - ), - ) - appendTo( - self.svg, - SVG.animateTransform( - attributeName="transform", - Id=new_frame_id, - type="rotate", - From=f"{self._old_heading},0,0", - to=f"{new_heading},0,0", - begin=previous_end, - dur=duration, - fill="freeze", - ), - ) - else: - x, y = self.screen._convert_coordinates(self._x, self._y) - self.svg.setAttribute( - "transform", - f"translate({x}, {y}) rotate({new_heading}, 0, 0)", - ) - self._old_heading = new_heading - - def filling(self): - """Return fillstate (True if filling, False else).""" - return self._fillpath is not None - - def begin_fill(self): - """Called just before drawing a shape to be filled.""" - self._fillpath = [(self._x, self._y)] - - def end_fill(self): - """Fill the shape drawn after the call begin_fill().""" - if self.filling() and len(self._fillpath) > 2: - self.screen._drawpoly( - self._fillpath, - outline=self._pencolor, - fill=self._fillcolor, - ) - else: - print("No path to fill.") - self._fillpath = None - - def dot(self, size=None, color=None): - """Draw a filled circle with diameter size, using color.""" - if size is None: - size = max(self._pensize + 4, 2 * self._pensize) - if color is None: - color = self._pencolor - item = self.screen._dot((self._x, self._y), size, color=color) - - def _write(self, txt, align, font, color=None): - """Performs the writing for write()""" - if color is None: - color = self._pencolor - self.screen._write((self._x, self._y), txt, align, font, color) - - def write(self, arg, align="left", font=("Arial", 8, "normal"), color=None): - """Write text at the current turtle position. - - Arguments: - arg -- info, which is to be written to the TurtleScreen; it will be - converted to a string. - align (optional) -- one of the strings "left", "center" or right" - font (optional) -- a triple (fontname, fontsize, fonttype) - """ - self._write(str(arg), align.lower(), font, color=color) - - def begin_poly(self): - """Start recording the vertices of a polygon.""" - self._poly = [(self._x, self._y)] - self._creatingPoly = True - - def end_poly(self): - """Stop recording the vertices of a polygon.""" - self._creatingPoly = False - - def get_poly(self): - """Return the lastly recorded polygon.""" - # check if there is any poly? - if self._poly is not None: - return tuple(self._poly) - - def getscreen(self): - """Return the TurtleScreen object, the turtle is drawing on.""" - return self.screen - - def getturtle(self): - """Return the Turtle object itself. - - Only reasonable use: as a function to return the 'anonymous turtle' - """ - return self - - getpen = getturtle - - def _make_copy(self, name=None): - """makes a copy of the current svg turtle, but possibly using a - different shape. This copy is then ready to be inserted - into a canvas.""" - - if name is None: - name = self.name - - # We recreate a copy of the existing turtle, possibly using a different - # name/shape; we set the opacity to - # 0 since there is no specific time associated with the creation of - # such an object: we do not want to show it early. - _turtle, rotation = self.screen.create_svg_turtle(self, name=name) - _turtle.setAttribute("opacity", 0 if self.screen._animate else 1) - _turtle.setAttribute("fill", self._fillcolor) - _turtle.setAttribute("stroke", self._pencolor) - - # We use timed animations to get it with the proper location, - # orientation and appear at the desired time. - previous_end, new_frame_id = self.screen._new_frame() - x, y = self.screen._convert_coordinates(self._x, self._y) - if self.screen._animate: - appendTo( - _turtle, - SVG.animateMotion( - begin=previous_end, dur=_CFG["min_duration"], fill="remove" - ), - ) - - appendTo( - _turtle, - SVG.animateMotion( - Id=new_frame_id, - From="%s,%s" % (x, y), - to="%s,%s" % (x, y), - dur=_CFG["min_duration"], - begin=previous_end, - fill="freeze", - ), - ) - appendTo( - _turtle, - SVG.animateTransform( - attributeName="transform", - type="rotate", - From=f"{self._old_heading},0,0", - to=f"{self._old_heading},0,0", - begin=previous_end, - dur=_CFG["min_duration"], - fill="freeze", - ), - ) - - appendTo( - _turtle, - SVG.animate( - begin=previous_end, - dur=_CFG["min_duration"], - fill="freeze", - attributeName="opacity", - attributeType="XML", - From=0, - to=1, - ), - ) - else: - _turtle.setAttribute( - "transform", - f"translate({x}, {y}) rotate({self._old_heading}, 0, 0)", - ) - _turtle.setAttribute("opacity", "1") - return _turtle - - def stamp(self): - """draws a permanent copy of the turtle at its current location""" - - _turtle = self._make_copy(name=self.name) - appendTo(self.screen.canvas, _turtle) - - def clone(self): - """Create and return a clone of the turtle.""" - n = Turtle(self.name) - - attrs = dir(self) - new_dict = {} - for attr in attrs: - if isinstance(getattr(self, attr), (int, str, float)): - new_dict[attr] = getattr(self, attr) - n.__dict__.update(**new_dict) - # ensure that visible characteristics are consistent with settings - if not n._shown: - n._shown = True # otherwise, hideturtle() would have not effect - n.hideturtle() - n.left(0) - n.fd(0) - n.color(n.color()) - return n - - -Pen = Turtle - - -def done(target=None): - """ - If target_id is given, the SVG element containing the turtle output will - become a child of the HtmlElement with that id. If no element with that id - can be found, a ValueError is raised. - - If no target_id is given (the default), the document body has a new child - appended to it. - """ - Screen().show_scene() - if target: - container = target - else: - import js - - container = js.document.createElement("div") - js.document.body.appendChild(container) - container.innerHTML = svg() - - -show_scene = done -mainloop = done - - -def replay_scene(): - "Start playing an animation by 'refreshing' the canvas." - sys.stderr.write("Warning: turtle.replay_scene() is not implemented.\n") - - -def restart(): - "For Brython turtle: clears the existing drawing and canvas" - _CFG.update(_default_cfg()) - Screen().reset() - Turtle._pen = None - - -# The following functions are auto-generated. - - -def back(distance): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.back(distance) - - -def backward(distance): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.backward(distance) - - -def begin_fill(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.begin_fill() - - -def begin_poly(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.begin_poly() - - -def bk(distance): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.bk(distance) - - -def circle(radius, extent=None, steps=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.circle(radius, extent, steps) - - -def clear(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.clear() - - -def clearstamp(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.clearstamp(*args, **kwargs) - - -def clearstamps(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.clearstamps(*args, **kwargs) - - -def clone(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.clone() - - -def color(*args): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.color(*args) - - -def degrees(fullcircle=360.0): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.degrees(fullcircle) - - -def distance(x, y=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.distance(x, y) - - -def dot(size=None, color=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.dot(size, color) - - -def down(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.down() - - -def end_fill(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.end_fill() - - -def end_poly(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.end_poly() - - -def fd(distance): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.fd(distance) - - -def fillcolor(color=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.fillcolor(color) - - -def filling(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.filling() - - -def forward(distance): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.forward(distance) - - -def get_poly(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.get_poly() - - -def getpen(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.getpen() - - -def getscreen(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.getscreen() - - -def get_shapepoly(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.get_shapepoly() - - -def getturtle(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.getturtle() - - -def goto(x, y=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.goto(x, y) - - -def heading(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.heading() - - -def hideturtle(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.hideturtle() - - -def home(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.home() - - -def ht(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.ht() - - -def isdown(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.isdown() - - -def isvisible(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.isvisible() - - -def left(angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.left(angle) - - -def lt(angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.lt(angle) - - -def onclick(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.onclick(*args, **kwargs) - - -def ondrag(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.ondrag(*args, **kwargs) - - -def onrelease(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.onrelease(*args, **kwargs) - - -def pd(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pd() - - -def pen(pen=None, **pendict): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pen(pen, **pendict) - - -def pencolor(color=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pencolor(color) - - -def pendown(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pendown() - - -def pensize(width=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pensize(width) - - -def penup(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.penup() - - -def pos(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pos() - - -def position(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.position() - - -def pu(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.pu() - - -def radians(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.radians() - - -def right(angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.right(angle) - - -def reset(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.reset() - - -def resizemode(rmode=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.resizemode(rmode) - - -def rt(angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.rt(angle) - - -def seth(to_angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.seth(to_angle) - - -def setheading(to_angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.setheading(to_angle) - - -def setpos(x, y=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.setpos(x, y) - - -def setposition(x, y=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.setposition(x, y) - - -def settiltangle(angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.settiltangle(angle) - - -def setundobuffer(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.setundobuffer(*args, **kwargs) - - -def setx(x): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.setx(x) - - -def sety(y): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.sety(y) - - -def shape(name=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.shape(name) - - -def shapesize(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.shapesize(*args, **kwargs) - - -def shapetransform(t11=None, t12=None, t21=None, t22=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.shapetransform(t11, t12, t21, t22) - - -def shearfactor(shear=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.shearfactor(shear) - - -def showturtle(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.showturtle() - - -def speed(speed=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.speed(speed) - - -def st(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.st() - - -def stamp(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.stamp() - - -def tilt(angle): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.tilt(angle) - - -def tiltangle(angle=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.tiltangle(angle) - - -def towards(x, y=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.towards(x, y) - - -def turtlesize(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.turtlesize(*args, **kwargs) - - -def undo(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.undo(*args, **kwargs) - - -def undobufferentries(*args, **kwargs): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.undobufferentries(*args, **kwargs) - - -def up(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.up() - - -def width(width=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.width(width) - - -def write(arg, align="left", font=("Arial", 8, "normal"), color=None): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.write(arg, align, font, color) - - -def xcor(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.xcor() - - -def ycor(): - if Turtle._pen is None: - Turtle._pen = Turtle() - return Turtle._pen.ycor() - - -def addshape(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.addshape(*args, **kwargs) - - -def animation(onoff): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.animation(onoff) - - -def bgcolor(color=None): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.bgcolor(color) - - -def bgpic(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.bgpic(*args, **kwargs) - - -def bye(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.bye(*args, **kwargs) - - -def clearscreen(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.clearscreen(*args, **kwargs) - - -def colormode(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.colormode(*args, **kwargs) - - -def delay(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.delay(*args, **kwargs) - - -def exitonclick(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.exitonclick(*args, **kwargs) - - -def getcanvas(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.getcanvas(*args, **kwargs) - - -def getshapes(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.getshapes(*args, **kwargs) - - -def listen(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.listen(*args, **kwargs) - - -def mode(_mode=None): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.mode(_mode) - - -def numinput(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.numinput(*args, **kwargs) - - -def onkey(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.onkey(*args, **kwargs) - - -def onkeypress(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.onkeypress(*args, **kwargs) - - -def onkeyrelease(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.onkeyrelease(*args, **kwargs) - - -def onscreenclick(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.onscreenclick(*args, **kwargs) - - -def ontimer(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.ontimer(*args, **kwargs) - - -def register_shape(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.register_shape(*args, **kwargs) - - -def resetscreen(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.resetscreen(*args, **kwargs) - - -def save(file): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.save(file) - - -def screensize(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.screensize(*args, **kwargs) - - -def setup(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.setup(*args, **kwargs) - - -def setworldcoordinates(llx, lly, urx, ury): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.setworldcoordinates(llx, lly, urx, ury) - - -def svg(): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.svg() - - -def textinput(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.textinput(*args, **kwargs) - - -def title(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.title(*args, **kwargs) - - -def tracer(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.tracer(*args, **kwargs) - - -def turtles(): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.turtles() - - -def update(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.update(*args, **kwargs) - - -def window_height(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.window_height(*args, **kwargs) - - -def window_width(*args, **kwargs): - if Turtle.screen is None: - Turtle.screen = Screen() - return Turtle.screen.window_width(*args, **kwargs) - - -__all__ = [ - "addshape", - "animation", - "bgcolor", - "bgpic", - "bye", - "clearscreen", - "colormode", - "delay", - "exitonclick", - "getcanvas", - "getshapes", - "listen", - "mode", - "numinput", - "onkey", - "onkeypress", - "onkeyrelease", - "onscreenclick", - "ontimer", - "register_shape", - "resetscreen", - "save", - "screensize", - "setup", - "setworldcoordinates", - "svg", - "textinput", - "title", - "tracer", - "turtles", - "update", - "window_height", - "window_width", - "back", - "backward", - "begin_fill", - "begin_poly", - "bk", - "circle", - "clear", - "clearstamp", - "clearstamps", - "clone", - "color", - "degrees", - "distance", - "dot", - "down", - "end_fill", - "end_poly", - "fd", - "fillcolor", - "filling", - "forward", - "get_poly", - "getpen", - "getscreen", - "get_shapepoly", - "getturtle", - "goto", - "heading", - "hideturtle", - "home", - "ht", - "isdown", - "isvisible", - "left", - "lt", - "onclick", - "ondrag", - "onrelease", - "pd", - "pen", - "pencolor", - "pendown", - "pensize", - "penup", - "pos", - "position", - "pu", - "radians", - "right", - "reset", - "resizemode", - "rt", - "seth", - "setheading", - "setpos", - "setposition", - "settiltangle", - "setundobuffer", - "setx", - "sety", - "shape", - "shapesize", - "shapetransform", - "shearfactor", - "showturtle", - "speed", - "st", - "stamp", - "tilt", - "tiltangle", - "towards", - "turtlesize", - "undo", - "undobufferentries", - "up", - "width", - "write", - "xcor", - "ycor", - "done", - "mainloop", - "restart", - "replay_scene", - "Turtle", - "Screen", -] diff --git a/pyscript.core/test/multi-turtle/turtle.toml b/pyscript.core/test/multi-turtle/turtle.toml deleted file mode 100644 index 3f67bd9a..00000000 --- a/pyscript.core/test/multi-turtle/turtle.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[fetch]] -files = ["svg.py", "turtle.py"] diff --git a/pyscript.core/test/no-sw.html b/pyscript.core/test/no-sw.html deleted file mode 100644 index 74693731..00000000 --- a/pyscript.core/test/no-sw.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - python - - - - - - - - diff --git a/pyscript.core/test/order.html b/pyscript.core/test/order.html deleted file mode 100644 index 1d49c617..00000000 --- a/pyscript.core/test/order.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - python - - - - - - - - - -
-# console should show this order too
-A
-B
-A
-C
-D
- - diff --git a/pyscript.core/test/order.js b/pyscript.core/test/order.js deleted file mode 100644 index eff441e3..00000000 --- a/pyscript.core/test/order.js +++ /dev/null @@ -1,11 +0,0 @@ -const { readFileSync } = require("fs"); - -require("http") - .createServer((req, res) => { - const content = readFileSync(__dirname + req.url); - res.setHeader("Access-Control-Allow-Origin", "*"); - setTimeout(() => { - res.end(content); - }, 1000); - }) - .listen(7357); diff --git a/pyscript.core/test/package.json b/pyscript.core/test/package.json deleted file mode 100644 index 1cd945a3..00000000 --- a/pyscript.core/test/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "commonjs" -} diff --git a/pyscript.core/test/plugins/index.html b/pyscript.core/test/plugins/index.html deleted file mode 100644 index 109eee59..00000000 --- a/pyscript.core/test/plugins/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - Plugins - - - - - - - def test_click(event): - print(event.type) - - print('Hello Console!') - - - diff --git a/pyscript.core/test/plugins/lua.html b/pyscript.core/test/plugins/lua.html deleted file mode 100644 index 69e01772..00000000 --- a/pyscript.core/test/plugins/lua.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - Plugins - - - - - - - print('Hello Console!') - - - diff --git a/pyscript.core/test/plugins/py-script.html b/pyscript.core/test/plugins/py-script.html deleted file mode 100644 index c436ec35..00000000 --- a/pyscript.core/test/plugins/py-script.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - PyScript Next - - - - -
- - Something something about something ... -
- - [[fetch]] - from = "../" - to_folder = "./" - files = ["a.py", "b.py"] - - - - - import js - import a, b - print('Hello Console!') - js.console.log(a.x, b.x) - 'Hello Web!' - - - # note the target is this element itself - display('second <py-script>') - - - from xworker import XWorker - # note this is late to the party simply because - # pyodide needs to be bootstrapped in the Worker too - XWorker('../a.py') - 'OK' - - - - - - - diff --git a/pyscript.core/test/print-a.py b/pyscript.core/test/print-a.py deleted file mode 100644 index c853ae9a..00000000 --- a/pyscript.core/test/print-a.py +++ /dev/null @@ -1 +0,0 @@ -print("A") diff --git a/pyscript.core/test/py-events.html b/pyscript.core/test/py-events.html deleted file mode 100644 index b0999a0c..00000000 --- a/pyscript.core/test/py-events.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - python events - - - - - - - - - - - - diff --git a/pyscript.core/test/py-events.py b/pyscript.core/test/py-events.py deleted file mode 100644 index f35fdc58..00000000 --- a/pyscript.core/test/py-events.py +++ /dev/null @@ -1,13 +0,0 @@ -def print_version(event): - import sys - - print(event.type) - print(sys.version) - - -class Printer: - def version(self, event): - print_version(event) - - -printer = Printer() diff --git a/pyscript.core/test/pyscript.html b/pyscript.core/test/pyscript.html deleted file mode 100644 index 84c5ccc4..00000000 --- a/pyscript.core/test/pyscript.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - py-script - - - - py-script - - - - import sys - display(sys.version, target="target") - -
- - diff --git a/pyscript.core/test/remote.html b/pyscript.core/test/remote.html deleted file mode 100644 index d0b34b08..00000000 --- a/pyscript.core/test/remote.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - python - - - - - - - - diff --git a/pyscript.core/test/ruby.html b/pyscript.core/test/ruby.html deleted file mode 100644 index 7d6635e6..00000000 --- a/pyscript.core/test/ruby.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Ruby - - - - - - - - - diff --git a/pyscript.core/test/shadow-dom.html b/pyscript.core/test/shadow-dom.html deleted file mode 100644 index 0c351203..00000000 --- a/pyscript.core/test/shadow-dom.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - python - - - - - - - - - - - diff --git a/pyscript.core/test/style.css b/pyscript.core/test/style.css deleted file mode 100644 index 69b2e77a..00000000 --- a/pyscript.core/test/style.css +++ /dev/null @@ -1,9 +0,0 @@ -html { - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", - Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", - sans-serif; -} - -:root python-script { - display: block; -} diff --git a/pyscript.core/test/table.html b/pyscript.core/test/table.html deleted file mode 100644 index 5479c6e1..00000000 --- a/pyscript.core/test/table.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - python - - - - - - - - - - -
check edge cases
- - diff --git a/pyscript.core/test/test.html b/pyscript.core/test/test.html deleted file mode 100644 index e9e0b2e6..00000000 --- a/pyscript.core/test/test.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - python - - - - - - - - - - - - - - - - - - diff --git a/pyscript.core/test/three.py b/pyscript.core/test/three.py deleted file mode 100644 index feb8fd7b..00000000 --- a/pyscript.core/test/three.py +++ /dev/null @@ -1,3 +0,0 @@ -import js - -js.document.currentScript.target.textContent = 1 + 2 diff --git a/pyscript.core/test/wasmoon.html b/pyscript.core/test/wasmoon.html deleted file mode 100644 index f828a387..00000000 --- a/pyscript.core/test/wasmoon.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - lua - - - - - - - - - diff --git a/pyscript.core/test/worker/index.html b/pyscript.core/test/worker/index.html deleted file mode 100644 index 23d448e3..00000000 --- a/pyscript.core/test/worker/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - python workers - - - -
See console ➡️
- - - - - - - - - - - - - - - - diff --git a/pyscript.core/test/worker/input.html b/pyscript.core/test/worker/input.html deleted file mode 100644 index 08ce0a18..00000000 --- a/pyscript.core/test/worker/input.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - python workers - - - - - - - - - diff --git a/pyscript.core/test/worker/input.lua b/pyscript.core/test/worker/input.lua deleted file mode 100644 index a341e2d0..00000000 --- a/pyscript.core/test/worker/input.lua +++ /dev/null @@ -1,2 +0,0 @@ -print('What is 2 + 3?') -print('Answer: ' .. xworker.sync.input('What is 2 + 3?')) diff --git a/pyscript.core/test/worker/input.py b/pyscript.core/test/worker/input.py deleted file mode 100644 index 7ae74ca7..00000000 --- a/pyscript.core/test/worker/input.py +++ /dev/null @@ -1,4 +0,0 @@ -from xworker import xworker - -print("What is 2 + 3?") -print("Answer: " + xworker.sync.input("What is 2 + 3?")) diff --git a/pyscript.core/test/worker/input.rb b/pyscript.core/test/worker/input.rb deleted file mode 100644 index d3e9966c..00000000 --- a/pyscript.core/test/worker/input.rb +++ /dev/null @@ -1,2 +0,0 @@ -puts "What is 2 + 3?" -puts $xworker[:sync].call("input", "What is 2 + 3?") diff --git a/pyscript.core/test/worker/worker.lua b/pyscript.core/test/worker/worker.lua deleted file mode 100644 index 0f89e387..00000000 --- a/pyscript.core/test/worker/worker.lua +++ /dev/null @@ -1,6 +0,0 @@ -function on_message(event) - print(event.data) - xworker.postMessage('Lua: Hello MicroPython 👋') -end - -xworker.onmessage = on_message diff --git a/pyscript.core/test/worker/worker.py b/pyscript.core/test/worker/worker.py deleted file mode 100644 index d33887f6..00000000 --- a/pyscript.core/test/worker/worker.py +++ /dev/null @@ -1,12 +0,0 @@ -import re -import a, b -from xworker import xworker - - -def on_message(event): - print(event.data) - foreign = re.search("^[^:]+", event.data).group(0) - xworker.postMessage("Python: Hello " + foreign + " 👋") - - -xworker.onmessage = on_message diff --git a/pyscript.core/test/worker/worker.rb b/pyscript.core/test/worker/worker.rb deleted file mode 100644 index 58ed4ef5..00000000 --- a/pyscript.core/test/worker/worker.rb +++ /dev/null @@ -1,6 +0,0 @@ -def on_message(event) - puts event[:data] - $xworker.call('postMessage', 'Ruby: Hello MicroPython 👋') -end - -$xworker[:onmessage] = -> (event) { on_message event }