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,336 @@
1
+ """
2
+ Quantum Plugin Manifest Parser
3
+
4
+ Parses quantum-plugin.yaml manifest files that define plugin metadata,
5
+ tag handlers, hooks, and other plugin configuration.
6
+ """
7
+
8
+ import yaml
9
+ from pathlib import Path
10
+ from dataclasses import dataclass, field
11
+ from typing import List, Dict, Any, Optional
12
+
13
+
14
+ class ManifestError(Exception):
15
+ """Error parsing or validating plugin manifest"""
16
+ pass
17
+
18
+
19
+ @dataclass
20
+ class TagDefinition:
21
+ """Defines a custom tag provided by a plugin"""
22
+ prefix: str # e.g., "my" for <my:tag>
23
+ handler: str # Module path: "src.tags:MyTagHandler"
24
+ tags: List[str] = field(default_factory=list) # Optional: specific tags, empty = all
25
+
26
+ def __post_init__(self):
27
+ if not self.prefix:
28
+ raise ManifestError("Tag definition requires 'prefix'")
29
+ if not self.handler:
30
+ raise ManifestError("Tag definition requires 'handler'")
31
+
32
+
33
+ @dataclass
34
+ class HookDefinition:
35
+ """Defines a lifecycle hook registration"""
36
+ hook_type: str # before_parse, after_parse, before_render, after_render, etc.
37
+ handler: str # Module path: "src.hooks:on_before_render"
38
+ priority: int = 100 # Lower = earlier execution
39
+
40
+ def __post_init__(self):
41
+ valid_hooks = [
42
+ 'before_parse', 'after_parse',
43
+ 'before_render', 'after_render',
44
+ 'before_execute', 'after_execute',
45
+ 'on_error', 'on_init', 'on_shutdown'
46
+ ]
47
+ if self.hook_type not in valid_hooks:
48
+ raise ManifestError(f"Invalid hook type: {self.hook_type}. Valid: {valid_hooks}")
49
+
50
+
51
+ @dataclass
52
+ class AdapterDefinition:
53
+ """Defines a custom adapter (e.g., for different output targets)"""
54
+ name: str # e.g., "my-adapter"
55
+ handler: str # Module path: "src.adapters:MyAdapter"
56
+ target: str = "html" # Target type: html, textual, desktop, etc.
57
+
58
+
59
+ @dataclass
60
+ class CLICommandDefinition:
61
+ """Defines a custom CLI command"""
62
+ name: str # Command name: "my-command"
63
+ handler: str # Module path: "src.cli:my_command"
64
+ help: str = "" # Help text
65
+ aliases: List[str] = field(default_factory=list)
66
+
67
+
68
+ @dataclass
69
+ class ASTNodeDefinition:
70
+ """Defines a custom AST node type"""
71
+ name: str # Node class name: "MyCustomNode"
72
+ handler: str # Module path: "src.nodes:MyCustomNode"
73
+ base: str = "QuantumNode" # Base class
74
+
75
+
76
+ @dataclass
77
+ class PluginManifest:
78
+ """
79
+ Complete plugin manifest from quantum-plugin.yaml
80
+
81
+ Example manifest:
82
+ ```yaml
83
+ name: my-plugin
84
+ version: 1.0.0
85
+ quantum: ">=1.0.0"
86
+ description: My custom plugin
87
+ author: Developer Name
88
+
89
+ tags:
90
+ - prefix: "my"
91
+ handler: "src.tags:MyTagHandler"
92
+ tags:
93
+ - custom-tag
94
+ - another-tag
95
+
96
+ hooks:
97
+ - hook_type: before_render
98
+ handler: "src.hooks:on_before_render"
99
+ priority: 50
100
+
101
+ adapters:
102
+ - name: custom-adapter
103
+ handler: "src.adapters:CustomAdapter"
104
+ target: html
105
+
106
+ cli:
107
+ - name: generate
108
+ handler: "src.cli:generate_cmd"
109
+ help: "Generate custom files"
110
+
111
+ nodes:
112
+ - name: CustomNode
113
+ handler: "src.nodes:CustomNode"
114
+
115
+ dependencies:
116
+ - other-plugin>=1.0.0
117
+ ```
118
+ """
119
+ name: str
120
+ version: str
121
+ quantum_version: str = ">=1.0.0"
122
+ description: str = ""
123
+ author: str = ""
124
+ license: str = ""
125
+ homepage: str = ""
126
+ repository: str = ""
127
+
128
+ # Plugin components
129
+ tags: List[TagDefinition] = field(default_factory=list)
130
+ hooks: List[HookDefinition] = field(default_factory=list)
131
+ adapters: List[AdapterDefinition] = field(default_factory=list)
132
+ cli_commands: List[CLICommandDefinition] = field(default_factory=list)
133
+ nodes: List[ASTNodeDefinition] = field(default_factory=list)
134
+
135
+ # Dependencies
136
+ dependencies: List[str] = field(default_factory=list)
137
+
138
+ # Source directory (set during loading)
139
+ source_path: Optional[Path] = None
140
+
141
+ @classmethod
142
+ def from_file(cls, manifest_path) -> 'PluginManifest':
143
+ """Load manifest from yaml file"""
144
+ if isinstance(manifest_path, str):
145
+ manifest_path = Path(manifest_path)
146
+ if not manifest_path.exists():
147
+ raise ManifestError(f"Manifest file not found: {manifest_path}")
148
+
149
+ try:
150
+ with open(manifest_path, 'r', encoding='utf-8') as f:
151
+ data = yaml.safe_load(f)
152
+ except yaml.YAMLError as e:
153
+ raise ManifestError(f"Invalid YAML in manifest: {e}")
154
+
155
+ if not data:
156
+ raise ManifestError("Empty manifest file")
157
+
158
+ return cls.from_dict(data, manifest_path.parent)
159
+
160
+ @classmethod
161
+ def from_dict(cls, data: Dict[str, Any], source_path: Path = None) -> 'PluginManifest':
162
+ """Create manifest from dictionary"""
163
+ # Validate required fields
164
+ if 'name' not in data:
165
+ raise ManifestError("Manifest requires 'name' field")
166
+ if 'version' not in data:
167
+ raise ManifestError("Manifest requires 'version' field")
168
+
169
+ # Parse tags
170
+ tags = []
171
+ for tag_data in data.get('tags', []):
172
+ if isinstance(tag_data, dict):
173
+ tags.append(TagDefinition(
174
+ prefix=tag_data.get('prefix', ''),
175
+ handler=tag_data.get('handler', ''),
176
+ tags=tag_data.get('tags', [])
177
+ ))
178
+
179
+ # Parse hooks
180
+ hooks = []
181
+ for hook_data in data.get('hooks', []):
182
+ if isinstance(hook_data, dict):
183
+ # Support both formats:
184
+ # - hook_type: before_render
185
+ # handler: "src.hooks:fn"
186
+ # - before_render: "src.hooks:fn"
187
+ hook_type = hook_data.get('hook_type')
188
+ handler = hook_data.get('handler')
189
+ priority = hook_data.get('priority', 100)
190
+
191
+ # Alternative format
192
+ if not hook_type:
193
+ for ht in ['before_parse', 'after_parse', 'before_render',
194
+ 'after_render', 'before_execute', 'after_execute',
195
+ 'on_error', 'on_init', 'on_shutdown']:
196
+ if ht in hook_data:
197
+ hook_type = ht
198
+ handler = hook_data[ht]
199
+ break
200
+
201
+ if hook_type and handler:
202
+ hooks.append(HookDefinition(
203
+ hook_type=hook_type,
204
+ handler=handler,
205
+ priority=priority
206
+ ))
207
+
208
+ # Parse adapters
209
+ adapters = []
210
+ for adapter_data in data.get('adapters', []):
211
+ if isinstance(adapter_data, dict):
212
+ adapters.append(AdapterDefinition(
213
+ name=adapter_data.get('name', ''),
214
+ handler=adapter_data.get('handler', ''),
215
+ target=adapter_data.get('target', 'html')
216
+ ))
217
+
218
+ # Parse CLI commands
219
+ cli_commands = []
220
+ for cli_data in data.get('cli', []):
221
+ if isinstance(cli_data, dict):
222
+ cli_commands.append(CLICommandDefinition(
223
+ name=cli_data.get('name', ''),
224
+ handler=cli_data.get('handler', ''),
225
+ help=cli_data.get('help', ''),
226
+ aliases=cli_data.get('aliases', [])
227
+ ))
228
+
229
+ # Parse AST nodes
230
+ nodes = []
231
+ for node_data in data.get('nodes', []):
232
+ if isinstance(node_data, dict):
233
+ nodes.append(ASTNodeDefinition(
234
+ name=node_data.get('name', ''),
235
+ handler=node_data.get('handler', ''),
236
+ base=node_data.get('base', 'QuantumNode')
237
+ ))
238
+
239
+ return cls(
240
+ name=data['name'],
241
+ version=data['version'],
242
+ quantum_version=data.get('quantum', '>=1.0.0'),
243
+ description=data.get('description', ''),
244
+ author=data.get('author', ''),
245
+ license=data.get('license', ''),
246
+ homepage=data.get('homepage', ''),
247
+ repository=data.get('repository', ''),
248
+ tags=tags,
249
+ hooks=hooks,
250
+ adapters=adapters,
251
+ cli_commands=cli_commands,
252
+ nodes=nodes,
253
+ dependencies=data.get('dependencies', []),
254
+ source_path=source_path
255
+ )
256
+
257
+ def to_dict(self) -> Dict[str, Any]:
258
+ """Convert manifest to dictionary"""
259
+ result = {
260
+ 'name': self.name,
261
+ 'version': self.version,
262
+ 'quantum': self.quantum_version,
263
+ }
264
+
265
+ if self.description:
266
+ result['description'] = self.description
267
+ if self.author:
268
+ result['author'] = self.author
269
+ if self.license:
270
+ result['license'] = self.license
271
+
272
+ if self.tags:
273
+ result['tags'] = [
274
+ {'prefix': t.prefix, 'handler': t.handler, 'tags': t.tags}
275
+ for t in self.tags
276
+ ]
277
+
278
+ if self.hooks:
279
+ result['hooks'] = [
280
+ {'hook_type': h.hook_type, 'handler': h.handler, 'priority': h.priority}
281
+ for h in self.hooks
282
+ ]
283
+
284
+ if self.adapters:
285
+ result['adapters'] = [
286
+ {'name': a.name, 'handler': a.handler, 'target': a.target}
287
+ for a in self.adapters
288
+ ]
289
+
290
+ if self.cli_commands:
291
+ result['cli'] = [
292
+ {'name': c.name, 'handler': c.handler, 'help': c.help, 'aliases': c.aliases}
293
+ for c in self.cli_commands
294
+ ]
295
+
296
+ if self.nodes:
297
+ result['nodes'] = [
298
+ {'name': n.name, 'handler': n.handler, 'base': n.base}
299
+ for n in self.nodes
300
+ ]
301
+
302
+ if self.dependencies:
303
+ result['dependencies'] = self.dependencies
304
+
305
+ return result
306
+
307
+ def validate(self) -> List[str]:
308
+ """Validate manifest and return list of errors"""
309
+ errors = []
310
+
311
+ # Name validation
312
+ if not self.name:
313
+ errors.append("Plugin name is required")
314
+ elif not self.name.replace('-', '').replace('_', '').isalnum():
315
+ errors.append(f"Invalid plugin name: {self.name}")
316
+
317
+ # Version validation
318
+ if not self.version:
319
+ errors.append("Plugin version is required")
320
+
321
+ # Tag validation
322
+ for tag in self.tags:
323
+ if not tag.prefix:
324
+ errors.append("Tag prefix is required")
325
+ if not tag.handler:
326
+ errors.append(f"Handler is required for tag prefix '{tag.prefix}'")
327
+
328
+ # Hook validation
329
+ for hook in self.hooks:
330
+ if not hook.handler:
331
+ errors.append(f"Handler is required for hook '{hook.hook_type}'")
332
+
333
+ return errors
334
+
335
+ def __repr__(self):
336
+ return f"<PluginManifest {self.name}@{self.version}>"
@@ -0,0 +1,371 @@
1
+ """
2
+ Quantum Plugin Registry
3
+
4
+ Central registry for all loaded plugins and their components.
5
+ Provides lookup and management for tags, nodes, adapters, and CLI commands.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Dict, List, Any, Optional, Type, Callable
10
+ from pathlib import Path
11
+ import logging
12
+
13
+ from .manifest import PluginManifest, TagDefinition, ASTNodeDefinition
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class PluginInfo:
20
+ """Information about a loaded plugin"""
21
+ name: str
22
+ version: str
23
+ manifest: PluginManifest
24
+ source_path: Path
25
+ enabled: bool = True
26
+ loaded_modules: Dict[str, Any] = field(default_factory=dict)
27
+
28
+ # Loaded components
29
+ tag_handlers: Dict[str, Any] = field(default_factory=dict) # prefix -> handler
30
+ node_classes: Dict[str, Type] = field(default_factory=dict) # name -> class
31
+ adapters: Dict[str, Any] = field(default_factory=dict) # name -> adapter
32
+ cli_commands: Dict[str, Callable] = field(default_factory=dict) # name -> function
33
+
34
+ def __repr__(self):
35
+ status = "enabled" if self.enabled else "disabled"
36
+ return f"<PluginInfo {self.name}@{self.version} ({status})>"
37
+
38
+
39
+ class TagHandler:
40
+ """
41
+ Base class for custom tag handlers.
42
+
43
+ Plugins should extend this class to handle custom tags.
44
+
45
+ Example:
46
+ class MyTagHandler(TagHandler):
47
+ prefix = "my"
48
+
49
+ def parse(self, element, parser):
50
+ # Parse XML element into AST node
51
+ return MyCustomNode(...)
52
+
53
+ def execute(self, node, context, runtime):
54
+ # Execute the node
55
+ return result
56
+
57
+ def render(self, node, renderer):
58
+ # Render node to HTML
59
+ return "<div>...</div>"
60
+ """
61
+
62
+ prefix: str = "" # Tag prefix, e.g., "my" for <my:tag>
63
+
64
+ def parse(self, element: Any, parser: Any) -> Any:
65
+ """
66
+ Parse XML element into AST node.
67
+
68
+ Args:
69
+ element: XML element (ElementTree Element)
70
+ parser: Quantum parser instance
71
+
72
+ Returns:
73
+ AST node
74
+ """
75
+ raise NotImplementedError("Tag handler must implement parse()")
76
+
77
+ def execute(self, node: Any, context: Any, runtime: Any) -> Any:
78
+ """
79
+ Execute the AST node.
80
+
81
+ Args:
82
+ node: AST node to execute
83
+ context: Execution context
84
+ runtime: Component runtime
85
+
86
+ Returns:
87
+ Execution result
88
+ """
89
+ return None
90
+
91
+ def render(self, node: Any, renderer: Any) -> str:
92
+ """
93
+ Render AST node to HTML.
94
+
95
+ Args:
96
+ node: AST node to render
97
+ renderer: Renderer instance
98
+
99
+ Returns:
100
+ HTML string
101
+ """
102
+ return ""
103
+
104
+ def get_supported_tags(self) -> List[str]:
105
+ """
106
+ Get list of supported tag names (without prefix).
107
+
108
+ Returns:
109
+ List of tag names, empty list means all tags with this prefix
110
+ """
111
+ return []
112
+
113
+
114
+ class PluginRegistry:
115
+ """
116
+ Central registry for all plugin components.
117
+
118
+ Provides lookup and management for:
119
+ - Plugin manifests and info
120
+ - Tag handlers by prefix
121
+ - AST node classes
122
+ - Adapters
123
+ - CLI commands
124
+ """
125
+
126
+ def __init__(self):
127
+ self._plugins: Dict[str, PluginInfo] = {}
128
+ self._tag_handlers: Dict[str, TagHandler] = {} # prefix -> handler
129
+ self._node_classes: Dict[str, Type] = {} # name -> class
130
+ self._adapters: Dict[str, Any] = {} # name -> adapter
131
+ self._cli_commands: Dict[str, Callable] = {} # name -> function
132
+ self._tag_prefixes: Dict[str, str] = {} # prefix -> plugin_name
133
+
134
+ def register_plugin(self, info: PluginInfo) -> None:
135
+ """
136
+ Register a loaded plugin.
137
+
138
+ Args:
139
+ info: Plugin information
140
+ """
141
+ if info.name in self._plugins:
142
+ logger.warning(f"Plugin '{info.name}' already registered, replacing")
143
+
144
+ self._plugins[info.name] = info
145
+ logger.info(f"Registered plugin: {info.name}@{info.version}")
146
+
147
+ def unregister_plugin(self, name: str) -> bool:
148
+ """
149
+ Unregister a plugin and remove all its components.
150
+
151
+ Args:
152
+ name: Plugin name
153
+
154
+ Returns:
155
+ True if plugin was found and removed
156
+ """
157
+ if name not in self._plugins:
158
+ return False
159
+
160
+ info = self._plugins[name]
161
+
162
+ # Remove tag handlers
163
+ for prefix in info.tag_handlers:
164
+ if prefix in self._tag_handlers:
165
+ del self._tag_handlers[prefix]
166
+ if prefix in self._tag_prefixes:
167
+ del self._tag_prefixes[prefix]
168
+
169
+ # Remove node classes
170
+ for node_name in info.node_classes:
171
+ if node_name in self._node_classes:
172
+ del self._node_classes[node_name]
173
+
174
+ # Remove adapters
175
+ for adapter_name in info.adapters:
176
+ if adapter_name in self._adapters:
177
+ del self._adapters[adapter_name]
178
+
179
+ # Remove CLI commands
180
+ for cmd_name in info.cli_commands:
181
+ if cmd_name in self._cli_commands:
182
+ del self._cli_commands[cmd_name]
183
+
184
+ del self._plugins[name]
185
+ logger.info(f"Unregistered plugin: {name}")
186
+ return True
187
+
188
+ def get_plugin(self, name: str) -> Optional[PluginInfo]:
189
+ """Get plugin info by name"""
190
+ return self._plugins.get(name)
191
+
192
+ def get_all_plugins(self) -> Dict[str, PluginInfo]:
193
+ """Get all registered plugins"""
194
+ return self._plugins
195
+
196
+ def get_enabled_plugins(self) -> Dict[str, PluginInfo]:
197
+ """Get only enabled plugins"""
198
+ return {k: v for k, v in self._plugins.items() if v.enabled}
199
+
200
+ # Tag Handler Management
201
+ def register_tag_handler(self, prefix: str, handler: TagHandler, plugin_name: str) -> None:
202
+ """
203
+ Register a tag handler for a prefix.
204
+
205
+ Args:
206
+ prefix: Tag prefix (e.g., "my" for <my:tag>)
207
+ handler: Tag handler instance
208
+ plugin_name: Name of the plugin providing this handler
209
+ """
210
+ if prefix in self._tag_handlers:
211
+ logger.warning(f"Tag prefix '{prefix}' already registered by {self._tag_prefixes.get(prefix)}")
212
+
213
+ self._tag_handlers[prefix] = handler
214
+ self._tag_prefixes[prefix] = plugin_name
215
+
216
+ # Also add to plugin info
217
+ if plugin_name in self._plugins:
218
+ self._plugins[plugin_name].tag_handlers[prefix] = handler
219
+
220
+ logger.debug(f"Registered tag handler: {prefix}:* from {plugin_name}")
221
+
222
+ def get_tag_handler(self, prefix: str) -> Optional[TagHandler]:
223
+ """Get tag handler for a prefix"""
224
+ return self._tag_handlers.get(prefix)
225
+
226
+ def has_tag_prefix(self, prefix: str) -> bool:
227
+ """Check if a tag prefix is registered"""
228
+ return prefix in self._tag_handlers
229
+
230
+ def get_all_tag_prefixes(self) -> List[str]:
231
+ """Get all registered tag prefixes"""
232
+ return list(self._tag_handlers.keys())
233
+
234
+ # AST Node Management
235
+ def register_node_class(self, name: str, node_class: Type, plugin_name: str) -> None:
236
+ """
237
+ Register a custom AST node class.
238
+
239
+ Args:
240
+ name: Node class name
241
+ node_class: Node class type
242
+ plugin_name: Name of the plugin providing this node
243
+ """
244
+ self._node_classes[name] = node_class
245
+
246
+ if plugin_name in self._plugins:
247
+ self._plugins[plugin_name].node_classes[name] = node_class
248
+
249
+ logger.debug(f"Registered node class: {name} from {plugin_name}")
250
+
251
+ def get_node_class(self, name: str) -> Optional[Type]:
252
+ """Get AST node class by name"""
253
+ return self._node_classes.get(name)
254
+
255
+ def get_all_node_classes(self) -> Dict[str, Type]:
256
+ """Get all registered node classes"""
257
+ return self._node_classes
258
+
259
+ # Adapter Management
260
+ def register_adapter(self, name: str, adapter: Any, plugin_name: str) -> None:
261
+ """
262
+ Register a custom adapter.
263
+
264
+ Args:
265
+ name: Adapter name
266
+ adapter: Adapter instance or class
267
+ plugin_name: Name of the plugin providing this adapter
268
+ """
269
+ self._adapters[name] = adapter
270
+
271
+ if plugin_name in self._plugins:
272
+ self._plugins[plugin_name].adapters[name] = adapter
273
+
274
+ logger.debug(f"Registered adapter: {name} from {plugin_name}")
275
+
276
+ def get_adapter(self, name: str) -> Optional[Any]:
277
+ """Get adapter by name"""
278
+ return self._adapters.get(name)
279
+
280
+ def get_all_adapters(self) -> Dict[str, Any]:
281
+ """Get all registered adapters"""
282
+ return self._adapters
283
+
284
+ # CLI Command Management
285
+ def register_cli_command(
286
+ self,
287
+ name: str,
288
+ handler: Callable,
289
+ plugin_name: str,
290
+ aliases: List[str] = None
291
+ ) -> None:
292
+ """
293
+ Register a CLI command.
294
+
295
+ Args:
296
+ name: Command name
297
+ handler: Command handler function
298
+ plugin_name: Name of the plugin providing this command
299
+ aliases: Optional command aliases
300
+ """
301
+ self._cli_commands[name] = handler
302
+
303
+ # Register aliases
304
+ for alias in (aliases or []):
305
+ self._cli_commands[alias] = handler
306
+
307
+ if plugin_name in self._plugins:
308
+ self._plugins[plugin_name].cli_commands[name] = handler
309
+
310
+ logger.debug(f"Registered CLI command: {name} from {plugin_name}")
311
+
312
+ def get_cli_command(self, name: str) -> Optional[Callable]:
313
+ """Get CLI command by name"""
314
+ return self._cli_commands.get(name)
315
+
316
+ def get_all_cli_commands(self) -> Dict[str, Callable]:
317
+ """Get all registered CLI commands"""
318
+ return self._cli_commands
319
+
320
+ # Utility methods
321
+ def enable_plugin(self, name: str) -> bool:
322
+ """Enable a plugin"""
323
+ if name in self._plugins:
324
+ self._plugins[name].enabled = True
325
+ return True
326
+ return False
327
+
328
+ def disable_plugin(self, name: str) -> bool:
329
+ """Disable a plugin"""
330
+ if name in self._plugins:
331
+ self._plugins[name].enabled = False
332
+ return True
333
+ return False
334
+
335
+ def clear(self) -> None:
336
+ """Clear all registered components"""
337
+ self._plugins.clear()
338
+ self._tag_handlers.clear()
339
+ self._node_classes.clear()
340
+ self._adapters.clear()
341
+ self._cli_commands.clear()
342
+ self._tag_prefixes.clear()
343
+
344
+ def get_stats(self) -> Dict[str, int]:
345
+ """Get registry statistics"""
346
+ return {
347
+ 'plugins': len(self._plugins),
348
+ 'enabled_plugins': len(self.get_enabled_plugins()),
349
+ 'tag_handlers': len(self._tag_handlers),
350
+ 'node_classes': len(self._node_classes),
351
+ 'adapters': len(self._adapters),
352
+ 'cli_commands': len(self._cli_commands)
353
+ }
354
+
355
+
356
+ # Global registry instance
357
+ _global_registry: Optional[PluginRegistry] = None
358
+
359
+
360
+ def get_registry() -> PluginRegistry:
361
+ """Get the global plugin registry instance"""
362
+ global _global_registry
363
+ if _global_registry is None:
364
+ _global_registry = PluginRegistry()
365
+ return _global_registry
366
+
367
+
368
+ def set_registry(registry: PluginRegistry) -> None:
369
+ """Set the global plugin registry instance"""
370
+ global _global_registry
371
+ _global_registry = registry