tw-framework 0.2.3__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.
Files changed (51) hide show
  1. tw_framework/__init__.py +13 -0
  2. tw_framework/advanced_diagnostics.py +25 -0
  3. tw_framework/asset_optimizer.py +30 -0
  4. tw_framework/ast_nodes.py +283 -0
  5. tw_framework/build.py +7 -0
  6. tw_framework/build_performance.py +26 -0
  7. tw_framework/build_report.py +84 -0
  8. tw_framework/cli.py +1258 -0
  9. tw_framework/code_splitting.py +76 -0
  10. tw_framework/common.py +22 -0
  11. tw_framework/compiler.py +4817 -0
  12. tw_framework/compiler_stats.py +51 -0
  13. tw_framework/dead_code.py +44 -0
  14. tw_framework/dependency_graph.py +91 -0
  15. tw_framework/deploy.py +7 -0
  16. tw_framework/dev.py +9 -0
  17. tw_framework/diagnostics.py +98 -0
  18. tw_framework/dynamic_imports.py +51 -0
  19. tw_framework/error_formatter.py +53 -0
  20. tw_framework/framework.py +3216 -0
  21. tw_framework/hydration.py +129 -0
  22. tw_framework/incremental_cache.py +53 -0
  23. tw_framework/interpreter.py +42 -0
  24. tw_framework/ir.py +171 -0
  25. tw_framework/lexer.py +52 -0
  26. tw_framework/lowering.py +68 -0
  27. tw_framework/parser.py +167 -0
  28. tw_framework/partial_rebuild.py +24 -0
  29. tw_framework/performance_analyzer.py +37 -0
  30. tw_framework/plugin_runtime.py +196 -0
  31. tw_framework/production_optimizer.py +82 -0
  32. tw_framework/reactivity.py +349 -0
  33. tw_framework/render_css.py +22 -0
  34. tw_framework/render_html.py +259 -0
  35. tw_framework/route_optimizer.py +40 -0
  36. tw_framework/router.py +7 -0
  37. tw_framework/runtime_model.py +23 -0
  38. tw_framework/runtime_values.py +31 -0
  39. tw_framework/semantic.py +162 -0
  40. tw_framework/server.py +532 -0
  41. tw_framework/signature.py +51 -0
  42. tw_framework/static_dynamic_auto.py +32 -0
  43. tw_framework/streaming.py +135 -0
  44. tw_framework/test_build_cli_pytest.py +1078 -0
  45. tw_framework/tree_shaking.py +48 -0
  46. tw_framework/twm_parser.py +274 -0
  47. tw_framework-0.2.3.dist-info/METADATA +8 -0
  48. tw_framework-0.2.3.dist-info/RECORD +51 -0
  49. tw_framework-0.2.3.dist-info/WHEEL +5 -0
  50. tw_framework-0.2.3.dist-info/entry_points.txt +2 -0
  51. tw_framework-0.2.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,48 @@
