python-fragments 0.25__tar.gz → 0.27__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.
Files changed (47) hide show
  1. {python_fragments-0.25 → python_fragments-0.27}/PKG-INFO +1 -1
  2. {python_fragments-0.25 → python_fragments-0.27}/fragments/ast_nodes.py +5 -3
  3. {python_fragments-0.25 → python_fragments-0.27}/fragments/grammar.py +3 -1
  4. {python_fragments-0.25 → python_fragments-0.27}/fragments/html/elements.py +1 -1
  5. {python_fragments-0.25 → python_fragments-0.27}/pyproject.toml +1 -1
  6. {python_fragments-0.25 → python_fragments-0.27}/python_fragments.egg-info/PKG-INFO +1 -1
  7. {python_fragments-0.25 → python_fragments-0.27}/python_fragments.egg-info/SOURCES.txt +1 -0
  8. {python_fragments-0.25 → python_fragments-0.27}/tests/test_grammar.py +23 -0
  9. python_fragments-0.27/tests/test_html_elements.py +31 -0
  10. {python_fragments-0.25 → python_fragments-0.27}/README.md +0 -0
  11. {python_fragments-0.25 → python_fragments-0.27}/fragments/__init__.py +0 -0
  12. {python_fragments-0.25 → python_fragments-0.27}/fragments/cli.py +0 -0
  13. {python_fragments-0.25 → python_fragments-0.27}/fragments/html/__init__.py +0 -0
  14. {python_fragments-0.25 → python_fragments-0.27}/fragments/loader.py +0 -0
  15. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/__init__.py +0 -0
  16. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/based_proxy.py +0 -0
  17. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/__init__.py +0 -0
  18. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/code_actions.py +0 -0
  19. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/completion.py +0 -0
  20. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/definition.py +0 -0
  21. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/diagnostics.py +0 -0
  22. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/document_highlight.py +0 -0
  23. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/document_symbols.py +0 -0
  24. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/folding_range.py +0 -0
  25. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/hover.py +0 -0
  26. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/inlay_hints.py +0 -0
  27. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/lifecycle.py +0 -0
  28. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/references.py +0 -0
  29. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/rename.py +0 -0
  30. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/semantic_tokens.py +0 -0
  31. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/client_message_handlers/signature_help.py +0 -0
  32. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/file_state.py +0 -0
  33. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/message_queue.py +0 -0
  34. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/pyright_notification_handlers/__init__.py +0 -0
  35. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/pyright_notification_handlers/capability.py +0 -0
  36. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/pyright_notification_handlers/configuration.py +0 -0
  37. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/pyright_notification_handlers/diagnostics.py +0 -0
  38. {python_fragments-0.25 → python_fragments-0.27}/fragments/lsp/types.py +0 -0
  39. {python_fragments-0.25 → python_fragments-0.27}/fragments/source.py +0 -0
  40. {python_fragments-0.25 → python_fragments-0.27}/fragments/transpiler.py +0 -0
  41. {python_fragments-0.25 → python_fragments-0.27}/fragments/types.py +0 -0
  42. {python_fragments-0.25 → python_fragments-0.27}/python_fragments.egg-info/dependency_links.txt +0 -0
  43. {python_fragments-0.25 → python_fragments-0.27}/python_fragments.egg-info/entry_points.txt +0 -0
  44. {python_fragments-0.25 → python_fragments-0.27}/python_fragments.egg-info/requires.txt +0 -0
  45. {python_fragments-0.25 → python_fragments-0.27}/python_fragments.egg-info/top_level.txt +0 -0
  46. {python_fragments-0.25 → python_fragments-0.27}/setup.cfg +0 -0
  47. {python_fragments-0.25 → python_fragments-0.27}/tests/test_source_map.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fragments
3
- Version: 0.25
3
+ Version: 0.27
4
4
  Summary: Modern HTML template rendering in Python
5
5
  Author-email: The Running Algorithm <services@therunningalgorithm.info>
6
6
  License: Proprietary
@@ -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
- transpiled_start -= 1
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
- self.transpiled_content = self.__template__.format(self.content)
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:
@@ -269,7 +269,9 @@ def expect_children(source: Source) -> tuple[Source, list[ASTHTMLChild]]:
269
269
  while not source.remaining().startswith("</"):
270
270
  source, child = expect_child(source)
271
271
  children.append(child)
272
- source, _ = source.eat_whitespace()
272
+ source_after_whitespace, _ = source.eat_whitespace()
273
+ if not source_after_whitespace.remaining().startswith("{{"):
274
+ source = source_after_whitespace
273
275
  return source, children
274
276
 
275
277
 
@@ -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 is not None else None,
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.25"
7
+ version = "0.27"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fragments
3
- Version: 0.25
3
+ Version: 0.27
4
4
  Summary: Modern HTML template rendering in Python
5
5
  Author-email: The Running Algorithm <services@therunningalgorithm.info>
6
6
  License: Proprietary
@@ -41,4 +41,5 @@ python_fragments.egg-info/entry_points.txt
41
41
  python_fragments.egg-info/requires.txt
42
42
  python_fragments.egg-info/top_level.txt
43
43
  tests/test_grammar.py
44
+ tests/test_html_elements.py
44
45
  tests/test_source_map.py
@@ -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)
@@ -362,6 +372,19 @@ def test_html_text_multiline_before_child_element():
362
372
  ]))
363
373
 
364
374
 
375
+ def test_whitespace_between_adjacent_interpolations_is_preserved():
376
+ source = Source.from_string("<><p>{{ a }} {{ b }}</p></>")
377
+ source, fragment = grammar.expect_fragment(source)
378
+ assert source.at_end()
379
+ assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
380
+ ASTHTMLElement(-1, -1, "p", {}, [
381
+ ASTInterpolation(-1, -1, "a", 1, 1),
382
+ ASTHTMLText(-1, -1, " "),
383
+ ASTInterpolation(-1, -1, "b", 1, 1),
384
+ ], False)
385
+ ]))
386
+
387
+
365
388
  # ---------------------------------------------------------------------------
366
389
  # Full integration (updated for new AST structure)
367
390
  # ---------------------------------------------------------------------------
@@ -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="" />'