pywire 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/METADATA +23 -1
- pywire-0.1.1.dist-info/RECORD +9 -0
- pywire/__init__.py +0 -2
- pywire/cli/__init__.py +0 -1
- pywire/cli/generators.py +0 -48
- pywire/cli/main.py +0 -309
- pywire/cli/tui.py +0 -563
- pywire/cli/validate.py +0 -26
- pywire/client/.prettierignore +0 -8
- pywire/client/.prettierrc +0 -7
- pywire/client/build.mjs +0 -73
- pywire/client/eslint.config.js +0 -46
- pywire/client/package.json +0 -39
- pywire/client/pnpm-lock.yaml +0 -2971
- pywire/client/src/core/app.ts +0 -263
- pywire/client/src/core/dom-updater.test.ts +0 -78
- pywire/client/src/core/dom-updater.ts +0 -321
- pywire/client/src/core/index.ts +0 -5
- pywire/client/src/core/transport-manager.test.ts +0 -179
- pywire/client/src/core/transport-manager.ts +0 -159
- pywire/client/src/core/transports/base.ts +0 -122
- pywire/client/src/core/transports/http.ts +0 -142
- pywire/client/src/core/transports/index.ts +0 -13
- pywire/client/src/core/transports/websocket.ts +0 -97
- pywire/client/src/core/transports/webtransport.ts +0 -149
- pywire/client/src/dev/dev-app.ts +0 -93
- pywire/client/src/dev/error-trace.test.ts +0 -97
- pywire/client/src/dev/error-trace.ts +0 -76
- pywire/client/src/dev/index.ts +0 -4
- pywire/client/src/dev/status-overlay.ts +0 -63
- pywire/client/src/events/handler.test.ts +0 -318
- pywire/client/src/events/handler.ts +0 -454
- pywire/client/src/pywire.core.ts +0 -22
- pywire/client/src/pywire.dev.ts +0 -27
- pywire/client/tsconfig.json +0 -17
- pywire/client/vitest.config.ts +0 -15
- pywire/compiler/__init__.py +0 -6
- pywire/compiler/ast_nodes.py +0 -304
- pywire/compiler/attributes/__init__.py +0 -6
- pywire/compiler/attributes/base.py +0 -24
- pywire/compiler/attributes/conditional.py +0 -37
- pywire/compiler/attributes/events.py +0 -55
- pywire/compiler/attributes/form.py +0 -37
- pywire/compiler/attributes/loop.py +0 -75
- pywire/compiler/attributes/reactive.py +0 -34
- pywire/compiler/build.py +0 -28
- pywire/compiler/build_artifacts.py +0 -342
- pywire/compiler/codegen/__init__.py +0 -5
- pywire/compiler/codegen/attributes/__init__.py +0 -6
- pywire/compiler/codegen/attributes/base.py +0 -19
- pywire/compiler/codegen/attributes/events.py +0 -35
- pywire/compiler/codegen/directives/__init__.py +0 -6
- pywire/compiler/codegen/directives/base.py +0 -16
- pywire/compiler/codegen/directives/path.py +0 -53
- pywire/compiler/codegen/generator.py +0 -2341
- pywire/compiler/codegen/template.py +0 -2178
- pywire/compiler/directives/__init__.py +0 -7
- pywire/compiler/directives/base.py +0 -20
- pywire/compiler/directives/component.py +0 -33
- pywire/compiler/directives/context.py +0 -93
- pywire/compiler/directives/layout.py +0 -49
- pywire/compiler/directives/no_spa.py +0 -24
- pywire/compiler/directives/path.py +0 -71
- pywire/compiler/directives/props.py +0 -88
- pywire/compiler/exceptions.py +0 -19
- pywire/compiler/interpolation/__init__.py +0 -6
- pywire/compiler/interpolation/base.py +0 -28
- pywire/compiler/interpolation/jinja.py +0 -272
- pywire/compiler/parser.py +0 -750
- pywire/compiler/paths.py +0 -29
- pywire/compiler/preprocessor.py +0 -43
- pywire/core/wire.py +0 -119
- pywire/py.typed +0 -0
- pywire/runtime/__init__.py +0 -7
- pywire/runtime/aioquic_server.py +0 -194
- pywire/runtime/app.py +0 -889
- pywire/runtime/compile_error_page.py +0 -195
- pywire/runtime/debug.py +0 -203
- pywire/runtime/dev_server.py +0 -434
- pywire/runtime/dev_server.py.broken +0 -268
- pywire/runtime/error_page.py +0 -64
- pywire/runtime/error_renderer.py +0 -23
- pywire/runtime/escape.py +0 -23
- pywire/runtime/files.py +0 -40
- pywire/runtime/helpers.py +0 -97
- pywire/runtime/http_transport.py +0 -253
- pywire/runtime/loader.py +0 -272
- pywire/runtime/logging.py +0 -72
- pywire/runtime/page.py +0 -384
- pywire/runtime/pydantic_integration.py +0 -52
- pywire/runtime/router.py +0 -229
- pywire/runtime/server.py +0 -25
- pywire/runtime/style_collector.py +0 -31
- pywire/runtime/upload_manager.py +0 -76
- pywire/runtime/validation.py +0 -449
- pywire/runtime/websocket.py +0 -665
- pywire/runtime/webtransport_handler.py +0 -195
- pywire-0.1.0.dist-info/RECORD +0 -104
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/WHEEL +0 -0
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/entry_points.txt +0 -0
- {pywire-0.1.0.dist-info → pywire-0.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
"""Jinja2-based interpolation parser."""
|
|
2
|
-
|
|
3
|
-
import ast
|
|
4
|
-
from typing import List, Union
|
|
5
|
-
|
|
6
|
-
from jinja2 import Environment
|
|
7
|
-
|
|
8
|
-
from pywire.compiler.ast_nodes import InterpolationNode
|
|
9
|
-
from pywire.compiler.interpolation.base import InterpolationParser
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class JinjaInterpolationParser(InterpolationParser):
|
|
13
|
-
"""Jinja2-based interpolation parser."""
|
|
14
|
-
|
|
15
|
-
def __init__(self) -> None:
|
|
16
|
-
self.env = Environment(
|
|
17
|
-
variable_start_string="{",
|
|
18
|
-
variable_end_string="}",
|
|
19
|
-
autoescape=True, # XSS protection
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
def _is_valid_python(self, text: str) -> bool:
|
|
23
|
-
"""Check if text is valid Python expression (or with format spec)."""
|
|
24
|
-
stripped = text.strip()
|
|
25
|
-
|
|
26
|
-
# 1. Try simple parse
|
|
27
|
-
# 1. Try simple parse
|
|
28
|
-
try:
|
|
29
|
-
from pywire.compiler.preprocessor import preprocess_python_code
|
|
30
|
-
|
|
31
|
-
preprocessed = preprocess_python_code(stripped)
|
|
32
|
-
ast.parse(preprocessed, mode="eval")
|
|
33
|
-
return True
|
|
34
|
-
except SyntaxError:
|
|
35
|
-
pass
|
|
36
|
-
|
|
37
|
-
# 2. CSS-like check: If unparseable and contains semicolon, assume CSS
|
|
38
|
-
if ";" in text:
|
|
39
|
-
return False
|
|
40
|
-
|
|
41
|
-
# 3. Format specifier check: Find top-level colon
|
|
42
|
-
balance = 0
|
|
43
|
-
quote = None
|
|
44
|
-
split_idx = -1
|
|
45
|
-
|
|
46
|
-
for i, char in enumerate(text):
|
|
47
|
-
if quote:
|
|
48
|
-
if char == quote:
|
|
49
|
-
if i > 0 and text[i - 1] != "\\":
|
|
50
|
-
quote = None
|
|
51
|
-
else:
|
|
52
|
-
if char in "\"'":
|
|
53
|
-
quote = char
|
|
54
|
-
elif char in "{[(":
|
|
55
|
-
balance += 1
|
|
56
|
-
elif char in "}])":
|
|
57
|
-
balance -= 1
|
|
58
|
-
elif char == ":" and balance == 0:
|
|
59
|
-
split_idx = i
|
|
60
|
-
break
|
|
61
|
-
|
|
62
|
-
if split_idx != -1:
|
|
63
|
-
# We strip the expression part to allow "{ x :.2f }"
|
|
64
|
-
expr = text[:split_idx].strip()
|
|
65
|
-
try:
|
|
66
|
-
ast.parse(expr, mode="eval")
|
|
67
|
-
return True
|
|
68
|
-
except SyntaxError:
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
return False
|
|
72
|
-
|
|
73
|
-
def parse(
|
|
74
|
-
self, text: str, line: int, col: int
|
|
75
|
-
) -> List[Union[str, InterpolationNode]]:
|
|
76
|
-
"""
|
|
77
|
-
Parse text with {expression} into mix of strings and InterpolationNodes.
|
|
78
|
-
Returns: ['Hello, ', InterpolationNode(expr='name'), '!']
|
|
79
|
-
Supports complex expressions: {"text" if condition else "other"}
|
|
80
|
-
"""
|
|
81
|
-
if not text:
|
|
82
|
-
return [""]
|
|
83
|
-
|
|
84
|
-
tokens: List[Union[str, InterpolationNode]] = []
|
|
85
|
-
i = 0
|
|
86
|
-
last_end = 0
|
|
87
|
-
|
|
88
|
-
while i < len(text):
|
|
89
|
-
if text[i] == "{":
|
|
90
|
-
# Find matching closing brace
|
|
91
|
-
brace_count = 1
|
|
92
|
-
j = i + 1
|
|
93
|
-
while j < len(text) and brace_count > 0:
|
|
94
|
-
if text[j] == "{":
|
|
95
|
-
brace_count += 1
|
|
96
|
-
elif text[j] == "}":
|
|
97
|
-
brace_count -= 1
|
|
98
|
-
j += 1
|
|
99
|
-
|
|
100
|
-
if brace_count == 0:
|
|
101
|
-
# Found matching brace
|
|
102
|
-
# Add any text before this brace
|
|
103
|
-
if i > last_end:
|
|
104
|
-
tokens.append(text[last_end:i])
|
|
105
|
-
|
|
106
|
-
expr = text[i + 1 : j - 1] # Extract expression without braces
|
|
107
|
-
|
|
108
|
-
# Check for {$html expr} syntax for raw/unescaped output
|
|
109
|
-
is_raw = False
|
|
110
|
-
if expr.lstrip().startswith("$html "):
|
|
111
|
-
is_raw = True
|
|
112
|
-
expr = expr.lstrip()[6:] # Strip "$html " prefix
|
|
113
|
-
|
|
114
|
-
if self._is_valid_python(expr):
|
|
115
|
-
# Calculate accurate line/col
|
|
116
|
-
# Count newlines before this position (relative to start of text)
|
|
117
|
-
prefix = text[:i]
|
|
118
|
-
newlines = prefix.count("\n")
|
|
119
|
-
current_line = line + newlines
|
|
120
|
-
|
|
121
|
-
last_nl_index = prefix.rfind("\n")
|
|
122
|
-
if last_nl_index != -1:
|
|
123
|
-
# Column is offset from last newline
|
|
124
|
-
current_column = i - last_nl_index - 1 # 0-indexed column?
|
|
125
|
-
# If text lines are 0-indexed column wise?
|
|
126
|
-
# Standard is 0-indexed usually for AST col_offset.
|
|
127
|
-
else:
|
|
128
|
-
# No newline, add to start col
|
|
129
|
-
current_column = col + i
|
|
130
|
-
|
|
131
|
-
tokens.append(
|
|
132
|
-
InterpolationNode(
|
|
133
|
-
expression=expr,
|
|
134
|
-
line=current_line,
|
|
135
|
-
column=current_column,
|
|
136
|
-
is_raw=is_raw,
|
|
137
|
-
)
|
|
138
|
-
)
|
|
139
|
-
else:
|
|
140
|
-
# Treat as literal
|
|
141
|
-
tokens.append(text[i:j])
|
|
142
|
-
|
|
143
|
-
last_end = j
|
|
144
|
-
i = j
|
|
145
|
-
else:
|
|
146
|
-
# Unmatched brace
|
|
147
|
-
i += 1
|
|
148
|
-
else:
|
|
149
|
-
i += 1
|
|
150
|
-
|
|
151
|
-
# Add any remaining text
|
|
152
|
-
if last_end < len(text):
|
|
153
|
-
tokens.append(text[last_end:])
|
|
154
|
-
|
|
155
|
-
# Post-process to merge adjacent strings
|
|
156
|
-
if not tokens:
|
|
157
|
-
return [text]
|
|
158
|
-
|
|
159
|
-
result: List[Union[str, InterpolationNode]] = []
|
|
160
|
-
current_str: List[str] = []
|
|
161
|
-
|
|
162
|
-
for token in tokens:
|
|
163
|
-
if isinstance(token, str):
|
|
164
|
-
current_str.append(token)
|
|
165
|
-
else:
|
|
166
|
-
if current_str:
|
|
167
|
-
merged = "".join(current_str)
|
|
168
|
-
if merged:
|
|
169
|
-
result.append(merged)
|
|
170
|
-
current_str = []
|
|
171
|
-
result.append(token)
|
|
172
|
-
|
|
173
|
-
if current_str:
|
|
174
|
-
merged = "".join(current_str)
|
|
175
|
-
if merged:
|
|
176
|
-
result.append(merged)
|
|
177
|
-
|
|
178
|
-
return result if result else [""]
|
|
179
|
-
|
|
180
|
-
def compile(self, text: str) -> str:
|
|
181
|
-
"""
|
|
182
|
-
Compile to Python f-string code for runtime.
|
|
183
|
-
'Hello {name}!' → f'Hello {self.name}!'
|
|
184
|
-
'Hello {"text" if cond else "other"}' → f'Hello {"text" if self.cond else "other"}'
|
|
185
|
-
"""
|
|
186
|
-
if not text:
|
|
187
|
-
return "''"
|
|
188
|
-
|
|
189
|
-
# For now, use simple replacement for self. references
|
|
190
|
-
# This is a simplification - ideally we'd parse the expression AST
|
|
191
|
-
import re
|
|
192
|
-
|
|
193
|
-
result = []
|
|
194
|
-
i = 0
|
|
195
|
-
last_end = 0
|
|
196
|
-
|
|
197
|
-
while i < len(text):
|
|
198
|
-
if text[i] == "{":
|
|
199
|
-
# Add text before brace
|
|
200
|
-
if i > last_end:
|
|
201
|
-
result.append(text[last_end:i])
|
|
202
|
-
|
|
203
|
-
# Find matching closing brace
|
|
204
|
-
brace_count = 1
|
|
205
|
-
j = i + 1
|
|
206
|
-
while j < len(text) and brace_count > 0:
|
|
207
|
-
if text[j] == "{":
|
|
208
|
-
brace_count += 1
|
|
209
|
-
elif text[j] == "}":
|
|
210
|
-
brace_count -= 1
|
|
211
|
-
j += 1
|
|
212
|
-
|
|
213
|
-
if brace_count == 0:
|
|
214
|
-
# Found matching brace
|
|
215
|
-
# CHECK IF VALID PYTHON before trying to compile
|
|
216
|
-
# (Though compile is usually called on text that parse() has
|
|
217
|
-
# already mostly validated,
|
|
218
|
-
# parse() returns nodes for valid interpolations.
|
|
219
|
-
# Wait, template codegen calls compile() on text_content of nodes.
|
|
220
|
-
# If parse() returned literal text for CSS, then compile() will
|
|
221
|
-
# see the curly braces!
|
|
222
|
-
# And compile() iterates braces independently.
|
|
223
|
-
# So compile() MUST also respect the validity check!)
|
|
224
|
-
|
|
225
|
-
expr = text[i + 1 : j - 1]
|
|
226
|
-
if self._is_valid_python(expr):
|
|
227
|
-
# Prepend self. to simple identifiers
|
|
228
|
-
# For simple identifiers, add self.
|
|
229
|
-
# For complex expressions, leave as is (they reference self.* already)
|
|
230
|
-
if re.match(r"^\w+$", expr):
|
|
231
|
-
result.append(f"{{self.{expr}}}")
|
|
232
|
-
else:
|
|
233
|
-
# Complex expression - assume it references self correctly
|
|
234
|
-
# Replace standalone identifiers with self. references
|
|
235
|
-
# This is simplistic but works for common cases
|
|
236
|
-
modified_expr = re.sub(
|
|
237
|
-
r"\b([a-zA-Z_]\w*)\b(?!\s*[(\[])",
|
|
238
|
-
lambda m: f"self.{str(m.group(1))}"
|
|
239
|
-
if m.group(1)
|
|
240
|
-
not in (
|
|
241
|
-
"if",
|
|
242
|
-
"else",
|
|
243
|
-
"and",
|
|
244
|
-
"or",
|
|
245
|
-
"not",
|
|
246
|
-
"in",
|
|
247
|
-
"is",
|
|
248
|
-
"True",
|
|
249
|
-
"False",
|
|
250
|
-
"None",
|
|
251
|
-
)
|
|
252
|
-
else m.group(1),
|
|
253
|
-
expr,
|
|
254
|
-
)
|
|
255
|
-
result.append(f"{{{modified_expr}}}")
|
|
256
|
-
else:
|
|
257
|
-
# Literal (CSS etc)
|
|
258
|
-
result.append(text[i:j])
|
|
259
|
-
|
|
260
|
-
last_end = j
|
|
261
|
-
i = j
|
|
262
|
-
else:
|
|
263
|
-
i += 1
|
|
264
|
-
else:
|
|
265
|
-
i += 1
|
|
266
|
-
|
|
267
|
-
# Add remaining text
|
|
268
|
-
if last_end < len(text):
|
|
269
|
-
result.append(text[last_end:])
|
|
270
|
-
|
|
271
|
-
compiled = "".join(result)
|
|
272
|
-
return f"f{repr(compiled)}"
|