Skip to content

Commit

Permalink
Add an HTML dialect (#121)
Browse files Browse the repository at this point in the history
* Add an HTML dialect

* Uses Pyalect and htm to register an HTML transpiler. Adds docs
  to this effect.

* test HTML dialect
  • Loading branch information
rmorshea committed Nov 15, 2019
1 parent 487102a commit 20be27f
Show file tree
Hide file tree
Showing 23 changed files with 730 additions and 211 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -17,6 +17,9 @@ dist
__pycache__/
*.py[cod]

# --- PyEnv ---
.python-version

# -- Python Tests ---
*.coverage
*.pytest_cache
Expand Down
14 changes: 8 additions & 6 deletions .pre-commit-config.yaml
Expand Up @@ -5,13 +5,15 @@ repos:
- id: black
language_version: python3.6
- repo: https://github.com/PyCQA/flake8
rev: 3.7.7
rev: 3.7.9
hooks:
- id: flake8
args: ["--config=src/py/.flake8", "--exclude=src/js"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.720
- repo: local
hooks:
- id: mypy
args: ["--config-file", "src/py/mypy.ini", "--strict"]
exclude: src/py/tests/*|setup.py
- id: mypy
name: mypy
language: system
entry: mypy --config-file src/py/mypy.ini
types: [python]
exclude: src/py/tests/*|setup.py
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -2,3 +2,4 @@
-r requirements/dev.txt
-r requirements/docs.txt
-r requirements/test.txt
-r requirements/extras.txt
10 changes: 10 additions & 0 deletions requirements/extras.txt
@@ -0,0 +1,10 @@
# extra=matplotlib
matplotlib

# extra=vdom
vdom

# extra=dialect
htm
pyalect
tagged
2 changes: 1 addition & 1 deletion requirements/test.txt
@@ -1,4 +1,4 @@
pytest
pytest==5.2.2
pytest-asyncio
pytest-cov
mypy
Expand Down
2 changes: 1 addition & 1 deletion scripts/test.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -e

pytest src/py/tests --headless --cov=idom --cov-fail-under=79
pytest src/py/tests --headless --cov=idom --cov-fail-under=82
black --verbose --check src/py
flake8 src/py
mypy src/py/idom --config-file=src/py/mypy.ini
Expand Down
24 changes: 18 additions & 6 deletions setup.py
Expand Up @@ -34,6 +34,7 @@
"platforms": "Linux, Mac OS X, Windows",
"keywords": ["interactive", "widgets", "DOM", "React"],
"include_package_data": True,
"zip_safe": False,
}


Expand All @@ -49,12 +50,23 @@
requirements.append(line)
package["install_requires"] = requirements

package["extras_require"] = {"matplotlib": ["matplotlib"], "vdom": ["vdom"]}

all_extras = set()
for extras in package["extras_require"].values():
all_extras.update(extras)
package["extras_require"]["all"] = all_extras
_current_extra_section = None
extra_requirements = {"all": []}
extra_requirements_path = os.path.join(here, "requirements", "extras.txt")
with open(extra_requirements_path, "r") as f:
for line in map(str.strip, f):
if line.startswith("#") and line[1:].strip().startswith("extra="):
_current_extra_section = line.split("=", 1)[1]
if _current_extra_section == "all":
raise ValueError("%r uses the reserved extra name 'all'")
extra_requirements[_current_extra_section] = []
elif _current_extra_section is not None:
extra_requirements[_current_extra_section].append(line)
extra_requirements["all"].append(line)
elif line:
msg = "No '# extra=<name>' header before requirements in %r"
raise ValueError(msg % extra_requirements_path)
package["extras_require"] = extra_requirements


# -----------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions src/py/docs/source/api.rst
Expand Up @@ -77,3 +77,8 @@ HTML Nodes
.. automodule:: idom.widgets.html
:members:
:undoc-members:

Dialect
-------

.. automodule:: idom.dialect
2 changes: 1 addition & 1 deletion src/py/docs/source/concepts.rst
Expand Up @@ -313,7 +313,7 @@ send them over the wire. To do that you need an
builtin subclass that relies on :mod:`sanic`, an async enabled web server. In principle
though, the base server class is capable of working with any other async enabled server
framework. Potential candidates range from newer frameworks like
`vibora <https://vibora.io/>`__ and
`vibora <https://vibora.io/>`__, `starlette <https://www.starlette.io/>`__, and
`aiohttp <https://aiohttp.readthedocs.io/en/stable/>`__ to older ones that are
starting to add support for asyncio like
`tornado <https://www.tornadoweb.org/en/stable/asyncio.html>`__.
Expand Down
8 changes: 7 additions & 1 deletion src/py/docs/source/conf.py
Expand Up @@ -166,6 +166,9 @@
)
]

# -- Options for Sphinx-Autodoc-Typehints output -------------------------------------------------

set_type_checking_flag = True

# -- Options for Epub output -------------------------------------------------

Expand All @@ -190,7 +193,10 @@
# -- Options for intersphinx extension ---------------------------------------

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
intersphinx_mapping = {
"https://docs.python.org/": None,
"https://pyalect.readthedocs.io/en/latest": None,
}

# -- Options for todo extension ----------------------------------------------

Expand Down
119 changes: 119 additions & 0 deletions src/py/docs/source/extras.rst
@@ -0,0 +1,119 @@
Extra Features
==============

Optionally installable features of IDOM.

.. contents::
:local:
:depth: 1

To install **all** features simply run

.. code-block::
pip install idom[all]
Compatibility with Nteract's VDOM
---------------------------------

As described in the :ref:`Specifications` section, IDOM implements a specification for
:term:`VDOM` defined by `Nteract <https://nteract.io>`_'s
`vdom <https://github.com/nteract/vdom>`_ package. As a result IDOM is compatible with
all its features (though VDOM does not support all features of IDOM yet). If you've
already installed ``vdom`` then you're all set! If you'd like to try it out just run:

.. code-block:: bash
pip install idom[vdom]
Python Language Extension
-------------------------

IDOM includes a transpiler for writing JSX-like syntax in a normal Python file!

.. code-block:: python
# dialect=html
from idom import html
message = "hello world!"
attrs = {"height": "10px", "width": "10px"}
model = html(f"<div ...{attrs}><p>{message}</p></div>")
assert model == {
"tagName": "div",
"attributes": {"height": "10px", "width": "10px"},
"children": [{"tagName": "p", "children": ["hello world!"]}],
}
With Jupyter and IPython support:

.. code-block:: python
%%dialect html
from idom import html
assert html(f"<div/>") == {"tagName": "div"}
That you can install with ``pip``:

.. code-block::
pip install idom[dialect]
Usage
.....

1. Import ``idom`` in your application's ``entrypoint.py``

2. Import ``your_module.py`` with a ``# dialect=html`` header comment.

3. Inside ``your_module.py`` import ``html`` from ``idom``

4. Run ``python entrypoint.py`` from your console.

So here's the files you should have set up:

.. code-block:: text
project
|- entrypoint.py
|- your_module.py
The contents of ``entrypoint.py`` should contain:

.. code-block::
import idom # this needs to be first!
import your_module
While ``your_module.py`` should contain the following:

.. code-block::
# dialect=html
from idom import html
assert html("<div/>") == {"tagName": "div"}
And that's it!


How It Works
............

Once ``idom`` has been imported at your application's entrypoint, any following modules
imported with a ``# dialect=html`` header comment get transpiled just before they're
executed. This is accomplished by using Pyalect_ to hook a transpiler into Pythons
import system. The :class:`~idom.dialect.HtmlDialectTranspiler` which implements
Pyalect_'s :class:`~pyalect.dialect.Transpiler` interface using some tooling from
htm.js_.


.. Links
.. =====
.. _Pyalect: https://pyalect.readthedocs.io/en/latest/
.. _htm.py: https://github.com/jviide/htm.py
.. _htm.js: https://github.com/developit/htm
1 change: 1 addition & 0 deletions src/py/docs/source/index.rst
Expand Up @@ -15,6 +15,7 @@ Libraries for defining and controlling interactive webpages with Python
concepts
examples
specs
extras
glossary
api

Expand Down
15 changes: 15 additions & 0 deletions src/py/docs/source/install.rst
Expand Up @@ -7,6 +7,17 @@ IDOM is on PyPI_ so all you need to do is use pip_ to install a **stable** versi
pip install idom
To setup up extra features:

.. code-block:: bash
# all extra features
pip install idom[all]
.. note::

To install *specific* features see :ref:`Extra Features`

To get a **pre-release**:

.. code-block:: bash
Expand All @@ -17,6 +28,10 @@ To get a **pre-release**:

Pre-releases may be unstable or subject to change

.. contents::
:local:
:depth: 1


Development Version
-------------------
Expand Down
16 changes: 15 additions & 1 deletion src/py/idom/__init__.py
Expand Up @@ -7,20 +7,34 @@
from .core.layout import Layout
from .core.vdom import vdom

from .widgets import html
from .widgets.html import html
from .widgets.common import hotswap, Eval, Import
from .widgets.display import display
from .widgets.inputs import Input
from .widgets.images import Image

from .tools import Var, html_to_vdom

try:
import pyalect
import tagged
import htm
except ImportError:
pass
else:
from . import dialect

del pyalect
del tagged
del htm


__all__ = [
"element",
"Element",
"event",
"Events",
"dialect",
"html",
"Layout",
"server",
Expand Down
6 changes: 2 additions & 4 deletions src/py/idom/core/vdom.py
Expand Up @@ -32,7 +32,6 @@ def vdom(
import_source: _ImportSourceArg = None,
) -> VdomDict:
"""A helper function for creating VDOM dictionaries.
Parameters:
tag:
The type of element (e.g. 'div', 'h1', 'img')
Expand Down Expand Up @@ -76,7 +75,7 @@ def vdom(
return model


class _VdomConstructor(Protocol):
class VdomDictConstructor(Protocol):
def __call__(
self,
*args: _AttributesAndChildrenArg,
Expand All @@ -86,9 +85,8 @@ def __call__(
...


def make_vdom_constructor(tag: str, allow_children: bool = True) -> _VdomConstructor:
def make_vdom_constructor(tag: str, allow_children: bool = True) -> VdomDictConstructor:
"""Return a constructor for VDOM dictionaries with the given tag name.
The resulting callable will have the same interface as :func:`vdom` but without its
first ``tag`` argument.
"""
Expand Down

0 comments on commit 20be27f

Please sign in to comment.