python-fragments 0.21__tar.gz → 0.25__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 (46) hide show
  1. {python_fragments-0.21 → python_fragments-0.25}/PKG-INFO +1 -1
  2. {python_fragments-0.21 → python_fragments-0.25}/fragments/ast_nodes.py +50 -4
  3. {python_fragments-0.21 → python_fragments-0.25}/fragments/grammar.py +13 -4
  4. {python_fragments-0.21 → python_fragments-0.25}/fragments/html/elements.py +7 -6
  5. {python_fragments-0.21 → python_fragments-0.25}/pyproject.toml +1 -1
  6. {python_fragments-0.21 → python_fragments-0.25}/python_fragments.egg-info/PKG-INFO +1 -1
  7. {python_fragments-0.21 → python_fragments-0.25}/tests/test_grammar.py +9 -8
  8. {python_fragments-0.21 → python_fragments-0.25}/README.md +0 -0
  9. {python_fragments-0.21 → python_fragments-0.25}/fragments/__init__.py +0 -0
  10. {python_fragments-0.21 → python_fragments-0.25}/fragments/cli.py +0 -0
  11. {python_fragments-0.21 → python_fragments-0.25}/fragments/html/__init__.py +0 -0
  12. {python_fragments-0.21 → python_fragments-0.25}/fragments/loader.py +0 -0
  13. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/__init__.py +0 -0
  14. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/based_proxy.py +0 -0
  15. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/__init__.py +0 -0
  16. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/code_actions.py +0 -0
  17. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/completion.py +0 -0
  18. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/definition.py +0 -0
  19. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/diagnostics.py +0 -0
  20. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/document_highlight.py +0 -0
  21. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/document_symbols.py +0 -0
  22. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/folding_range.py +0 -0
  23. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/hover.py +0 -0
  24. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/inlay_hints.py +0 -0
  25. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/lifecycle.py +0 -0
  26. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/references.py +0 -0
  27. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/rename.py +0 -0
  28. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/semantic_tokens.py +0 -0
  29. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/client_message_handlers/signature_help.py +0 -0
  30. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/file_state.py +0 -0
  31. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/message_queue.py +0 -0
  32. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/pyright_notification_handlers/__init__.py +0 -0
  33. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/pyright_notification_handlers/capability.py +0 -0
  34. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/pyright_notification_handlers/configuration.py +0 -0
  35. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/pyright_notification_handlers/diagnostics.py +0 -0
  36. {python_fragments-0.21 → python_fragments-0.25}/fragments/lsp/types.py +0 -0
  37. {python_fragments-0.21 → python_fragments-0.25}/fragments/source.py +0 -0
  38. {python_fragments-0.21 → python_fragments-0.25}/fragments/transpiler.py +0 -0
  39. {python_fragments-0.21 → python_fragments-0.25}/fragments/types.py +0 -0
  40. {python_fragments-0.21 → python_fragments-0.25}/python_fragments.egg-info/SOURCES.txt +0 -0
  41. {python_fragments-0.21 → python_fragments-0.25}/python_fragments.egg-info/dependency_links.txt +0 -0
  42. {python_fragments-0.21 → python_fragments-0.25}/python_fragments.egg-info/entry_points.txt +0 -0
  43. {python_fragments-0.21 → python_fragments-0.25}/python_fragments.egg-info/requires.txt +0 -0
  44. {python_fragments-0.21 → python_fragments-0.25}/python_fragments.egg-info/top_level.txt +0 -0
  45. {python_fragments-0.21 → python_fragments-0.25}/setup.cfg +0 -0
  46. {python_fragments-0.21 → python_fragments-0.25}/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.21
3
+ Version: 0.25
4
4
  Summary: Modern HTML template rendering in Python
5
5
  Author-email: The Running Algorithm <services@therunningalgorithm.info>
6
6
  License: Proprietary
@@ -241,7 +241,7 @@ class ASTComponent:
241
241
  source_start: int = field(compare=False)
242
242
  source_end: int = field(compare=False)
243
243
 
244
- name: str
244
+ name: "ASTComponentName"
245
245
  arguments: dict[str, "ASTComponentArgument"]
246
246
  children: Sequence["ASTHTMLChild"]
247
247
 
@@ -253,7 +253,8 @@ class ASTComponent:
253
253
 
254
254
  def transpile(self, transpiled_start: int) -> None:
255
255
  self.transpiled_start = transpiled_start
