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,365 @@
1
+ """
2
+ UI Engine - Cross-Target Validator
3
+
4
+ Analyzes UI AST to detect features that may not translate well between
5
+ different targets (HTML, Textual). Returns compatibility reports and warnings.
6
+
7
+ Usage:
8
+ from quantum.runtime.ui_validator import UIValidator
9
+
10
+ validator = UIValidator()
11
+ report = validator.validate(app.ui_windows, app.ui_children)
12
+
13
+ print(f"Compatibility Score: {report.compatibility_score}%")
14
+ for warning in report.warnings:
15
+ print(f" - {warning}")
16
+ """
17
+
18
+ from dataclasses import dataclass, field
19
+ from typing import List, Set, Dict, Optional
20
+ from enum import Enum
21
+
22
+ from quantum.core.ast_nodes import QuantumNode
23
+ from quantum.core.features.ui_engine.src.ast_nodes import (
24
+ UIWindowNode, UIHBoxNode, UIVBoxNode, UIPanelNode,
25
+ UITabPanelNode, UITabNode, UIGridNode, UIAccordionNode,
26
+ UISectionNode, UIDividedBoxNode, UIFormNode, UIFormItemNode,
27
+ UISpacerNode, UIScrollBoxNode,
28
+ UITextNode, UIButtonNode, UIInputNode, UICheckboxNode,
29
+ UIRadioNode, UISwitchNode, UISelectNode, UITableNode,
30
+ UIColumnNode, UIListNode, UIItemNode, UIImageNode,
31
+ UILinkNode, UIProgressNode, UITreeNode, UIMenuNode,
32
+ UIOptionNode, UILogNode, UIMarkdownNode, UIHeaderNode,
33
+ UIFooterNode, UIRuleNode, UILoadingNode, UIBadgeNode,
34
+ )
35
+
36
+
37
+ class Severity(Enum):
38
+ """Warning severity levels."""
39
+ INFO = "info" # FYI, no impact
40
+ WARNING = "warning" # Works differently
41
+ ERROR = "error" # Does not work
42
+
43
+
44
+ @dataclass
45
+ class CompatibilityIssue:
46
+ """Single compatibility issue."""
47
+ feature: str
48
+ message: str
49
+ severity: Severity
50
+ target: str # Which target is affected
51
+ node_type: str
52
+ suggestion: Optional[str] = None
53
+
54
+
55
+ @dataclass
56
+ class ValidationReport:
57
+ """Complete validation report."""
58
+ issues: List[CompatibilityIssue] = field(default_factory=list)
59
+ features_used: Set[str] = field(default_factory=set)
60
+ widgets_count: int = 0
61
+ containers_count: int = 0
62
+
63
+ @property
64
+ def compatibility_score(self) -> int:
65
+ """Calculate compatibility score (0-100) based on issues."""
66
+ if not self.issues:
67
+ return 100
68
+
69
+ # Weight by severity
70
+ error_count = sum(1 for i in self.issues if i.severity == Severity.ERROR)
71
+ warning_count = sum(1 for i in self.issues if i.severity == Severity.WARNING)
72
+ info_count = sum(1 for i in self.issues if i.severity == Severity.INFO)
73
+
74
+ # Calculate penalty
75
+ total_elements = max(1, self.widgets_count + self.containers_count)
76
+ penalty = (error_count * 20 + warning_count * 5 + info_count * 1)
77
+ score = max(0, 100 - penalty)
78
+
79
+ return score
80
+
81
+ @property
82
+ def html_compatible(self) -> bool:
83
+ """Check if fully HTML compatible."""
84
+ return not any(i.target == 'html' and i.severity == Severity.ERROR
85
+ for i in self.issues)
86
+
87
+ @property
88
+ def textual_compatible(self) -> bool:
89
+ """Check if fully Textual compatible."""
90
+ return not any(i.target == 'textual' and i.severity == Severity.ERROR
91
+ for i in self.issues)
92
+
93
+ @property
94
+ def warnings(self) -> List[str]:
95
+ """Get formatted warning messages."""
96
+ return [f"[{i.severity.value}] {i.feature}: {i.message}"
97
+ for i in self.issues]
98
+
99
+
100
+ # Feature compatibility matrix
101
+ FEATURE_COMPAT = {
102
+ # Feature: (html_support, textual_support, message, suggestion)
103
+ 'image': (True, 'degraded',
104
+ "Images display as placeholder text in terminal",
105
+ "Consider using ASCII art or omit images for TUI target"),
106
+
107
+ 'link_external': (True, 'degraded',
108
+ "External links are not clickable in terminal",
109
+ "Link URL is shown as text instead"),
110
+
111
+ 'gap': (True, False,
112
+ "CSS gap property is not supported in Textual",
113
+ "Layout may differ; children won't have automatic spacing"),
114
+
115
+ 'justify_between': (True, False,
116
+ "justify-content: space-between not available in Textual",
117
+ "Items will be centered instead"),
118
+
119
+ 'justify_around': (True, False,
120
+ "justify-content: space-around not available in Textual",
121
+ "Items will be centered instead"),
122
+
123
+ 'font_size': (True, False,
124
+ "Font sizes cannot be changed in terminal",
125
+ "Use text weight (bold) for emphasis instead"),
126
+
127
+ 'pixel_units': (True, 'converted',
128
+ "Pixel values are converted to character cells (÷8)",
129
+ "Results may vary; prefer tokens like 'sm', 'md', 'lg'"),
130
+
131
+ 'animation': (True, False,
132
+ "CSS animations are not supported in terminal",
133
+ None),
134
+
135
+ 'gradient': (True, False,
136
+ "CSS gradients are not supported in terminal",
137
+ "Use solid colors instead"),
138
+
139
+ 'shadow': (True, False,
140
+ "Box shadows are not supported in terminal",
141
+ None),
142
+
143
+ 'border_radius': (True, False,
144
+ "Border radius is not supported in terminal",
145
+ "Borders will appear rectangular"),
146
+ }
147
+
148
+
149
+ class UIValidator:
150
+ """Validates UI AST for cross-target compatibility."""
151
+
152
+ def __init__(self):
153
+ self._report: ValidationReport = None
154
+ self._target: str = 'all' # 'html', 'textual', or 'all'
155
+
156
+ def validate(self, windows: List[QuantumNode],
157
+ ui_children: List[QuantumNode],
158
+ target: str = 'all') -> ValidationReport:
159
+ """
160
+ Validate UI AST for target compatibility.
161
+
162
+ Args:
163
+ windows: List of UIWindowNode
164
+ ui_children: List of top-level UI nodes
165
+ target: 'html', 'textual', or 'all' (check both)
166
+
167
+ Returns:
168
+ ValidationReport with issues and compatibility score
169
+ """
170
+ self._report = ValidationReport()
171
+ self._target = target
172
+
173
+ # Walk all windows
174
+ for window in windows:
175
+ self._validate_node(window)
176
+
177
+ # Walk top-level children
178
+ for child in ui_children:
179
+ self._validate_node(child)
180
+
181
+ return self._report
182
+
183
+ def _validate_node(self, node: QuantumNode):
184
+ """Recursively validate a node and its children."""
185
+ if node is None:
186
+ return
187
+
188
+ # Classify node
189
+ if self._is_container(node):
190
+ self._report.containers_count += 1
191
+ self._validate_container(node)
192
+ elif self._is_widget(node):
193
+ self._report.widgets_count += 1
194
+ self._validate_widget(node)
195
+
196
+ # Validate layout attributes common to all nodes
197
+ self._validate_layout_attrs(node)
198
+
199
+ # Recurse into children
200
+ if hasattr(node, 'children') and node.children:
201
+ for child in node.children:
202
+ self._validate_node(child)
203
+
204
+ def _is_container(self, node) -> bool:
205
+ """Check if node is a container type."""
206
+ return isinstance(node, (
207
+ UIWindowNode, UIHBoxNode, UIVBoxNode, UIPanelNode,
208
+ UITabPanelNode, UITabNode, UIGridNode, UIAccordionNode,
209
+ UISectionNode, UIDividedBoxNode, UIFormNode, UIFormItemNode,
210
+ UIScrollBoxNode, UIListNode, UIItemNode, UIMenuNode,
211
+ ))
212
+
213
+ def _is_widget(self, node) -> bool:
214
+ """Check if node is a widget type."""
215
+ return isinstance(node, (
216
+ UITextNode, UIButtonNode, UIInputNode, UICheckboxNode,
217
+ UIRadioNode, UISwitchNode, UISelectNode, UITableNode,
218
+ UIProgressNode, UITreeNode, UILogNode, UIMarkdownNode,
219
+ UIHeaderNode, UIFooterNode, UIRuleNode, UILoadingNode,
220
+ UIImageNode, UILinkNode, UIBadgeNode, UISpacerNode,
221
+ UIColumnNode, UIOptionNode,
222
+ ))
223
+
224
+ def _validate_container(self, node):
225
+ """Validate container-specific features."""
226
+ pass # Containers generally work well across targets
227
+
228
+ def _validate_widget(self, node):
229
+ """Validate widget-specific features."""
230
+ # Image - limited in terminal
231
+ if isinstance(node, UIImageNode):
232
+ self._add_feature_issue('image', type(node).__name__)
233
+
234
+ # External link - not clickable in terminal
235
+ if isinstance(node, UILinkNode):
236
+ if node.external:
237
+ self._add_feature_issue('link_external', type(node).__name__)
238
+
239
+ # Markdown - supported in both but rendering differs
240
+ if isinstance(node, UIMarkdownNode):
241
+ self._report.features_used.add('markdown')
242
+
243
+ def _validate_layout_attrs(self, node):
244
+ """Validate layout attributes for compatibility."""
245
+ node_type = type(node).__name__
246
+
247
+ # Gap
248
+ if hasattr(node, 'gap') and node.gap:
249
+ self._add_feature_issue('gap', node_type)
250
+
251
+ # Justify
252
+ if hasattr(node, 'justify') and node.justify:
253
+ if node.justify == 'between':
254
+ self._add_feature_issue('justify_between', node_type)
255
+ elif node.justify == 'around':
256
+ self._add_feature_issue('justify_around', node_type)
257
+
258
+ # Pixel values
259
+ if hasattr(node, 'width') and node.width:
260
+ if self._is_pixel_value(node.width):
261
+ self._add_feature_issue('pixel_units', node_type)
262
+
263
+ if hasattr(node, 'height') and node.height:
264
+ if self._is_pixel_value(node.height):
265
+ self._add_feature_issue('pixel_units', node_type)
266
+
267
+ if hasattr(node, 'padding') and node.padding:
268
+ if self._is_pixel_value(node.padding):
269
+ self._add_feature_issue('pixel_units', node_type)
270
+
271
+ if hasattr(node, 'margin') and node.margin:
272
+ if self._is_pixel_value(node.margin):
273
+ self._add_feature_issue('pixel_units', node_type)
274
+
275
+ # Font size
276
+ if hasattr(node, 'size') and node.size:
277
+ # For text nodes
278
+ self._add_feature_issue('font_size', node_type)
279
+
280
+ def _is_pixel_value(self, value: str) -> bool:
281
+ """Check if value is a raw pixel value (not a token)."""
282
+ if not value:
283
+ return False
284
+ value = str(value).strip().lower()
285
+ # Check if it's a number or ends with 'px'
286
+ if value.endswith('px'):
287
+ return True
288
+ try:
289
+ int(value)
290
+ return True
291
+ except ValueError:
292
+ return False
293
+
294
+ def _add_feature_issue(self, feature: str, node_type: str):
295
+ """Add a feature compatibility issue to the report."""
296
+ if feature in self._report.features_used:
297
+ return # Already reported
298
+
299
+ self._report.features_used.add(feature)
300
+
301
+ if feature not in FEATURE_COMPAT:
302
+ return
303
+
304
+ html_support, textual_support, message, suggestion = FEATURE_COMPAT[feature]
305
+
306
+ # Determine severity for Textual target
307
+ if textual_support is False:
308
+ severity = Severity.WARNING
309
+ elif textual_support == 'degraded':
310
+ severity = Severity.INFO
311
+ elif textual_support == 'converted':
312
+ severity = Severity.INFO
313
+ else:
314
+ return # Fully supported
315
+
316
+ # Only add if checking this target
317
+ if self._target in ('all', 'textual'):
318
+ self._report.issues.append(CompatibilityIssue(
319
+ feature=feature,
320
+ message=message,
321
+ severity=severity,
322
+ target='textual',
323
+ node_type=node_type,
324
+ suggestion=suggestion,
325
+ ))
326
+
327
+
328
+ def validate_cross_target(windows: List[QuantumNode],
329
+ ui_children: List[QuantumNode]) -> ValidationReport:
330
+ """
331
+ Convenience function to validate UI for cross-target compatibility.
332
+
333
+ Args:
334
+ windows: List of UIWindowNode
335
+ ui_children: List of top-level UI nodes
336
+
337
+ Returns:
338
+ ValidationReport with compatibility score and issues
339
+ """
340
+ validator = UIValidator()
341
+ return validator.validate(windows, ui_children)
342
+
343
+
344
+ def print_validation_report(report: ValidationReport):
345
+ """Print a formatted validation report."""
346
+ print(f"\n{'='*60}")
347
+ print(f"UI Cross-Target Compatibility Report")
348
+ print(f"{'='*60}")
349
+ print(f"Containers: {report.containers_count}")
350
+ print(f"Widgets: {report.widgets_count}")
351
+ print(f"Compatibility Score: {report.compatibility_score}%")
352
+ print(f"HTML Compatible: {'Yes' if report.html_compatible else 'No'}")
353
+ print(f"Textual Compatible: {'Yes' if report.textual_compatible else 'No'}")
354
+
355
+ if report.issues:
356
+ print(f"\nIssues ({len(report.issues)}):")
357
+ for issue in report.issues:
358
+ icon = {'info': 'ℹ', 'warning': '⚠', 'error': '✖'}.get(issue.severity.value, '•')
359
+ print(f" {icon} [{issue.target}] {issue.feature}: {issue.message}")
360
+ if issue.suggestion:
361
+ print(f" └─ Suggestion: {issue.suggestion}")
362
+ else:
363
+ print("\nNo compatibility issues found!")
364
+
365
+ print(f"{'='*60}\n")
@@ -0,0 +1,221 @@
1
+ """The view of a page as data, for renderers that are not a browser (UI-3).
2
+
3
+ The web server executes a page as always, and — when asked with
4
+ `Accept: application/vnd.quantum.view+json` — answers with this tree instead
5
+ of HTML. The console renderer (quantum/runtime/ui_console.py) draws it and
6
+ sends the same q:action posts a browser would. One runtime, two renderers:
7
+ nothing about the page's logic is translated.
8
+
9
+ A view node is a dict:
10
+
11
+ {"type": "hbox", "props": {...}, "children": [...]}
12
+ {"type": "button", "props": {"text": "Salvar"},
13
+ "event": {"action": "salvar", "fields": {"id": "3"}}}
14
+ {"type": "text", "props": {"text": "..."}} # also plain text/HTML
15
+ """
16
+
17
+ import copy
18
+ from typing import Any, Dict, List
19
+
20
+ from quantum.core.ast_nodes import CommentNode, HTMLNode, TextNode
21
+ from quantum.core.features.conditionals.src.ast_node import IfNode
22
+ from quantum.core.features.loops.src.ast_node import LoopNode
23
+ from quantum.core.features.ui_engine.src.ast_nodes import (
24
+ UIColumnNode, UIFormNode, UIListNode, UIPagerNode, UITableNode, UIWindowNode, is_ui_node)
25
+ from quantum.runtime.ui_pager import pager_items
26
+ from quantum.runtime.ui_table_edit import cell_field, edit_plan, sort_link
27
+ from quantum.runtime.ui_form_rules import FIELDS, form_context, generated_fields, prepare_field
28
+ from quantum.runtime.ui_page_renderer import cell_value, source_rows
29
+
30
+ # Fields that are structure or events, not props.
31
+ _NAO_PROPS = {'children', 'on_click', 'on_submit', 'on_change', 'on_select', 'send', 'source', 'as_var', 'values',
32
+ 'edit', 'edit_datasource', 'sort',
33
+ # DEV-2: where the element is in the .q — never sent to a client
34
+ 'source_line', 'source_file'}
35
+
36
+
37
+ def _tipo(node) -> str:
38
+ nome = type(node).__name__
39
+ return nome[2:-4].lower() if nome.startswith('UI') and nome.endswith('Node') else nome.lower()
40
+
41
+
42
+ def page_title(ast, renderer) -> str:
43
+ """UI-4: a page's title, the same for every renderer — the first ui:window's
44
+ `title` (bindings resolved), else the component's name."""
45
+ for node in getattr(ast, 'statements', []):
46
+ if isinstance(node, UIWindowNode) and node.title:
47
+ titulo = renderer._apply_databinding(node.title)
48
+ if titulo not in (None, ''):
49
+ return str(titulo)
50
+ return getattr(ast, 'name', '') or ''
51
+
52
+
53
+ class UIViewBuilder:
54
+ """Walks a page's nodes with the page renderer's evaluation (loops, ifs, bindings)."""
55
+
56
+ def __init__(self, renderer):
57
+ self.r = renderer
58
+
59
+ def build(self, nodes) -> List[Dict[str, Any]]:
60
+ vista: List[Dict[str, Any]] = []
61
+ for node in nodes:
62
+ vista.extend(self._node(node))
63
+ return vista
64
+
65
+ def _texto(self, valor: str) -> str:
66
+ resolvido = self.r._apply_databinding(valor)
67
+ return '' if resolvido is None else str(resolvido)
68
+
69
+ def _campos(self, envio) -> Dict[str, str]:
70
+ campos = {}
71
+ if envio:
72
+ for par in self._texto(envio).split(','):
73
+ if '=' in par:
74
+ campo, valor = (p.strip() for p in par.split('=', 1))
75
+ campos[campo] = valor
76
+ return campos
77
+
78
+ def _node(self, node) -> List[Dict[str, Any]]:
79
+ if is_ui_node(node):
80
+ return [self._ui(node)]
81
+ if isinstance(node, LoopNode):
82
+ return self._loop(node)
83
+ if isinstance(node, IfNode):
84
+ return self.build(self._ramo(node))
85
+ if isinstance(node, TextNode):
86
+ texto = self._texto(node.content).strip()
87
+ return [{'type': 'text', 'props': {'text': texto}}] if texto else []
88
+ if isinstance(node, HTMLNode):
89
+ # HTML inside a UI page: its text, in order (a terminal draws no tags).
90
+ filhos = self.build(node.children or [])
91
+ return filhos
92
+ if isinstance(node, CommentNode):
93
+ return []
94
+ return []
95
+
96
+ def _ui(self, node) -> Dict[str, Any]:
97
+ if isinstance(node, UIPagerNode):
98
+ # UI-11: the same items the browser gets, as links the console follows.
99
+ filhos = []
100
+ for item in pager_items(self.r, node):
101
+ if item['href']:
102
+ filhos.append({'type': 'link', 'props': {'to': item['href'], 'text': item['label']},
103
+ 'children': []})
104
+ else:
105
+ texto = f"[{item['label']}]" if item['current'] else item['label']
106
+ filhos.append({'type': 'text', 'props': {'text': texto}, 'children': []})
107
+ return {'type': 'hbox', 'props': {'ui_id': getattr(node, 'ui_id', None)}, 'children': filhos}
108
+ erro = None
109
+ if getattr(node, 'search', None) and getattr(node, 'value', None) in (None, ''):
110
+ # UI-12: the search field shows the search in the URL, as in the browser.
111
+ try:
112
+ atuais = self.r.context.get_variable('query')
113
+ except Exception:
114
+ atuais = None
115
+ if isinstance(atuais, dict) and atuais.get(node.bind) not in (None, ''):
116
+ node = copy.copy(node)
117
+ node.value = str(atuais[node.bind])
118
+ if isinstance(node, FIELDS):
119
+ # UI-9: the same rules, values and errors the browser's form gets.
120
+ node, erro = prepare_field(node, getattr(self, '_form_ctx', None), escape=False)
121
+ if isinstance(node, UIFormNode):
122
+ anterior = getattr(self, '_form_ctx', None)
123
+ acao = (node.on_submit or '').strip().rstrip('()') or None
124
+ self._form_ctx = form_context(self.r, node, acao)
125
+ try:
126
+ com_campos = copy.copy(node)
127
+ com_campos.children = generated_fields(node, self._form_ctx) # UI-10
128
+ return self._ui_sem_contexto(com_campos, None)
129
+ finally:
130
+ self._form_ctx = anterior
131
+ return self._ui_sem_contexto(node, erro)
132
+
133
+ def _ui_sem_contexto(self, node, erro) -> Dict[str, Any]:
134
+ props = {}
135
+ for nome, valor in vars(node).items():
136
+ if nome in _NAO_PROPS or nome.startswith('_') or valor is None:
137
+ continue
138
+ if isinstance(valor, str):
139
+ props[nome] = self._texto(valor) if '{' in valor else valor
140
+ elif isinstance(valor, (bool, int, float)):
141
+ props[nome] = valor
142
+ if 'content' in props:
143
+ props['text'] = props.pop('content')
144
+ if isinstance(node, UITableNode) and node.source:
145
+ return self._tabela(node, props)
146
+ if isinstance(node, UIListNode) and node.source:
147
+ filhos = []
148
+ for linha in source_rows(self.r, node, 'ui:list', 'item'):
149
+ filhos.extend(self.build(node.children) if node.children
150
+ else [{'type': 'item', 'props': {}, 'children': [
151
+ {'type': 'text', 'props': {'text': '' if linha is None else str(linha)}}]}])
152
+ return {'type': 'list', 'props': props, 'children': filhos}
153
+ if erro:
154
+ props['error'] = erro
155
+ vista = {'type': _tipo(node), 'props': props,
156
+ 'children': self.build(getattr(node, 'children', None) or [])}
157
+ acao = getattr(node, 'on_click', None) or getattr(node, 'on_submit', None)
158
+ if acao:
159
+ vista['event'] = {'action': acao.strip().rstrip('()'), 'fields': self._campos(getattr(node, 'send', None))}
160
+ return vista
161
+
162
+ def _tabela(self, node: UITableNode, props: Dict[str, Any]) -> Dict[str, Any]:
163
+ """UI-5: a table with its rows; each cell is a list of view nodes.
164
+ UI-13: sortable headers carry the link; an editable cell is a form."""
165
+ colunas = [c for c in node.children if isinstance(c, UIColumnNode)]
166
+ plano = edit_plan(self.r, node)
167
+ estado = getattr(self.r, 'form_state', None)
168
+ linhas = []
169
+ for linha in source_rows(self.r, node, 'ui:table', 'row'):
170
+ celulas = []
171
+ for c in colunas:
172
+ if c.children:
173
+ celulas.append(self.build(c.children))
174
+ elif plano and c.key in plano['columns']:
175
+ campo, ocultos, erro = cell_field(plano, plano['columns'][c.key], linha, estado, escape=False)
176
+ vista_campo = self._ui(campo)
177
+ if erro:
178
+ vista_campo['props']['error'] = erro
179
+ celulas.append([{'type': 'form', 'props': {},
180
+ 'event': {'action': ocultos.pop('action'), 'fields': ocultos},
181
+ 'children': [vista_campo]}])
182
+ else:
183
+ celulas.append([{'type': 'text', 'props': {'text': cell_value(linha, c)}}])
184
+ linhas.append(celulas)
185
+ cabecalho = []
186
+ for c in colunas:
187
+ coluna = {'label': c.label or c.key or '', 'align': c.align, 'width': c.column_width}
188
+ if getattr(node, 'sort', False) and c.key and not c.children:
189
+ href, marca = sort_link(self.r, c.key)
190
+ coluna['to'] = href
191
+ coluna['label'] += f' {marca}' if marca else ''
192
+ cabecalho.append(coluna)
193
+ return {'type': 'table', 'props': props, 'columns': cabecalho, 'rows': linhas, 'children': []}
194
+
195
+ def _ramo(self, node: IfNode) -> list:
196
+ if self.r._evaluate_condition(node.condition):
197
+ return list(node.if_body)
198
+ for bloco in node.elseif_blocks or []:
199
+ cond = bloco.get('condition') if isinstance(bloco, dict) else getattr(bloco, 'condition', None)
200
+ corpo = bloco.get('body') if isinstance(bloco, dict) else getattr(bloco, 'body', None)
201
+ if cond is not None and self.r._evaluate_condition(cond):
202
+ return list(corpo or [])
203
+ return list(node.else_body or [])
204
+
205
+ def _loop(self, node: LoopNode) -> List[Dict[str, Any]]:
206
+ # The same iteration as HTMLRenderer._render_loop.
207
+ vista: List[Dict[str, Any]] = []
208
+ ctx = self.r.context
209
+ originais = ctx.local_vars.copy()
210
+ try:
211
+ for indice, item in enumerate(self.r._get_loop_items(node) or []):
212
+ ctx.set_variable(node.var_name, item, scope='local')
213
+ if node.loop_type == 'query' and isinstance(item, dict):
214
+ for campo, valor in item.items():
215
+ ctx.set_variable(f'{node.var_name}.{campo}', valor, scope='local')
216
+ if node.index_name:
217
+ ctx.set_variable(node.index_name, indice, scope='local')
218
+ vista.extend(self.build(node.body))
219
+ finally:
220
+ ctx.local_vars = originais
221
+ return vista