Skip to content

Commit 8f2761c

Browse files
Implement spacing rules
1 parent b57d798 commit 8f2761c

File tree

4 files changed

+168
-18
lines changed

4 files changed

+168
-18
lines changed

pyhtml/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@
297297
from .__tag_base import Tag, create_tag, SelfClosingTag, WhitespaceSensitiveTag
298298

299299
from .__tags import input_, object_
300+
from .__render_options import Options
300301

301302
__all__ = [
302303
'Tag',
@@ -307,6 +308,7 @@
307308
'Comment',
308309
'input_',
309310
'object_',
311+
'Options',
310312
'html',
311313
'base',
312314
'head',

pyhtml/__tag_base.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -146,20 +146,34 @@ def _render(self, indent: str, options: FullOptions) -> list[str]:
146146
opening += f"</{self._get_tag_name()}>"
147147
return [opening]
148148
else:
149-
out = [opening]
149+
indent_increase = options.indent if options.spacing == "\n" else ""
150150
# Children
151-
out.extend(
152-
util.render_children(
153-
self.children,
154-
self._escape_children(),
155-
indent + options.indent,
156-
options,
157-
)
151+
children = util.render_children(
152+
self.children,
153+
self._escape_children(),
154+
indent + indent_increase,
155+
options,
158156
)
159-
# Closing tag
160-
out.append(f"{indent}</{self._get_tag_name()}>")
161-
162-
return out
157+
closing = f"</{self._get_tag_name()}>"
158+
if options.spacing == "\n":
159+
return [
160+
opening,
161+
*children,
162+
f"{indent}{closing}",
163+
]
164+
else:
165+
# Children must have at least one line, since we would have
166+
# taken the `if not len(self.children)` branch if there were
167+
# no children to render
168+
out: list[str] = [
169+
opening + options.spacing + children[0],
170+
*children[1:],
171+
]
172+
# Add the closing tag onto the end
173+
return [
174+
*out[:-1],
175+
out[-1] + options.spacing + closing,
176+
]
163177

164178
def render(self) -> str:
165179
"""
@@ -179,9 +193,7 @@ class SelfClosingTag(Tag):
179193
Self-closing tags don't contain child elements
180194
"""
181195

182-
def __init__(
183-
self, *options: Options, **attributes: AttributeType
184-
) -> None:
196+
def __init__(self, *options: Options, **attributes: AttributeType) -> None:
185197
# Self-closing tags don't allow children
186198
super().__init__(*options, **attributes)
187199

pyhtml/__util.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,30 @@ def render_children(
125125
126126
Elements are placed in the same string
127127
"""
128-
rendered = []
128+
rendered: list[str] = []
129129
for ele in children:
130-
rendered.extend(
131-
render_inline_element(ele, escape_strings, indent, options)
130+
rendered_child = render_inline_element(
131+
ele, escape_strings, indent, options
132132
)
133+
if options.spacing == "\n":
134+
rendered.extend(rendered_child)
135+
else:
136+
# Custom spacing
137+
if len(rendered) == 0:
138+
rendered_child[0] = rendered_child[0].strip()
139+
rendered = rendered_child
140+
else:
141+
*r_head, r_tail = rendered
142+
# rendered_child should not have length zero, otherwise
143+
# something has gone horribly wrong, and it's ok to crash
144+
c_head, *c_tail = rendered_child
145+
# Join it all nicely
146+
rendered = [
147+
*r_head,
148+
# Remove leading whitespace caused by indentation rules
149+
r_tail + options.spacing + c_head.lstrip(),
150+
*c_tail,
151+
]
133152
return rendered
134153

135154

tests/render_options_test.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
# Tests / render options test
3+
4+
Test cases for specifying rendering options
5+
"""
6+
7+
import pyhtml as p
8+
9+
10+
def test_indent():
11+
doc = p.body(
12+
p.Options(indent="\t"),
13+
p.span(),
14+
p.div(),
15+
)
16+
17+
assert str(doc) == "\n".join(
18+
[
19+
"<body>",
20+
"\t<span></span>",
21+
"\t<div></div>",
22+
"</body>",
23+
]
24+
)
25+
26+
27+
def test_mixed_indent():
28+
doc = p.body(
29+
p.div(
30+
p.Options(indent="\t"),
31+
p.div(),
32+
),
33+
)
34+
35+
assert str(doc) == "\n".join(
36+
[
37+
"<body>",
38+
" <div>",
39+
" \t<div></div>",
40+
" </div>",
41+
"</body>",
42+
]
43+
)
44+
45+
46+
def test_spacing():
47+
doc = p.body(
48+
p.Options(spacing=" "),
49+
p.div(),
50+
)
51+
52+
assert str(doc) == "\n".join(
53+
[
54+
"<body> <div></div> </body>",
55+
]
56+
)
57+
58+
59+
def test_mixed_spacing():
60+
doc = p.body(
61+
p.div(
62+
p.Options(spacing=" "),
63+
p.div(),
64+
),
65+
)
66+
67+
assert str(doc) == "\n".join(
68+
[
69+
"<body>",
70+
" <div> <div></div> </div>",
71+
"</body>",
72+
]
73+
)
74+
75+
76+
def test_spacing_inner_newline():
77+
doc = p.body(
78+
p.div(
79+
p.Options(spacing=" "),
80+
p.div(
81+
p.Options(spacing="\n"),
82+
p.div(),
83+
),
84+
),
85+
)
86+
87+
assert str(doc) == "\n".join(
88+
[
89+
"<body>",
90+
" <div> <div>",
91+
" <div></div>",
92+
" </div> </div>",
93+
"</body>",
94+
]
95+
)
96+
97+
98+
def test_indent_and_spacing_inner_newline():
99+
doc = p.body(
100+
p.div(
101+
p.Options(spacing=" "),
102+
p.div(
103+
p.Options(spacing="\n", indent="\t"),
104+
p.div(),
105+
),
106+
),
107+
)
108+
109+
assert str(doc) == "\n".join(
110+
[
111+
"<body>",
112+
" <div> <div>",
113+
" \t<div></div>",
114+
" </div> </div>",
115+
"</body>",
116+
]
117+
)

0 commit comments

Comments
 (0)