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>
This commit is contained in:
175
examples/plugins/example_processor.py
Normal file
175
examples/plugins/example_processor.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
Example processor plugin for MarkiTect.
|
||||
|
||||
This demonstrates how to create a custom processor plugin.
|
||||
"""
|
||||
|
||||
import re
|
||||
from markitect.plugins.base import ProcessorPlugin, PluginMetadata, PluginType
|
||||
from markitect.plugins.decorators import register_plugin
|
||||
|
||||
|
||||
@register_plugin("example_processor")
|
||||
class ExampleProcessor(ProcessorPlugin):
|
||||
"""
|
||||
Example processor that demonstrates various text processing capabilities.
|
||||
|
||||
This processor can:
|
||||
- Convert text to different cases
|
||||
- Clean up whitespace
|
||||
- Remove or replace patterns
|
||||
- Add prefixes/suffixes to lines
|
||||
"""
|
||||
|
||||
@property
|
||||
def metadata(self) -> PluginMetadata:
|
||||
return PluginMetadata(
|
||||
name="example_processor",
|
||||
version="1.0.0",
|
||||
description="Example processor demonstrating text transformations",
|
||||
author="MarkiTect Team",
|
||||
plugin_type=PluginType.PROCESSOR
|
||||
)
|
||||
|
||||
def process(self, content: str, **kwargs) -> str:
|
||||
"""
|
||||
Process content with various transformations.
|
||||
|
||||
Args:
|
||||
content: Input content to process
|
||||
**kwargs: Processing options:
|
||||
- case: 'upper', 'lower', 'title', 'sentence'
|
||||
- clean_whitespace: bool
|
||||
- remove_pattern: regex pattern to remove
|
||||
- replace_pattern: dict with 'pattern' and 'replacement'
|
||||
- line_prefix: string to add to start of each line
|
||||
- line_suffix: string to add to end of each line
|
||||
|
||||
Returns:
|
||||
Processed content
|
||||
"""
|
||||
if not isinstance(content, str):
|
||||
return content
|
||||
|
||||
result = content
|
||||
|
||||
# Case transformations
|
||||
case = kwargs.get('case', '').lower()
|
||||
if case == 'upper':
|
||||
result = result.upper()
|
||||
elif case == 'lower':
|
||||
result = result.lower()
|
||||
elif case == 'title':
|
||||
result = result.title()
|
||||
elif case == 'sentence':
|
||||
result = self._sentence_case(result)
|
||||
|
||||
# Clean whitespace
|
||||
if kwargs.get('clean_whitespace', False):
|
||||
result = self._clean_whitespace(result)
|
||||
|
||||
# Remove pattern
|
||||
remove_pattern = kwargs.get('remove_pattern')
|
||||
if remove_pattern:
|
||||
result = re.sub(remove_pattern, '', result)
|
||||
|
||||
# Replace pattern
|
||||
replace_pattern = kwargs.get('replace_pattern')
|
||||
if replace_pattern and isinstance(replace_pattern, dict):
|
||||
pattern = replace_pattern.get('pattern')
|
||||
replacement = replace_pattern.get('replacement', '')
|
||||
if pattern:
|
||||
result = re.sub(pattern, replacement, result)
|
||||
|
||||
# Line prefix/suffix
|
||||
line_prefix = kwargs.get('line_prefix', '')
|
||||
line_suffix = kwargs.get('line_suffix', '')
|
||||
if line_prefix or line_suffix:
|
||||
lines = result.split('\n')
|
||||
lines = [f"{line_prefix}{line}{line_suffix}" for line in lines]
|
||||
result = '\n'.join(lines)
|
||||
|
||||
return result
|
||||
|
||||
def can_process(self, content: str, **kwargs) -> bool:
|
||||
"""Check if content can be processed (any string content)."""
|
||||
return isinstance(content, str)
|
||||
|
||||
def _sentence_case(self, text: str) -> str:
|
||||
"""Convert text to sentence case (first letter capitalized)."""
|
||||
if not text:
|
||||
return text
|
||||
return text[0].upper() + text[1:].lower()
|
||||
|
||||
def _clean_whitespace(self, text: str) -> str:
|
||||
"""Clean up whitespace in text."""
|
||||
# Remove trailing whitespace from each line
|
||||
lines = [line.rstrip() for line in text.split('\n')]
|
||||
|
||||
# Remove multiple consecutive empty lines
|
||||
cleaned_lines = []
|
||||
prev_empty = False
|
||||
for line in lines:
|
||||
if line.strip():
|
||||
cleaned_lines.append(line)
|
||||
prev_empty = False
|
||||
elif not prev_empty:
|
||||
cleaned_lines.append(line)
|
||||
prev_empty = True
|
||||
|
||||
return '\n'.join(cleaned_lines)
|
||||
|
||||
def validate_config(self) -> list:
|
||||
"""Validate plugin configuration."""
|
||||
errors = []
|
||||
|
||||
case = self.config.get('case', '').lower()
|
||||
if case and case not in ['upper', 'lower', 'title', 'sentence']:
|
||||
errors.append(f"Invalid case option: {case}")
|
||||
|
||||
replace_pattern = self.config.get('replace_pattern')
|
||||
if replace_pattern and not isinstance(replace_pattern, dict):
|
||||
errors.append("replace_pattern must be a dictionary with 'pattern' and 'replacement' keys")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
# Example usage:
|
||||
if __name__ == '__main__':
|
||||
# Test the processor
|
||||
processor = ExampleProcessor()
|
||||
|
||||
test_content = """ Hello World
|
||||
This is a test.
|
||||
|
||||
|
||||
Multiple empty lines above. """
|
||||
|
||||
# Test case conversion
|
||||
result = processor.process(test_content, case='upper')
|
||||
print("Upper case:")
|
||||
print(result)
|
||||
print()
|
||||
|
||||
# Test whitespace cleaning
|
||||
result = processor.process(test_content, clean_whitespace=True)
|
||||
print("Cleaned whitespace:")
|
||||
print(repr(result))
|
||||
print()
|
||||
|
||||
# Test pattern replacement
|
||||
result = processor.process(
|
||||
test_content,
|
||||
replace_pattern={'pattern': r'\s+', 'replacement': ' '}
|
||||
)
|
||||
print("Normalized spaces:")
|
||||
print(repr(result))
|
||||
print()
|
||||
|
||||
# Test line prefix
|
||||
result = processor.process(
|
||||
"Line 1\nLine 2\nLine 3",
|
||||
line_prefix=">> "
|
||||
)
|
||||
print("With prefix:")
|
||||
print(result)
|
||||
Reference in New Issue
Block a user