diff --git a/engine/app.py b/engine/app.py index 98cddd1..a504122 100644 --- a/engine/app.py +++ b/engine/app.py @@ -2,23 +2,33 @@ Application orchestrator — boot sequence, signal handling, main loop wiring. """ -import sys -import os -import time -import signal import atexit +import os +import signal +import sys import termios +import time import tty from engine import config, render -from engine.terminal import ( - RST, G_HI, G_MID, G_DIM, W_DIM, W_GHOST, CLR, CURSOR_OFF, CURSOR_ON, tw, - slow_print, boot_ln, -) from engine.fetch import fetch_all, fetch_poetry, load_cache, save_cache -from engine.ntfy import NtfyPoller from engine.mic import MicMonitor +from engine.ntfy import NtfyPoller from engine.scroll import stream +from engine.terminal import ( + CLR, + CURSOR_OFF, + CURSOR_ON, + G_DIM, + G_HI, + G_MID, + RST, + W_DIM, + W_GHOST, + boot_ln, + slow_print, + tw, +) TITLE = [ " ███╗ ███╗ █████╗ ██╗███╗ ██╗██╗ ██╗███╗ ██╗███████╗", diff --git a/engine/config.py b/engine/config.py index f509b23..3f2caea 100644 --- a/engine/config.py +++ b/engine/config.py @@ -3,8 +3,8 @@ Configuration constants, CLI flags, and glyph tables. """ import sys - from pathlib import Path + _REPO_ROOT = Path(__file__).resolve().parent.parent _FONT_EXTENSIONS = {".otf", ".ttf", ".ttc"} diff --git a/engine/effects.py b/engine/effects.py index bad95de..3edb265 100644 --- a/engine/effects.py +++ b/engine/effects.py @@ -7,8 +7,8 @@ import random from datetime import datetime from engine import config -from engine.terminal import RST, DIM, G_LO, G_DIM, W_GHOST, C_DIM from engine.sources import FEEDS, POETRY_SOURCES +from engine.terminal import C_DIM, DIM, G_DIM, G_LO, RST, W_GHOST def noise(w): diff --git a/engine/fetch.py b/engine/fetch.py index 906a8b3..1c55861 100644 --- a/engine/fetch.py +++ b/engine/fetch.py @@ -3,19 +3,20 @@ RSS feed fetching, Project Gutenberg parsing, and headline caching. Depends on: config, sources, filter, terminal. """ -import re import json import pathlib +import re import urllib.request from datetime import datetime import feedparser from engine import config +from engine.filter import skip, strip_tags from engine.sources import FEEDS, POETRY_SOURCES -from engine.filter import strip_tags, skip from engine.terminal import boot_ln + # ─── SINGLE FEED ────────────────────────────────────────── def fetch_feed(url): try: diff --git a/engine/mic.py b/engine/mic.py index ffa6fa5..7d09740 100644 --- a/engine/mic.py +++ b/engine/mic.py @@ -6,8 +6,8 @@ Gracefully degrades if sounddevice/numpy are unavailable. import atexit try: - import sounddevice as _sd import numpy as _np + import sounddevice as _sd _HAS_MIC = True except Exception: _HAS_MIC = False diff --git a/engine/ntfy.py b/engine/ntfy.py index 25dd6a5..777c225 100644 --- a/engine/ntfy.py +++ b/engine/ntfy.py @@ -13,8 +13,8 @@ Reusable by any visualizer: """ import json -import time import threading +import time import urllib.request diff --git a/engine/render.py b/engine/render.py index 1ef5adf..3025601 100644 --- a/engine/render.py +++ b/engine/render.py @@ -4,15 +4,15 @@ Font loading, text rasterization, word-wrap, gradient coloring, headline block a Depends on: config, terminal, sources, translate. """ -import re import random +import re from pathlib import Path from PIL import Image, ImageDraw, ImageFont from engine import config +from engine.sources import NO_UPPER, SCRIPT_FONTS, SOURCE_LANGS from engine.terminal import RST -from engine.sources import SCRIPT_FONTS, SOURCE_LANGS, NO_UPPER from engine.translate import detect_location_language, translate_headline # ─── GRADIENT ───────────────────────────────────────────── @@ -62,7 +62,7 @@ def font(): f"No primary font selected. Add .otf/.ttf/.ttc files to {config.FONT_DIR}." ) key = (config.FONT_PATH, config.FONT_INDEX, config.FONT_SZ) - if _FONT_OBJ is None or _FONT_OBJ_KEY != key: + if _FONT_OBJ is None or key != _FONT_OBJ_KEY: _FONT_OBJ = ImageFont.truetype( config.FONT_PATH, config.FONT_SZ, index=config.FONT_INDEX ) diff --git a/engine/scroll.py b/engine/scroll.py index f9b7f39..d5e815c 100644 --- a/engine/scroll.py +++ b/engine/scroll.py @@ -3,16 +3,23 @@ Render engine — ticker content, scroll motion, message panel, and firehose ove Depends on: config, terminal, render, effects, ntfy, mic. """ +import random import re import sys import time -import random from datetime import datetime from engine import config -from engine.terminal import RST, W_COOL, CLR, tw, th +from engine.effects import ( + fade_line, + firehose_line, + glitch_bar, + next_headline, + noise, + vis_trunc, +) from engine.render import big_wrap, lr_gradient, lr_gradient_opposite, make_block -from engine.effects import noise, glitch_bar, fade_line, vis_trunc, next_headline, firehose_line +from engine.terminal import CLR, RST, W_COOL, th, tw def stream(items, ntfy_poller, mic_monitor): diff --git a/engine/terminal.py b/engine/terminal.py index 8ca7112..4194e5e 100644 --- a/engine/terminal.py +++ b/engine/terminal.py @@ -4,8 +4,8 @@ No internal dependencies. """ import os -import sys import random +import sys import time # ─── ANSI ───────────────────────────────────────────────── diff --git a/engine/translate.py b/engine/translate.py index 57bb795..ab4c273 100644 --- a/engine/translate.py +++ b/engine/translate.py @@ -3,10 +3,10 @@ Google Translate wrapper and location→language detection. Depends on: sources (for LOCATION_LANGS). """ -import re import json -import urllib.request +import re import urllib.parse +import urllib.request from engine.sources import LOCATION_LANGS diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 3216b87..766d9fa 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -14,21 +14,25 @@ class TestTerminalDimensions: def test_tw_returns_columns(self): """tw() returns terminal width.""" - with patch.object(sys.stdout, "isatty", return_value=True): - with patch("os.get_terminal_size") as mock_size: - mock_size.return_value = io.StringIO("columns=120") - mock_size.columns = 120 - result = terminal.tw() - assert isinstance(result, int) + with ( + patch.object(sys.stdout, "isatty", return_value=True), + patch("os.get_terminal_size") as mock_size, + ): + mock_size.return_value = io.StringIO("columns=120") + mock_size.columns = 120 + result = terminal.tw() + assert isinstance(result, int) def test_th_returns_lines(self): """th() returns terminal height.""" - with patch.object(sys.stdout, "isatty", return_value=True): - with patch("os.get_terminal_size") as mock_size: - mock_size.return_value = io.StringIO("lines=30") - mock_size.lines = 30 - result = terminal.th() - assert isinstance(result, int) + with ( + patch.object(sys.stdout, "isatty", return_value=True), + patch("os.get_terminal_size") as mock_size, + ): + mock_size.return_value = io.StringIO("lines=30") + mock_size.lines = 30 + result = terminal.th() + assert isinstance(result, int) def test_tw_fallback_on_error(self): """tw() falls back to 80 on error.""" @@ -84,9 +88,11 @@ class TestTypeOut: @patch("time.sleep") def test_type_out_uses_color(self, mock_sleep): """type_out applies color codes.""" - with patch("sys.stdout", new_callable=io.StringIO): - with patch("random.random", return_value=0.5): - terminal.type_out("Test", color=terminal.G_HI) + with ( + patch("sys.stdout", new_callable=io.StringIO), + patch("random.random", return_value=0.5), + ): + terminal.type_out("Test", color=terminal.G_HI) class TestSlowPrint: