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:
154
markitect/plugins/builtin/formatters.py
Normal file
154
markitect/plugins/builtin/formatters.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
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'
|
||||
Reference in New Issue
Block a user