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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- 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
|