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,51 @@
1
+ """
2
+ Compiler statistics for TW Framework.
3
+
4
+ Tracks pages compiled, components compiled, files reused from cache,
5
+ files rebuilt, build duration, and cache hit rate.
6
+ """
7
+
8
+ import logging
9
+ import time
10
+ from typing import Any, Dict
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CompilerStats:
16
+ """Collects compiler statistics during a build."""
17
+
18
+ def __init__(self):
19
+ self.start_time = time.time()
20
+ self.pages_compiled = 0
21
+ self.components_compiled = 0
22
+ self.files_reused_from_cache = 0
23
+ self.files_rebuilt = 0
24
+ self.cache_hits = 0
25
+ self.cache_misses = 0
26
+
27
+ @property
28
+ def cache_hit_rate(self) -> float:
29
+ total = self.cache_hits + self.cache_misses
30
+ if total == 0:
31
+ return 0.0
32
+ return self.cache_hits / total
33
+
34
+ @property
35
+ def build_duration(self) -> float:
36
+ return time.time() - self.start_time
37
+
38
+ def to_dict(self) -> Dict[str, Any]:
39
+ return {
40
+ "build_duration_seconds": round(self.build_duration, 2),
41
+ "pages_compiled": self.pages_compiled,
42
+ "components_compiled": self.components_compiled,
43
+ "files_reused_from_cache": self.files_reused_from_cache,
44
+ "files_rebuilt": self.files_rebuilt,
45
+ "cache_hits": self.cache_hits,
46
+ "cache_misses": self.cache_misses,
47
+ "cache_hit_rate": round(self.cache_hit_rate, 2),
48
+ }
49
+
50
+
51
+ __all__ = ["CompilerStats"]
@@ -0,0 +1,44 @@
1
+ """
2
+ Dead code detection for TW Framework.
3
+
4
+ Detects unused pages, APIs, middleware, components, and layouts.
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ from typing import Dict, List
10
+
11
+ from . import compiler
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def detect_dead_code(project_root: str) -> Dict[str, List[str]]:
17
+ """Return lists of dead code items."""
18
+ dead = {
19
+ "pages": [],
20
+ "apis": [],
21
+ "middleware": [],
22
+ "components": [],
23
+ "layouts": [],
24
+ }
25
+
26
+ # Discover all pages
27
+ pages = compiler.discover_pages()
28
+ page_paths = {p["path"] for p in pages}
29
+
30
+ # Check for orphaned page files
31
+ pages_dir = compiler.PAGES_DIR
32
+ if os.path.isdir(pages_dir):
33
+ for root, _, files in os.walk(pages_dir):
34
+ for fname in files:
35
+ if fname.endswith(".tw"):
36
+ full_path = os.path.join(root, fname)
37
+ if full_path not in page_paths:
38
+ dead["pages"].append(full_path)
39
+
40
+ # TODO: Add API, middleware, component, and layout detection
41
+ return dead
42
+
43
+
44
+ __all__ = ["detect_dead_code"]
@@ -0,0 +1,91 @@
1
+ """
2
+ Complete project dependency graph for TW Framework.
3
+
4
+ Tracks pages, components, layouts, middleware, APIs, imports, and assets.
5
+ Persisted between builds for incremental rebuilds.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import os
11
+ from typing import Dict, List, Optional, Set
12
+
13
+ from . import compiler
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ GRAPH_FILE = "dependency-graph.json"
18
+
19
+
20
+ class DependencyGraph:
21
+ """Maintains a directed graph of project dependencies."""
22
+
23
+ def __init__(self, project_root: str):
24
+ self.project_root = project_root
25
+ self.graph: Dict[str, List[str]] = {}
26
+ self.reverse: Dict[str, List[str]] = {}
27
+ self._load()
28
+
29
+ def _load(self):
30
+ path = os.path.join(self.project_root, ".tw", GRAPH_FILE)
31
+ if os.path.exists(path):
32
+ try:
33
+ with open(path, "r", encoding="utf-8") as f:
34
+ data = json.load(f)
35
+ self.graph = data.get("forward", {})
36
+ self.reverse = data.get("reverse", {})
37
+ except (json.JSONDecodeError, OSError):
38
+ logger.warning("Failed to load dependency graph; starting fresh")
39
+ self.graph = {}
40
+ self.reverse = {}
41
+
42
+ def save(self):
43
+ path = os.path.join(self.project_root, ".tw", GRAPH_FILE)
44
+ os.makedirs(os.path.dirname(path), exist_ok=True)
45
+ with open(path, "w", encoding="utf-8") as f:
46
+ json.dump({"forward": self.graph, "reverse": self.reverse}, f, indent=2)
47
+
48
+ def add_dependency(self, source: str, target: str):
49
+ if source not in self.graph:
50
+ self.graph[source] = []
51
+ if target not in self.graph[source]:
52
+ self.graph[source].append(target)
53
+ if target not in self.reverse:
54
+ self.reverse[target] = []
55
+ if source not in self.reverse[target]:
56
+ self.reverse[target].append(source)
57
+
58
+ def get_dependents(self, node: str) -> List[str]:
59
+ """Return all nodes that depend on the given node."""
60
+ return self.reverse.get(node, [])
61
+
62
+ def get_dependencies(self, node: str) -> List[str]:
63
+ """Return all nodes that the given node depends on."""
64
+ return self.graph.get(node, [])
65
+
66
+ def get_affected_nodes(self, changed_nodes: Set[str]) -> Set[str]:
67
+ """Return all nodes that need to be rebuilt when the given nodes change."""
68
+ affected = set(changed_nodes)
69
+ queue = list(changed_nodes)
70
+ while queue:
71
+ current = queue.pop(0)
72
+ for dependent in self.get_dependents(current):
73
+ if dependent not in affected:
74
+ affected.add(dependent)
75
+ queue.append(dependent)
76
+ return affected
77
+
78
+ def build_from_project(self):
79
+ """Build the dependency graph by scanning the project."""
80
+ self.graph = {}
81
+ self.reverse = {}
82
+ pages = compiler.discover_pages()
83
+ for page in pages:
84
+ page_path = page["path"]
85
+ deps = compiler.collect_page_dependencies(page_path)
86
+ for dep in deps:
87
+ self.add_dependency(page_path, dep)
88
+ self.save()
89
+
90
+
91
+ __all__ = ["DependencyGraph"]
tw_framework/deploy.py ADDED
@@ -0,0 +1,7 @@
1
+ from .framework import ( # noqa: F401
2
+ deploy_with_cloudflare,
3
+ deploy_with_docker,
4
+ deploy_with_netlify,
5
+ deploy_with_vercel,
6
+ run_deploy,
7
+ )
tw_framework/dev.py ADDED
@@ -0,0 +1,9 @@
1
+ from .framework import ( # noqa: F401
2
+ DEFAULT_DEV_HOST,
3
+ DEFAULT_DEV_PORT,
4
+ TWDevState,
5
+ TWFileWatcher,
6
+ TWProject,
7
+ make_dev_handler,
8
+ run_dev_server,
9
+ )
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Iterable, List, Optional
5
+
6
+
7
+ @dataclass
8
+ class Diagnostic:
9
+ severity: str
10
+ code: str
11
+ message: str
12
+ file_path: str = ""
13
+ line: int = 0
14
+ col: int = 0
15
+ suggestion: Optional[str] = None
16
+ notes: List[str] = field(default_factory=list)
17
+ phase: Optional[str] = None
18
+ exception_type: Optional[str] = None
19
+ category: str = "compiler"
20
+ relative_path: str = ""
21
+ absolute_path: str = ""
22
+ source_snippet: str = ""
23
+ highlight: str = ""
24
+ reason: str = ""
25
+ expected: str = ""
26
+ found: str = ""
27
+ why: str = ""
28
+ doc_link: str = ""
29
+ parser_state: str = ""
30
+ traceback: str = ""
31
+
32
+ @classmethod
33
+ def from_legacy(cls, item):
34
+ return cls(
35
+ severity=getattr(item, "severity", "error"),
36
+ code=getattr(item, "code", "TW0000"),
37
+ message=getattr(item, "message", str(item)),
38
+ file_path=getattr(item, "file_path", "") or "",
39
+ line=getattr(item, "line", 0) or 0,
40
+ col=getattr(item, "col", 0) or 0,
41
+ suggestion=getattr(item, "suggestion", None),
42
+ notes=list(getattr(item, "notes", []) or []),
43
+ category=getattr(item, "category", "compiler") or "compiler",
44
+ relative_path=getattr(item, "relative_path", "") or "",
45
+ absolute_path=getattr(item, "absolute_path", "") or "",
46
+ source_snippet=getattr(item, "source_snippet", "") or "",
47
+ highlight=getattr(item, "highlight", "") or "",
48
+ reason=getattr(item, "reason", "") or "",
49
+ expected=getattr(item, "expected", "") or "",
50
+ found=getattr(item, "found", "") or "",
51
+ why=getattr(item, "why", "") or "",
52
+ doc_link=getattr(item, "doc_link", "") or "",
53
+ parser_state=getattr(item, "parser_state", "") or "",
54
+ traceback=getattr(item, "traceback", "") or "",
55
+ phase=getattr(item, "phase", None),
56
+ exception_type=getattr(item, "exception_type", None),
57
+ )
58
+
59
+ def to_dict(self):
60
+ return {
61
+ "severity": self.severity,
62
+ "code": self.code,
63
+ "message": self.message,
64
+ "file_path": self.file_path,
65
+ "line": self.line,
66
+ "col": self.col,
67
+ "suggestion": self.suggestion,
68
+ "notes": list(self.notes),
69
+ "phase": self.phase,
70
+ "exception_type": self.exception_type,
71
+ }
72
+
73
+
74
+ def format_advanced_error(diag: Diagnostic, project_root: str = "") -> str:
75
+ """Format a diagnostic with all required fields."""
76
+ from .error_formatter import format_error
77
+ return format_error(diag, project_root)
78
+
79
+
80
+ class DiagnosticBag:
81
+ def __init__(self):
82
+ self.items: List[Diagnostic] = []
83
+
84
+ def add(self, diagnostic: Diagnostic):
85
+ self.items.append(diagnostic)
86
+
87
+ def extend(self, diagnostics: Iterable[Diagnostic]):
88
+ self.items.extend(diagnostics)
89
+
90
+ @property
91
+ def has_errors(self) -> bool:
92
+ return any(item.severity == "error" for item in self.items)
93
+
94
+ def to_list(self):
95
+ return [item.to_dict() for item in self.items]
96
+
97
+
98
+ __all__ = ["Diagnostic", "DiagnosticBag"]
@@ -0,0 +1,51 @@
1
+ """
2
+ Dynamic imports / lazy loading support for TW Framework.
3
+
4
+ Components with `lazy true` are compiled into separate chunks and loaded on demand.
5
+ """
6
+
7
+ import hashlib
8
+ import json
9
+ import logging
10
+ import os
11
+ from typing import Dict, List, Optional
12
+
13
+ from . import compiler
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def extract_lazy_components(page_path: str) -> List[str]:
19
+ """Return a list of component names that are marked as lazy in the given page."""
20
+ raw = compiler.read_text_file(page_path)
21
+ lazy_components = []
22
+ # Simple regex-based detection (in production, use the AST)
23
+ import re
24
+ for match in re.finditer(r'(\w+)\s*\{[^}]*lazy\s+true[^}]*\}', raw):
25
+ lazy_components.append(match.group(1))
26
+ return lazy_components
27
+
28
+
29
+ def generate_lazy_chunks(page_path: str, output_dir: str) -> Dict[str, str]:
30
+ """Generate separate chunks for lazy components and return a mapping."""
31
+ lazy_comps = extract_lazy_components(page_path)
32
+ chunk_map = {}
33
+ for comp_name in lazy_comps:
34
+ comp_path = compiler.resolve_component_path(comp_name)
35
+ if comp_path and os.path.exists(comp_path):
36
+ raw = compiler.read_text_file(comp_path)
37
+ # In production, compile the component to JS
38
+ js_content = f"// Lazy component: {comp_name}\n"
39
+ digest = hashlib.sha256(js_content.encode("utf-8")).hexdigest()[:12]
40
+ filename = f"lazy_{comp_name}.{digest}.js"
41
+ chunk_dir = os.path.join(output_dir, "_tw", "chunks")
42
+ os.makedirs(chunk_dir, exist_ok=True)
43
+ chunk_path = os.path.join(chunk_dir, filename)
44
+ if not os.path.exists(chunk_path):
45
+ with open(chunk_path, "w", encoding="utf-8") as f:
46
+ f.write(js_content)
47
+ chunk_map[comp_name] = f"/_tw/chunks/{filename}"
48
+ return chunk_map
49
+
50
+
51
+ __all__ = ["extract_lazy_components", "generate_lazy_chunks"]
@@ -0,0 +1,53 @@
1
+ """
2
+ Better error formatting for TW Framework.
3
+
4
+ Every error includes: error code, category, severity, file, relative path,
5
+ absolute path, line, column, highlighted code, explanation, reason,
6
+ expected, found, suggestions, and documentation link.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from typing import Optional
12
+
13
+ from .diagnostics import Diagnostic
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def format_error(diag: Diagnostic, project_root: str) -> str:
19
+ """Format a diagnostic into a human-readable string with all required fields."""
20
+ lines = []
21
+ code_display = diag.code if str(diag.code).upper().startswith("TW") else f"TW{diag.code}"
22
+ lines.append(f"{code_display} • {diag.category} Error")
23
+ lines.append("")
24
+ lines.append(f"Severity: {diag.severity.upper()}")
25
+ if diag.file_path:
26
+ rel_path = os.path.relpath(diag.file_path, project_root) if project_root else diag.file_path
27
+ lines.append(f"Project: {rel_path}")
28
+ lines.append(f"Absolute: {os.path.abspath(diag.file_path)}")
29
+ if diag.line:
30
+ lines.append(f"Line: {diag.line}")
31
+ if diag.col:
32
+ lines.append(f"Column: {diag.col}")
33
+ if diag.source_snippet:
34
+ lines.append("")
35
+ lines.append("Code:")
36
+ lines.append(diag.source_snippet)
37
+ if diag.reason:
38
+ lines.append("")
39
+ lines.append(f"Reason: {diag.reason}")
40
+ if diag.expected:
41
+ lines.append(f"Expected: {diag.expected}")
42
+ if diag.found:
43
+ lines.append(f"Found: {diag.found}")
44
+ if diag.why:
45
+ lines.append(f"Why: {diag.why}")
46
+ if diag.suggestion:
47
+ lines.append(f"Suggestion: {diag.suggestion}")
48
+ if diag.doc_link:
49
+ lines.append(f"Docs: {diag.doc_link}")
50
+ return "\n".join(lines)
51
+
52
+
53
+ __all__ = ["format_error"]