1
+ """
2
+ Tree shaking for TW Framework.
3
+
4
+ Removes unused components, CSS, JavaScript, assets, and imports.
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ from typing import Dict, List, Set
10
+
11
+ from . import compiler
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def shake_project(project_root: str) -> Dict[str, List[str]]:
17
+ """Analyze the project and return lists of unused items."""
18
+ unused = {
19
+ "components": [],
20
+ "css": [],
21
+ "js": [],
22
+ "assets": [],
23
+ "imports": [],
24
+ }
25
+
26
+ # Discover all pages and their used components
27
+ pages = compiler.discover_pages()
28
+ used_components: Set[str] = set()
29
+ for page in pages:
30
+ raw = compiler.read_text_file(page["path"])
31
+ directives = compiler.extract_directives_from_source(raw, compiler.HOME_DIR)
32
+ for comp_name in directives.get("imports", []):
33
+ used_components.add(comp_name)
34
+
35
+ # Find all component files
36
+ components_dir = compiler.COMPONENTS_DIR
37
+ if os.path.isdir(components_dir):
38
+ for fname in os.listdir(components_dir):
39
+ if fname.endswith(".tw"):
40
+ comp_name = fname[:-3]
41
+ if comp_name not in used_components:
42
+ unused["components"].append(comp_name)
43
+
44
+ # TODO: Add CSS, JS, assets, and import analysis
45
+ return unused
46
+
47
+
48
+ __all__ = ["shake_project"]
@@ -0,0 +1,274 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Dict, List, Tuple
5
+
6
+
7
+ _IDENT_RE = re.compile(r"^[A-Za-z_$][A-Za-z0-9_$]*$")
8
+
9
+ # Supports:
10
+ # export async function name(...) { ... }
11
+ # async function name(...) { ... }
12
+ # export function name(...) { ... }
13
+ # function name(...) { ... }
14
+ # export async fn name(...) { ... }
15
+ # async fn name(...) { ... }
16
+ # export fn name(...) { ... }
17
+ # fn name(...) { ... }
18
+ _FUNC_HEADER_RE = re.compile(
19
+ r"""
20
+ (?P<prefix>\bexport\b\s+)?(?P<async>\basync\b\s+)?(?P<kw>\bfunction\b|\bfn\b)\s+
21
+ (?P<name>[A-Za-z_$][A-Za-z0-9_$]*)\s*
22
+ (?P<params>\([^)]*\))?\s*
23
+ \{
24
+ """,
25
+ re.VERBOSE,
26
+ )
27
+
28
+
29
+ class TWMParseError(Exception):
30
+ pass
31
+
32
+
33
+ def _scan_matching_brace(source: str, open_brace_index: int) -> int:
34
+ """
35
+ Returns index of the matching closing brace `}` for the `{` at open_brace_index.
36
+ Implements JS-like string/comment awareness so braces inside strings/comments
37
+ don't affect nesting.
38
+ """
39
+ if open_brace_index < 0 or open_brace_index >= len(source) or source[open_brace_index] != "{":
40
+ raise TWMParseError("Internal error: expected `{` at open brace index")
41
+
42
+ i = open_brace_index + 1
43
+ depth = 1
44
+ mode = "code" # code|string_d|string_s|template|line_comment|block_comment
45
+
46
+ while i < len(source):
47
+ ch = source[i]
48
+
49
+ if mode == "line_comment":
50
+ if ch == "\n":
51
+ mode = "code"
52
+ i += 1
53
+ continue
54
+
55
+ if mode == "block_comment":
56
+ if ch == "*" and i + 1 < len(source) and source[i + 1] == "/":
57
+ i += 2
58
+ mode = "code"
59
+ continue
60
+ i += 1
61
+ continue
62
+
63
+ if mode in {"string_d", "string_s"}:
64
+ if ch == "\\":
65
+ i += 2
66
+ continue
67
+ if (mode == "string_d" and ch == '"') or (mode == "string_s" and ch == "'"):
68
+ mode = "code"
69
+ i += 1
70
+ continue
71
+
72
+ if mode == "template":
73
+ if ch == "\\":
74
+ i += 2
75
+ continue
76
+ if ch == "`":
77
+ mode = "code"
78
+ i += 1
79
+ continue
80
+
81
+ # mode == "code"
82
+ if ch == "/" and i + 1 < len(source) and source[i + 1] == "/":
83
+ mode = "line_comment"
84
+ i += 2
85
+ continue
86
+ if ch == "/" and i + 1 < len(source) and source[i + 1] == "*":
87
+ mode = "block_comment"
88
+ i += 2
89
+ continue
90
+ if ch == '"':
91
+ mode = "string_d"
92
+ i += 1
93
+ continue
94
+ if ch == "'":
95
+ mode = "string_s"
96
+ i += 1
97
+ continue
98
+ if ch == "`":
99
+ mode = "template"
100
+ i += 1
101
+ continue
102
+
103
+ if ch == "{":
104
+ depth += 1
105
+ i += 1
106
+ continue
107
+ if ch == "}":
108
+ depth -= 1
109
+ if depth == 0:
110
+ return i
111
+ i += 1
112
+ continue
113
+
114
+ i += 1
115
+
116
+ raise TWMParseError("Unterminated `{ ... }` block in `.twm` source")
117
+
118
+
119
+ def parse_twm_functions(source: str) -> List[Dict[str, str]]:
120
+ """
121
+ Minimal `.twm` surface:
122
+ - `.twm` files are declarative: top-level code is NOT allowed.
123
+ - Only function declarations are supported in the MVP.
124
+ """
125
+ src = str(source or "")
126
+ functions: List[Dict[str, str]] = []
127
+
128
+ consumed_spans: List[Tuple[int, int]] = []
129
+ for match in _FUNC_HEADER_RE.finditer(src):
130
+ name = match.group("name")
131
+ params = match.group("params") or "()"
132
+ if not _IDENT_RE.match(name):
133
+ raise TWMParseError(f"Invalid function name: {name!r}")
134
+
135
+ # Find the exact opening brace for this match.
136
+ open_brace = src.find("{", match.start())
137
+ close_brace = _scan_matching_brace(src, open_brace)
138
+ body = src[open_brace + 1 : close_brace]
139
+
140
+ functions.append(
141
+ {
142
+ "name": name,
143
+ "params": params,
144
+ "body": body,
145
+ "async": bool(match.group("async")),
146
+ }
147
+ )
148
+ consumed_spans.append((match.start(), close_brace + 1))
149
+
150
+ # Enforce "no top-level execution": after removing function spans, remaining
151
+ # source must be only whitespace/comments.
152
+ scratch = list(src)
153
+ for start, end in consumed_spans:
154
+ for i in range(start, end):
155
+ scratch[i] = " "
156
+ remainder = "".join(scratch).strip()
157
+ if remainder:
158
+ # Allow top-level comments (line + block) and whitespace.
159
+ # This is safe here because function bodies were already stripped out,
160
+ # so this pass only affects truly top-level text.
161
+ remainder = re.sub(r"//.*?$", "", remainder, flags=re.MULTILINE)
162
+ remainder = re.sub(r"/\*.*?\*/", "", remainder, flags=re.DOTALL)
163
+ remainder = remainder.strip()
164
+ if remainder:
165
+ raise TWMParseError(
166
+ "Top-level statements are not allowed in `.twm`. "
167
+ "Only `function`/`fn` declarations are supported so modules never auto-execute."
168
+ )
169
+
170
+ return functions
171
+
172
+
173
+ def compile_twm_module_to_js(source: str, *, module_id: str) -> str:
174
+ funcs = parse_twm_functions(source)
175
+ lines: List[str] = []
176
+ lines.append(f"// TW module: {module_id}")
177
+ for fn in funcs:
178
+ name = fn["name"]
179
+ params = fn["params"]
180
+ body = fn["body"]
181
+ async_prefix = "async " if fn.get("async") else ""
182
+ lines.append(f"{async_prefix}function {name}{params}{{{body}\n}}")
183
+ lines.append(f"window.__twRegister('{_js_string(name)}', {name});")
184
+ lines.append("")
185
+ return "\n".join(lines)
186
+
187
+
188
+ def compile_twm_module_to_cjs(source: str, *, module_id: str) -> str:
189
+ """
190
+ Compile `.twm` into a CommonJS module for server-side execution (Node.js).
191
+
192
+ Important:
193
+ - `.twm` already forbids top-level statements, so generating a Node module
194
+ is safe from accidental auto-execution.
195
+ - This output does NOT use `window` / browser globals.
196
+ """
197
+ funcs = parse_twm_functions(source)
198
+ lines: List[str] = []
199
+ lines.append(f"// TW server module: {module_id}")
200
+ lines.append("'use strict';")
201
+ lines.append("")
202
+ for fn in funcs:
203
+ name = fn["name"]
204
+ params = fn["params"]
205
+ body = fn["body"]
206
+ async_prefix = "async " if fn.get("async") else ""
207
+ lines.append(f"{async_prefix}function {name}{params}{{{body}\n}}")
208
+ lines.append(f"exports.{name} = {name};")
209
+ lines.append("")
210
+ return "\n".join(lines)
211
+
212
+
213
+ def build_page_twm_bundle_js(
214
+ sources: List[Dict[str, str]],
215
+ *,
216
+ page_source_path: str = "",
217
+ ) -> str:
218
+ """
219
+ Produces a per-page JS bundle that:
220
+ - creates the module registry
221
+ - registers functions from loaded `.twm` files
222
+ - registers functions from local `SCRIPT { ... }` blocks
223
+ """
224
+ parts: List[str] = []
225
+ parts.append("(function(){")
226
+ parts.append(" window.__twRegistry = window.__twRegistry || Object.create(null);")
227
+ parts.append(" window.__twRegister = window.__twRegister || function(name, fn){")
228
+ parts.append(" if (!name) return;")
229
+ parts.append(" window.__twRegistry[name] = fn;")
230
+ parts.append(" };")
231
+ parts.append(" window.__twInvoke = window.__twInvoke || function(name, event){")
232
+ parts.append(" try {")
233
+ parts.append(" var fn = window.__twRegistry && window.__twRegistry[name];")
234
+ parts.append(" if (typeof fn === 'function') return fn(event);")
235
+ parts.append(" var g = window[name];")
236
+ parts.append(" if (typeof g === 'function') return g(event);")
237
+ parts.append(" console.warn('[tw] Missing handler:', name);")
238
+ parts.append(" } catch (e) {")
239
+ parts.append(" console.error('[tw] Handler error:', name, e);")
240
+ parts.append(" }")
241
+ parts.append(" };")
242
+ if page_source_path:
243
+ parts.append(f" // Source page: {page_source_path}")
244
+ parts.append("")
245
+
246
+ for item in sources or []:
247
+ if item.get("kind") == "file":
248
+ path = item.get("path") or ""
249
+ with open(path, "r", encoding="utf-8") as f:
250
+ src = f.read()
251
+ parts.append(compile_twm_module_to_js(src, module_id=path))
252
+ parts.append("")
253
+ continue
254
+ if item.get("kind") == "inline":
255
+ src = item.get("source") or ""
256
+ parts.append(compile_twm_module_to_js(src, module_id="<inline SCRIPT>"))
257
+ parts.append("")
258
+ continue
259
+
260
+ parts.append("})();")
261
+ return "\n".join(parts)
262
+
263
+
264
+ def _js_string(value: str) -> str:
265
+ return str(value).replace("\\", "\\\\").replace("'", "\\'")
266
+
267
+
268
+ __all__ = [
269
+ "TWMParseError",
270
+ "build_page_twm_bundle_js",
271
+ "compile_twm_module_to_cjs",
272
+ "compile_twm_module_to_js",
273
+ "parse_twm_functions",
274
+ ]
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: tw-framework
3
+ Version: 0.2.3
4
+ Summary: TW framework CLI with create, dev, build, and deploy commands
5
+ Author: TW Framework
6
+ Requires-Python: >=3.9
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest>=7; extra == "dev"
@@ -0,0 +1,51 @@
1
+ tw_framework/__init__.py,sha256=ocJAQdW_NYoTvGg4z8Qxg3TAZTof43UG5EzxT5zt7-w,396
2
+ tw_framework/advanced_diagnostics.py,sha256=ZhPoULtguXa4WxdsGoPo4InRTaqrc9RKtj4puEy1rls,633
3
+ tw_framework/asset_optimizer.py,sha256=bLsSXLxEQyp0IBO_kg0BevkraQkFb9laG_ATE4aVaf0,763
4
+ tw_framework/ast_nodes.py,sha256=Fb-Quwlm8GFYBiEmP37c0eYWvHd_tVrIyY97_GbQTto,8982
5
+ tw_framework/build.py,sha256=-70Nlcd92XL4sGBIgXo8qYa4ki7rUmz3gmKpa0CT_JY,150
6
+ tw_framework/build_performance.py,sha256=tMFUHkmbRtQ_FWgSMU18G5i5L_Cs8CSUlYsSk39t9y4,720
7
+ tw_framework/build_report.py,sha256=SRQHDF-BxFkjdqjMPKg5bDcGMxHh0OSnpfF1_6fLUgs,2933
8
+ tw_framework/cli.py,sha256=DrMKsOk17wNRHy-82Gef_t1oDPg8GZk2J-sH3vODz9Y,42759
9
+ tw_framework/code_splitting.py,sha256=3cajdS38eiWzEPaEWDpaRxOlCK1GAz4wmD0jJk8ds7I,2490
10
+ tw_framework/common.py,sha256=2irzk5G6s_Ezn9VGSezjqErKcP_eZuZHg2qBK0almsg,598
11
+ tw_framework/compiler.py,sha256=5NQ_kYKrfh8mYX851NAgSWpisRu4S8HQoevqwDfuQ0s,179647
12
+ tw_framework/compiler_stats.py,sha256=8Ccp8eVkOwVAkklBwb9moECMxjuF0FuWfIfXpItgNYI,1441
13
+ tw_framework/dead_code.py,sha256=ALwJQzPuXAOIpyrUMA4S99HAS8D0iwOwk8rnXjKxJ2o,1093
14
+ tw_framework/dependency_graph.py,sha256=qXWrLXRtZ7DYzQKch8vuqaLiCwU3e4Rw1TEFA2wiCls,3156
15
+ tw_framework/deploy.py,sha256=t1bBX-rR72ftcTbpv9-wr2AB7qfuEJEpvqqdwIDzNC0,158
16
+ tw_framework/dev.py,sha256=jXydz5JWS6HnH9OTrsOE1esywuAqlZuoKpl8NJWAaVs,177
17
+ tw_framework/diagnostics.py,sha256=1DnxEuN2sIfL8KoEXQfkAmL_E2Y11JHL2V4OUMw5UkU,3238
18
+ tw_framework/dynamic_imports.py,sha256=3wwiZDRCZuKY9u6cl1EAi6nkp1YRP9bNkLq7Yktd7eY,1903
19
+ tw_framework/error_formatter.py,sha256=-iKlFhBoDhupWmQomHhrNE5D8NiKR1b_JIJExEYzeuQ,1750
20
+ tw_framework/framework.py,sha256=mZBjq4vYRWbpdm0buIrqjrXFFfkqj4ssaNeG2srHehg,135149
21
+ tw_framework/hydration.py,sha256=6fgmE4ptmYLmduvIsUo__M5OiHpC6cZniL1T65agy1g,4580
22
+ tw_framework/incremental_cache.py,sha256=wNumlfjtgyzgZXfsp-S-BLxF4O6GVPrSs2LoZoXJQZg,1459
23
+ tw_framework/interpreter.py,sha256=K3N4zkVfsgPtMicpAAUL5Gl9OLqUrM3EeolfltI6fZc,1869
24
+ tw_framework/ir.py,sha256=9y209G73Up3e7fgzo71UMUcJaRr09EsbNoD4ZdrtmiE,4770
25
+ tw_framework/lexer.py,sha256=B9H26-kaq3e7MWeGCJozGKUJZb5PAYQYBgHtp53jEfQ,1106
26
+ tw_framework/lowering.py,sha256=0AyikxKo089RGkLfmI_DvT3kkBhi31RO37OZV6fs1NI,2573
27
+ tw_framework/parser.py,sha256=xqRcnYzkhd3YRcepotXbnHiMfR7Ht7qNILuyvzUimXU,5604
28
+ tw_framework/partial_rebuild.py,sha256=sx9yIsKDg3V2kKBYMlouWHCd8mMeOI7lj5JeaUDCIGs,685
29
+ tw_framework/performance_analyzer.py,sha256=Isj-eXU_7wfug8Vn6_DUL_rSd0Ld-ONxR2N4NyOc1so,916
30
+ tw_framework/plugin_runtime.py,sha256=3fFuQuiOb15os-n6ToE--L9-d6jtMxrmBTNlzmXNcSk,6956
31
+ tw_framework/production_optimizer.py,sha256=kGaY9kjXn9AQoZV922xklJq9_KMA2NwsFzAGmReTNxI,2944
32
+ tw_framework/reactivity.py,sha256=4zwUL7GgAJAN6Ih1ZulkQ2biMKT9BLjH7T1KZnhDNeM,12986
33
+ tw_framework/render_css.py,sha256=R6UIoI-9cCtKlP0n3oQy1Nvh8wwp-cwOpNkJ5smO-a4,526
34
+ tw_framework/render_html.py,sha256=1JtEyU2KWAPZM3AcRRhU6bJ0Er0fvaYOIYLM8Nbbq5s,10495
35
+ tw_framework/route_optimizer.py,sha256=f33Jswk_Ir-K-RNb4fm3uKT0m0Osfid5I92BXP84puA,1062
36
+ tw_framework/router.py,sha256=v2IKV1RwuDsJl8sKC69oPBHg0u-aIr-oXtA2ZvPuatY,164
37
+ tw_framework/runtime_model.py,sha256=AMBoEnKZZMGim02ZnQiC5JykPxGoCZILySDWjMuyTXc,726
38
+ tw_framework/runtime_values.py,sha256=zJyiZyc-m43CmaIJLZjQCmQzJhp7eh722FFGAx3BAmw,929
39
+ tw_framework/semantic.py,sha256=HBsEeX8Fn0ah6MRuRSsEo7uOvfNAoRt1UuAnfCkm7Dw,6883
40
+ tw_framework/server.py,sha256=Na6h1J-2SWgqovb-1HM7_p_R8lFZS1rK1ub6HVcobAQ,22883
41
+ tw_framework/signature.py,sha256=tsF1pN9tdTGrt2s2VLaLFpeoFLwasf9Md4S3l2YIyIQ,2058
42
+ tw_framework/static_dynamic_auto.py,sha256=1PNGbhItvcwtU12Vglqx-TngYiIxCTaGXuHHuCr-R18,908
43
+ tw_framework/streaming.py,sha256=nz0qHeL3KNQi5kaWRdkVLEPhHsM0ud9yblEPcjUv9j8,5530
44
+ tw_framework/test_build_cli_pytest.py,sha256=fcc2oHNwlYidg5PMflTvyzgD8ZbJq0xxXgZdDZ10NBw,35065
45
+ tw_framework/tree_shaking.py,sha256=v9S4c86IxxIMRPJjUqHEMPMKPBIaOA5G9efni5CZvHk,1327
46
+ tw_framework/twm_parser.py,sha256=NEeORHowG6d-7GSgYYdLSTuQb7_V0U4a6gS_cdXdAFo,8805
47
+ tw_framework-0.2.3.dist-info/METADATA,sha256=Z0DN_V2ZgV98K0wRdKVoF-V1qQ2gqrq8BapFhnohmj0,232
48
+ tw_framework-0.2.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
49
+ tw_framework-0.2.3.dist-info/entry_points.txt,sha256=q_xnwoJ_004m94C-VXA7oBQ4JjlplL7sIvHRXjTyviw,45
50
+ tw_framework-0.2.3.dist-info/top_level.txt,sha256=yhYT7MZtaMnbwNbT9vFmjyGikzheb1-Gk-gGUOcNv6k,13
51
+ tw_framework-0.2.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tw = tw_framework.cli:main
@@ -0,0 +1 @@
1
+ tw_framework