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.
Files changed (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. 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