Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow conditional rendering based on bool. #41

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -55,14 +59,33 @@ and safe to directly insert variable data via f-strings:
<div></div>

>>> error = 'Enter a valid email address.'
>>> print(div[error and b[error]])
>>> print(div[has_error and b[error_message]])
<div><b>Enter a valid email address.</b></div>

# Inline if/else can also be used:
>>> print(div[b[error] if error else None])
<div><b>Enter a valid email address.</b></div>
```

```pycon title="Conditional rendering based on a bool variable"
>>> from htpy import div
>>> is_happy = True
>>> print(div[is_happy and "😄"])
<div>😄</div>

>>> is_sad = False
>>> print(div[is_sad and "😔"])
pelme marked this conversation as resolved.
Show resolved Hide resolved
<div></div>

>>> is_allowed = True
>>> print(div[is_allowed or "Access denied!"])
<div></div>

>>> is_allowed = False
>>> print(div[is_allowed or "Access denied!"])
<div>Access denied</div>
```

### Loops / Iterating Over Children

You can pass a list, tuple or generator to generate multiple children:
Expand Down
10 changes: 9 additions & 1 deletion htpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__"):
Expand Down Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions tests/test_children.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ def test_custom_element() -> None:
assert str(el) == "<my-custom-element></my-custom-element>"


def test_ignore_none() -> None:
assert str(div[None]) == "<div></div>"
@pytest.mark.parametrize("ignored_value", [None, True, False])
def test_ignored(ignored_value: Any) -> None:
assert str(div[ignored_value]) == "<div></div>"


def test_iter() -> None:
Expand Down Expand Up @@ -197,7 +198,7 @@ def test_callable_in_generator() -> None:
assert str(div[((lambda: "hi") for _ in range(1))]) == "<div>hi</div>"


@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])