256
- transpiled_start = transpiled_start + len(self.name) + 2
256
+ self.name.transpile(self.transpiled_start)
257
+ transpiled_start = self.name.transpiled_end + 2
257
258
  for child in self.children:
258
259
  child.transpile(transpiled_start)
259
260
  transpiled_start = child.transpiled_end + 1
@@ -262,7 +263,7 @@ class ASTComponent:
262
263
  children = ",".join(child.transpiled_content for child in self.children)
263
264
 
264
265
  if len(self.arguments) == 0:
265
- self.transpiled_content = self.__template__.format(self.name, children, "")
266
+ self.transpiled_content = self.__template__.format(self.name.transpiled_content, children, "")
266
267
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
267
268
  return
268
269
 
@@ -272,10 +273,13 @@ class ASTComponent:
272
273
  transpiled_start = attribute.transpiled_end + 1
273
274
 
274
275
  attributes = ",".join(attribute.transpiled_content for attribute in self.arguments.values())
275
- self.transpiled_content = self.__template__.format(self.name, children, attributes)
276
+ self.transpiled_content = self.__template__.format(self.name.transpiled_content, children, attributes)
276
277
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
277
278
 
278
279
  def map_offset(self, offset: int) -> int | None:
280
+ if self.name.source_start < offset < self.name.source_end:
281
+ return self.name.map_offset(offset)
282
+
279
283
  for attribute in self.arguments.values():
280
284
  if attribute.source_start <= offset <= attribute.source_end:
281
285
  return attribute.map_offset(offset)
@@ -287,6 +291,9 @@ class ASTComponent:
287
291
  return None
288
292
 
289
293
  def unmap_offset(self, offset: int) -> int | None:
294
+ if self.name.transpiled_start < offset < self.name.transpiled_end:
295
+ return self.name.unmap_offset(offset)
296
+
290
297
  for attribute in self.arguments.values():
291
298
  if attribute.transpiled_start <= offset <= attribute.transpiled_end:
292
299
  return attribute.unmap_offset(offset)
@@ -298,6 +305,37 @@ class ASTComponent:
298
305
  return None
299
306
 
300
307
 
308
+ @dataclass(slots=True)
309
+ class ASTComponentName:
310
+ source_start: int = field(compare=False)
311
+ source_end: int = field(compare=False)
312
+
313
+ name: str
314
+
315
+ transpiled_content: str = field(init=False)
316
+ transpiled_start: int = field(init=False)
317
+ transpiled_end: int = field(init=False)
318
+
319
+ def transpile(self, offset: int) -> None:
320
+ self.transpiled_start = offset
321
+ self.transpiled_content = self.name
322
+ self.transpiled_end = offset + len(self.name)
323
+
324
+ def map_offset(self, offset: int) -> int | None:
325
+ if self.source_start <= offset <= self.source_end:
326
+ specific_offset = offset - self.source_start
327
+ return self.transpiled_start + specific_offset
328
+
329
+ return None
330
+
331
+ def unmap_offset(self, offset: int) -> int | None:
332
+ if self.transpiled_start <= offset <= self.transpiled_end:
333
+ specific_offset = offset - self.transpiled_start
334
+ return self.source_start + specific_offset
335
+
336
+ return None
337
+
338
+
301
339
  @dataclass(slots=True)
302
340
  class ASTComponentArgument:
303
341
  source_start: int = field(compare=False)
@@ -327,6 +365,10 @@ class ASTComponentArgument:
327
365
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
328
366
 
329
367
  def map_offset(self, offset: int) -> int | None:
368
+ if self.source_start <= offset <= self.source_start + len(self.name):
369
+ specific_offset = offset - self.source_start
370
+ return self.transpiled_start + specific_offset
371
+
330
372
  if self.interpolation is None:
331
373
  return None
332
374
 
@@ -336,6 +378,10 @@ class ASTComponentArgument:
336
378
  return None
337
379
 
338
380
  def unmap_offset(self, offset: int) -> int | None:
381
+ if self.transpiled_start <= offset <= self.transpiled_start + len(self.name):
382
+ specific_offset = offset - self.transpiled_start
383
+ return self.source_start + specific_offset
384
+
339
385
  if self.interpolation is None:
340
386
  return None
341
387
 
