quantum-framework 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -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 +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,159 @@
1
+ """
2
+ Parar o servidor iniciado por `quantum start` e CONFERIR que ele parou.
3
+
4
+ O `quantum stop` antigo matava o PID do `.quantum.pid`, imprimia "Server
5
+ stopped" e saia com 0 — sem olhar se alguma coisa tinha parado. Com
6
+ `reload: true` (o padrao do quantum.config.yaml) o Werkzeug serve a partir de
7
+ um processo FILHO; o arquivo tinha so o PID do pai. No Windows,
8
+ `taskkill /F` sem `/T` derruba so o pai, e o filho seguia vivo, orfao,
9
+ respondendo HTTP 200 na mesma porta.
10
+
11
+ Agora:
12
+ - o `.quantum.pid` guarda o pai e, quando ha reloader, o filho (uma linha
13
+ cada — o formato antigo, uma linha so, continua valido);
14
+ - o stop derruba a arvore de cada PID, espera, e so declara sucesso se
15
+ nenhum deles continuar vivo. Se sobrar algum, diz qual e sai com 1.
16
+ """
17
+
18
+ import os
19
+ import signal
20
+ import subprocess
21
+ import time
22
+ from pathlib import Path
23
+ from typing import Callable, List
24
+
25
+ PID_FILE = '.quantum.pid'
26
+
27
+
28
+ def read_pids(pid_file: Path) -> List[int]:
29
+ """PIDs listados no arquivo, na ordem, sem repetir.
30
+
31
+ Levanta ValueError se o arquivo nao tiver nenhum inteiro valido.
32
+ """
33
+ pids: List[int] = []
34
+ for linha in pid_file.read_text(encoding='utf-8').split():
35
+ pid = int(linha)
36
+ if pid not in pids:
37
+ pids.append(pid)
38
+ if not pids:
39
+ raise ValueError(f"{pid_file} is empty")
40
+ return pids
41
+
42
+
43
+ def pid_alive(pid: int) -> bool:
44
+ """O processo existe e ainda nao terminou?"""
45
+ if pid <= 0:
46
+ return False
47
+ if os.name == 'nt':
48
+ return _pid_alive_windows(pid)
49
+ try:
50
+ os.kill(pid, 0)
51
+ except ProcessLookupError:
52
+ return False
53
+ except PermissionError:
54
+ pass # existe, so nao e nosso
55
+ return not _exited_unreaped(pid)
56
+
57
+
58
+ def _exited_unreaped(pid: int) -> bool:
59
+ """O PID ainda existe na tabela, mas o processo ja terminou?"""
60
+ # Um processo que terminou e ainda nao foi recolhido pelo pai (zumbi)
61
+ # responde a os.kill(pid, 0) como se estivesse vivo. Nao esta: nao roda,
62
+ # nao segura porta. Sem isso, parar um servidor cujo pai demora a chamar
63
+ # wait() — um supervisor, ou o proprio pytest — dava "still running".
64
+ try:
65
+ with open(f'/proc/{pid}/stat', 'rb') as f:
66
+ estado = f.read().rsplit(b')', 1)[1].split()[0]
67
+ except FileNotFoundError:
68
+ return os.path.isdir('/proc') # /proc existe e o pid nao: ja foi embora
69
+ except (OSError, IndexError):
70
+ return False # sem /proc (macOS): fica com o resultado do os.kill
71
+ return estado in (b'Z', b'X')
72
+
73
+
74
+ def _pid_alive_windows(pid: int) -> bool:
75
+ # os.kill(pid, 0) NAO serve no Windows: qualquer sinal que nao seja
76
+ # CTRL_C/CTRL_BREAK vira TerminateProcess — a checagem mataria o processo.
77
+ import ctypes
78
+ from ctypes import wintypes
79
+
80
+ PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
81
+ STILL_ACTIVE = 259
82
+ ERROR_ACCESS_DENIED = 5
83
+
84
+ kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
85
+ kernel32.OpenProcess.restype = wintypes.HANDLE
86
+ handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
87
+ if not handle:
88
+ return ctypes.get_last_error() == ERROR_ACCESS_DENIED
89
+ try:
90
+ codigo = wintypes.DWORD()
91
+ if not kernel32.GetExitCodeProcess(handle, ctypes.byref(codigo)):
92
+ return False
93
+ return codigo.value == STILL_ACTIVE
94
+ finally:
95
+ kernel32.CloseHandle(handle)
96
+
97
+
98
+ def _terminate(pid: int, force: bool) -> None:
99
+ if os.name == 'nt':
100
+ # /T derruba a arvore inteira: o filho do reloader junto com o pai.
101
+ subprocess.run(['taskkill', '/F', '/T', '/PID', str(pid)],
102
+ capture_output=True)
103
+ return
104
+ try:
105
+ os.kill(pid, signal.SIGKILL if force else signal.SIGTERM)
106
+ except (ProcessLookupError, PermissionError):
107
+ pass
108
+
109
+
110
+ def _wait_until_dead(pids: List[int], timeout: float,
111
+ alive: Callable[[int], bool]) -> List[int]:
112
+ limite = time.monotonic() + timeout
113
+ vivos = [p for p in pids if alive(p)]
114
+ while vivos and time.monotonic() < limite:
115
+ time.sleep(0.2)
116
+ vivos = [p for p in vivos if alive(p)]
117
+ return vivos
118
+
119
+
120
+ def stop_server(pid_file: Path = Path(PID_FILE), grace: float = 5.0,
121
+ alive: Callable[[int], bool] = pid_alive,
122
+ terminate: Callable[[int, bool], None] = _terminate) -> int:
123
+ """Para o servidor do `pid_file`. Devolve o codigo de saida do comando."""
124
+ if not pid_file.exists():
125
+ print(f"No running server found ({pid_file.name} not found)")
126
+ return 1
127
+
128
+ try:
129
+ pids = read_pids(pid_file)
130
+ except (ValueError, OSError) as e:
131
+ print(f"Invalid PID file: {e}")
132
+ pid_file.unlink(missing_ok=True)
133
+ return 1
134
+
135
+ vivos = [p for p in pids if alive(p)]
136
+ if not vivos:
137
+ print(f"No running server found (PID {', '.join(map(str, pids))} "
138
+ f"is not running; removed stale {pid_file.name})")
139
+ pid_file.unlink(missing_ok=True)
140
+ return 1
141
+
142
+ for pid in vivos:
143
+ terminate(pid, False)
144
+ restantes = _wait_until_dead(vivos, grace, alive)
145
+
146
+ if restantes:
147
+ # SIGTERM ignorado (POSIX). No Windows o taskkill /F ja e forcado.
148
+ for pid in restantes:
149
+ terminate(pid, True)
150
+ restantes = _wait_until_dead(restantes, 2.0, alive)
151
+
152
+ if restantes:
153
+ print(f"Could not stop the server: PID {', '.join(map(str, restantes))} "
154
+ f"is still running. Stop it manually.")
155
+ return 1
156
+
157
+ print(f"Server stopped (PID {', '.join(map(str, vivos))})")
158
+ pid_file.unlink(missing_ok=True)
159
+ return 0
quantum/cli/utils.py ADDED
@@ -0,0 +1,334 @@
1
+ """
2
+ Quantum CLI Utilities
3
+
4
+ Color output, progress bars, and common CLI helpers using rich.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional, List, Dict, Any
11
+ from contextlib import contextmanager
12
+
13
+ # Try to import rich, provide helpful error if missing
14
+ try:
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+ from rich.table import Table
18
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
19
+ from rich.syntax import Syntax
20
+ from rich.tree import Tree
21
+ from rich.text import Text
22
+ from rich.theme import Theme
23
+ from rich.markdown import Markdown
24
+ RICH_AVAILABLE = True
25
+ except ImportError:
26
+ RICH_AVAILABLE = False
27
+
28
+
29
+ # Quantum theme colors
30
+ QUANTUM_THEME = Theme({
31
+ "info": "cyan",
32
+ "success": "green",
33
+ "warning": "yellow",
34
+ "error": "bold red",
35
+ "highlight": "bold magenta",
36
+ "dim": "dim white",
37
+ "command": "bold blue",
38
+ "path": "underline cyan",
39
+ })
40
+
41
+
42
+ class QuantumConsole:
43
+ """Rich console wrapper for Quantum CLI output."""
44
+
45
+ def __init__(self, quiet: bool = False, no_color: bool = False):
46
+ """Initialize console with optional quiet/no-color modes."""
47
+ self.quiet = quiet
48
+ self.no_color = no_color or os.environ.get('NO_COLOR', '') != ''
49
+
50
+ if RICH_AVAILABLE:
51
+ self.console = Console(
52
+ theme=QUANTUM_THEME,
53
+ force_terminal=not no_color,
54
+ no_color=no_color
55
+ )
56
+ else:
57
+ self.console = None
58
+
59
+ def _fallback_print(self, message: str, prefix: str = ""):
60
+ """Fallback print when rich is not available."""
61
+ if prefix:
62
+ print(f"[{prefix}] {message}")
63
+ else:
64
+ print(message)
65
+
66
+ def info(self, message: str) -> None:
67
+ """Print info message."""
68
+ if self.quiet:
69
+ return
70
+ if self.console:
71
+ self.console.print(f"[info][INFO][/info] {message}")
72
+ else:
73
+ self._fallback_print(message, "INFO")
74
+
75
+ def success(self, message: str) -> None:
76
+ """Print success message."""
77
+ if self.console:
78
+ self.console.print(f"[success][SUCCESS][/success] {message}")
79
+ else:
80
+ self._fallback_print(message, "SUCCESS")
81
+
82
+ def warning(self, message: str) -> None:
83
+ """Print warning message."""
84
+ if self.console:
85
+ self.console.print(f"[warning][WARNING][/warning] {message}")
86
+ else:
87
+ self._fallback_print(message, "WARNING")
88
+
89
+ def error(self, message: str) -> None:
90
+ """Print error message."""
91
+ if self.console:
92
+ self.console.print(f"[error][ERROR][/error] {message}", style="error")
93
+ else:
94
+ self._fallback_print(message, "ERROR")
95
+
96
+ def print(self, message: str = "", style: Optional[str] = None) -> None:
97
+ """Print generic message."""
98
+ if self.quiet:
99
+ return
100
+ if self.console:
101
+ self.console.print(message, style=style)
102
+ else:
103
+ print(message)
104
+
105
+ def header(self, title: str, subtitle: Optional[str] = None) -> None:
106
+ """Print header with title and optional subtitle."""
107
+ if self.quiet:
108
+ return
109
+ if self.console:
110
+ text = f"[bold magenta]{title}[/bold magenta]"
111
+ if subtitle:
112
+ text += f"\n[dim]{subtitle}[/dim]"
113
+ self.console.print(Panel(text, border_style="magenta"))
114
+ else:
115
+ print(f"\n=== {title} ===")
116
+ if subtitle:
117
+ print(f" {subtitle}")
118
+ print()
119
+
120
+ def panel(self, content: str, title: Optional[str] = None, style: str = "blue") -> None:
121
+ """Print content in a panel."""
122
+ if self.quiet:
123
+ return
124
+ if self.console:
125
+ self.console.print(Panel(content, title=title, border_style=style))
126
+ else:
127
+ if title:
128
+ print(f"\n--- {title} ---")
129
+ print(content)
130
+ print()
131
+
132
+ def table(self, headers: List[str], rows: List[List[str]], title: Optional[str] = None) -> None:
133
+ """Print a table."""
134
+ if self.console:
135
+ table = Table(title=title, show_header=True, header_style="bold cyan")
136
+ for header in headers:
137
+ table.add_column(header)
138
+ for row in rows:
139
+ table.add_row(*row)
140
+ self.console.print(table)
141
+ else:
142
+ if title:
143
+ print(f"\n{title}")
144
+ # Simple table fallback
145
+ col_widths = [max(len(str(row[i])) for row in [headers] + rows) for i in range(len(headers))]
146
+ header_line = " | ".join(h.ljust(w) for h, w in zip(headers, col_widths))
147
+ print(header_line)
148
+ print("-" * len(header_line))
149
+ for row in rows:
150
+ print(" | ".join(str(c).ljust(w) for c, w in zip(row, col_widths)))
151
+
152
+ def tree(self, label: str, items: Dict[str, Any]) -> None:
153
+ """Print a tree structure."""
154
+ if self.console:
155
+ tree = Tree(f"[bold]{label}[/bold]")
156
+ self._build_tree(tree, items)
157
+ self.console.print(tree)
158
+ else:
159
+ print(f"{label}")
160
+ self._print_tree_fallback(items, indent=2)
161
+
162
+ def _build_tree(self, parent, items: Dict[str, Any]) -> None:
163
+ """Recursively build tree."""
164
+ for key, value in items.items():
165
+ if isinstance(value, dict):
166
+ branch = parent.add(f"[cyan]{key}/[/cyan]")
167
+ self._build_tree(branch, value)
168
+ elif isinstance(value, list):
169
+ branch = parent.add(f"[cyan]{key}/[/cyan]")
170
+ for item in value:
171
+ if isinstance(item, dict):
172
+ self._build_tree(branch, item)
173
+ else:
174
+ branch.add(str(item))
175
+ else:
176
+ parent.add(f"{key}: [dim]{value}[/dim]")
177
+
178
+ def _print_tree_fallback(self, items: Dict[str, Any], indent: int = 0) -> None:
179
+ """Fallback tree printing."""
180
+ for key, value in items.items():
181
+ if isinstance(value, dict):
182
+ print(" " * indent + f"{key}/")
183
+ self._print_tree_fallback(value, indent + 2)
184
+ elif isinstance(value, list):
185
+ print(" " * indent + f"{key}/")
186
+ for item in value:
187
+ print(" " * (indent + 2) + str(item))
188
+ else:
189
+ print(" " * indent + f"{key}: {value}")
190
+
191
+ def code(self, code: str, language: str = "xml", title: Optional[str] = None) -> None:
192
+ """Print syntax-highlighted code."""
193
+ if self.quiet:
194
+ return
195
+ if self.console:
196
+ syntax = Syntax(code, language, theme="monokai", line_numbers=True)
197
+ if title:
198
+ self.console.print(Panel(syntax, title=title, border_style="green"))
199
+ else:
200
+ self.console.print(syntax)
201
+ else:
202
+ if title:
203
+ print(f"\n--- {title} ---")
204
+ print(code)
205
+
206
+ @contextmanager
207
+ def progress(self, description: str = "Working...", total: Optional[int] = None):
208
+ """Context manager for progress indication."""
209
+ if self.quiet or not self.console:
210
+ yield None
211
+ return
212
+
213
+ if total:
214
+ with Progress(
215
+ SpinnerColumn(),
216
+ TextColumn("[progress.description]{task.description}"),
217
+ BarColumn(),
218
+ TaskProgressColumn(),
219
+ console=self.console
220
+ ) as progress:
221
+ task = progress.add_task(description, total=total)
222
+ yield lambda n=1: progress.update(task, advance=n)
223
+ else:
224
+ with Progress(
225
+ SpinnerColumn(),
226
+ TextColumn("[progress.description]{task.description}"),
227
+ console=self.console
228
+ ) as progress:
229
+ task = progress.add_task(description, total=None)
230
+ yield lambda: None
231
+
232
+ def spinner(self, description: str = "Working..."):
233
+ """Create a spinner context manager."""
234
+ if self.quiet or not self.console:
235
+ @contextmanager
236
+ def noop():
237
+ yield
238
+ return noop()
239
+
240
+ return self.console.status(description, spinner="dots")
241
+
242
+
243
+ # Global console instance
244
+ console = QuantumConsole()
245
+
246
+
247
+ def get_console(quiet: bool = False, no_color: bool = False) -> QuantumConsole:
248
+ """Get a configured console instance."""
249
+ return QuantumConsole(quiet=quiet, no_color=no_color)
250
+
251
+
252
+ def validate_project_name(name: str) -> tuple[bool, str]:
253
+ """Validate project name.
254
+
255
+ Returns:
256
+ Tuple of (is_valid, error_message)
257
+ """
258
+ import re
259
+
260
+ if not name:
261
+ return False, "Project name cannot be empty"
262
+
263
+ if len(name) < 2:
264
+ return False, "Project name must be at least 2 characters"
265
+
266
+ if len(name) > 50:
267
+ return False, "Project name must be 50 characters or less"
268
+
269
+ if not re.match(r'^[a-zA-Z][a-zA-Z0-9_-]*$', name):
270
+ return False, "Project name must start with a letter and contain only letters, numbers, underscores, and hyphens"
271
+
272
+ # Check for reserved names
273
+ reserved = ['quantum', 'src', 'dist', 'build', 'node_modules', 'test', 'tests']
274
+ if name.lower() in reserved:
275
+ return False, f"'{name}' is a reserved name"
276
+
277
+ return True, ""
278
+
279
+
280
+ def find_project_root() -> Optional[Path]:
281
+ """Find the project root by looking for quantum.config.yaml or .q files."""
282
+ current = Path.cwd()
283
+
284
+ # Check current and parent directories
285
+ for path in [current] + list(current.parents):
286
+ if (path / 'quantum.config.yaml').exists():
287
+ return path
288
+ if list(path.glob('*.q')):
289
+ return path
290
+ # Stop at home directory
291
+ if path == Path.home():
292
+ break
293
+
294
+ return None
295
+
296
+
297
+ def find_q_files(path: Path, recursive: bool = True) -> List[Path]:
298
+ """Find all .q files in a directory."""
299
+ if recursive:
300
+ return list(path.rglob('*.q'))
301
+ return list(path.glob('*.q'))
302
+
303
+
304
+ def get_quantum_version() -> str:
305
+ """Get Quantum version from package or default."""
306
+ try:
307
+ # Try to read from version file
308
+ version_file = Path(__file__).parent.parent / '__version__.py'
309
+ if version_file.exists():
310
+ content = version_file.read_text()
311
+ # Parse version = "x.y.z"
312
+ for line in content.splitlines():
313
+ if line.startswith('version'):
314
+ return line.split('=')[1].strip().strip('"\'')
315
+ except Exception:
316
+ pass
317
+
318
+ return "1.0.0-dev"
319
+
320
+
321
+ def ensure_dependencies(*packages: str) -> bool:
322
+ """Check if required packages are installed."""
323
+ missing = []
324
+ for pkg in packages:
325
+ try:
326
+ __import__(pkg.replace('-', '_'))
327
+ except ImportError:
328
+ missing.append(pkg)
329
+
330
+ if missing:
331
+ console.error(f"Missing dependencies: {', '.join(missing)}")
332
+ console.info(f"Install with: pip install {' '.join(missing)}")
333
+ return False
334
+ return True
@@ -0,0 +1,30 @@
1
+ """
2
+ Quantum Transpiler
3
+ ==================
4
+
5
+ Transpiles Quantum (.q) files to native Python and JavaScript code.
6
+
7
+ Usage:
8
+ from quantum.compiler import Transpiler
9
+
10
+ transpiler = Transpiler(target='python')
11
+ result = transpiler.compile_file('app.q')
12
+ print(result.code)
13
+
14
+ CLI:
15
+ quantum compile app.q -o app.py
16
+ quantum compile app.q -o app.js --target=javascript
17
+ """
18
+
19
+ from .transpiler import Transpiler, CompilationResult
20
+ from .base_generator import CodeGenerator
21
+ from .expression_transformer import ExpressionTransformer
22
+
23
+ __all__ = [
24
+ 'Transpiler',
25
+ 'CompilationResult',
26
+ 'CodeGenerator',
27
+ 'ExpressionTransformer',
28
+ ]
29
+
30
+ __version__ = '1.0.0'