Complete plugin system implementation providing extensible architecture for MarkiTect: 🏗️ **Core Plugin Architecture**: - BasePlugin abstract class with lifecycle management (initialize/cleanup) - Specialized plugin types: ProcessorPlugin, FormatterPlugin, ValidatorPlugin, ExporterPlugin, CommandPlugin - PluginMetadata system with version, dependencies, and type information - Plugin initialization and configuration validation 🔍 **Plugin Discovery & Management**: - PluginManager with automatic discovery from built-in modules and directories - PluginRegistry for centralized plugin registration and lifecycle management - Support for plugin loading, unloading, and reloading with configuration - Plugin discovery from multiple sources (built-in, directories, packages) 🛠️ **CLI Integration**: - markitect plugin-list: List all available plugins with metadata - markitect plugin-load: Load plugins with optional configuration - markitect plugin-unload: Unload plugins and cleanup resources - markitect plugin-info: Show detailed plugin information - markitect plugin-discover: Discover and refresh plugin catalog 📦 **Built-in Plugins**: - JSON/YAML/Table formatters for output formatting - Markdown/Text processors for content processing - Auto-registered via @register_plugin decorator - Comprehensive configuration options 🔧 **Developer Experience**: - @register_plugin decorator for easy plugin registration - Plugin configuration validation and error handling - Comprehensive API documentation with examples - Plugin development guide and best practices 📋 **Example Plugins**: - Advanced text processor with case conversion and pattern replacement - XML/CSV formatters demonstrating custom output formats - Complete examples showing plugin development patterns 🧪 **Test Coverage**: - 59 comprehensive tests covering all plugin functionality - Tests for plugin lifecycle, registration, discovery, and CLI integration - Error handling and edge case coverage - Built-in plugin validation Technical Implementation: - Plugin types: processor, formatter, validator, exporter, generator, importer, transformer, extension, backend, command - Configuration-driven plugin management with YAML/JSON support - Graceful error handling and plugin isolation - Plugin dependency validation and compatibility checking 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
207 lines
5.9 KiB
Python
207 lines
5.9 KiB
Python
"""
|
|
Plugin registry for managing discovered and loaded plugins.
|
|
|
|
This module provides a central registry for all plugins in the system.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional, Type, Any
|
|
from .base import BasePlugin, PluginType
|
|
|
|
|
|
class PluginRegistry:
|
|
"""Central registry for managing plugins."""
|
|
|
|
def __init__(self):
|
|
"""Initialize empty registry."""
|
|
self._plugins: Dict[str, Type[BasePlugin]] = {}
|
|
self._instances: Dict[str, BasePlugin] = {}
|
|
self._plugins_by_type: Dict[PluginType, List[str]] = {}
|
|
|
|
def register(self, plugin_class: Type[BasePlugin], name: Optional[str] = None) -> str:
|
|
"""
|
|
Register a plugin class.
|
|
|
|
Args:
|
|
plugin_class: Plugin class to register
|
|
name: Optional plugin name (uses class name if not provided)
|
|
|
|
Returns:
|
|
The name the plugin was registered under
|
|
|
|
Raises:
|
|
ValueError: If plugin name already exists
|
|
"""
|
|
if name is None:
|
|
name = plugin_class.__name__
|
|
|
|
if name in self._plugins:
|
|
raise ValueError(f"Plugin '{name}' is already registered")
|
|
|
|
self._plugins[name] = plugin_class
|
|
|
|
# Create instance to get metadata
|
|
try:
|
|
instance = plugin_class()
|
|
plugin_type = instance.metadata.plugin_type
|
|
|
|
if plugin_type not in self._plugins_by_type:
|
|
self._plugins_by_type[plugin_type] = []
|
|
|
|
self._plugins_by_type[plugin_type].append(name)
|
|
except Exception:
|
|
# If we can't get metadata, register as generic extension
|
|
if PluginType.EXTENSION not in self._plugins_by_type:
|
|
self._plugins_by_type[PluginType.EXTENSION] = []
|
|
self._plugins_by_type[PluginType.EXTENSION].append(name)
|
|
|
|
return name
|
|
|
|
def unregister(self, name: str) -> bool:
|
|
"""
|
|
Unregister a plugin.
|
|
|
|
Args:
|
|
name: Plugin name to unregister
|
|
|
|
Returns:
|
|
True if plugin was unregistered, False if not found
|
|
"""
|
|
if name not in self._plugins:
|
|
return False
|
|
|
|
# Remove from instances if loaded
|
|
if name in self._instances:
|
|
instance = self._instances[name]
|
|
instance.cleanup()
|
|
del self._instances[name]
|
|
|
|
# Remove from type mapping
|
|
for plugin_type, plugin_names in self._plugins_by_type.items():
|
|
if name in plugin_names:
|
|
plugin_names.remove(name)
|
|
break
|
|
|
|
# Remove from main registry
|
|
del self._plugins[name]
|
|
return True
|
|
|
|
def get_plugin(self, name: str, config: Dict[str, Any] = None) -> Optional[BasePlugin]:
|
|
"""
|
|
Get plugin instance by name.
|
|
|
|
Args:
|
|
name: Plugin name
|
|
config: Optional configuration for plugin
|
|
|
|
Returns:
|
|
Plugin instance or None if not found
|
|
"""
|
|
if name not in self._plugins:
|
|
return None
|
|
|
|
# Return existing instance if already loaded and no new config
|
|
if name in self._instances and config is None:
|
|
return self._instances[name]
|
|
|
|
# Create new instance
|
|
try:
|
|
plugin_class = self._plugins[name]
|
|
instance = plugin_class(config)
|
|
|
|
if instance.initialize():
|
|
self._instances[name] = instance
|
|
return instance
|
|
except Exception:
|
|
pass
|
|
|
|
return None
|
|
|
|
def get_plugins_by_type(self, plugin_type: PluginType) -> List[str]:
|
|
"""
|
|
Get list of plugin names by type.
|
|
|
|
Args:
|
|
plugin_type: Type of plugins to retrieve
|
|
|
|
Returns:
|
|
List of plugin names of the specified type
|
|
"""
|
|
return self._plugins_by_type.get(plugin_type, []).copy()
|
|
|
|
def list_plugins(self) -> Dict[str, Dict[str, Any]]:
|
|
"""
|
|
List all registered plugins with metadata.
|
|
|
|
Returns:
|
|
Dictionary mapping plugin names to metadata
|
|
"""
|
|
result = {}
|
|
|
|
for name, plugin_class in self._plugins.items():
|
|
try:
|
|
instance = plugin_class()
|
|
metadata = instance.metadata
|
|
result[name] = {
|
|
'name': metadata.name,
|
|
'version': metadata.version,
|
|
'description': metadata.description,
|
|
'author': metadata.author,
|
|
'type': metadata.plugin_type.value,
|
|
'dependencies': metadata.dependencies,
|
|
'markitect_version': metadata.markitect_version,
|
|
'loaded': name in self._instances
|
|
}
|
|
except Exception:
|
|
result[name] = {
|
|
'name': name,
|
|
'error': 'Failed to load metadata'
|
|
}
|
|
|
|
return result
|
|
|
|
def is_loaded(self, name: str) -> bool:
|
|
"""
|
|
Check if plugin is loaded.
|
|
|
|
Args:
|
|
name: Plugin name
|
|
|
|
Returns:
|
|
True if plugin is loaded
|
|
"""
|
|
return name in self._instances
|
|
|
|
def reload_plugin(self, name: str, config: Dict[str, Any] = None) -> bool:
|
|
"""
|
|
Reload a plugin with new configuration.
|
|
|
|
Args:
|
|
name: Plugin name
|
|
config: New configuration
|
|
|
|
Returns:
|
|
True if reload successful
|
|
"""
|
|
if name not in self._plugins:
|
|
return False
|
|
|
|
# Cleanup existing instance
|
|
if name in self._instances:
|
|
self._instances[name].cleanup()
|
|
del self._instances[name]
|
|
|
|
# Load with new config
|
|
return self.get_plugin(name, config) is not None
|
|
|
|
def cleanup_all(self) -> None:
|
|
"""Cleanup all loaded plugin instances."""
|
|
for instance in self._instances.values():
|
|
try:
|
|
instance.cleanup()
|
|
except Exception:
|
|
pass
|
|
self._instances.clear()
|
|
|
|
|
|
# Global plugin registry instance
|
|
plugin_registry = PluginRegistry() |