@@ -4,6 +4,7 @@ import re
4
4
  from fragments.ast_nodes import (
5
5
  ASTComponent,
6
6
  ASTComponentArgument,
7
+ ASTComponentName,
7
8
  ASTControlNode,
8
9
  ASTFragment,
9
10
  ASTHTMLAttribute,
@@ -20,6 +21,7 @@ from fragments.source import Source
20
21
  HTML_IDENTIFIER = r"[a-zA-Z][a-zA-Z0-9_:.-]*"
21
22
  HTML_ATTRIBUTE_NAME = r"[a-zA-Z:-_][a-zA-Z0-9_:.-]*"
22
23
  HTML_TEXT = r"([\s\S]*?)(?=<|{{)"
24
+ CHILDREN_META_COMPONENT = r"<[\S]*Children\.\.\.[\S]*/>"
23
25
 
24
26
 
25
27
  class ParsingError(Exception):
@@ -139,7 +141,7 @@ def expect_component(source: Source) -> tuple[Source, ASTComponent | ASTControlN
139
141
  """An pseudo-element that actually resolves into a user-defined function call."""
140
142
  source_start = source.offset
141
143
  source = expect_string(source, "<")
142
- source, name = expect_regex(source, r"[A-Z][a-zA-Z0-9_]*", "component name")
144
+ source, name = expect_component_name(source)
143
145
  source, _ = source.eat_whitespace()
144
146
 
145
147
  arguments: dict[str, ASTComponentArgument] = {}
@@ -169,12 +171,12 @@ def expect_component(source: Source) -> tuple[Source, ASTComponent | ASTControlN
169
171
  source = expect_string(source, ">")
170
172
  source, children = expect_children(source)
171
173
  source = expect_string(source, "</")
172
- source, closing_name = expect_regex(source, r"[A-Z][a-zA-Z0-9_]*", "component name")
174
+ source, closing_name = expect_component_name(source)
173
175
  source, _ = source.eat_whitespace()
174
176
  source = expect_string(source, ">")
175
177
 
176
- if name != closing_name:
177
- raise ParsingError(f"Element closed ({closing_name!r}) is not the same as currently opened element ({name!r})", source.offset)
178
+ if name.name != closing_name.name:
179
+ raise ParsingError(f"Element closed ({closing_name.name}) is not the same as currently opened element ({name.name})", source.offset)
178
180
 
179
181
  return source, ASTControlNode[ASTComponent].wrap_child(
180
182
  ASTComponent(source_start=source_start, source_end=source.offset, name=name, arguments=arguments, children=children),
@@ -183,6 +185,13 @@ def expect_component(source: Source) -> tuple[Source, ASTComponent | ASTControlN
183
185
  )
184
186
 
185
187
 
188
+ def expect_component_name(source: Source) -> tuple[Source, ASTComponentName]:
189
+ """An identifier corresponding with a Python function."""
190
+ source_start = source.offset
191
+ source, name = expect_regex(source, r"[A-Z][a-zA-Z0-9_]*", "component name")
192
+ return source, ASTComponentName(source_start, source.offset, name)
193
+
194
+
186
195
  def expect_html_comment(source: Source) -> tuple[Source, ASTHTMLComment]:
187
196
  source_start = source.offset
188
197
  source = expect_string(source, "<!--")
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from typing import Any
3
3
  from fragments.types import Children
4
+ import html
4
5
 
5
6
 
6
7
  def sequence(children: Children) -> str:
@@ -15,7 +16,7 @@ def el(
15
16
  ) -> str:
16
17
  tag_contents = [
17
18
  name,
18
- classes_to_string(attributes.pop("classes")) if "classes" in attributes else None,
19
+ className_to_string(attributes.pop("className")) if "className" in attributes else None,
19
20
  style_to_string(attributes.pop("style")) if "style" in attributes else None,
20
21
  attributes_to_string(attributes) if attributes is not None else None,
21
22
  ]
@@ -46,7 +47,7 @@ def attribute_to_string(name: str, value: Any) -> str:
46
47
  value = list(value)
47
48
 
48
49
  if isinstance(value, dict) or isinstance(value, list):
49
- value = json.dumps(value)
50
+ value = html.escape(json.dumps(value))
50
51
 
51
52
  if isinstance(value, bool):
52
53
  value = str(value).lower()
@@ -54,11 +55,11 @@ def attribute_to_string(name: str, value: Any) -> str:
54
55
  return f'{name}="{value}"'
55
56
 
56
57
 
57
- def classes_to_string(classes: list[str] | str) -> str:
58
- if isinstance(classes, list):
59
- inner = " ".join(classes)
58
+ def className_to_string(contents: list[str] | str) -> str:
59
+ if isinstance(contents, list):
60
+ inner = " ".join(contents)
60
61
  else:
61
- inner = classes
62
+ inner = contents
62
63
 
63
64
  return f'class="{inner}"'
64
65
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-fragments"
7
- version = "0.21"
7
+ version = "0.25"
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.21
3
+ Version: 0.25
4
4
  Summary: Modern HTML template rendering in Python
5
5
  Author-email: The Running Algorithm <services@therunningalgorithm.info>
6
6
  License: Proprietary
@@ -4,6 +4,7 @@ from fragments import grammar
4
4
  from fragments.ast_nodes import (
5
5
  ASTComponent,
6
6
  ASTComponentArgument,
7
+ ASTComponentName,
7
8
  ASTControlNode,
8
9
  ASTFragment,
9
10
  ASTHTMLAttribute,
@@ -209,7 +210,7 @@ def test_component_self_closing():
209
210
  source, fragment = grammar.expect_fragment(source)
210
211
  assert source.at_end()
211
212
  assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
212
- ASTComponent(-1, -1, "MyComp", {}, [])
213
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {}, [])
213
214
  ]))
214
215
 
215
216
 
@@ -218,7 +219,7 @@ def test_component_with_double_quote_argument():
218
219
  source, fragment = grammar.expect_fragment(source)
219
220
  assert source.at_end()
220
221
  assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
221
- ASTComponent(-1, -1, "MyComp", {
222
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {
222
223
  "name": ASTComponentArgument(-1, -1, "name", '"hello"', None)
223
224
  }, [])
224
225
  ]))
