Skip to content

Commit 73e1d35

Browse files
rename internal BirdNode attrs to params (#53)
* rename internal `BirdNode` attrs to params in preparation of adding props * fix mypy
1 parent a66059c commit 73e1d35

File tree

5 files changed

+151
-62
lines changed

5 files changed

+151
-62
lines changed

src/django_bird/components/attrs.py

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
5+
from django import template
6+
from django.template.context import Context
7+
from django.utils.safestring import SafeString
8+
from django.utils.safestring import mark_safe
9+
10+
from django_bird._typing import TagBits
11+
12+
13+
@dataclass
14+
class Param:
15+
name: str
16+
value: str | bool | None
17+
18+
def render(self, context: Context) -> str:
19+
if self.value is None:
20+
return ""
21+
if isinstance(self.value, bool) and self.value:
22+
return self.name
23+
try:
24+
value = template.Variable(str(self.value)).resolve(context)
25+
except template.VariableDoesNotExist:
26+
value = self.value
27+
return f'{self.name}="{value}"'
28+
29+
@classmethod
30+
def from_bit(cls, bit: str):
31+
value: str | bool
32+
if "=" in bit:
33+
name, value = bit.split("=", 1)
34+
value = value.strip("'\"")
35+
else:
36+
name, value = bit, True
37+
return cls(name, value)
38+
39+
40+
@dataclass
41+
class Params:
42+
attrs: list[Param]
43+
44+
def render_attrs(self, context: Context) -> SafeString:
45+
rendered = " ".join(attr.render(context) for attr in self.attrs)
46+
return mark_safe(rendered)
47+
48+
@classmethod
49+
def from_bits(cls, bits: TagBits):
50+
params: list[Param] = []
51+
for bit in bits:
52+
param = Param.from_bit(bit)
53+
params.append(param)
54+
return cls(attrs=params)

src/django_bird/templatetags/tags/bird.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from django_bird._typing import TagBits
1515
from django_bird._typing import override
16-
from django_bird.components.attrs import Attrs
16+
from django_bird.components.params import Params
1717
from django_bird.components.slots import DEFAULT_SLOT
1818
from django_bird.components.slots import Slots
1919
from django_bird.components.templates import get_template_names
@@ -25,9 +25,9 @@
2525
def do_bird(parser: Parser, token: Token) -> BirdNode:
2626
bits = token.split_contents()
2727
name = parse_bird_name(bits)
28-
attrs = parse_attrs(bits)
29-
nodelist = parse_nodelist(attrs, parser)
30-
return BirdNode(name, attrs, nodelist)
28+
params = Params.from_bits(bits[2:])
29+
nodelist = parse_nodelist(bits, parser)
30+
return BirdNode(name, params, nodelist)
3131

3232

3333
def parse_bird_name(bits: TagBits) -> str:
@@ -41,14 +41,10 @@ def parse_bird_name(bits: TagBits) -> str:
4141
return bits[1].strip("'\"")
4242

4343

44-
def parse_attrs(bits: TagBits) -> TagBits:
45-
return bits[2:]
46-
47-
48-
def parse_nodelist(attrs: TagBits, parser: Parser) -> NodeList | None:
44+
def parse_nodelist(bits: TagBits, parser: Parser) -> NodeList | None:
4945
# self-closing tag
5046
# {% bird name / %}
51-
if len(attrs) > 0 and attrs[-1] == "/":
47+
if len(bits) > 0 and bits[-1] == "/":
5248
nodelist = None
5349
else:
5450
nodelist = parser.parse((END_TAG,))
@@ -57,9 +53,9 @@ def parse_nodelist(attrs: TagBits, parser: Parser) -> NodeList | None:
5753

5854

5955
class BirdNode(template.Node):
60-
def __init__(self, name: str, attrs: list[str], nodelist: NodeList | None) -> None:
56+
def __init__(self, name: str, params: Params, nodelist: NodeList | None) -> None:
6157
self.name = name
62-
self.attrs = attrs
58+
self.params = params
6359
self.nodelist = nodelist
6460

6561
@override
@@ -78,11 +74,11 @@ def get_component_name(self, context: Context) -> str:
7874
return name
7975

8076
def get_component_context_data(self, context: Context) -> dict[str, Any]:
81-
attrs = Attrs.parse(self.attrs, context)
77+
attrs = self.params.render_attrs(context)
8278
slots = Slots.collect(self.nodelist, context).render()
8379
default_slot = slots.get(DEFAULT_SLOT) or context.get("slot")
8480
return {
85-
"attrs": attrs.flatten(),
81+
"attrs": attrs,
8682
"slot": default_slot,
8783
"slots": slots,
8884
}

tests/components/test_params.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from django_bird.components.params import Param
6+
from django_bird.components.params import Params
7+
8+
9+
class TestParam:
10+
@pytest.mark.parametrize(
11+
"param,context,expected",
12+
[
13+
(Param(name="class", value="btn"), {}, 'class="btn"'),
14+
(Param(name="class", value="btn"), {"btn": "blue"}, 'class="blue"'),
15+
(Param(name="disabled", value=True), {}, "disabled"),
16+
(Param(name="disabled", value=None), {}, ""),
17+
],
18+
)
19+
def test_render(self, param, context, expected):
20+
assert param.render(context) == expected
21+
22+
@pytest.mark.parametrize(
23+
"bit,expected",
24+
[
25+
("class='btn'", Param(name="class", value="btn")),
26+
('class="btn"', Param(name="class", value="btn")),
27+
("disabled", Param(name="disabled", value=True)),
28+
],
29+
)
30+
def test_from_bit(self, bit, expected):
31+
assert Param.from_bit(bit) == expected
32+
33+
34+
class TestParams:
35+
@pytest.mark.parametrize(
36+
"params,context,expected",
37+
[
38+
(Params(attrs=[Param(name="class", value="btn")]), {}, 'class="btn"'),
39+
(
40+
Params(attrs=[Param(name="class", value="btn")]),
41+
{"btn": "blue"},
42+
'class="blue"',
43+
),
44+
(Params(attrs=[Param(name="disabled", value=True)]), {}, "disabled"),
45+
(
46+
Params(
47+
attrs=[
48+
Param(name="class", value="btn"),
49+
Param(name="disabled", value=True),
50+
]
51+
),
52+
{},
53+
'class="btn" disabled',
54+
),
55+
],
56+
)
57+
def test_render_attrs(self, params, context, expected):
58+
assert params.render_attrs(context) == expected
59+
60+
@pytest.mark.parametrize(
61+
"bits,expected",
62+
[
63+
(["class='btn'"], Params(attrs=[Param(name="class", value="btn")])),
64+
(['class="btn"'], Params(attrs=[Param(name="class", value="btn")])),
65+
(["disabled"], Params(attrs=[Param(name="disabled", value=True)])),
66+
],
67+
)
68+
def test_from_bits(self, bits, expected):
69+
assert Params.from_bits(bits) == expected

tests/templatetags/test_bird.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from django.template.base import TokenType
1212
from django.template.exceptions import TemplateSyntaxError
1313

14+
from django_bird.components.params import Param
15+
from django_bird.components.params import Params
1416
from django_bird.templatetags.tags.bird import END_TAG
1517
from django_bird.templatetags.tags.bird import TAG
1618
from django_bird.templatetags.tags.bird import BirdNode
@@ -44,20 +46,28 @@ def test_missing_argument(self):
4446
do_bird(parser, token)
4547

4648
@pytest.mark.parametrize(
47-
"attrs,expected_attrs",
49+
"params,expected_params",
4850
[
49-
("class='btn'", ["class='btn'"]),
50-
("class='btn' id='my-btn'", ["class='btn'", "id='my-btn'"]),
51-
("disabled", ["disabled"]),
51+
("class='btn'", Params(attrs=[Param(name="class", value="btn")])),
52+
(
53+
"class='btn' id='my-btn'",
54+
Params(
55+
attrs=[
56+
Param(name="class", value="btn"),
57+
Param(name="id", value="my-btn"),
58+
]
59+
),
60+
),
61+
("disabled", Params(attrs=[Param(name="disabled", value=True)])),
5262
],
5363
)
54-
def test_node_attrs(self, attrs, expected_attrs):
55-
token = Token(TokenType.BLOCK, f"{TAG} button {attrs}")
64+
def test_node_params(self, params, expected_params):
65+
token = Token(TokenType.BLOCK, f"{TAG} button {params}")
5666
parser = Parser(
5767
[Token(TokenType.BLOCK, END_TAG)],
5868
)
5969
node = do_bird(parser, token)
60-
assert node.attrs == expected_attrs
70+
assert node.params == expected_params
6171

6272
@pytest.mark.parametrize(
6373
"component,template,context,expected",
@@ -274,7 +284,7 @@ class TestNode:
274284
)
275285
def test_get_component_name(self, name, context, expected, create_bird_template):
276286
create_bird_template(name=name, content="<button>Click me</button>")
277-
node = BirdNode(name=name, attrs=[], nodelist=None)
287+
node = BirdNode(name=name, params=Params([]), nodelist=None)
278288

279289
component_name = node.get_component_name(context=Context(context))
280290

0 commit comments

Comments
 (0)