Files
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

154 lines
4.5 KiB
Python

"""
Built-in formatter plugins for MarkiTect.
These formatters provide various output formats for MarkiTect data.
"""
import json
import yaml
from typing import Any
from ..base import FormatterPlugin, PluginMetadata, PluginType
from ..decorators import register_plugin
@register_plugin("json_formatter")
class JsonFormatter(FormatterPlugin):
"""JSON output formatter."""
@property
def metadata(self) -> PluginMetadata:
return PluginMetadata(
name="json_formatter",
version="1.0.0",
description="Format output as JSON",
author="MarkiTect Team",
plugin_type=PluginType.FORMATTER
)
def format(self, data: Any, **kwargs) -> str:
"""Format data as JSON."""
indent = kwargs.get('indent', 2)
ensure_ascii = kwargs.get('ensure_ascii', False)
return json.dumps(data, indent=indent, ensure_ascii=ensure_ascii)
def get_file_extension(self) -> str:
"""Get JSON file extension."""
return '.json'
@register_plugin("yaml_formatter")
class YamlFormatter(FormatterPlugin):
"""YAML output formatter."""
@property
def metadata(self) -> PluginMetadata:
return PluginMetadata(
name="yaml_formatter",
version="1.0.0",
description="Format output as YAML",
author="MarkiTect Team",
plugin_type=PluginType.FORMATTER
)
def format(self, data: Any, **kwargs) -> str:
"""Format data as YAML."""
default_flow_style = kwargs.get('default_flow_style', False)
indent = kwargs.get('indent', 2)
return yaml.dump(data, default_flow_style=default_flow_style, indent=indent)
def get_file_extension(self) -> str:
"""Get YAML file extension."""
return '.yaml'
@register_plugin("table_formatter")
class TableFormatter(FormatterPlugin):
"""Table output formatter for structured data."""
@property
def metadata(self) -> PluginMetadata:
return PluginMetadata(
name="table_formatter",
version="1.0.0",
description="Format output as ASCII table",
author="MarkiTect Team",
plugin_type=PluginType.FORMATTER
)
def format(self, data: Any, **kwargs) -> str:
"""Format data as ASCII table."""
if not isinstance(data, (list, tuple)):
return str(data)
if not data:
return "No data"
# Handle list of dictionaries (most common case)
if isinstance(data[0], dict):
return self._format_dict_table(data, **kwargs)
# Handle simple list
return self._format_simple_table(data, **kwargs)
def _format_dict_table(self, data: list, **kwargs) -> str:
"""Format list of dictionaries as table."""
if not data:
return "No data"
# Get all unique keys
all_keys = set()
for item in data:
if isinstance(item, dict):
all_keys.update(item.keys())
headers = sorted(all_keys)
# Calculate column widths
col_widths = {}
for header in headers:
col_widths[header] = len(str(header))
for item in data:
if isinstance(item, dict) and header in item:
col_widths[header] = max(col_widths[header], len(str(item[header])))
# Build table
lines = []
# Header
header_line = "| " + " | ".join(h.ljust(col_widths[h]) for h in headers) + " |"
lines.append(header_line)
# Separator
sep_line = "|-" + "-|-".join("-" * col_widths[h] for h in headers) + "-|"
lines.append(sep_line)
# Data rows
for item in data:
if isinstance(item, dict):
row_line = "| " + " | ".join(
str(item.get(h, "")).ljust(col_widths[h]) for h in headers
) + " |"
lines.append(row_line)
return "\n".join(lines)
def _format_simple_table(self, data: list, **kwargs) -> str:
"""Format simple list as single-column table."""
max_width = max(len(str(item)) for item in data)
max_width = max(max_width, len("Value"))
lines = []
lines.append(f"| {'Value'.ljust(max_width)} |")
lines.append(f"|-{'-' * max_width}-|")
for item in data:
lines.append(f"| {str(item).ljust(max_width)} |")
return "\n".join(lines)
def get_file_extension(self) -> str:
"""Get table file extension."""
return '.txt'