@@ -229,7 +230,7 @@ def test_component_with_single_quote_argument():
229
230
  source, fragment = grammar.expect_fragment(source)
230
231
  assert source.at_end()
231
232
  assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
232
- ASTComponent(-1, -1, "MyComp", {
233
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {
233
234
  "name": ASTComponentArgument(-1, -1, "name", "'hello'", None)
234
235
  }, [])
235
236
  ]))
@@ -240,7 +241,7 @@ def test_component_with_interpolation_argument():
240
241
  source, fragment = grammar.expect_fragment(source)
241
242
  assert source.at_end()
242
243
  assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
243
- ASTComponent(-1, -1, "MyComp", {
244
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {
244
245
  "value": ASTComponentArgument(-1, -1, "value", None, ASTInterpolation(-1, -1, "expr", 1, 1))
245
246
  }, [])
246
247
  ]))
@@ -251,7 +252,7 @@ def test_component_with_children():
251
252
  source, fragment = grammar.expect_fragment(source)
252
253
  assert source.at_end()
253
254
  assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
254
- ASTComponent(-1, -1, "MyComp", {}, [
255
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {}, [
255
256
  ASTHTMLElement(-1, -1, "p", {}, [ASTHTMLText(-1, -1, "text")], False)
256
257
  ])
257
258
  ]))
@@ -262,7 +263,7 @@ def test_component_whitespace_stripped_from_children():
262
263
  source, fragment = grammar.expect_fragment(source)
263
264
  assert source.at_end()
264
265
  assert _transpiled(fragment) == _transpiled(ASTFragment(-1, -1, [
265
- ASTComponent(-1, -1, "MyComp", {}, [
266
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {}, [
266
267
  ASTHTMLElement(-1, -1, "p", {}, [ASTHTMLText(-1, -1, "text")], False)
267
268
  ])
268
269
  ]))
@@ -276,7 +277,7 @@ def test_component_with_if():
276
277
  ASTControlNode(-1, -1,
277
278
  ASTInterpolation(-1, -1, "condition", 1, 1),
278
279
  None,
279
- ASTComponent(-1, -1, "MyComp", {}, []),
280
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {}, []),
280
281
  )
281
282
  ]))
282
283
 
@@ -289,7 +290,7 @@ def test_component_with_for():
289
290
  ASTControlNode(-1, -1,
290
291
  None,
291
292
  ASTInterpolation(-1, -1, "item in items", 1, 1),
292
- ASTComponent(-1, -1, "MyComp", {}, []),
293
+ ASTComponent(-1, -1, ASTComponentName(-1, -1, "MyComp"), {}, []),
293
294
  )
294
295
  ]))
295
296