diff --git a/README.md b/README.md index c659630..e62702a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Python Hiccup -This project started out as a fun challenge, and now evolving into something that could be useful. - -Current status: _experimental_ +Python Hiccup is a library for representing HTML using plain Python data structures. [![CircleCI](https://dl.circleci.com/status-badge/img/gh/DavidVujic/python-hiccup/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/DavidVujic/python-hiccup/tree/main) @@ -14,6 +12,8 @@ Current status: _experimental_ This is a Python implementation of the Hiccup syntax. Python Hiccup is a library for representing HTML in Python. Using `list` or `tuple` to represent HTML elements, and `dict` to represent the element attributes. +_This project started out as a fun coding challenge, and now evolving into something useful for Python Dev teams._ + ## Usage Create server side HTML using plain Python data structures. This library should also be possible to combine with PyScript, but I haven't tested that out yet. diff --git a/pyproject.toml b/pyproject.toml index d472fae..321a573 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "python-hiccup" -version = "0.2.0" -description = "Add your description here" +version = "0.3.0" +description = "Python Hiccup is a library for representing HTML using plain Python data structures" readme = "README.md" requires-python = ">=3.10" dependencies = [] diff --git a/src/python_hiccup/html/core.py b/src/python_hiccup/html/core.py index b58dc26..2ee9431 100644 --- a/src/python_hiccup/html/core.py +++ b/src/python_hiccup/html/core.py @@ -5,7 +5,7 @@ from collections.abc import Mapping, Sequence from functools import reduce -from python_hiccup.transform import transform +from python_hiccup.transform import CONTENT_TAG, transform def _element_allows_raw_content(element: str) -> bool: @@ -56,23 +56,28 @@ def _suffix(element_data: str) -> str: return "" if any(s in normalized for s in specials) else " /" -def _to_html(tag: Mapping) -> list: +def _is_content(element: str) -> bool: + return element == CONTENT_TAG + + +def _to_html(tag: Mapping, parent: str = "") -> list: element = next(iter(tag.keys())) child = next(iter(tag.values())) + if _is_content(element): + return [_escape(str(child), parent)] + attributes = reduce(_to_attributes, tag.get("attributes", []), "") bool_attributes = reduce(_to_bool_attributes, tag.get("boolean_attributes", []), "") element_attributes = attributes + bool_attributes - content = [_escape(str(c), element) for c in tag.get("content", [])] - - matrix = [_to_html(c) for c in child] + matrix = [_to_html(c, element) for c in child] flattened: list = reduce(operator.iadd, matrix, []) begin = f"{element}{element_attributes}" if element_attributes else element - if flattened or content: - return [f"<{begin}>", *flattened, *content, f""] + if flattened: + return [f"<{begin}>", *flattened, f""] if _closing_tag(element): return [f"<{begin}>", f""] diff --git a/src/python_hiccup/transform/__init__.py b/src/python_hiccup/transform/__init__.py index f040478..328b5e2 100644 --- a/src/python_hiccup/transform/__init__.py +++ b/src/python_hiccup/transform/__init__.py @@ -1,5 +1,5 @@ """Transform Hiccup syntax.""" -from python_hiccup.transform.core import transform +from python_hiccup.transform.core import CONTENT_TAG, transform -__all__ = ["transform"] +__all__ = ["CONTENT_TAG", "transform"] diff --git a/src/python_hiccup/transform/core.py b/src/python_hiccup/transform/core.py index 6587664..1561864 100644 --- a/src/python_hiccup/transform/core.py +++ b/src/python_hiccup/transform/core.py @@ -10,7 +10,8 @@ ATTRIBUTES = "attributes" BOOLEAN_ATTRIBUTES = "boolean_attributes" CHILDREN = "children" -CONTENT = "content" + +CONTENT_TAG = "<::HICCUP_CONTENT::>" def _is_attribute(item: Item) -> bool: @@ -25,12 +26,6 @@ def _is_child(item: Item) -> bool: return isinstance(item, list | tuple) -def _is_content(item: Item) -> bool: - pipeline = [_is_attribute, _is_boolean_attribute, _is_child] - - return not any(fn(item) for fn in pipeline) - - def _is_sibling(item: Item) -> bool: return _is_child(item) @@ -40,8 +35,6 @@ def _key_for_group(item: Item) -> str: return ATTRIBUTES if _is_boolean_attribute(item): return BOOLEAN_ATTRIBUTES - if _is_content(item): - return CONTENT return CHILDREN @@ -67,6 +60,9 @@ def _extract_from_tag(tag: str) -> tuple[str, dict]: def _transform_tags(tags: Sequence) -> dict: + if not isinstance(tags, list | tuple): + return {CONTENT_TAG: tags} + first, *rest = tags element, extracted = _extract_from_tag(first) diff --git a/test/test_render_html.py b/test/test_render_html.py index a172e10..dedfe5c 100644 --- a/test/test_render_html.py +++ b/test/test_render_html.py @@ -132,6 +132,13 @@ def test_generates_an_element_with_children() -> None: def test_allows_numeric_values_in_content() -> None: """Assert that numeric values are allowed as the content of an element.""" - data = ["ul", ["li", 1]] + data = ["ul", ["li", 1], ["li", 2.2]] - assert render(data) == "" + assert render(data) == "" + + +def test_order_of_items() -> None: + """Assert that items of different types are ordered as expected.""" + data = ["h1", "some ", ["span.pys", ""]] + + assert render(data) == '

some <py>

' diff --git a/uv.lock b/uv.lock index ac3b866..5b5447f 100644 --- a/uv.lock +++ b/uv.lock @@ -103,7 +103,7 @@ wheels = [ [[package]] name = "python-hiccup" -version = "0.2.0" +version = "0.3.0" source = { editable = "." } [package.dev-dependencies]