"""
Palette exporter plugin system.
This module provides a plugin-based architecture for palette exporters.
Exporters automatically register themselves on import, and can be discovered
via the registry functions.
Architecture:
1. Each exporter subclasses PaletteExporter (from base.py)
2. Exporters use @register_exporter decorator to auto-register
3. Registry tracks all available exporters
4. CLI and export.py query registry to discover formats
Usage:
>>> from color_tools.exporters import get_exporter, list_export_formats
>>>
>>> # List available formats
>>> formats = list_export_formats('filaments')
>>> print(formats)
{'autoforge': 'AutoForge filament library CSV format', ...}
>>>
>>> # Get an exporter and use it
>>> exporter = get_exporter('json')
>>> from color_tools.palette import Palette
>>> palette = Palette.load_default()
>>> path = exporter.export_colors(palette.colors, 'output.json')
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from color_tools.exporters.base import PaletteExporter
# Global registry of exporters
# Maps format name -> exporter class
_EXPORTERS: dict[str, type[PaletteExporter]] = {}
[docs]
def register_exporter(cls: type[PaletteExporter]) -> type[PaletteExporter]:
"""
Decorator to register an exporter class.
Automatically instantiates the exporter and registers it in the global
registry using its metadata.name as the key.
Args:
cls: Exporter class to register (must subclass PaletteExporter)
Returns:
The same class (unchanged), for decorator chaining
Raises:
ValueError: If an exporter with this name is already registered
Example:
>>> from color_tools.exporters import register_exporter
>>> from color_tools.exporters.base import PaletteExporter, ExporterMetadata
>>>
>>> @register_exporter
... class MyExporter(PaletteExporter):
... @property
... def metadata(self):
... return ExporterMetadata(...)
... # ... implementation
"""
# Instantiate to get metadata
instance = cls()
name = instance.metadata.name
# Check for duplicates
if name in _EXPORTERS:
raise ValueError(
f"Exporter '{name}' is already registered. "
f"Each exporter must have a unique name."
)
# Register the class (not instance - we'll instantiate on demand)
_EXPORTERS[name] = cls
return cls
[docs]
def get_exporter(format_name: str) -> PaletteExporter:
"""
Get exporter instance by format name.
Args:
format_name: Name of the export format (e.g., 'json', 'csv', 'autoforge')
Returns:
Fresh instance of the requested exporter
Raises:
ValueError: If format_name is not recognized
Example:
>>> exporter = get_exporter('json')
>>> print(exporter.metadata.description)
JSON format (raw data, backup/restore)
"""
if format_name not in _EXPORTERS:
available = ', '.join(sorted(_EXPORTERS.keys()))
raise ValueError(
f"Unknown export format: '{format_name}'. "
f"Available formats: {available}"
)
# Return fresh instance
return _EXPORTERS[format_name]()
def get_export_formats_dict() -> dict[str, dict[str, str | bool]]:
"""
Get EXPORT_FORMATS-compatible dictionary.
This provides backward compatibility with the old EXPORT_FORMATS dict
that existed in export.py. Returns format metadata in the legacy structure.
Returns:
Dict mapping format name to metadata dict with keys:
- description: Human-readable description
- file_extension: File extension (without dot)
- applies_to: 'colors', 'filaments', or 'both'
Example:
>>> formats = get_export_formats_dict()
>>> print(formats['json']['applies_to'])
both
"""
result = {}
for name, exporter_class in _EXPORTERS.items():
exporter = exporter_class()
meta = exporter.metadata
# Determine applies_to
if meta.supports_colors and meta.supports_filaments:
applies_to = 'both'
elif meta.supports_colors:
applies_to = 'colors'
elif meta.supports_filaments:
applies_to = 'filaments'
else:
applies_to = 'none' # Should never happen
result[name] = {
'description': meta.description,
'file_extension': meta.file_extension,
'applies_to': applies_to,
}
return result
# Import all exporters to trigger registration
# This must be at the end so the registry functions are defined first
from color_tools.exporters.base import PaletteExporter, ExporterMetadata
from color_tools.exporters.csv_exporter import CSVExporter
from color_tools.exporters.autoforge_exporter import AutoForgeExporter
from color_tools.exporters.json_exporter import JSONExporter
from color_tools.exporters.gpl_exporter import GPLExporter
from color_tools.exporters.hex_exporter import HexExporter
from color_tools.exporters.jascpal_exporter import JascPalExporter
from color_tools.exporters.paintnet_exporter import PaintNetExporter
from color_tools.exporters.lospec_exporter import LospecExporter
# Create EXPORT_FORMATS for backward compatibility with old export.py
EXPORT_FORMATS = get_export_formats_dict()
# Public API
__all__ = [
'PaletteExporter',
'ExporterMetadata',
'register_exporter',
'get_exporter',
'list_export_formats',
'EXPORT_FORMATS',
]