python-fragments 0.9__tar.gz → 0.12__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.9 → python_fragments-0.12}/PKG-INFO +1 -1
  2. {python_fragments-0.9 → python_fragments-0.12}/fragments/ast_nodes.py +199 -79
  3. python_fragments-0.12/fragments/cli.py +44 -0
  4. {python_fragments-0.9 → python_fragments-0.12}/fragments/grammar.py +94 -33
  5. {python_fragments-0.9 → python_fragments-0.12}/fragments/html/elements.py +1 -1
  6. {python_fragments-0.9 → python_fragments-0.12}/fragments/source.py +7 -0
  7. {python_fragments-0.9 → python_fragments-0.12}/pyproject.toml +1 -1
  8. {python_fragments-0.9 → python_fragments-0.12}/python_fragments.egg-info/PKG-INFO +1 -1
  9. python_fragments-0.12/tests/test_grammar.py +333 -0
  10. {python_fragments-0.9 → python_fragments-0.12}/tests/test_source_map.py +113 -2
  11. python_fragments-0.9/fragments/cli.py +0 -20
  12. python_fragments-0.9/tests/test_grammar.py +0 -86
  13. {python_fragments-0.9 → python_fragments-0.12}/README.md +0 -0
  14. {python_fragments-0.9 → python_fragments-0.12}/fragments/__init__.py +0 -0
  15. {python_fragments-0.9 → python_fragments-0.12}/fragments/html/__init__.py +0 -0
  16. {python_fragments-0.9 → python_fragments-0.12}/fragments/loader.py +0 -0
  17. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/__init__.py +0 -0
  18. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/based_proxy.py +0 -0
  19. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/__init__.py +0 -0
  20. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/code_actions.py +0 -0
  21. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/completion.py +0 -0
  22. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/definition.py +0 -0
  23. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/diagnostics.py +0 -0
  24. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/document_highlight.py +0 -0
  25. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/document_symbols.py +0 -0
  26. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/folding_range.py +0 -0
  27. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/hover.py +0 -0
  28. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/inlay_hints.py +0 -0
  29. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/lifecycle.py +0 -0
  30. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/references.py +0 -0
  31. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/rename.py +0 -0
  32. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/semantic_tokens.py +0 -0
  33. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/client_message_handlers/signature_help.py +0 -0
  34. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/file_state.py +0 -0
  35. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/message_queue.py +0 -0
  36. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/pyright_notification_handlers/__init__.py +0 -0
  37. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/pyright_notification_handlers/capability.py +0 -0
  38. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/pyright_notification_handlers/configuration.py +0 -0
  39. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/pyright_notification_handlers/diagnostics.py +0 -0
  40. {python_fragments-0.9 → python_fragments-0.12}/fragments/lsp/types.py +0 -0
  41. {python_fragments-0.9 → python_fragments-0.12}/fragments/transpiler.py +0 -0
  42. {python_fragments-0.9 → python_fragments-0.12}/python_fragments.egg-info/SOURCES.txt +0 -0
  43. {python_fragments-0.9 → python_fragments-0.12}/python_fragments.egg-info/dependency_links.txt +0 -0
  44. {python_fragments-0.9 → python_fragments-0.12}/python_fragments.egg-info/entry_points.txt +0 -0
  45. {python_fragments-0.9 → python_fragments-0.12}/python_fragments.egg-info/requires.txt +0 -0
  46. {python_fragments-0.9 → python_fragments-0.12}/python_fragments.egg-info/top_level.txt +0 -0
  47. {python_fragments-0.9 → python_fragments-0.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fragments
3
- Version: 0.9
3
+ Version: 0.12
4
4
  Summary: Modern HTML template rendering in Python
5
5
  Author-email: The Running Algorithm <services@therunningalgorithm.info>
6
6
  License: Proprietary
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Sequence
3
3
 
4
+ type ASTHTMLChild = ASTHTMLElement | ASTHTMLComment | ASTHTMLText | ASTInterpolation | ASTComponent | ASTControlNode
5
+
4
6
 
5
7
  @dataclass(slots=True)
6
8
  class ASTModule:
@@ -29,12 +31,12 @@ class ASTModule:
29
31
 
30
32
  def map_offset(self, offset: int) -> int | None:
31
33
  for owner in self.children:
32
- if owner.source_start < offset <= owner.source_end:
34
+ if owner.source_start <= offset <= owner.source_end:
33
35
  return owner.map_offset(offset)
34
36
 
35
37
  def unmap_offset(self, offset: int) -> int | None:
36
38
  for owner in self.children:
37
- if owner.transpiled_start < offset <= owner.transpiled_end:
39
+ if owner.transpiled_start <= offset <= owner.transpiled_end:
38
40
  return owner.unmap_offset(offset)
39
41
 
40
42
 
@@ -56,14 +58,14 @@ class ASTPython:
56
58
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
57
59
 
58
60
  def map_offset(self, offset: int) -> int | None:
59
- if self.source_start < offset <= self.source_end:
61
+ if self.source_start <= offset <= self.source_end:
60
62
  specific_offset = offset - self.source_start
61
63
  return self.transpiled_start + specific_offset
62
64
 
63
65
  return None
64
66
 
65
67
  def unmap_offset(self, offset: int) -> int | None:
66
- if self.transpiled_start < offset <= self.transpiled_end:
68
+ if self.transpiled_start <= offset <= self.transpiled_end:
67
69
  specific_offset = offset - self.transpiled_start
68
70
  return self.source_start + specific_offset
69
71
 
@@ -75,7 +77,7 @@ class ASTFragment:
75
77
  source_start: int = field(compare=False)
76
78
  source_end: int = field(compare=False)
77
79
 
78
- children: list["ASTHTMLElement | ASTHTMLComment | ASTHTMLText | ASTInterpolation"]
80
+ children: list["ASTHTMLChild"]
79
81
 
80
82
  transpiled_content: str = field(init=False)
81
83
  transpiled_start: int = field(init=False)
@@ -96,14 +98,14 @@ class ASTFragment:
96
98
 
97
99
  def map_offset(self, offset: int) -> int | None:
98
100
  for owner in self.children:
99
- if owner.source_start < offset <= owner.source_end:
101
+ if owner.source_start <= offset <= owner.source_end:
100
102
  return owner.map_offset(offset)
101
103
 
102
104
  return None
103
105
 
104
106
  def unmap_offset(self, offset: int) -> int | None:
105
107
  for owner in self.children:
106
- if owner.transpiled_start < offset <= owner.transpiled_end:
108
+ if owner.transpiled_start <= offset <= owner.transpiled_end:
107
109
  return owner.unmap_offset(offset)
108
110
 
109
111
  return None
@@ -116,114 +118,229 @@ class ASTHTMLElement:
116
118
 
117
119
  name: str
118
120
  attributes: dict[str, "ASTHTMLAttribute"]
119
- if_attribute: "ASTInterpolation | None"
120
- for_attribute: "ASTInterpolation | None"
121
- children: Sequence["ASTHTMLElement | ASTHTMLComment | ASTHTMLText | ASTInterpolation"]
121
+ children: Sequence["ASTHTMLChild"]
122
122
  one_line: bool
123
123
 
124
124
  transpiled_content: str = field(init=False)
125
125
  transpiled_start: int = field(init=False)
126
126
  transpiled_end: int = field(init=False)
127
127
 
128
- __for_template__: str = """sequence([{} for {}])"""
129
- __if_template__: str = """{} if {} else ''"""
130
- __element_template__: str = """el("{}",[{}],oneline={},{})"""
131
- __component_template__: str = """{}([{}],{})"""
128
+ __element_template__: str = """el("{}",[{}],oneline={},attributes={})"""
132
129
 
133
130
  def transpile(self, transpiled_start: int) -> None:
134
131
  self.transpiled_start = transpiled_start
135
-
136
- if self.for_attribute is not None:
137
- transpiled_start += 10
138
-
139
- if self.name[0].capitalize() == self.name[0]:
140
- self._transpile_component_call(transpiled_start)
141
- else:
142
- self._transpile_element_call(transpiled_start)
143
-
144
- if self.if_attribute is not None:
145
- self.if_attribute.transpile(self.transpiled_end + 4)
146
- self.transpiled_content = self.__if_template__.format(self.transpiled_content, self.if_attribute.transpiled_content)
147
- self.transpiled_end = self.if_attribute.transpiled_end + 8
148
- elif self.for_attribute is not None:
149
- self.for_attribute.transpile(self.transpiled_end + 5)
150
- self.transpiled_content = self.__for_template__.format(self.transpiled_content, self.for_attribute.transpiled_content)
151
- self.transpiled_end = self.for_attribute.transpiled_end + 2
152
-
153
- def _transpile_element_call(self, start: int) -> None:
154
- transpiled_start = start + len(self.name) + 7
132
+ transpiled_start = transpiled_start + len(self.name) + 7
155
133
  for child in self.children:
156
134
  child.transpile(transpiled_start)
157
135
  transpiled_start = child.transpiled_end + 1
158
- transpiled_start -= 1
136
+ if self.children:
137
+ transpiled_start -= 1
159
138
  children = ",".join(child.transpiled_content for child in self.children)
160
-
161
139
  oneline_offset = len(str(self.one_line))
162
-
163
- transpiled_start += 10 + oneline_offset + 1
164
-
140
+ transpiled_start += 10 + oneline_offset + 12 + 1 # ],oneline= + oneline + ,attributes= + {
165
141
  for attribute in self.attributes.values():
166
142
  attribute.transpile(transpiled_start)
167
143
  transpiled_start = attribute.transpiled_end + 1
168
144
  transpiled_start -= 1
169
145
 
170
- attributes = ",".join(attribute.transpiled_content for attribute in self.attributes.values())
146
+ attributes = "{" + ",".join(attribute.transpiled_content for attribute in self.attributes.values()) + "}"
171
147
  self.transpiled_content = self.__element_template__.format(self.name, children, self.one_line, attributes)
172
- self.transpiled_end = start + len(self.transpiled_content)
148
+ self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
149
+
150
+ def map_offset(self, offset: int) -> int | None:
151
+ for attribute in self.attributes.values():
152
+ if attribute.source_start <= offset <= attribute.source_end:
153
+ return attribute.map_offset(offset)
154
+
155
+ for child in self.children:
156
+ if child.source_start <= offset <= child.source_end:
157
+ return child.map_offset(offset)
158
+
159
+ return None
160
+
161
+ def unmap_offset(self, offset: int) -> int | None:
162
+ for attribute in self.attributes.values():
163
+ if attribute.transpiled_start <= offset <= attribute.transpiled_end:
164
+ return attribute.unmap_offset(offset)
173
165
 
174
- def _transpile_component_call(self, start: int) -> None:
175
- transpiled_start = start + len(self.name) + 2
166
+ for child in self.children:
167
+ if child.transpiled_start <= offset <= child.transpiled_end:
168
+ return child.unmap_offset(offset)
169
+
170
+ return None
171
+
172
+
173
+ @dataclass(slots=True)
174
+ class ASTControlNode[T: (ASTHTMLElement, ASTComponent)]:
175
+ source_start: int = field(compare=False)
176
+ source_end: int = field(compare=False)
177
+
178
+ if_interpolation: "ASTInterpolation | None"
179
+ for_interpolation: "ASTInterpolation | None"
180
+ child: T
181
+
182
+ transpiled_content: str = field(init=False)
183
+ transpiled_start: int = field(init=False)
184
+ transpiled_end: int = field(init=False)
185
+
186
+ __for_template__: str = "sequence([{} for {}])"
187
+ __if_template__: str = "{} if {} else ''"
188
+
189
+ def transpile(self, transpiled_start: int) -> None:
190
+ self.transpiled_start = transpiled_start
191
+ if self.for_interpolation is not None:
192
+ self.child.transpile(transpiled_start + 10) # sequence([
193
+ self.for_interpolation.transpile(self.child.transpiled_end + 5) # child + for
194
+ self.transpiled_content = self.__for_template__.format(self.child.transpiled_content, self.for_interpolation.transpiled_content)
195
+ elif self.if_interpolation is not None:
196
+ self.child.transpile(transpiled_start)
197
+ self.if_interpolation.transpile(self.child.transpiled_end + 4) # child + if
198
+ self.transpiled_content = self.__if_template__.format(self.child.transpiled_content, self.if_interpolation.transpiled_content)
199
+ self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
200
+
201
+ def map_offset(self, offset: int) -> int | None:
202
+ if self.if_interpolation is not None and self.if_interpolation.source_start <= offset <= self.if_interpolation.source_end:
203
+ return self.if_interpolation.map_offset(offset)
204
+
205
+ if self.for_interpolation is not None and self.for_interpolation.source_start <= offset <= self.for_interpolation.source_end:
206
+ return self.for_interpolation.map_offset(offset)
207
+
208
+ if self.child.source_start <= offset <= self.child.source_end:
209
+ return self.child.map_offset(offset)
210
+
211
+ return None
212
+
213
+ def unmap_offset(self, offset: int) -> int | None:
214
+ if self.child.transpiled_start <= offset <= self.child.transpiled_end:
215
+ return self.child.unmap_offset(offset)
216
+
217
+ if self.if_interpolation is not None and self.if_interpolation.transpiled_start <= offset <= self.if_interpolation.transpiled_end:
218
+ return self.if_interpolation.unmap_offset(offset)
219
+
220
+ if self.for_interpolation is not None and self.for_interpolation.transpiled_start <= offset <= self.for_interpolation.transpiled_end:
221
+ return self.for_interpolation.unmap_offset(offset)
222
+
223
+ return None
224
+
225
+ @classmethod
226
+ def wrap_child(
227
+ cls,
228
+ child: T,
229
+ if_interpolation: "ASTInterpolation | None",
230
+ for_interpolation: "ASTInterpolation | None",
231
+ ) -> "ASTControlNode[T] | T":
232
+ """If the child needs a control node, wrap it."""
233
+ if if_interpolation is None and for_interpolation is None:
234
+ return child
235
+
236
+ return ASTControlNode(child.source_start, child.source_end, if_interpolation, for_interpolation, child)
237
+
238
+
239
+ @dataclass(slots=True)
240
+ class ASTComponent:
241
+ source_start: int = field(compare=False)
242
+ source_end: int = field(compare=False)
243
+
244
+ name: str
245
+ arguments: dict[str, "ASTComponentArgument"]
246
+ children: Sequence["ASTHTMLChild"]
247
+
248
+ transpiled_content: str = field(init=False)
249
+ transpiled_start: int = field(init=False)
250
+ transpiled_end: int = field(init=False)
251
+
252
+ __template__: str = """{}([{}],{})"""
253
+
254
+ def transpile(self, transpiled_start: int) -> None:
255
+ self.transpiled_start = transpiled_start
256
+ transpiled_start = transpiled_start + len(self.name) + 2
176
257
  for child in self.children:
177
258
  child.transpile(transpiled_start)
178
259
  transpiled_start = child.transpiled_end + 1
179
- transpiled_start -= 1
260
+ if self.children:
261
+ transpiled_start -= 1
180
262
  children = ",".join(child.transpiled_content for child in self.children)
181
263
 
182
- if len(self.attributes) == 0:
183
- self.transpiled_content = self.__component_template__.format(self.name, children, "")
184
- self.transpiled_end = start + len(self.transpiled_content)
264
+ if len(self.arguments) == 0:
265
+ self.transpiled_content = self.__template__.format(self.name, children, "")
266
+ self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
185
267
  return
186
268
 
187
269
  transpiled_start += 2
188
- for attribute in self.attributes.values():
270
+ for attribute in self.arguments.values():
189
271
  attribute.transpile(transpiled_start)
190
272
  transpiled_start = attribute.transpiled_end + 1
191
273
 
192
- attributes = ",".join(attribute.transpiled_content for attribute in self.attributes.values())
193
- self.transpiled_content = self.__component_template__.format(self.name, children, attributes)
194
- self.transpiled_end = start + len(self.transpiled_content)
274
+ 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_end = self.transpiled_start + len(self.transpiled_content)
195
277
 
196
278
  def map_offset(self, offset: int) -> int | None:
197
- for attribute in self.attributes.values():
198
- if attribute.source_start < offset <= attribute.source_end:
279
+ for attribute in self.arguments.values():
280
+ if attribute.source_start <= offset <= attribute.source_end:
199
281
  return attribute.map_offset(offset)
200
282
 
201
283
  for child in self.children:
202
- if child.source_start < offset <= child.source_end:
284
+ if child.source_start <= offset <= child.source_end:
203
285
  return child.map_offset(offset)
204
286
 
205
- if self.if_attribute is not None and self.if_attribute.source_start < offset <= self.if_attribute.source_end:
206
- return self.if_attribute.map_offset(offset)
207
-
208
- if self.for_attribute is not None and self.for_attribute.source_start < offset <= self.for_attribute.source_end:
209
- return self.for_attribute.map_offset(offset)
210
-
211
287
  return None
212
288
 
213
289
  def unmap_offset(self, offset: int) -> int | None:
214
- for attribute in self.attributes.values():
215
- if attribute.transpiled_start < offset <= attribute.transpiled_end:
290
+ for attribute in self.arguments.values():
291
+ if attribute.transpiled_start <= offset <= attribute.transpiled_end:
216
292
  return attribute.unmap_offset(offset)
217
293
 
218
294
  for child in self.children:
219
- if child.transpiled_start < offset <= child.transpiled_end:
295
+ if child.transpiled_start <= offset <= child.transpiled_end:
220
296
  return child.unmap_offset(offset)
221
297
 
222
- if self.if_attribute is not None and self.if_attribute.transpiled_start < offset <= self.if_attribute.transpiled_end:
223
- return self.if_attribute.unmap_offset(offset)
298
+ return None
299
+
224
300
 
225
- if self.for_attribute is not None and self.for_attribute.transpiled_start < offset <= self.for_attribute.transpiled_end:
226
- return self.for_attribute.unmap_offset(offset)
301
+ @dataclass(slots=True)
302
+ class ASTComponentArgument:
303
+ source_start: int = field(compare=False)
304
+ source_end: int = field(compare=False)
305
+
306
+ name: str
307
+ string_literal: str | None
308
+ interpolation: "ASTInterpolation | None"
309
+
310
+ transpiled_content: str = field(init=False)
311
+ transpiled_start: int = field(init=False)
312
+ transpiled_end: int = field(init=False)
313
+
314
+ __template__: str = "{}={}"
315
+
316
+ def transpile(self, transpiled_start: int) -> None:
317
+ self.transpiled_start = transpiled_start
318
+
319
+ if self.string_literal is not None:
320
+ self.transpiled_content = self.__template__.format(self.name, self.string_literal)
321
+ self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
322
+ return
323
+
324
+ assert self.interpolation is not None
325
+ self.interpolation.transpile(self.transpiled_start + len(self.name) + 1)
326
+ self.transpiled_content = self.__template__.format(self.name, self.interpolation.transpiled_content)
327
+ self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
328
+
329
+ def map_offset(self, offset: int) -> int | None:
330
+ if self.interpolation is None:
331
+ return None
332
+
333
+ if self.interpolation.source_start <= offset <= self.interpolation.source_end:
334
+ return self.interpolation.map_offset(offset)
335
+
336
+ return None
337
+
338
+ def unmap_offset(self, offset: int) -> int | None:
339
+ if self.interpolation is None:
340
+ return None
341
+
342
+ if self.interpolation.transpiled_start <= offset <= self.interpolation.transpiled_end:
343
+ return self.interpolation.unmap_offset(offset)
227
344
 
228
345
  return None
229
346
 
@@ -259,34 +376,37 @@ class ASTHTMLAttribute:
259
376
  source_end: int = field(compare=False)
260
377
 
261
378
  name: str
262
- value: str | None
379
+ string_literal: str | None
263
380
  interpolation: "ASTInterpolation | None"
264
381
 
265
382
  transpiled_content: str = field(init=False)
266
383
  transpiled_start: int = field(init=False)
267
384
  transpiled_end: int = field(init=False)
268
385
 
269
- __value_template__: str = '{}="{}"'
270
- __interpolation_template__: str = "{}={}"
386
+ __template__: str = '"{}":{}'
271
387
 
272
388
  def transpile(self, transpiled_start: int) -> None:
273
389
  self.transpiled_start = transpiled_start
274
390
 
275
- if self.value is not None:
276
- self.transpiled_content = self.__value_template__.format(self.name, self.value)
391
+ if self.string_literal is not None:
392
+ self.transpiled_content = self.__template__.format(self.name, self.string_literal)
277
393
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
278
394
  return
279
395
 
280
- assert self.interpolation is not None
281
- self.interpolation.transpile(self.transpiled_start + len(self.name) + 1)
282
- self.transpiled_content = self.__interpolation_template__.format(self.name, self.interpolation.transpiled_content)
396
+ if self.interpolation is None:
397
+ self.transpiled_content = self.__template__.format(self.name, "None")
398
+ self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
399
+ return
400
+
401
+ self.interpolation.transpile(self.transpiled_start + 1 + len(self.name) + 2) # " + name + ":
402
+ self.transpiled_content = self.__template__.format(self.name, self.interpolation.transpiled_content)
283
403
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
284
404
 
285
405
  def map_offset(self, offset: int) -> int | None:
286
406
  if self.interpolation is None:
287
407
  return None
288
408
 
289
- if self.interpolation.source_start < offset <= self.interpolation.source_end:
409
+ if self.interpolation.source_start <= offset <= self.interpolation.source_end:
290
410
  return self.interpolation.map_offset(offset)
291
411
 
292
412
  return None
@@ -295,7 +415,7 @@ class ASTHTMLAttribute:
295
415
  if self.interpolation is None:
296
416
  return None
297
417
 
298
- if self.interpolation.transpiled_start < offset <= self.interpolation.transpiled_end:
418
+ if self.interpolation.transpiled_start <= offset <= self.interpolation.transpiled_end:
299
419
  return self.interpolation.unmap_offset(offset)
300
420
 
301
421
  return None
@@ -344,14 +464,14 @@ class ASTInterpolation:
344
464
  self.transpiled_end = self.transpiled_start + len(self.transpiled_content)
345
465
 
346
466
  def map_offset(self, offset: int) -> int | None:
347
- if self.source_start + 2 + self.leading_whitespace < offset <= self.source_end - 2 - self.trailing_whitespace:
467
+ if self.source_start + 2 + self.leading_whitespace <= offset <= self.source_end - 2 - self.trailing_whitespace:
348
468
  specific_offset = offset - self.source_start - 2 - self.leading_whitespace
349
469
  return self.transpiled_start + specific_offset
350
470
 
351
471
  return None
352
472
 
353
473
  def unmap_offset(self, offset: int) -> int | None:
354
- if self.transpiled_start < offset <= self.transpiled_end:
474
+ if self.transpiled_start <= offset <= self.transpiled_end:
355
475
  specific_offset = offset - self.transpiled_start
356
476
  return self.source_start + specific_offset + 2 + self.leading_whitespace
357
477
 
@@ -0,0 +1,44 @@
1
+ import argparse
2
+
3
+ from fragments import grammar, transpiler
4
+ from fragments.source import Source
5
+
6
+
7
+ def parse_args() -> argparse.Namespace:
8
+ parser = argparse.ArgumentParser("fragments", description="Fragments transpilation CLI tool")
9
+ command_parser = parser.add_subparsers(required=True)
10
+ transpile_parser = command_parser.add_parser("transpile")
11
+ transpile_parser.add_argument("--input", type=str, required=True)
12
+ transpile_parser.set_defaults(func=transpile)
13
+ ast_parser = command_parser.add_parser("ast")
14
+ ast_parser.add_argument("--input", type=str, required=True)
15
+ ast_parser.set_defaults(func=ast)
16
+ return parser.parse_args()
17
+
18
+
19
+ def transpile(args: argparse.Namespace) -> None:
20
+ """Transpile the specified file."""
21
+ with open(args.input, "r") as f:
22
+ source = f.read()
23
+ print(transpiler.transpile(source))
24
+
25
+
26
+ def ast(args: argparse.Namespace) -> None:
27
+ """Print the AST of the specified file."""
28
+ with open(args.input, "r") as f:
29
+ source_string = f.read()
30
+
31
+ source = Source.from_string(source_string)
32
+ _, module_ast = grammar.expect_module(source)
33
+ module_ast.transpile()
34
+ print(module_ast)
35
+
36
+
37
+ def main():
38
+ args = parse_args()
39
+ assert hasattr(args, "func")
40
+ args.func(args)
41
+
42
+
43
+ if __name__ == "__main__":
44
+ main()