Skip to content

Commit 429e40a

Browse files
Merge pull request #112 from COMP1010UNSW/maddy-fix-paragraph-spacing
Fix incorrect indentation when rendering paragraph elements
2 parents fdef82c + 931fffa commit 429e40a

File tree

8 files changed

+122
-21
lines changed

8 files changed

+122
-21
lines changed

pyhtml/__tag_base.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,12 @@ def _escape_children(self) -> bool:
121121
"""
122122
return True
123123

124-
def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
124+
def _render(
125+
self,
126+
indent: str,
127+
options: FullRenderOptions,
128+
skip_indent: bool = False,
129+
) -> list[str]:
125130
"""
126131
Renders tag and its children to a list of strings where each string is
127132
a single line of output.
@@ -132,6 +137,8 @@ def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
132137
string to use for indentation
133138
options : FullOptions
134139
rendering options
140+
skip_indent : bool
141+
whether to skip indentation for this element
135142
136143
Returns
137144
-------
@@ -148,8 +155,13 @@ def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
148155
)
149156
)
150157

158+
# Indentation to use before opening tag
159+
indent_pre = "" if skip_indent else indent
160+
# Indentation to use before closing tag
161+
indent_post = "" if skip_indent or options.indent is None else indent
162+
151163
# Tag and attributes
152-
opening = f"{indent}<{self._get_tag_name()}"
164+
opening = f"{indent_pre}<{self._get_tag_name()}"
153165

154166
# Add pre-content
155167
if (pre := self._get_tag_pre_content()) is not None:
@@ -177,7 +189,7 @@ def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
177189
return [
178190
opening,
179191
*children,
180-
f"{indent}{closing}",
192+
f"{indent_post}{closing}",
181193
]
182194
else:
183195
# Children must have at least one line, since we would have
@@ -190,7 +202,12 @@ def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
190202
# Add the closing tag onto the end
191203
return [
192204
*out[:-1],
193-
out[-1] + options.spacing + closing,
205+
# Only include post indentation if it's on a different line
206+
# to the pre indentation
207+
(indent_post if len(out) > 1 else "")
208+
+ out[-1]
209+
+ options.spacing
210+
+ closing,
194211
]
195212

