@voodocs/cli 0.1.0
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.
- package/LICENSE +37 -0
- package/README.md +153 -0
- package/USAGE.md +314 -0
- package/cli.py +1340 -0
- package/examples/.cursorrules +437 -0
- package/examples/instructions/.claude/instructions.md +372 -0
- package/examples/instructions/.cursorrules +437 -0
- package/examples/instructions/.windsurfrules +437 -0
- package/examples/instructions/VOODOCS_INSTRUCTIONS.md +437 -0
- package/examples/math_example.py +41 -0
- package/examples/phase2_test.py +24 -0
- package/examples/test_compound_conditions.py +40 -0
- package/examples/test_math_example.py +186 -0
- package/lib/darkarts/README.md +115 -0
- package/lib/darkarts/__init__.py +16 -0
- package/lib/darkarts/annotations/__init__.py +34 -0
- package/lib/darkarts/annotations/parser.py +618 -0
- package/lib/darkarts/annotations/types.py +181 -0
- package/lib/darkarts/cli.py +128 -0
- package/lib/darkarts/core/__init__.py +32 -0
- package/lib/darkarts/core/interface.py +256 -0
- package/lib/darkarts/core/loader.py +231 -0
- package/lib/darkarts/core/plugin.py +215 -0
- package/lib/darkarts/core/registry.py +146 -0
- package/lib/darkarts/exceptions.py +51 -0
- package/lib/darkarts/parsers/typescript/dist/cli.d.ts +9 -0
- package/lib/darkarts/parsers/typescript/dist/cli.d.ts.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/cli.js +69 -0
- package/lib/darkarts/parsers/typescript/dist/cli.js.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/parser.d.ts +111 -0
- package/lib/darkarts/parsers/typescript/dist/parser.d.ts.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/parser.js +365 -0
- package/lib/darkarts/parsers/typescript/dist/parser.js.map +1 -0
- package/lib/darkarts/parsers/typescript/package-lock.json +51 -0
- package/lib/darkarts/parsers/typescript/package.json +19 -0
- package/lib/darkarts/parsers/typescript/src/cli.ts +41 -0
- package/lib/darkarts/parsers/typescript/src/parser.ts +408 -0
- package/lib/darkarts/parsers/typescript/tsconfig.json +19 -0
- package/lib/darkarts/plugins/voodocs/__init__.py +379 -0
- package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +151 -0
- package/lib/darkarts/plugins/voodocs/annotation_validator.py +280 -0
- package/lib/darkarts/plugins/voodocs/api_spec_generator.py +486 -0
- package/lib/darkarts/plugins/voodocs/documentation_generator.py +610 -0
- package/lib/darkarts/plugins/voodocs/html_exporter.py +260 -0
- package/lib/darkarts/plugins/voodocs/instruction_generator.py +706 -0
- package/lib/darkarts/plugins/voodocs/pdf_exporter.py +66 -0
- package/lib/darkarts/plugins/voodocs/test_generator.py +636 -0
- package/package.json +70 -0
- package/requirements.txt +13 -0
- package/templates/ci/github-actions.yml +73 -0
- package/templates/ci/gitlab-ci.yml +35 -0
- package/templates/ci/pre-commit-hook.sh +26 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin loader for discovering and loading plugins.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import importlib.util
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
from .plugin import DarkArtsPlugin, PluginError
|
|
12
|
+
from .registry import PluginRegistry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginLoader:
|
|
16
|
+
"""Load plugins from various sources."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, registry: PluginRegistry):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the loader.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
registry: The plugin registry to register loaded plugins
|
|
24
|
+
"""
|
|
25
|
+
self.registry = registry
|
|
26
|
+
|
|
27
|
+
def load_from_module(self, module_path: str, class_name: Optional[str] = None) -> DarkArtsPlugin:
|
|
28
|
+
"""
|
|
29
|
+
Load plugin from Python module.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
module_path: Python module path (e.g., 'darkarts.plugins.math')
|
|
33
|
+
class_name: Optional class name (if not provided, looks for DarkArtsPlugin subclass)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The loaded plugin instance
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
PluginError: If loading fails
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
module = importlib.import_module(module_path)
|
|
43
|
+
except ImportError as e:
|
|
44
|
+
raise PluginError(f"Failed to import module '{module_path}': {e}")
|
|
45
|
+
|
|
46
|
+
if class_name:
|
|
47
|
+
if not hasattr(module, class_name):
|
|
48
|
+
raise PluginError(f"Module '{module_path}' has no class '{class_name}'")
|
|
49
|
+
plugin_class = getattr(module, class_name)
|
|
50
|
+
else:
|
|
51
|
+
# Find DarkArtsPlugin subclass
|
|
52
|
+
plugin_class = None
|
|
53
|
+
for attr_name in dir(module):
|
|
54
|
+
attr = getattr(module, attr_name)
|
|
55
|
+
if (isinstance(attr, type) and
|
|
56
|
+
issubclass(attr, DarkArtsPlugin) and
|
|
57
|
+
attr is not DarkArtsPlugin):
|
|
58
|
+
plugin_class = attr
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
if not plugin_class:
|
|
62
|
+
raise PluginError(f"No DarkArtsPlugin subclass found in '{module_path}'")
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
plugin = plugin_class()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
raise PluginError(f"Failed to instantiate plugin: {e}")
|
|
68
|
+
|
|
69
|
+
return plugin
|
|
70
|
+
|
|
71
|
+
def load_from_file(self, file_path: str, class_name: Optional[str] = None) -> DarkArtsPlugin:
|
|
72
|
+
"""
|
|
73
|
+
Load plugin from Python file.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
file_path: Path to Python file
|
|
77
|
+
class_name: Optional class name
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The loaded plugin instance
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
PluginError: If loading fails
|
|
84
|
+
"""
|
|
85
|
+
file_path = Path(file_path).resolve()
|
|
86
|
+
|
|
87
|
+
if not file_path.exists():
|
|
88
|
+
raise PluginError(f"File not found: {file_path}")
|
|
89
|
+
|
|
90
|
+
if not file_path.suffix == '.py':
|
|
91
|
+
raise PluginError(f"File must be a Python file: {file_path}")
|
|
92
|
+
|
|
93
|
+
# Load module from file
|
|
94
|
+
module_name = file_path.stem
|
|
95
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
96
|
+
|
|
97
|
+
if not spec or not spec.loader:
|
|
98
|
+
raise PluginError(f"Failed to load spec from file: {file_path}")
|
|
99
|
+
|
|
100
|
+
module = importlib.util.module_from_spec(spec)
|
|
101
|
+
sys.modules[module_name] = module
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
spec.loader.exec_module(module)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
raise PluginError(f"Failed to execute module: {e}")
|
|
107
|
+
|
|
108
|
+
# Find and instantiate plugin class
|
|
109
|
+
if class_name:
|
|
110
|
+
if not hasattr(module, class_name):
|
|
111
|
+
raise PluginError(f"Module has no class '{class_name}'")
|
|
112
|
+
plugin_class = getattr(module, class_name)
|
|
113
|
+
else:
|
|
114
|
+
plugin_class = None
|
|
115
|
+
for attr_name in dir(module):
|
|
116
|
+
attr = getattr(module, attr_name)
|
|
117
|
+
if (isinstance(attr, type) and
|
|
118
|
+
issubclass(attr, DarkArtsPlugin) and
|
|
119
|
+
attr is not DarkArtsPlugin):
|
|
120
|
+
plugin_class = attr
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
if not plugin_class:
|
|
124
|
+
raise PluginError(f"No DarkArtsPlugin subclass found in file")
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
plugin = plugin_class()
|
|
128
|
+
except Exception as e:
|
|
129
|
+
raise PluginError(f"Failed to instantiate plugin: {e}")
|
|
130
|
+
|
|
131
|
+
return plugin
|
|
132
|
+
|
|
133
|
+
def load_from_directory(self, directory: str) -> List[DarkArtsPlugin]:
|
|
134
|
+
"""
|
|
135
|
+
Load all plugins from a directory.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
directory: Path to directory containing plugin files
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of loaded plugins
|
|
142
|
+
"""
|
|
143
|
+
directory = Path(directory).resolve()
|
|
144
|
+
|
|
145
|
+
if not directory.exists():
|
|
146
|
+
raise PluginError(f"Directory not found: {directory}")
|
|
147
|
+
|
|
148
|
+
if not directory.is_dir():
|
|
149
|
+
raise PluginError(f"Not a directory: {directory}")
|
|
150
|
+
|
|
151
|
+
plugins = []
|
|
152
|
+
|
|
153
|
+
for file_path in directory.glob("*.py"):
|
|
154
|
+
if file_path.name.startswith("_"):
|
|
155
|
+
continue # Skip __init__.py and private files
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
plugin = self.load_from_file(str(file_path))
|
|
159
|
+
plugins.append(plugin)
|
|
160
|
+
except PluginError as e:
|
|
161
|
+
print(f"Warning: Failed to load plugin from {file_path}: {e}")
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
return plugins
|
|
165
|
+
|
|
166
|
+
def load_builtin_plugins(self) -> List[DarkArtsPlugin]:
|
|
167
|
+
"""
|
|
168
|
+
Load built-in plugins.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of loaded built-in plugins
|
|
172
|
+
"""
|
|
173
|
+
plugins = []
|
|
174
|
+
|
|
175
|
+
# Try to load math plugin
|
|
176
|
+
try:
|
|
177
|
+
plugin = self.load_from_module("darkarts.plugins.math", "MathPlugin")
|
|
178
|
+
plugins.append(plugin)
|
|
179
|
+
except PluginError:
|
|
180
|
+
pass # Math plugin not yet implemented
|
|
181
|
+
|
|
182
|
+
# Try to load voodocs plugin
|
|
183
|
+
try:
|
|
184
|
+
plugin = self.load_from_module("darkarts.plugins.voodocs", "VooDocsPlugin")
|
|
185
|
+
plugins.append(plugin)
|
|
186
|
+
except PluginError:
|
|
187
|
+
pass # VooDocs plugin not yet implemented
|
|
188
|
+
|
|
189
|
+
return plugins
|
|
190
|
+
|
|
191
|
+
def load_and_register(self, source: str, source_type: str = "module") -> DarkArtsPlugin:
|
|
192
|
+
"""
|
|
193
|
+
Load a plugin and register it.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
source: Module path, file path, or directory path
|
|
197
|
+
source_type: Type of source ('module', 'file', or 'directory')
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The loaded plugin (or list of plugins for directory)
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
PluginError: If loading or registration fails
|
|
204
|
+
"""
|
|
205
|
+
if source_type == "module":
|
|
206
|
+
plugin = self.load_from_module(source)
|
|
207
|
+
self.registry.register(plugin)
|
|
208
|
+
return plugin
|
|
209
|
+
elif source_type == "file":
|
|
210
|
+
plugin = self.load_from_file(source)
|
|
211
|
+
self.registry.register(plugin)
|
|
212
|
+
return plugin
|
|
213
|
+
elif source_type == "directory":
|
|
214
|
+
plugins = self.load_from_directory(source)
|
|
215
|
+
for plugin in plugins:
|
|
216
|
+
self.registry.register(plugin)
|
|
217
|
+
return plugins
|
|
218
|
+
else:
|
|
219
|
+
raise PluginError(f"Unknown source type: {source_type}")
|
|
220
|
+
|
|
221
|
+
def load_and_register_builtin(self) -> List[DarkArtsPlugin]:
|
|
222
|
+
"""
|
|
223
|
+
Load and register all built-in plugins.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List of loaded plugins
|
|
227
|
+
"""
|
|
228
|
+
plugins = self.load_builtin_plugins()
|
|
229
|
+
for plugin in plugins:
|
|
230
|
+
self.registry.register(plugin)
|
|
231
|
+
return plugins
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core plugin system for DarkArts.
|
|
3
|
+
|
|
4
|
+
This module defines the base classes and interfaces for the DarkArts plugin system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PluginCapability(Enum):
|
|
14
|
+
"""Standard plugin capabilities."""
|
|
15
|
+
PARSE = "parse"
|
|
16
|
+
ANALYZE = "analyze"
|
|
17
|
+
EXECUTE = "execute"
|
|
18
|
+
EXPLAIN = "explain"
|
|
19
|
+
LEARN = "learn"
|
|
20
|
+
MULTI_STEP = "multi_step"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class PluginMetadata:
|
|
25
|
+
"""Metadata about a plugin."""
|
|
26
|
+
name: str
|
|
27
|
+
version: str
|
|
28
|
+
description: str
|
|
29
|
+
author: str
|
|
30
|
+
dependencies: List[str] = field(default_factory=list)
|
|
31
|
+
capabilities: List[str] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
def has_capability(self, capability: str) -> bool:
|
|
34
|
+
"""Check if plugin has a specific capability."""
|
|
35
|
+
return capability in self.capabilities
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class PluginInput:
|
|
40
|
+
"""Input to a plugin."""
|
|
41
|
+
content: str
|
|
42
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
|
43
|
+
options: Dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
def get_option(self, key: str, default: Any = None) -> Any:
|
|
46
|
+
"""Get an option value with default."""
|
|
47
|
+
return self.options.get(key, default)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class PluginOutput:
|
|
52
|
+
"""Output from a plugin."""
|
|
53
|
+
success: bool
|
|
54
|
+
result: Any
|
|
55
|
+
explanation: Optional[str] = None
|
|
56
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
57
|
+
error: Optional[str] = None
|
|
58
|
+
steps: List[Dict[str, Any]] = field(default_factory=list)
|
|
59
|
+
|
|
60
|
+
def add_step(self, name: str, description: str, result: Any = None):
|
|
61
|
+
"""Add an execution step."""
|
|
62
|
+
self.steps.append({
|
|
63
|
+
"name": name,
|
|
64
|
+
"description": description,
|
|
65
|
+
"result": result,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
def add_metadata(self, key: str, value: Any):
|
|
69
|
+
"""Add metadata."""
|
|
70
|
+
self.metadata[key] = value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DarkArtsPlugin(ABC):
|
|
74
|
+
"""Base class for all DarkArts plugins."""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
"""Initialize the plugin."""
|
|
78
|
+
self._learning_enabled = True
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def metadata(self) -> PluginMetadata:
|
|
83
|
+
"""Return plugin metadata."""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def parse(self, input: PluginInput) -> Any:
|
|
88
|
+
"""
|
|
89
|
+
Parse domain-specific input into internal representation.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
input: The input to parse
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Parsed representation (domain-specific)
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ParseError: If parsing fails
|
|
99
|
+
"""
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def analyze(self, parsed: Any) -> Dict[str, Any]:
|
|
104
|
+
"""
|
|
105
|
+
Analyze the parsed input for complexity, requirements, etc.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
parsed: The parsed representation
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Analysis results as a dictionary
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def execute(self, parsed: Any, analysis: Dict[str, Any]) -> PluginOutput:
|
|
117
|
+
"""
|
|
118
|
+
Execute the main processing logic.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
parsed: The parsed representation
|
|
122
|
+
analysis: Analysis results from analyze()
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Plugin output with results
|
|
126
|
+
"""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
def explain(self, output: PluginOutput, level: str = "student") -> str:
|
|
130
|
+
"""
|
|
131
|
+
Generate natural language explanation (optional override).
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
output: The plugin output to explain
|
|
135
|
+
level: Explanation level (student, teacher, expert)
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Natural language explanation
|
|
139
|
+
"""
|
|
140
|
+
if output.explanation:
|
|
141
|
+
return output.explanation
|
|
142
|
+
|
|
143
|
+
if not output.success:
|
|
144
|
+
return f"Execution failed: {output.error}"
|
|
145
|
+
|
|
146
|
+
return f"Result: {output.result}"
|
|
147
|
+
|
|
148
|
+
def learn(self, input: PluginInput, output: PluginOutput) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Learn from this execution (optional override).
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
input: The input that was processed
|
|
154
|
+
output: The output that was generated
|
|
155
|
+
"""
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
def enable_learning(self, enabled: bool = True):
|
|
159
|
+
"""Enable or disable learning for this plugin."""
|
|
160
|
+
self._learning_enabled = enabled
|
|
161
|
+
|
|
162
|
+
def is_learning_enabled(self) -> bool:
|
|
163
|
+
"""Check if learning is enabled."""
|
|
164
|
+
return self._learning_enabled
|
|
165
|
+
|
|
166
|
+
def validate_input(self, input: PluginInput) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Validate input before processing (optional override).
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
input: The input to validate
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
True if valid, False otherwise
|
|
175
|
+
"""
|
|
176
|
+
return bool(input.content and input.content.strip())
|
|
177
|
+
|
|
178
|
+
def preprocess(self, input: PluginInput) -> PluginInput:
|
|
179
|
+
"""
|
|
180
|
+
Preprocess input before parsing (optional override).
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
input: The input to preprocess
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Preprocessed input
|
|
187
|
+
"""
|
|
188
|
+
return input
|
|
189
|
+
|
|
190
|
+
def postprocess(self, output: PluginOutput) -> PluginOutput:
|
|
191
|
+
"""
|
|
192
|
+
Postprocess output before returning (optional override).
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
output: The output to postprocess
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Postprocessed output
|
|
199
|
+
"""
|
|
200
|
+
return output
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ParseError(Exception):
|
|
204
|
+
"""Exception raised when parsing fails."""
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class ExecutionError(Exception):
|
|
209
|
+
"""Exception raised when execution fails."""
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class PluginError(Exception):
|
|
214
|
+
"""Base exception for plugin-related errors."""
|
|
215
|
+
pass
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin registry for managing and discovering plugins.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
from .plugin import DarkArtsPlugin, PluginMetadata, PluginError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PluginRegistry:
|
|
10
|
+
"""Registry for managing plugins."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the registry."""
|
|
14
|
+
self._plugins: Dict[str, DarkArtsPlugin] = {}
|
|
15
|
+
self._metadata: Dict[str, PluginMetadata] = {}
|
|
16
|
+
|
|
17
|
+
def register(self, plugin: DarkArtsPlugin) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Register a plugin.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
plugin: The plugin to register
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
PluginError: If plugin with same name already registered
|
|
26
|
+
"""
|
|
27
|
+
metadata = plugin.metadata
|
|
28
|
+
|
|
29
|
+
if metadata.name in self._plugins:
|
|
30
|
+
raise PluginError(f"Plugin '{metadata.name}' is already registered")
|
|
31
|
+
|
|
32
|
+
# Check dependencies
|
|
33
|
+
for dep in metadata.dependencies:
|
|
34
|
+
if dep not in self._plugins:
|
|
35
|
+
raise PluginError(
|
|
36
|
+
f"Plugin '{metadata.name}' depends on '{dep}' which is not registered"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self._plugins[metadata.name] = plugin
|
|
40
|
+
self._metadata[metadata.name] = metadata
|
|
41
|
+
|
|
42
|
+
def unregister(self, name: str) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Unregister a plugin.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name: Name of the plugin to unregister
|
|
48
|
+
"""
|
|
49
|
+
if name in self._plugins:
|
|
50
|
+
del self._plugins[name]
|
|
51
|
+
del self._metadata[name]
|
|
52
|
+
|
|
53
|
+
def get(self, name: str) -> Optional[DarkArtsPlugin]:
|
|
54
|
+
"""
|
|
55
|
+
Get a plugin by name.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Name of the plugin
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The plugin, or None if not found
|
|
62
|
+
"""
|
|
63
|
+
return self._plugins.get(name)
|
|
64
|
+
|
|
65
|
+
def get_metadata(self, name: str) -> Optional[PluginMetadata]:
|
|
66
|
+
"""
|
|
67
|
+
Get plugin metadata by name.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
name: Name of the plugin
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The plugin metadata, or None if not found
|
|
74
|
+
"""
|
|
75
|
+
return self._metadata.get(name)
|
|
76
|
+
|
|
77
|
+
def list(self) -> List[PluginMetadata]:
|
|
78
|
+
"""
|
|
79
|
+
List all registered plugins.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of plugin metadata
|
|
83
|
+
"""
|
|
84
|
+
return list(self._metadata.values())
|
|
85
|
+
|
|
86
|
+
def list_names(self) -> List[str]:
|
|
87
|
+
"""
|
|
88
|
+
List all registered plugin names.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of plugin names
|
|
92
|
+
"""
|
|
93
|
+
return list(self._plugins.keys())
|
|
94
|
+
|
|
95
|
+
def has_plugin(self, name: str) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Check if a plugin is registered.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
name: Name of the plugin
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
True if registered, False otherwise
|
|
104
|
+
"""
|
|
105
|
+
return name in self._plugins
|
|
106
|
+
|
|
107
|
+
def has_capability(self, capability: str) -> List[str]:
|
|
108
|
+
"""
|
|
109
|
+
Find plugins with a specific capability.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
capability: The capability to search for
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of plugin names with that capability
|
|
116
|
+
"""
|
|
117
|
+
return [
|
|
118
|
+
name for name, meta in self._metadata.items()
|
|
119
|
+
if capability in meta.capabilities
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def clear(self) -> None:
|
|
123
|
+
"""Clear all registered plugins."""
|
|
124
|
+
self._plugins.clear()
|
|
125
|
+
self._metadata.clear()
|
|
126
|
+
|
|
127
|
+
def __len__(self) -> int:
|
|
128
|
+
"""Return number of registered plugins."""
|
|
129
|
+
return len(self._plugins)
|
|
130
|
+
|
|
131
|
+
def __contains__(self, name: str) -> bool:
|
|
132
|
+
"""Check if plugin is registered."""
|
|
133
|
+
return name in self._plugins
|
|
134
|
+
|
|
135
|
+
def __iter__(self):
|
|
136
|
+
"""Iterate over plugin names."""
|
|
137
|
+
return iter(self._plugins.keys())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Global registry instance
|
|
141
|
+
_global_registry = PluginRegistry()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_global_registry() -> PluginRegistry:
|
|
145
|
+
"""Get the global plugin registry."""
|
|
146
|
+
return _global_registry
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VooDocs Custom Exceptions
|
|
3
|
+
|
|
4
|
+
Provides specific exception types for better error handling and user feedback.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VooDocsError(Exception):
|
|
9
|
+
"""Base exception for all VooDocs errors."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParserError(VooDocsError):
|
|
14
|
+
"""Raised when a parser encounters an error."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ParserNotBuiltError(ParserError):
|
|
19
|
+
"""Raised when the TypeScript parser hasn't been built."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, parser_name="TypeScript"):
|
|
22
|
+
self.parser_name = parser_name
|
|
23
|
+
super().__init__(
|
|
24
|
+
f"{parser_name} parser not built. "
|
|
25
|
+
f"Run: cd lib/darkarts/parsers/typescript && npm install && npm run build"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AnnotationError(VooDocsError):
|
|
30
|
+
"""Raised when annotation parsing fails."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InvalidAnnotationError(AnnotationError):
|
|
35
|
+
"""Raised when an annotation has invalid syntax or structure."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GeneratorError(VooDocsError):
|
|
40
|
+
"""Raised when a generator encounters an error."""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ConfigurationError(VooDocsError):
|
|
45
|
+
"""Raised when configuration is invalid or missing."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FileNotFoundError(VooDocsError):
|
|
50
|
+
"""Raised when a required file is not found."""
|
|
51
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;GAKG"}
|