diff --git a/docs/usage.md b/docs/usage.md index 90b517a..e07cf9f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -43,9 +43,13 @@ and safe to directly insert variable data via f-strings: ### Conditional Rendering -`None` will not render anything. This can be useful to conditionally render some content. +`True`, `False` and `None` will not render anything. Python's `and` and `or` +operators will +[short-circuit](https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not). +You can use this to conditionally render content with inline `and` and +`or`. -```pycon title="Conditional rendering" +```pycon title="Conditional rendering with a value that may be None" >>> from htpy import div, b >>> error = None @@ -55,7 +59,7 @@ and safe to directly insert variable data via f-strings:
>>> error = 'Enter a valid email address.' ->>> print(div[error and b[error]]) +>>> print(div[has_error and b[error_message]])
Enter a valid email address.
# Inline if/else can also be used: @@ -63,6 +67,25 @@ and safe to directly insert variable data via f-strings:
Enter a valid email address.
``` +```pycon title="Conditional rendering based on a bool variable" +>>> from htpy import div +>>> is_happy = True +>>> print(div[is_happy and "😄"]) +
😄
+ +>>> is_sad = False +>>> print(div[is_sad and "😔"]) +
+ +>>> is_allowed = True +>>> print(div[is_allowed or "Access denied!"]) +
+ +>>> is_allowed = False +>>> print(div[is_allowed or "Access denied!"]) +
Access denied
+``` + ### Loops / Iterating Over Children You can pass a list, tuple or generator to generate multiple children: diff --git a/htpy/__init__.py b/htpy/__init__.py index b59fecf..bb7d6a3 100644 --- a/htpy/__init__.py +++ b/htpy/__init__.py @@ -110,6 +110,12 @@ def iter_node(x: Node) -> Iterator[str]: if x is None: return + if x is True: + return + + if x is False: + return + if isinstance(x, BaseElement): yield from x elif isinstance(x, str) or hasattr(x, "__html__"): @@ -227,7 +233,9 @@ def __html__(self) -> str: ... _ClassNamesDict: TypeAlias = dict[str, bool] _ClassNames: TypeAlias = Iterable[str | None | bool | _ClassNamesDict] | _ClassNamesDict -Node: TypeAlias = None | str | BaseElement | _HasHtml | Iterable["Node"] | Callable[[], "Node"] +Node: TypeAlias = ( + None | bool | str | BaseElement | _HasHtml | Iterable["Node"] | Callable[[], "Node"] +) Attribute: TypeAlias = None | bool | str | _HasHtml | _ClassNames diff --git a/tests/test_children.py b/tests/test_children.py index f2e574a..1c96364 100644 --- a/tests/test_children.py +++ b/tests/test_children.py @@ -104,8 +104,9 @@ def test_custom_element() -> None: assert str(el) == "" -def test_ignore_none() -> None: - assert str(div[None]) == "
" +@pytest.mark.parametrize("ignored_value", [None, True, False]) +def test_ignored(ignored_value: Any) -> None: + assert str(div[ignored_value]) == "
" def test_iter() -> None: @@ -197,7 +198,7 @@ def test_callable_in_generator() -> None: assert str(div[((lambda: "hi") for _ in range(1))]) == "
hi
" -@pytest.mark.parametrize("not_a_child", [1234, True, False, b"foo", object(), object]) +@pytest.mark.parametrize("not_a_child", [1234, b"foo", object(), object, 1, 0]) def test_invalid_child(not_a_child: Any) -> None: with pytest.raises(ValueError, match="is not a valid child element"): str(div[not_a_child])