quantum-framework 0.18.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/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -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 +548 -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 +565 -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 +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -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_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -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 +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -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/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -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 +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -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 +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -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 +79 -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 +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -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/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 +164 -0
- quantum/core/xml_lines.py +81 -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 +25 -0
- quantum/runtime/action_handler.py +532 -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/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -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 +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -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 +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -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 +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -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/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -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/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Component Composer — <Component prop="…">slot content</Component>.
|
|
3
|
+
|
|
4
|
+
COMP-1..4 (SPEC.md). What this used to get wrong, found by building the admin
|
|
5
|
+
layout on it:
|
|
6
|
+
|
|
7
|
+
- the component was searched in ./components relative to the process's
|
|
8
|
+
working directory — paths.components from quantum.config.yaml and the
|
|
9
|
+
`from=` of q:import were ignored;
|
|
10
|
+
- a component that was not found, or failed, became an HTML comment and the
|
|
11
|
+
page answered 200 with the section missing;
|
|
12
|
+
- the child ran as ComponentRuntime() with no configuration, so a q:query
|
|
13
|
+
or q:invoke service= inside a child component had no datasources/services;
|
|
14
|
+
- slot content was grafted into the child's AST and evaluated in the
|
|
15
|
+
CHILD's scope: a q:loop over the page's rows inside a layout saw nothing.
|
|
16
|
+
Worse, the graft mutated the child AST held in the resolver cache, so the
|
|
17
|
+
first request's slot content replaced the q:slot for every later request;
|
|
18
|
+
- props used get_variable only (no expressions) and fell back to the
|
|
19
|
+
placeholder text on failure.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
from typing import Any, Dict, List
|
|
24
|
+
|
|
25
|
+
from quantum.core.ast_nodes import ComponentCallNode, ComponentNode, HTMLNode, QuantumNode, SlotNode
|
|
26
|
+
from quantum.core.expressions import ExpressionError
|
|
27
|
+
from quantum.runtime.component import ComponentRuntime
|
|
28
|
+
from quantum.runtime.component_resolver import ComponentNotFoundError, ComponentResolver
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ComponentCompositionError(Exception):
|
|
32
|
+
"""A component call could not be composed; the message names the component."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RenderedHTML(QuantumNode):
|
|
36
|
+
"""Markup the parent already rendered (slot content), inserted as-is."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, html: str):
|
|
39
|
+
self.html = html
|
|
40
|
+
|
|
41
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
42
|
+
return {"type": "rendered_html", "length": len(self.html)}
|
|
43
|
+
|
|
44
|
+
def validate(self) -> List[str]:
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ComponentComposer:
|
|
49
|
+
def __init__(self, resolver: ComponentResolver):
|
|
50
|
+
self.resolver = resolver
|
|
51
|
+
|
|
52
|
+
def compose(self, call: ComponentCallNode, parent) -> str:
|
|
53
|
+
"""Render `call` as a child of the HTMLRenderer `parent`."""
|
|
54
|
+
from quantum.runtime.renderer import HTMLRenderer
|
|
55
|
+
|
|
56
|
+
nome, origem = parent.imports.get(call.component_name, (call.component_name, None))
|
|
57
|
+
try:
|
|
58
|
+
child = self.resolver.resolve(nome, origem).ast
|
|
59
|
+
except ComponentNotFoundError as exc:
|
|
60
|
+
raise ComponentCompositionError(f"<{call.component_name}>: {exc}") from exc
|
|
61
|
+
|
|
62
|
+
props = self._props(child, call, parent)
|
|
63
|
+
# COMP-4: the same configuration and the same session, application and
|
|
64
|
+
# request scopes as the page — a layout showing {session.userName}
|
|
65
|
+
# used to see an empty session.
|
|
66
|
+
props.update(_session_scope=parent.context.session_vars,
|
|
67
|
+
_application_scope=parent.context.application_vars,
|
|
68
|
+
_request_scope=parent.context.request_vars)
|
|
69
|
+
runtime = ComponentRuntime(config=parent.config)
|
|
70
|
+
runtime.execute_component(child, props)
|
|
71
|
+
|
|
72
|
+
# COMP-3: slot content belongs to the page — rendered here, with the
|
|
73
|
+
# page's variables, before the child sees it.
|
|
74
|
+
slot = RenderedHTML(parent.render_all(call.children)) if call.children else None
|
|
75
|
+
preenchido = ComponentNode(name=child.name, component_type=child.component_type)
|
|
76
|
+
preenchido.statements = self._com_slot(child.statements, slot)
|
|
77
|
+
preenchido.params, preenchido.returns, preenchido.functions = child.params, child.returns, child.functions
|
|
78
|
+
|
|
79
|
+
renderer = HTMLRenderer(runtime.execution_context, components_dir=parent.components_dir,
|
|
80
|
+
function_resolver=runtime._resolve_expression_function, config=parent.config)
|
|
81
|
+
return renderer.render(preenchido)
|
|
82
|
+
|
|
83
|
+
def _props(self, child: ComponentNode, call: ComponentCallNode, parent) -> Dict[str, Any]:
|
|
84
|
+
props = {}
|
|
85
|
+
for definicao in child.params:
|
|
86
|
+
if definicao.name in call.props:
|
|
87
|
+
props[definicao.name] = self._avaliar(call.props[definicao.name], definicao.name, call, parent)
|
|
88
|
+
elif definicao.default is not None:
|
|
89
|
+
props[definicao.name] = definicao.default
|
|
90
|
+
elif definicao.required:
|
|
91
|
+
raise ComponentCompositionError(
|
|
92
|
+
f"<{call.component_name}> requires the prop {definicao.name!r}")
|
|
93
|
+
return props
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _avaliar(valor, prop, call, parent):
|
|
97
|
+
"""COMP-2: a prop is an attribute of a tag — an expression that fails is an error."""
|
|
98
|
+
if not isinstance(valor, str) or "{" not in valor:
|
|
99
|
+
return valor
|
|
100
|
+
variaveis = parent.context.get_all_variables()
|
|
101
|
+
try:
|
|
102
|
+
inteira = re.fullmatch(r"\s*\{([^{}]*)\}\s*", valor)
|
|
103
|
+
if inteira:
|
|
104
|
+
return parent._expressions.evaluate(inteira.group(1), variaveis)
|
|
105
|
+
return re.sub(r"\{([^{}]*)\}",
|
|
106
|
+
lambda m: str(parent._expressions.evaluate(m.group(1), variaveis)), valor)
|
|
107
|
+
except ExpressionError as exc:
|
|
108
|
+
raise ComponentCompositionError(
|
|
109
|
+
f"<{call.component_name} {prop}=\"{valor}\">: {exc}") from exc
|
|
110
|
+
|
|
111
|
+
def _com_slot(self, nodes: List[QuantumNode], slot) -> List[QuantumNode]:
|
|
112
|
+
"""Copy of `nodes` with each q:slot replaced — the cached child AST is never touched."""
|
|
113
|
+
resultado = []
|
|
114
|
+
for node in nodes:
|
|
115
|
+
if isinstance(node, SlotNode):
|
|
116
|
+
# Only the default slot receives the call's children; named
|
|
117
|
+
# slots keep their default content (named slots are not
|
|
118
|
+
# implemented, as before).
|
|
119
|
+
if slot is not None and (node.name or "default") == "default":
|
|
120
|
+
resultado.append(slot)
|
|
121
|
+
else:
|
|
122
|
+
resultado.extend(node.default_content or [])
|
|
123
|
+
elif isinstance(node, HTMLNode) and self._tem_slot(node.children):
|
|
124
|
+
copia = HTMLNode(tag=node.tag, attributes=dict(node.attributes),
|
|
125
|
+
children=self._com_slot(node.children, slot), self_closing=node.self_closing)
|
|
126
|
+
resultado.append(copia)
|
|
127
|
+
else:
|
|
128
|
+
resultado.append(node)
|
|
129
|
+
return resultado
|
|
130
|
+
|
|
131
|
+
def _tem_slot(self, nodes) -> bool:
|
|
132
|
+
return any(isinstance(n, SlotNode) or (isinstance(n, HTMLNode) and self._tem_slot(n.children))
|
|
133
|
+
for n in nodes)
|
|
@@ -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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""The one place a `condition` is evaluated (q:if, q:elseif, guards).
|
|
2
|
+
|
|
3
|
+
ComponentRuntime (statements) and HTMLRenderer (markup) each had their own
|
|
4
|
+
copy of this function. They drifted: IF-2 (`condition="1"` was false) existed
|
|
5
|
+
in one and not the other. Both call this now.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
from quantum.core.expression_diagnostics import report_unresolved
|
|
12
|
+
from quantum.core.expressions import ExpressionError, ExpressionEvaluator, UndefinedError
|
|
13
|
+
|
|
14
|
+
_BRACES = re.compile(r'\{([^}]+)\}')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def evaluate_condition(evaluator: ExpressionEvaluator, condition: str,
|
|
18
|
+
variables: Dict[str, Any], guard: bool = False) -> bool:
|
|
19
|
+
"""True or False for a condition, by the rules of SPEC IF-2, EXPR-5, EXPR-8, AUTH-6.
|
|
20
|
+
|
|
21
|
+
The condition is evaluated as an expression with the braces stripped:
|
|
22
|
+
the variable NAMES stay in place for the evaluator to resolve, so no
|
|
23
|
+
value is ever parsed as syntax. (It used to interpolate the values into
|
|
24
|
+
the text and eval() the result — code assembled out of data; a record
|
|
25
|
+
named "'; __import__(...)" became executable. PUBLIC_RELEASE_PLAN P0.1.)
|
|
26
|
+
"""
|
|
27
|
+
if not condition:
|
|
28
|
+
return False
|
|
29
|
+
stripped = _BRACES.sub(lambda m: m.group(1), condition).strip()
|
|
30
|
+
try:
|
|
31
|
+
# EXPR-8: a key a scope does not have (session.userId before login)
|
|
32
|
+
# is None here, so `not session.authenticated` is true for a visitor.
|
|
33
|
+
return bool(evaluator.evaluate(stripped, variables or {}, absent_scope_key_is_none=True))
|
|
34
|
+
except UndefinedError as exc:
|
|
35
|
+
if guard:
|
|
36
|
+
# AUTH-6: a guard decides whether the page and its actions run.
|
|
37
|
+
# Reading a failure as false would let everything through, so in
|
|
38
|
+
# a guard it is an error: the page fails closed.
|
|
39
|
+
raise ExpressionError(
|
|
40
|
+
f"guard condition {condition!r} could not be evaluated: {exc}. A guard "
|
|
41
|
+
f"must decide; test each part first, e.g. "
|
|
42
|
+
f"`not session.user or not session.user.is_admin`") from exc
|
|
43
|
+
# EXPR-5: a condition is a presence test — a missing name, key or
|
|
44
|
+
# attribute makes it FALSE, never true. Before, the literal
|
|
45
|
+
# placeholder 'session.user.is_admin' came back as a non-empty string,
|
|
46
|
+
# read as true: the admin branch rendered for a logged-out visitor.
|
|
47
|
+
report_unresolved(stripped, ExpressionError(
|
|
48
|
+
f"condition {condition!r} could not be evaluated; treated as false"))
|
|
49
|
+
return False
|
|
50
|
+
except ExpressionError as exc:
|
|
51
|
+
# A syntax error or a function that does not exist is an error:
|
|
52
|
+
# reading it as false is how `age >= 18 && ok` took the else branch
|
|
53
|
+
# for every input.
|
|
54
|
+
raise ExpressionError(f"condition {condition!r} could not be evaluated: {exc}") from exc
|