quantum-framework 0.9.0__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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Component Composer
|
|
3
|
+
|
|
4
|
+
Phase 2: Component Composition
|
|
5
|
+
Orchestrates component rendering with props and slots.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Any, Optional
|
|
11
|
+
|
|
12
|
+
# Fix imports
|
|
13
|
+
|
|
14
|
+
from quantum.core.ast_nodes import (
|
|
15
|
+
ComponentNode, ComponentCallNode, SlotNode, QuantumNode,
|
|
16
|
+
HTMLNode, TextNode, ImportNode
|
|
17
|
+
)
|
|
18
|
+
from quantum.runtime.component_resolver import ComponentResolver, ComponentNotFoundError
|
|
19
|
+
from quantum.runtime.component import ComponentRuntime
|
|
20
|
+
from quantum.runtime.execution_context import ExecutionContext
|
|
21
|
+
from quantum.runtime.renderer import HTMLRenderer
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ComponentComposer:
|
|
25
|
+
"""
|
|
26
|
+
Composes components with props and slots.
|
|
27
|
+
|
|
28
|
+
Phase 2: Component Composition
|
|
29
|
+
|
|
30
|
+
Responsibilities:
|
|
31
|
+
- Load child components
|
|
32
|
+
- Pass props from parent to child
|
|
33
|
+
- Inject slot content
|
|
34
|
+
- Render composed output
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, resolver: ComponentResolver):
|
|
38
|
+
self.resolver = resolver
|
|
39
|
+
|
|
40
|
+
def compose(
|
|
41
|
+
self,
|
|
42
|
+
component_call: ComponentCallNode,
|
|
43
|
+
parent_context: ExecutionContext
|
|
44
|
+
) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Compose and render a component call.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
component_call: The <ComponentName /> call
|
|
50
|
+
parent_context: Parent component's execution context
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Rendered HTML string
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# 1. Resolve component
|
|
57
|
+
try:
|
|
58
|
+
metadata = self.resolver.resolve(component_call.component_name)
|
|
59
|
+
except ComponentNotFoundError as e:
|
|
60
|
+
# Return error placeholder
|
|
61
|
+
return f'<!-- Component Error: {e} -->'
|
|
62
|
+
|
|
63
|
+
child_component = metadata.ast
|
|
64
|
+
|
|
65
|
+
# 2. Prepare props (merge call props + defaults)
|
|
66
|
+
props = self._prepare_props(child_component, component_call.props, parent_context)
|
|
67
|
+
|
|
68
|
+
# 3. Execute child component with props
|
|
69
|
+
runtime = ComponentRuntime()
|
|
70
|
+
runtime.execute_component(child_component, props)
|
|
71
|
+
|
|
72
|
+
# 4. Replace slots with content from parent
|
|
73
|
+
processed_ast = self._process_slots(
|
|
74
|
+
child_component,
|
|
75
|
+
component_call.children,
|
|
76
|
+
parent_context
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# 5. Render child component
|
|
80
|
+
renderer = HTMLRenderer(runtime.execution_context)
|
|
81
|
+
html = renderer.render(processed_ast)
|
|
82
|
+
|
|
83
|
+
return html
|
|
84
|
+
|
|
85
|
+
def _prepare_props(
|
|
86
|
+
self,
|
|
87
|
+
child_component: ComponentNode,
|
|
88
|
+
call_props: Dict[str, str],
|
|
89
|
+
parent_context: ExecutionContext
|
|
90
|
+
) -> Dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Prepare props for child component.
|
|
93
|
+
|
|
94
|
+
1. Get component parameter definitions
|
|
95
|
+
2. Merge with call props
|
|
96
|
+
3. Apply databinding from parent context
|
|
97
|
+
4. Validate required props
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
props = {}
|
|
101
|
+
|
|
102
|
+
# Process each parameter definition
|
|
103
|
+
for param_def in child_component.params:
|
|
104
|
+
param_name = param_def.name
|
|
105
|
+
|
|
106
|
+
# Get value from call props or use default
|
|
107
|
+
if param_name in call_props:
|
|
108
|
+
value = call_props[param_name]
|
|
109
|
+
|
|
110
|
+
# Apply databinding from parent context
|
|
111
|
+
value = self._apply_databinding_from_context(value, parent_context)
|
|
112
|
+
|
|
113
|
+
props[param_name] = value
|
|
114
|
+
|
|
115
|
+
elif param_def.default is not None:
|
|
116
|
+
props[param_name] = param_def.default
|
|
117
|
+
|
|
118
|
+
elif param_def.required:
|
|
119
|
+
# Required prop missing!
|
|
120
|
+
raise ComponentCompositionError(
|
|
121
|
+
f"Required prop '{param_name}' missing for component '{child_component.name}'"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return props
|
|
125
|
+
|
|
126
|
+
def _apply_databinding_from_context(self, value: str, context: ExecutionContext) -> Any:
|
|
127
|
+
"""
|
|
128
|
+
Apply databinding from parent context.
|
|
129
|
+
|
|
130
|
+
Examples:
|
|
131
|
+
"{user.name}" → "John Doe"
|
|
132
|
+
"{product.price}" → 29.99
|
|
133
|
+
"Hello {name}!" → "Hello World!"
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
if not isinstance(value, str):
|
|
137
|
+
return value
|
|
138
|
+
|
|
139
|
+
# Check if entire value is a binding expression
|
|
140
|
+
if value.startswith('{') and value.endswith('}'):
|
|
141
|
+
expression = value[1:-1].strip()
|
|
142
|
+
try:
|
|
143
|
+
return context.get_variable(expression)
|
|
144
|
+
except:
|
|
145
|
+
return value
|
|
146
|
+
|
|
147
|
+
# Mixed content - apply string interpolation
|
|
148
|
+
import re
|
|
149
|
+
pattern = r'\{([^}]+)\}'
|
|
150
|
+
|
|
151
|
+
def replace_binding(match):
|
|
152
|
+
expr = match.group(1).strip()
|
|
153
|
+
try:
|
|
154
|
+
result = context.get_variable(expr)
|
|
155
|
+
return str(result) if result is not None else ''
|
|
156
|
+
except:
|
|
157
|
+
return match.group(0)
|
|
158
|
+
|
|
159
|
+
return re.sub(pattern, replace_binding, value)
|
|
160
|
+
|
|
161
|
+
def _process_slots(
|
|
162
|
+
self,
|
|
163
|
+
child_component: ComponentNode,
|
|
164
|
+
slot_content: List[QuantumNode],
|
|
165
|
+
parent_context: ExecutionContext
|
|
166
|
+
) -> ComponentNode:
|
|
167
|
+
"""
|
|
168
|
+
Replace <q:slot> nodes with content from parent.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
child_component: Child component AST
|
|
172
|
+
slot_content: Children from parent's component call
|
|
173
|
+
parent_context: Parent's execution context
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Modified component AST with slots filled
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
# Pre-process slot content to apply databinding from parent context
|
|
180
|
+
processed_slot_content = self._apply_parent_databinding_to_nodes(
|
|
181
|
+
slot_content,
|
|
182
|
+
parent_context
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Build slot content map: {slot_name: [nodes]}
|
|
186
|
+
slot_content_map = {
|
|
187
|
+
'default': processed_slot_content
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# TODO: Support named slots
|
|
191
|
+
# For now, all content goes to default slot
|
|
192
|
+
|
|
193
|
+
# Traverse child component and replace SlotNodes
|
|
194
|
+
processed_statements = self._replace_slots_in_nodes(
|
|
195
|
+
child_component.statements,
|
|
196
|
+
slot_content_map,
|
|
197
|
+
parent_context
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Create modified component
|
|
201
|
+
modified_component = ComponentNode(
|
|
202
|
+
name=child_component.name,
|
|
203
|
+
component_type=child_component.component_type
|
|
204
|
+
)
|
|
205
|
+
modified_component.statements = processed_statements
|
|
206
|
+
modified_component.params = child_component.params
|
|
207
|
+
modified_component.returns = child_component.returns
|
|
208
|
+
modified_component.functions = child_component.functions
|
|
209
|
+
|
|
210
|
+
return modified_component
|
|
211
|
+
|
|
212
|
+
def _replace_slots_in_nodes(
|
|
213
|
+
self,
|
|
214
|
+
nodes: List[QuantumNode],
|
|
215
|
+
slot_content_map: Dict[str, List[QuantumNode]],
|
|
216
|
+
parent_context: ExecutionContext
|
|
217
|
+
) -> List[QuantumNode]:
|
|
218
|
+
"""
|
|
219
|
+
Recursively replace SlotNodes in node tree.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
result = []
|
|
223
|
+
|
|
224
|
+
for node in nodes:
|
|
225
|
+
if isinstance(node, SlotNode):
|
|
226
|
+
# Replace with slot content
|
|
227
|
+
slot_name = node.name
|
|
228
|
+
content = slot_content_map.get(slot_name, node.default_content)
|
|
229
|
+
result.extend(content)
|
|
230
|
+
|
|
231
|
+
elif isinstance(node, HTMLNode):
|
|
232
|
+
# Process children recursively
|
|
233
|
+
processed_children = self._replace_slots_in_nodes(
|
|
234
|
+
node.children,
|
|
235
|
+
slot_content_map,
|
|
236
|
+
parent_context
|
|
237
|
+
)
|
|
238
|
+
node.children = processed_children
|
|
239
|
+
result.append(node)
|
|
240
|
+
|
|
241
|
+
else:
|
|
242
|
+
# Keep node as-is
|
|
243
|
+
result.append(node)
|
|
244
|
+
|
|
245
|
+
return result
|
|
246
|
+
|
|
247
|
+
def _apply_parent_databinding_to_nodes(
|
|
248
|
+
self,
|
|
249
|
+
nodes: List[QuantumNode],
|
|
250
|
+
parent_context: ExecutionContext
|
|
251
|
+
) -> List[QuantumNode]:
|
|
252
|
+
"""
|
|
253
|
+
Apply databinding from parent context to nodes recursively.
|
|
254
|
+
|
|
255
|
+
This ensures that slot content uses parent's variables, not child's.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
nodes: List of nodes (TextNode, HTMLNode, etc)
|
|
259
|
+
parent_context: Parent component's execution context
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Nodes with databinding applied
|
|
263
|
+
"""
|
|
264
|
+
from quantum.core.ast_nodes import TextNode, HTMLNode
|
|
265
|
+
|
|
266
|
+
result = []
|
|
267
|
+
|
|
268
|
+
for node in nodes:
|
|
269
|
+
if isinstance(node, TextNode):
|
|
270
|
+
# Apply databinding to text content
|
|
271
|
+
if node.has_databinding:
|
|
272
|
+
processed_content = self._apply_databinding_from_context(
|
|
273
|
+
node.content,
|
|
274
|
+
parent_context
|
|
275
|
+
)
|
|
276
|
+
# Create new TextNode with resolved content
|
|
277
|
+
new_node = TextNode(processed_content)
|
|
278
|
+
new_node.has_databinding = False # Already resolved
|
|
279
|
+
result.append(new_node)
|
|
280
|
+
else:
|
|
281
|
+
result.append(node)
|
|
282
|
+
|
|
283
|
+
elif isinstance(node, HTMLNode):
|
|
284
|
+
# Process children recursively
|
|
285
|
+
processed_children = self._apply_parent_databinding_to_nodes(
|
|
286
|
+
node.children,
|
|
287
|
+
parent_context
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Process attributes (for databinding in attributes)
|
|
291
|
+
processed_attrs = {}
|
|
292
|
+
for key, value in node.attributes.items():
|
|
293
|
+
if isinstance(value, str) and '{' in value:
|
|
294
|
+
processed_attrs[key] = self._apply_databinding_from_context(
|
|
295
|
+
value,
|
|
296
|
+
parent_context
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
processed_attrs[key] = value
|
|
300
|
+
|
|
301
|
+
# Create new HTMLNode with processed content
|
|
302
|
+
new_node = HTMLNode(
|
|
303
|
+
tag=node.tag,
|
|
304
|
+
attributes=processed_attrs,
|
|
305
|
+
children=processed_children,
|
|
306
|
+
self_closing=node.self_closing
|
|
307
|
+
)
|
|
308
|
+
result.append(new_node)
|
|
309
|
+
|
|
310
|
+
else:
|
|
311
|
+
# Other node types - pass through
|
|
312
|
+
result.append(node)
|
|
313
|
+
|
|
314
|
+
return result
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class ComponentCompositionError(Exception):
|
|
318
|
+
"""Error during component composition"""
|
|
319
|
+
pass
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Component Resolver
|
|
3
|
+
|
|
4
|
+
Phase 2: Component Composition
|
|
5
|
+
Responsible for finding, loading, and caching imported components.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Optional, List
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
# Fix imports
|
|
14
|
+
|
|
15
|
+
from quantum.core.parser import QuantumParser, QuantumParseError
|
|
16
|
+
from quantum.core.ast_nodes import ComponentNode, ImportNode
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ComponentMetadata:
|
|
21
|
+
"""Metadata about a resolved component"""
|
|
22
|
+
name: str
|
|
23
|
+
path: Path
|
|
24
|
+
ast: ComponentNode
|
|
25
|
+
imports: List[ImportNode]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ComponentResolver:
|
|
29
|
+
"""
|
|
30
|
+
Resolves and loads Quantum components.
|
|
31
|
+
|
|
32
|
+
Phase 2: Component Composition
|
|
33
|
+
|
|
34
|
+
Responsibilities:
|
|
35
|
+
- Find component .q files by name
|
|
36
|
+
- Load and parse components
|
|
37
|
+
- Cache parsed components
|
|
38
|
+
- Resolve component dependencies
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, components_dir: str = "./components"):
|
|
42
|
+
self.components_dir = Path(components_dir)
|
|
43
|
+
self.parser = QuantumParser()
|
|
44
|
+
|
|
45
|
+
# Component cache: {name: ComponentMetadata}
|
|
46
|
+
self.cache: Dict[str, ComponentMetadata] = {}
|
|
47
|
+
|
|
48
|
+
def resolve(self, component_name: str, from_path: Optional[str] = None) -> ComponentMetadata:
|
|
49
|
+
"""
|
|
50
|
+
Resolve component by name.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
component_name: Name of component (e.g., "Header", "Button")
|
|
54
|
+
from_path: Optional relative path (e.g., "./ui", "../shared")
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
ComponentMetadata with AST and metadata
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ComponentNotFoundError: If component file doesn't exist
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# Check cache first
|
|
64
|
+
cache_key = f"{from_path or 'default'}:{component_name}"
|
|
65
|
+
if cache_key in self.cache:
|
|
66
|
+
return self.cache[cache_key]
|
|
67
|
+
|
|
68
|
+
# Find component file
|
|
69
|
+
component_path = self._find_component_file(component_name, from_path)
|
|
70
|
+
|
|
71
|
+
if not component_path.exists():
|
|
72
|
+
raise ComponentNotFoundError(
|
|
73
|
+
f"Component '{component_name}' not found. "
|
|
74
|
+
f"Searched: {component_path}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Parse component
|
|
78
|
+
try:
|
|
79
|
+
ast = self.parser.parse_file(str(component_path))
|
|
80
|
+
|
|
81
|
+
if not isinstance(ast, ComponentNode):
|
|
82
|
+
raise ComponentNotFoundError(
|
|
83
|
+
f"File '{component_path}' is not a component "
|
|
84
|
+
f"(got {type(ast).__name__})"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Extract imports from component
|
|
88
|
+
imports = [stmt for stmt in ast.statements if isinstance(stmt, ImportNode)]
|
|
89
|
+
|
|
90
|
+
# Create metadata
|
|
91
|
+
metadata = ComponentMetadata(
|
|
92
|
+
name=component_name,
|
|
93
|
+
path=component_path,
|
|
94
|
+
ast=ast,
|
|
95
|
+
imports=imports
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Cache it
|
|
99
|
+
self.cache[cache_key] = metadata
|
|
100
|
+
|
|
101
|
+
return metadata
|
|
102
|
+
|
|
103
|
+
except QuantumParseError as e:
|
|
104
|
+
raise ComponentNotFoundError(
|
|
105
|
+
f"Failed to parse component '{component_name}': {e}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _find_component_file(self, component_name: str, from_path: Optional[str] = None) -> Path:
|
|
109
|
+
"""
|
|
110
|
+
Find component .q file.
|
|
111
|
+
|
|
112
|
+
Resolution order:
|
|
113
|
+
1. If from_path provided: {from_path}/{ComponentName}.q
|
|
114
|
+
2. Default: {components_dir}/{ComponentName}.q
|
|
115
|
+
3. Try lowercase: {components_dir}/{componentname}.q
|
|
116
|
+
4. Try snake_case: {components_dir}/{component_name}.q
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Try from_path first if provided
|
|
120
|
+
if from_path:
|
|
121
|
+
from_dir = self.components_dir / from_path.lstrip('./')
|
|
122
|
+
path = from_dir / f"{component_name}.q"
|
|
123
|
+
if path.exists():
|
|
124
|
+
return path
|
|
125
|
+
|
|
126
|
+
# Try exact name
|
|
127
|
+
path = self.components_dir / f"{component_name}.q"
|
|
128
|
+
if path.exists():
|
|
129
|
+
return path
|
|
130
|
+
|
|
131
|
+
# Try lowercase
|
|
132
|
+
path = self.components_dir / f"{component_name.lower()}.q"
|
|
133
|
+
if path.exists():
|
|
134
|
+
return path
|
|
135
|
+
|
|
136
|
+
# Try snake_case conversion
|
|
137
|
+
snake_case_name = self._to_snake_case(component_name)
|
|
138
|
+
path = self.components_dir / f"{snake_case_name}.q"
|
|
139
|
+
if path.exists():
|
|
140
|
+
return path
|
|
141
|
+
|
|
142
|
+
# Return original path (will trigger not found error)
|
|
143
|
+
return self.components_dir / f"{component_name}.q"
|
|
144
|
+
|
|
145
|
+
def _to_snake_case(self, name: str) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Convert PascalCase to snake_case.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
ProductCard → product_card
|
|
151
|
+
AdminLayout → admin_layout
|
|
152
|
+
UserProfileHeader → user_profile_header
|
|
153
|
+
"""
|
|
154
|
+
import re
|
|
155
|
+
# Insert underscore before uppercase letters
|
|
156
|
+
result = re.sub(r'(?<!^)(?=[A-Z])', '_', name)
|
|
157
|
+
return result.lower()
|
|
158
|
+
|
|
159
|
+
def clear_cache(self):
|
|
160
|
+
"""Clear component cache (useful for development hot-reload)"""
|
|
161
|
+
self.cache.clear()
|
|
162
|
+
|
|
163
|
+
def preload(self, component_names: List[str]):
|
|
164
|
+
"""Preload components into cache"""
|
|
165
|
+
for name in component_names:
|
|
166
|
+
try:
|
|
167
|
+
self.resolve(name)
|
|
168
|
+
except ComponentNotFoundError:
|
|
169
|
+
pass # Skip missing components
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ComponentNotFoundError(Exception):
|
|
173
|
+
"""Component file not found or invalid"""
|
|
174
|
+
pass
|