Files
markitect-main/markitect/plugins/base.py
tegwick b0de32d083 feat: implement comprehensive plugin architecture and extensions system (issue #19)
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>
2025-10-03 11:23:32 +02:00

272 lines
7.1 KiB
Python

"""
Base classes and interfaces for MarkiTect plugins.
This module defines the core plugin architecture that all plugins must implement.
"""
from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict, Any, Optional, List, Union
from pathlib import Path
import inspect
class PluginType(Enum):
"""Types of plugins supported by MarkiTect."""
PROCESSOR = "processor" # Content processors (markdown, etc.)
FORMATTER = "formatter" # Output formatters (JSON, YAML, etc.)
VALIDATOR = "validator" # Content validators
EXPORTER = "exporter" # Export handlers (PDF, HTML, etc.)
GENERATOR = "generator" # Content generators (templates, stubs, etc.)
IMPORTER = "importer" # Import handlers (various formats)
TRANSFORMER = "transformer" # Content transformers
EXTENSION = "extension" # General extensions
BACKEND = "backend" # Storage/API backends
COMMAND = "command" # CLI command extensions
class PluginMetadata:
"""Metadata about a plugin."""
def __init__(self,
name: str,
version: str,
description: str,
author: str = "",
plugin_type: PluginType = PluginType.EXTENSION,
dependencies: List[str] = None,
markitect_version: str = ">=0.1.0"):
self.name = name
self.version = version
self.description = description
self.author = author
self.plugin_type = plugin_type
self.dependencies = dependencies or []
self.markitect_version = markitect_version
class BasePlugin(ABC):
"""Abstract base class for all MarkiTect plugins."""
def __init__(self, config: Dict[str, Any] = None):
"""
Initialize plugin with configuration.
Args:
config: Plugin-specific configuration dictionary
"""
self.config = config or {}
self._metadata = None
self._initialized = False
@property
@abstractmethod
def metadata(self) -> PluginMetadata:
"""Return plugin metadata."""
pass
def initialize(self) -> bool:
"""
Initialize the plugin. Called after plugin is loaded.
Returns:
True if initialization successful, False otherwise
"""
try:
self._initialize()
self._initialized = True
return True
except Exception:
return False
def _initialize(self) -> None:
"""
Override this method to implement plugin-specific initialization.
Default implementation does nothing.
"""
pass
def cleanup(self) -> None:
"""
Cleanup plugin resources. Called when plugin is unloaded.
Override this method to implement cleanup logic.
"""
pass
@property
def is_initialized(self) -> bool:
"""Check if plugin is initialized."""
return self._initialized
def validate_config(self) -> List[str]:
"""
Validate plugin configuration.
Returns:
List of validation error messages (empty if valid)
"""
return []
class ProcessorPlugin(BasePlugin):
"""Base class for content processor plugins."""
@abstractmethod
def process(self, content: str, **kwargs) -> str:
"""
Process content and return processed result.
Args:
content: Input content to process
**kwargs: Additional processing parameters
Returns:
Processed content
"""
pass
def can_process(self, content: str, **kwargs) -> bool:
"""
Check if this processor can handle the given content.
Args:
content: Content to check
**kwargs: Additional context
Returns:
True if processor can handle content
"""
return True
class FormatterPlugin(BasePlugin):
"""Base class for output formatter plugins."""
@abstractmethod
def format(self, data: Any, **kwargs) -> str:
"""
Format data to string representation.
Args:
data: Data to format
**kwargs: Formatting options
Returns:
Formatted string
"""
pass
@abstractmethod
def get_file_extension(self) -> str:
"""
Get the file extension for this format.
Returns:
File extension (e.g., '.json', '.yaml')
"""
pass
class ValidatorPlugin(BasePlugin):
"""Base class for content validator plugins."""
@abstractmethod
def validate(self, content: str, **kwargs) -> List[str]:
"""
Validate content and return list of errors.
Args:
content: Content to validate
**kwargs: Validation options
Returns:
List of validation error messages (empty if valid)
"""
pass
class ExporterPlugin(BasePlugin):
"""Base class for export handler plugins."""
@abstractmethod
def export(self, data: Any, output_path: Path, **kwargs) -> bool:
"""
Export data to file.
Args:
data: Data to export
output_path: Output file path
**kwargs: Export options
Returns:
True if export successful
"""
pass
@abstractmethod
def get_supported_formats(self) -> List[str]:
"""
Get list of supported export formats.
Returns:
List of format names (e.g., ['pdf', 'html'])
"""
pass
class CommandPlugin(BasePlugin):
"""Base class for CLI command extension plugins."""
@abstractmethod
def get_commands(self) -> Dict[str, Any]:
"""
Get Click commands provided by this plugin.
Returns:
Dictionary mapping command names to Click command objects
"""
pass
def get_command_group_name(self) -> Optional[str]:
"""
Get the command group name if commands should be grouped.
Returns:
Group name or None for top-level commands
"""
return None
# Plugin discovery and loading utilities
def get_plugin_class_from_module(module, plugin_type: PluginType = None) -> List[type]:
"""
Discover plugin classes in a module.
Args:
module: Python module to search
plugin_type: Optional plugin type filter
Returns:
List of plugin classes found
"""
plugin_classes = []
for name, obj in inspect.getmembers(module, inspect.isclass):
if (issubclass(obj, BasePlugin) and
obj != BasePlugin and
not inspect.isabstract(obj)):
# Check plugin type if specified
if plugin_type:
try:
instance = obj()
if instance.metadata.plugin_type == plugin_type:
plugin_classes.append(obj)
except Exception:
# Skip if we can't instantiate or get metadata
continue
else:
plugin_classes.append(obj)
return plugin_classes