""" 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 RENDERING = "rendering" # UI rendering engines (edit, view modes) 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