- Created analysis/visual_output_comparison.md with detailed architectural comparison - Added capture utilities for output comparison (capture_output.py, capture_upstream.py, compare_outputs.py) - Captured and compared output from upstream/main vs sideline branch - Documented fundamental architectural differences in rendering approaches - Updated Gitea issue #50 with findings
187 lines
4.9 KiB
Python
187 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Capture output from upstream/main branch.
|
|
|
|
This script captures the output of upstream/main Mainline using NullDisplay
|
|
and saves it to a JSON file for comparison with sideline branch.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add upstream/main to path
|
|
sys.path.insert(0, "/tmp/upstream_mainline")
|
|
|
|
|
|
def capture_upstream_output(
|
|
output_file: str,
|
|
frames: int = 60,
|
|
width: int = 80,
|
|
height: int = 24,
|
|
):
|
|
"""Capture upstream/main output.
|
|
|
|
Args:
|
|
output_file: Path to save captured output
|
|
frames: Number of frames to capture
|
|
width: Terminal width
|
|
height: Terminal height
|
|
"""
|
|
print(f"Capturing upstream/main output...")
|
|
|
|
try:
|
|
# Import upstream modules
|
|
from engine import config, themes
|
|
from engine.display import NullDisplay
|
|
from engine.fetch import fetch_all, load_cache
|
|
from engine.scroll import stream
|
|
from engine.ntfy import NtfyPoller
|
|
from engine.mic import MicMonitor
|
|
except ImportError as e:
|
|
print(f"Error importing upstream modules: {e}")
|
|
print("Make sure upstream/main is in the Python path")
|
|
return False
|
|
|
|
# Create a custom NullDisplay that captures frames
|
|
class CapturingNullDisplay:
|
|
def __init__(self, width, height, max_frames):
|
|
self.width = width
|
|
self.height = height
|
|
self.max_frames = max_frames
|
|
self.frame_count = 0
|
|
self.frames = []
|
|
|
|
def init(self, width: int, height: int) -> None:
|
|
self.width = width
|
|
self.height = height
|
|
|
|
def show(self, buffer: list[str], border: bool = False) -> None:
|
|
if self.frame_count < self.max_frames:
|
|
self.frames.append(list(buffer))
|
|
self.frame_count += 1
|
|
if self.frame_count >= self.max_frames:
|
|
raise StopIteration("Frame limit reached")
|
|
|
|
def clear(self) -> None:
|
|
pass
|
|
|
|
def cleanup(self) -> None:
|
|
pass
|
|
|
|
def get_frames(self):
|
|
return self.frames
|
|
|
|
display = CapturingNullDisplay(width, height, frames)
|
|
|
|
# Load items (use cached headlines)
|
|
items = load_cache()
|
|
if not items:
|
|
print("No cached items found, fetching...")
|
|
result = fetch_all()
|
|
if isinstance(result, tuple):
|
|
items, linked, failed = result
|
|
else:
|
|
items = result
|
|
if not items:
|
|
print("Error: No items available")
|
|
return False
|
|
|
|
print(f"Loaded {len(items)} items")
|
|
|
|
# Create ntfy poller and mic monitor (upstream uses these)
|
|
ntfy_poller = NtfyPoller(config.NTFY_TOPIC, reconnect_delay=5, display_secs=30)
|
|
mic_monitor = MicMonitor()
|
|
|
|
# Run stream for specified number of frames
|
|
print(f"Capturing {frames} frames...")
|
|
|
|
try:
|
|
# Run the stream
|
|
stream(
|
|
items=items,
|
|
ntfy_poller=ntfy_poller,
|
|
mic_monitor=mic_monitor,
|
|
display=display,
|
|
)
|
|
except StopIteration:
|
|
print("Frame limit reached")
|
|
except Exception as e:
|
|
print(f"Error during capture: {e}")
|
|
# Continue to save what we have
|
|
|
|
# Get captured frames
|
|
captured_frames = display.get_frames()
|
|
print(f"Retrieved {len(captured_frames)} frames from display")
|
|
|
|
# Save to JSON
|
|
output_path = Path(output_file)
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
recording_data = {
|
|
"version": 1,
|
|
"preset": "upstream_demo",
|
|
"display": "null",
|
|
"width": width,
|
|
"height": height,
|
|
"frame_count": len(captured_frames),
|
|
"frames": [
|
|
{
|
|
"frame_number": i,
|
|
"buffer": frame,
|
|
"width": width,
|
|
"height": height,
|
|
}
|
|
for i, frame in enumerate(captured_frames)
|
|
],
|
|
}
|
|
|
|
with open(output_path, "w") as f:
|
|
json.dump(recording_data, f, indent=2)
|
|
|
|
print(f"Saved recording to {output_path}")
|
|
return True
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Capture upstream/main output")
|
|
parser.add_argument(
|
|
"--output",
|
|
default="output/upstream_demo.json",
|
|
help="Output file path (default: output/upstream_demo.json)",
|
|
)
|
|
parser.add_argument(
|
|
"--frames",
|
|
type=int,
|
|
default=60,
|
|
help="Number of frames to capture (default: 60)",
|
|
)
|
|
parser.add_argument(
|
|
"--width",
|
|
type=int,
|
|
default=80,
|
|
help="Terminal width (default: 80)",
|
|
)
|
|
parser.add_argument(
|
|
"--height",
|
|
type=int,
|
|
default=24,
|
|
help="Terminal height (default: 24)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
success = capture_upstream_output(
|
|
output_file=args.output,
|
|
frames=args.frames,
|
|
width=args.width,
|
|
height=args.height,
|
|
)
|
|
|
|
return 0 if success else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
exit(main())
|