196213
def render(self) -> str:
@@ -217,11 +234,17 @@ def __init__(
217234
# Self-closing tags don't allow children
218235
super().__init__(*options, **attributes)
219236

220-
def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
237+
def _render(
238+
self,
239+
indent: str,
240+
options: FullRenderOptions,
241+
skip_indent: bool = False,
242+
) -> list[str]:
221243
"""
222244
Renders tag and its children to a list of strings where each string is
223245
a single line of output
224246
"""
247+
indent_str = "" if skip_indent else indent
225248
attributes = util.filter_attributes(
226249
util.dict_union(
227250
self._get_default_attributes(self.attributes),
@@ -230,18 +253,18 @@ def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
230253
)
231254
if len(attributes):
232255
return [
233-
f"{indent}<{self._get_tag_name()} "
256+
f"{indent_str}<{self._get_tag_name()} "
234257
f"{util.render_tag_attributes(attributes)}/>"
235258
]
236259
else:
237-
return [f"{indent}<{self._get_tag_name()}/>"]
260+
return [f"{indent_str}<{self._get_tag_name()}/>"]
238261

239262

240263
@deprecated(
241264
"Overload `_get_default_render_options` to return "
242265
"`RenderOptions(indent=None, spacing='')` instead"
243266
)
244-
class WhitespaceSensitiveTag(Tag):
267+
class WhitespaceSensitiveTag(Tag): # pragma: no cover
245268
"""
246269
Whitespace-sensitive tags are tags where whitespace needs to be respected.
247270
"""

pyhtml/__tags/comment.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ def _get_tag_name(self) -> str:
4545
# and is never used since we override _render
4646
return "!--" # pragma: no cover
4747

48-
def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
48+
def _render(
49+
self,
50+
indent: str,
51+
options: FullRenderOptions,
52+
skip_indent: bool = False,
53+
) -> list[str]:
4954
"""
5055
Override of render, to render comments
5156
"""

pyhtml/__tags/dangerous_raw_html.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ def _get_tag_name(self) -> str:
4747
# and is never used since we override _render
4848
return "!!!DANGEROUS RAW HTML!!!" # pragma: no cover
4949

50-
def _render(self, indent: str, options: FullRenderOptions) -> list[str]:
50+
def _render(
51+
self,
52+
indent: str,
53+
options: FullRenderOptions,
54+
skip_indent: bool = False,
55+
) -> list[str]:
5156
return self.html_data.splitlines()
5257

5358
def _get_default_render_options(self) -> RenderOptions:

pyhtml/__util.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,24 @@ def render_inline_element(
101101
"""
102102
from .__tag_base import Tag
103103

104+
skip_indent = options.spacing is not None and "\n" not in options.spacing
105+
104106
if isinstance(ele, Tag):
105-
return ele._render(indent, options)
107+
return ele._render(indent, options, skip_indent)
106108
elif isinstance(ele, type) and issubclass(ele, Tag):
107109
e = ele()
108-
return e._render(indent, options)
110+
return e._render(indent, options, skip_indent)
109111
else:
110112
# Remove newlines from strings when inline rendering
111113
if escape_strings:
112-
return increase_indent([escape_string(str(ele))], indent)
114+
return increase_indent(
115+
[escape_string(line) for line in str(ele).splitlines()],
116+
"" if skip_indent else indent,
117+
)
113118
else:
114-
return increase_indent([str(ele)], indent)
119+
return increase_indent(
120+
str(ele).splitlines(), "" if skip_indent else indent
121+
)
115122

116123

117124
def render_children(
@@ -135,7 +142,6 @@ def render_children(
135142
else:
136143
# Custom spacing
137144
if len(rendered) == 0:
138-
rendered_child[0] = rendered_child[0].strip()
139145
rendered = rendered_child
140146
else:
141147
*r_head, r_tail = rendered
@@ -145,8 +151,8 @@ def render_children(
145151
# Join it all nicely
146152
rendered = [
147153
*r_head,
148-
# Remove leading whitespace caused by indentation rules
149-
r_tail + options.spacing + c_head.lstrip(),
154+
# Join using spacing as separator
155+
r_tail + options.spacing + c_head,
150156
*c_tail,
151157
]
152158
return rendered

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pyhtml-enhanced"
3-
version = "2.2.2"
3+
version = "2.2.3"
44
description = "A library for building HTML documents with a simple and learnable syntax"
55
readme = "README.md"
66
license = "MIT"

tests/basic_rendering_test.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_renders_elements_with_children():
4646
)
4747

4848

49-
def test_renders_deeply_nested_children():
49+
def test_renders_nested_children():
5050
doc = body(
5151
div(
5252
span("Hello world"),
@@ -66,6 +66,30 @@ def test_renders_deeply_nested_children():
6666
)
6767

6868

69+
def test_renders_deeply_nested_children():
70+
doc = body(
71+
div(
72+
span(
73+
div("Hello world"),
74+
),
75+
),
76+
)
77+
78+
assert str(doc) == "\n".join(
79+
[
80+
"<body>",
81+
" <div>",
82+
" <span>",
83+
" <div>",
84+
" Hello world",
85+
" </div>",
86+
" </span>",
87+
" </div>",
88+
"</body>",
89+
]
90+
)
91+
92+
6993
def test_renders_attributes():
7094
doc = body(foo="bar")
7195

tests/render_options_test.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ def test_mixed_spacing():
7777
)
7878

7979

80+
def test_spacing_str():
81+
doc = p.body(
82+
p.div(
83+
p.RenderOptions(spacing=" "),
84+
p.span(p.RenderOptions(spacing="\n"), "hi"),
85+
),
86+
)
87+
88+
assert str(doc) == "\n".join(
89+
[
90+
"<body>",
91+
" <div> <span>",
92+
" hi",
93+
" </span> </div>",
94+
"</body>",
95+
]
96+
)
97+
98+
8099
def test_spacing_inner_newline():
81100
doc = p.body(
82101
p.div(
@@ -121,6 +140,24 @@ def test_indent_and_spacing_inner_newline():
121140
)
122141

123142

124-
def test_default_render_options():
143+
def test_default_render_options_paragraph():
125144
doc = p.p("Paragraph")
126145
assert str(doc) == "<p>Paragraph</p>"
146+
147+
148+
def test_extra_space_is_respected_in_paragraphs():
149+
doc = p.p(" Paragraph ")
150+
assert str(doc) == "<p> Paragraph </p>"
151+
152+
153+
def test_paragraphs_render_in_body():
154+
doc = p.body(
155+
p.p("Paragraph"),
156+
)
157+
assert str(doc) == "\n".join(
158+
[
159+
"<body>",
160+
" <p>Paragraph</p>",
161+
"</body>",
162+
]
163+
)

tests/whitespace_sensitive_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def test_textarea():
2121

2222

2323
def test_indentation_ignored():
24-
assert str(p.body(p.pre("hello\nworld"))) == '\n'.join([
24+
doc = p.body(p.pre("hello\nworld"))
25+
assert str(doc) == '\n'.join([
2526
"<body>",
2627
" <pre>hello",
2728
"world</pre>",

0 commit comments

Comments
 (0)