python-fragments 0.26__tar.gz → 0.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_fragments-0.26 → python_fragments-0.28}/PKG-INFO +1 -1
- {python_fragments-0.26 → python_fragments-0.28}/fragments/ast_nodes.py +5 -3
- {python_fragments-0.26 → python_fragments-0.28}/fragments/grammar.py +1 -1
- {python_fragments-0.26 → python_fragments-0.28}/fragments/html/elements.py +1 -1
- {python_fragments-0.26 → python_fragments-0.28}/pyproject.toml +1 -1
- {python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/PKG-INFO +1 -1
- {python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/SOURCES.txt +1 -0
- {python_fragments-0.26 → python_fragments-0.28}/tests/test_grammar.py +43 -0
- python_fragments-0.28/tests/test_html_elements.py +31 -0
- {python_fragments-0.26 → python_fragments-0.28}/README.md +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/__init__.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/cli.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/html/__init__.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/loader.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/__init__.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/based_proxy.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/__init__.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/code_actions.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/completion.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/definition.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/diagnostics.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/document_highlight.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/document_symbols.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/folding_range.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/hover.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/inlay_hints.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/lifecycle.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/references.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/rename.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/semantic_tokens.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/signature_help.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/file_state.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/message_queue.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/pyright_notification_handlers/__init__.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/pyright_notification_handlers/capability.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/pyright_notification_handlers/configuration.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/pyright_notification_handlers/diagnostics.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/types.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/source.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/transpiler.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/fragments/types.py +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/dependency_links.txt +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/entry_points.txt +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/requires.txt +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/top_level.txt +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/setup.cfg +0 -0
- {python_fragments-0.26 → python_fragments-0.28}/tests/test_source_map.py +0 -0
|
@@ -91,7 +91,8 @@ class ASTFragment:
|
|
|
91
91
|
for child in self.children:
|
|
92
92
|
child.transpile(transpiled_start)
|
|
93
93
|
transpiled_start = child.transpiled_end + 1
|
|
94
|
-
|
|
94
|
+
if len(self.children) > 0:
|
|
95
|
+
transpiled_start -= 1
|
|
95
96
|
|
|
96
97
|
self.transpiled_content = self.__template__.format(",".join(child.transpiled_content for child in self.children))
|
|
97
98
|
self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
|
|
@@ -133,7 +134,7 @@ class ASTHTMLElement:
|
|
|
133
134
|
for child in self.children:
|
|
134
135
|
child.transpile(transpiled_start)
|
|
135
136
|
transpiled_start = child.transpiled_end + 1
|
|
136
|
-
if self.children:
|
|
137
|
+
if len(self.children) > 0:
|
|
137
138
|
transpiled_start -= 1
|
|
138
139
|
children = ",".join(child.transpiled_content for child in self.children)
|
|
139
140
|
oneline_offset = len(str(self.one_line))
|
|
@@ -406,7 +407,8 @@ class ASTHTMLComment:
|
|
|
406
407
|
|
|
407
408
|
def transpile(self, transpiled_start: int) -> None:
|
|
408
409
|
self.transpiled_start = transpiled_start
|
|
409
|
-
|
|
410
|
+
escaped_content = self.content.replace("\n", "\\n").replace("\t", "\\t").replace("\r", "\\r").replace('"', '\\"')
|
|
411
|
+
self.transpiled_content = self.__template__.format(escaped_content)
|
|
410
412
|
self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
|
|
411
413
|
|
|
412
414
|
def map_offset(self, offset: int) -> None:
|
|
@@ -277,7 +277,7 @@ def expect_children(source: Source) -> tuple[Source, list[ASTHTMLChild]]:
|
|
|
277
277
|
|
|
278
278
|
def expect_interpolation(source: Source) -> tuple[Source, ASTInterpolation]:
|
|
279
279
|
"""An interpolation block."""
|
|
280
|
-
INTERPOLATION_EXPRESSION = r"([\s\S]*?)(
|
|
280
|
+
INTERPOLATION_EXPRESSION = r"([\s\S]*?)(?=\s*}})"
|
|
281
281
|
source_start = source.offset
|
|
282
282
|
source = expect_string(source, "{{")
|
|
283
283
|
source, leading_whitespace = source.eat_whitespace()
|
|
@@ -18,7 +18,7 @@ def el(
|
|
|
18
18
|
name,
|
|
19
19
|
className_to_string(attributes.pop("className")) if "className" in attributes else None,
|
|
20
20
|
style_to_string(attributes.pop("style")) if "style" in attributes else None,
|
|
21
|
-
attributes_to_string(attributes) if attributes
|
|
21
|
+
attributes_to_string(attributes) if attributes else None,
|
|
22
22
|
]
|
|
23
23
|
tag_contents = [item for item in tag_contents if item is not None]
|
|
24
24
|
tag_contents_string = " ".join(tag_contents)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-fragments"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.28"
|
|
8
8
|
description = "Modern HTML template rendering in Python"
|
|
9
9
|
authors = [{ name = "The Running Algorithm", email = "services@therunningalgorithm.info" }]
|
|
10
10
|
readme = "README.md"
|
|
@@ -46,6 +46,16 @@ def test_comment_multiline():
|
|
|
46
46
|
]))
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def test_comment_multiline_escaped_in_transpile():
|
|
50
|
+
source = Source.from_string("<><!-- line one\nline two --></>")
|
|
51
|
+
_, fragment = grammar.expect_fragment(source)
|
|
52
|
+
fragment.transpile(0)
|
|
53
|
+
comment = fragment.children[0]
|
|
54
|
+
assert isinstance(comment, ASTHTMLComment)
|
|
55
|
+
assert "\\n" in comment.transpiled_content
|
|
56
|
+
assert "\n" not in comment.transpiled_content
|
|
57
|
+
|
|
58
|
+
|
|
49
59
|
def test_comment_inside_element():
|
|
50
60
|
source = Source.from_string("<><div><!-- note --></div></>")
|
|
51
61
|
source, fragment = grammar.expect_fragment(source)
|
|
@@ -375,6 +385,39 @@ def test_whitespace_between_adjacent_interpolations_is_preserved():
|
|
|
375
385
|
]))
|
|
376
386
|
|
|
377
387
|
|
|
388
|
+
def test_interpolation_no_trailing_whitespace():
|
|
389
|
+
source = Source.from_string("<><NotificationBar user={{ user}} /></>")
|
|
390
|
+
source, fragment = grammar.expect_fragment(source)
|
|
391
|
+
assert source.at_end()
|
|
392
|
+
assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
|
|
393
|
+
ASTComponent(-1, -1, ASTComponentName(-1, -1, "NotificationBar"), {
|
|
394
|
+
"user": ASTComponentArgument(-1, -1, "user", None, ASTInterpolation(-1, -1, "user", 1, 0))
|
|
395
|
+
}, [])
|
|
396
|
+
]))
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def test_interpolation_no_leading_whitespace():
|
|
400
|
+
source = Source.from_string("<><NotificationBar user={{user }} /></>")
|
|
401
|
+
source, fragment = grammar.expect_fragment(source)
|
|
402
|
+
assert source.at_end()
|
|
403
|
+
assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
|
|
404
|
+
ASTComponent(-1, -1, ASTComponentName(-1, -1, "NotificationBar"), {
|
|
405
|
+
"user": ASTComponentArgument(-1, -1, "user", None, ASTInterpolation(-1, -1, "user", 0, 1))
|
|
406
|
+
}, [])
|
|
407
|
+
]))
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_interpolation_no_whitespace():
|
|
411
|
+
source = Source.from_string("<><NotificationBar user={{user}} /></>")
|
|
412
|
+
source, fragment = grammar.expect_fragment(source)
|
|
413
|
+
assert source.at_end()
|
|
414
|
+
assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
|
|
415
|
+
ASTComponent(-1, -1, ASTComponentName(-1, -1, "NotificationBar"), {
|
|
416
|
+
"user": ASTComponentArgument(-1, -1, "user", None, ASTInterpolation(-1, -1, "user", 0, 0))
|
|
417
|
+
}, [])
|
|
418
|
+
]))
|
|
419
|
+
|
|
420
|
+
|
|
378
421
|
# ---------------------------------------------------------------------------
|
|
379
422
|
# Full integration (updated for new AST structure)
|
|
380
423
|
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from fragments.html.elements import el, className_to_string
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_className_list_joined_with_spaces():
|
|
5
|
+
result = className_to_string(["foo", "bar", "baz"])
|
|
6
|
+
assert result == 'class="foo bar baz"'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_className_empty_list():
|
|
10
|
+
result = className_to_string([])
|
|
11
|
+
assert result == 'class=""'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_className_single_item_list():
|
|
15
|
+
result = className_to_string(["only"])
|
|
16
|
+
assert result == 'class="only"'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_className_string_passthrough():
|
|
20
|
+
result = className_to_string("already-a-string")
|
|
21
|
+
assert result == 'class="already-a-string"'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_el_className_list_attribute():
|
|
25
|
+
result = el("div", ["content"], False, {"className": ["foo", "bar"]})
|
|
26
|
+
assert result == '<div class="foo bar">content</div>'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_el_className_empty_list():
|
|
30
|
+
result = el("div", [], True, {"className": []})
|
|
31
|
+
assert result == '<div class="" />'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/completion.py
RENAMED
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/definition.py
RENAMED
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/hover.py
RENAMED
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/inlay_hints.py
RENAMED
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/lifecycle.py
RENAMED
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/references.py
RENAMED
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/fragments/lsp/client_message_handlers/rename.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fragments-0.26 → python_fragments-0.28}/python_fragments.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|