feat: Implement Sideline plugin system with consistent terminology
This commit implements the Sideline/Mainline split with a clean plugin architecture: ## Core Changes ### Sideline Framework (New Directory) - Created directory containing the reusable pipeline framework - Moved pipeline core, controllers, adapters, and registry to - Moved display system to - Moved effects system to - Created plugin system with security and compatibility management in - Created preset pack system with ASCII art encoding in - Added default font (Corptic) to - Added terminal ANSI constants to ### Mainline Application (Updated) - Created for Mainline stage component registration - Updated to register Mainline stages at startup - Updated as a compatibility shim re-exporting from sideline ### Terminology Consistency - : Base class for all pipeline components (sources, effects, displays, cameras) - : Base class for distributable plugin packages (was ) - : Base class for visual effects (was ) - Backward compatibility aliases maintained for existing code ## Key Features - Plugin discovery via entry points and explicit registration - Security permissions system for plugins - Compatibility management with semantic version constraints - Preset pack system for distributable configurations - Default font bundled with Sideline (Corptic.otf) ## Testing - Updated tests to register Mainline stages before discovery - All StageRegistry tests passing Note: This is a major refactoring that separates the framework (Sideline) from the application (Mainline), enabling Sideline to be used by other applications.
This commit is contained in:
211
sideline/preset_packs/encoder.py
Normal file
211
sideline/preset_packs/encoder.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Preset pack encoder with ASCII art compression.
|
||||
|
||||
Compresses plugin code and encodes it as ASCII art for fun and version control.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import zlib
|
||||
import textwrap
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class PresetPackEncoder:
|
||||
"""Encodes and decodes preset packs with ASCII art compression."""
|
||||
|
||||
# ASCII art frame characters
|
||||
FRAME_TOP_LEFT = "┌"
|
||||
FRAME_TOP_RIGHT = "┐"
|
||||
FRAME_BOTTOM_LEFT = "└"
|
||||
FRAME_BOTTOM_RIGHT = "┘"
|
||||
FRAME_HORIZONTAL = "─"
|
||||
FRAME_VERTICAL = "│"
|
||||
|
||||
# Data block characters (for visual representation)
|
||||
DATA_CHARS = " ░▒▓█"
|
||||
|
||||
@classmethod
|
||||
def encode_plugin_code(cls, code: str, name: str = "plugin") -> str:
|
||||
"""Encode plugin code as ASCII art.
|
||||
|
||||
Args:
|
||||
code: Python source code to encode
|
||||
name: Plugin name for metadata
|
||||
|
||||
Returns:
|
||||
ASCII art encoded plugin code
|
||||
"""
|
||||
# Compress the code
|
||||
compressed = zlib.compress(code.encode("utf-8"))
|
||||
|
||||
# Encode as base64
|
||||
b64 = base64.b64encode(compressed).decode("ascii")
|
||||
|
||||
# Wrap in ASCII art frame
|
||||
return cls._wrap_in_ascii_art(b64, name)
|
||||
|
||||
@classmethod
|
||||
def decode_plugin_code(cls, ascii_art: str) -> str:
|
||||
"""Decode ASCII art to plugin code.
|
||||
|
||||
Args:
|
||||
ascii_art: ASCII art encoded plugin code
|
||||
|
||||
Returns:
|
||||
Decoded Python source code
|
||||
"""
|
||||
# Extract base64 from ASCII art
|
||||
b64 = cls._extract_from_ascii_art(ascii_art)
|
||||
|
||||
# Decode base64
|
||||
compressed = base64.b64decode(b64)
|
||||
|
||||
# Decompress
|
||||
code = zlib.decompress(compressed).decode("utf-8")
|
||||
|
||||
return code
|
||||
|
||||
@classmethod
|
||||
def _wrap_in_ascii_art(cls, data: str, name: str) -> str:
|
||||
"""Wrap data in ASCII art frame."""
|
||||
# Calculate frame width
|
||||
max_line_length = 60
|
||||
lines = textwrap.wrap(data, max_line_length)
|
||||
|
||||
# Find longest line for frame width
|
||||
longest_line = max(len(line) for line in lines) if lines else 0
|
||||
frame_width = longest_line + 4 # 2 padding + 2 borders
|
||||
|
||||
# Build ASCII art
|
||||
result = []
|
||||
|
||||
# Top border
|
||||
result.append(
|
||||
cls.FRAME_TOP_LEFT
|
||||
+ cls.FRAME_HORIZONTAL * (frame_width - 2)
|
||||
+ cls.FRAME_TOP_RIGHT
|
||||
)
|
||||
|
||||
# Plugin name header
|
||||
name_line = f" {name} "
|
||||
name_padding = frame_width - 2 - len(name_line)
|
||||
left_pad = name_padding // 2
|
||||
right_pad = name_padding - left_pad
|
||||
result.append(
|
||||
cls.FRAME_VERTICAL
|
||||
+ " " * left_pad
|
||||
+ name_line
|
||||
+ " " * right_pad
|
||||
+ cls.FRAME_VERTICAL
|
||||
)
|
||||
|
||||
# Separator line
|
||||
result.append(
|
||||
cls.FRAME_VERTICAL
|
||||
+ cls.FRAME_HORIZONTAL * (frame_width - 2)
|
||||
+ cls.FRAME_VERTICAL
|
||||
)
|
||||
|
||||
# Data lines
|
||||
for line in lines:
|
||||
padding = frame_width - 2 - len(line)
|
||||
result.append(
|
||||
cls.FRAME_VERTICAL + line + " " * padding + cls.FRAME_VERTICAL
|
||||
)
|
||||
|
||||
# Bottom border
|
||||
result.append(
|
||||
cls.FRAME_BOTTOM_LEFT
|
||||
+ cls.FRAME_HORIZONTAL * (frame_width - 2)
|
||||
+ cls.FRAME_BOTTOM_RIGHT
|
||||
)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
@classmethod
|
||||
def _extract_from_ascii_art(cls, ascii_art: str) -> str:
|
||||
"""Extract base64 data from ASCII art frame."""
|
||||
lines = ascii_art.strip().split("\n")
|
||||
|
||||
# Skip top and bottom borders, header, and separator
|
||||
data_lines = lines[3:-1]
|
||||
|
||||
# Extract data from between frame characters
|
||||
extracted = []
|
||||
for line in data_lines:
|
||||
if len(line) > 2:
|
||||
# Remove frame characters and extract content
|
||||
content = line[1:-1].rstrip()
|
||||
extracted.append(content)
|
||||
|
||||
return "".join(extracted)
|
||||
|
||||
@classmethod
|
||||
def encode_toml(cls, toml_data: str, name: str = "pack") -> str:
|
||||
"""Encode TOML data as ASCII art.
|
||||
|
||||
Args:
|
||||
toml_data: TOML configuration data
|
||||
name: Pack name
|
||||
|
||||
Returns:
|
||||
ASCII art encoded TOML
|
||||
"""
|
||||
# Compress
|
||||
compressed = zlib.compress(toml_data.encode("utf-8"))
|
||||
|
||||
# Encode as base64
|
||||
b64 = base64.b64encode(compressed).decode("ascii")
|
||||
|
||||
# Create visual representation using data characters
|
||||
visual_data = cls._data_to_visual(b64)
|
||||
|
||||
return cls._wrap_in_ascii_art(visual_data, name)
|
||||
|
||||
@classmethod
|
||||
def decode_toml(cls, ascii_art: str) -> str:
|
||||
"""Decode ASCII art to TOML data.
|
||||
|
||||
Args:
|
||||
ascii_art: ASCII art encoded TOML
|
||||
|
||||
Returns:
|
||||
Decoded TOML data
|
||||
"""
|
||||
# Extract base64 from ASCII art
|
||||
b64 = cls._extract_from_ascii_art(ascii_art)
|
||||
|
||||
# Decode base64
|
||||
compressed = base64.b64decode(b64)
|
||||
|
||||
# Decompress
|
||||
toml_data = zlib.decompress(compressed).decode("utf-8")
|
||||
|
||||
return toml_data
|
||||
|
||||
@classmethod
|
||||
def _data_to_visual(cls, data: str) -> str:
|
||||
"""Convert base64 data to visual representation.
|
||||
|
||||
This creates a fun visual pattern based on the data.
|
||||
"""
|
||||
# Simple mapping: each character to a data block character
|
||||
# This is purely for visual appeal
|
||||
visual = ""
|
||||
for i, char in enumerate(data):
|
||||
# Use character code to select visual block
|
||||
idx = ord(char) % len(cls.DATA_CHARS)
|
||||
visual += cls.DATA_CHARS[idx]
|
||||
|
||||
# Add line breaks for visual appeal
|
||||
if (i + 1) % 60 == 0:
|
||||
visual += "\n"
|
||||
|
||||
return visual
|
||||
|
||||
@classmethod
|
||||
def get_visual_representation(cls, data: str) -> str:
|
||||
"""Get a visual representation of data for display."""
|
||||
compressed = zlib.compress(data.encode("utf-8"))
|
||||
b64 = base64.b64encode(compressed).decode("ascii")
|
||||
return cls._data_to_visual(b64)
|
||||
Reference in New Issue
Block a user