Skip to content
This repository was archived by the owner on May 2, 2026. It is now read-only.

Commit c3ae5f9

Browse files
authored
Merge pull request #168 from viu-media/feature/preview-scripts-rewrite-to-python
2 parents 8e803e8 + bf06d7e commit c3ae5f9

25 files changed

Lines changed: 1605 additions & 1246 deletions
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
"""
2+
ANSI utilities for FZF preview scripts.
3+
4+
Lightweight stdlib-only utilities to replace Rich dependency in preview scripts.
5+
Provides RGB color formatting, table rendering, and markdown stripping.
6+
"""
7+
8+
import os
9+
import re
10+
import shutil
11+
import textwrap
12+
import unicodedata
13+
14+
15+
def get_terminal_width() -> int:
16+
"""
17+
Get terminal width, prioritizing FZF preview environment variables.
18+
19+
Returns:
20+
Terminal width in columns
21+
"""
22+
fzf_cols = os.environ.get("FZF_PREVIEW_COLUMNS")
23+
if fzf_cols:
24+
return int(fzf_cols)
25+
return shutil.get_terminal_size((80, 24)).columns
26+
27+
28+
def display_width(text: str) -> int:
29+
"""
30+
Calculate the actual display width of text, accounting for wide characters.
31+
32+
Args:
33+
text: Text to measure
34+
35+
Returns:
36+
Display width in terminal columns
37+
"""
38+
width = 0
39+
for char in text:
40+
# East Asian Width property: 'F' (Fullwidth) and 'W' (Wide) take 2 columns
41+
if unicodedata.east_asian_width(char) in ("F", "W"):
42+
width += 2
43+
else:
44+
width += 1
45+
return width
46+
47+
48+
def rgb_color(r: int, g: int, b: int, text: str, bold: bool = False) -> str:
49+
"""
50+
Format text with RGB color using ANSI escape codes.
51+
52+
Args:
53+
r: Red component (0-255)
54+
g: Green component (0-255)
55+
b: Blue component (0-255)
56+
text: Text to colorize
57+
bold: Whether to make text bold
58+
59+
Returns:
60+
ANSI-escaped colored text
61+
"""
62+
color_code = f"\x1b[38;2;{r};{g};{b}m"
63+
bold_code = "\x1b[1m" if bold else ""
64+
reset = "\x1b[0m"
65+
return f"{color_code}{bold_code}{text}{reset}"
66+
67+
68+
def parse_color(color_csv: str) -> tuple[int, int, int]:
69+
"""
70+
Parse RGB color from comma-separated string.
71+
72+
Args:
73+
color_csv: Color as 'R,G,B' string
74+
75+
Returns:
76+
Tuple of (r, g, b) integers
77+
"""
78+
parts = color_csv.split(",")
79+
return int(parts[0]), int(parts[1]), int(parts[2])
80+
81+
82+
def print_rule(sep_color: str) -> None:
83+
"""
84+
Print a horizontal rule line.
85+
86+
Args:
87+
sep_color: Color as 'R,G,B' string
88+
"""
89+
width = get_terminal_width()
90+
r, g, b = parse_color(sep_color)
91+
print(rgb_color(r, g, b, "─" * width))
92+
93+
94+
def print_table_row(
95+
key: str, value: str, header_color: str, key_width: int, value_width: int
96+
) -> None:
97+
"""
98+
Print a two-column table row with left-aligned key and right-aligned value.
99+
100+
Args:
101+
key: Left column text (header/key)
102+
value: Right column text (value)
103+
header_color: Color for key as 'R,G,B' string
104+
key_width: Width for key column
105+
value_width: Width for value column
106+
"""
107+
r, g, b = parse_color(header_color)
108+
key_styled = rgb_color(r, g, b, key, bold=True)
109+
110+
# Get actual terminal width
111+
term_width = get_terminal_width()
112+
113+
# Calculate display widths accounting for wide characters
114+
key_display_width = display_width(key)
115+
116+
# Calculate actual value width based on terminal and key display width
117+
actual_value_width = max(20, term_width - key_display_width - 2)
118+
119+
# Wrap value if it's too long (use character count, not display width for wrapping)
120+
value_lines = textwrap.wrap(str(value), width=actual_value_width) if value else [""]
121+
122+
if not value_lines:
123+
value_lines = [""]
124+
125+
# Print first line with properly aligned value
126+
first_line = value_lines[0]
127+
first_line_display_width = display_width(first_line)
128+
129+
# Use manual spacing to right-align based on display width
130+
spacing = term_width - key_display_width - first_line_display_width - 2
131+
if spacing > 0:
132+
print(f"{key_styled} {' ' * spacing}{first_line}")
133+
else:
134+
print(f"{key_styled} {first_line}")
135+
136+
# Print remaining wrapped lines (left-aligned, indented)
137+
for line in value_lines[1:]:
138+
print(f"{' ' * (key_display_width + 2)}{line}")
139+
140+
141+
def strip_markdown(text: str) -> str:
142+
"""
143+
Strip markdown formatting from text.
144+
145+
Removes:
146+
- Headers (# ## ###)
147+
- Bold (**text** or __text__)
148+
- Italic (*text* or _text_)
149+
- Links ([text](url))
150+
- Code blocks (```code```)
151+
- Inline code (`code`)
152+
153+
Args:
154+
text: Markdown-formatted text
155+
156+
Returns:
157+
Plain text with markdown removed
158+
"""
159+
if not text:
160+
return ""
161+
162+
# Remove code blocks first
163+
text = re.sub(r"```[\s\S]*?```", "", text)
164+
165+
# Remove inline code
166+
text = re.sub(r"`([^`]+)`", r"\1", text)
167+
168+
# Remove headers
169+
text = re.sub(r"^#{1,6}\s+", "", text, flags=re.MULTILINE)
170+
171+
# Remove bold (** or __)
172+
text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
173+
text = re.sub(r"__(.+?)__", r"\1", text)
174+
175+
# Remove italic (* or _)
176+
text = re.sub(r"\*(.+?)\*", r"\1", text)
177+
text = re.sub(r"_(.+?)_", r"\1", text)
178+
179+
# Remove links, keep text
180+
text = re.sub(r"\[(.+?)\]\(.+?\)", r"\1", text)
181+
182+
# Remove images
183+
text = re.sub(r"!\[.*?\]\(.+?\)", "", text)
184+
185+
return text.strip()
186+
187+
188+
def wrap_text(text: str, width: int | None = None) -> str:
189+
"""
190+
Wrap text to terminal width.
191+
192+
Args:
193+
text: Text to wrap
194+
width: Width to wrap to (defaults to terminal width)
195+
196+
Returns:
197+
Wrapped text
198+
"""
199+
if width is None:
200+
width = get_terminal_width()
201+
202+
return textwrap.fill(text, width=width)

viu_media/assets/scripts/fzf/airing-schedule-info.template.sh

Lines changed: 0 additions & 22 deletions
This file was deleted.

viu_media/assets/scripts/fzf/airing-schedule-preview.template.sh

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import sys
2+
from _ansi_utils import (
3+
print_rule,
4+
print_table_row,
5+
strip_markdown,
6+
wrap_text,
7+
get_terminal_width,
8+
)
9+
10+
HEADER_COLOR = sys.argv[1]
11+
SEPARATOR_COLOR = sys.argv[2]
12+
13+
# Get terminal dimensions
14+
term_width = get_terminal_width()
15+
16+
# Print title centered
17+
print("{ANIME_TITLE}".center(term_width))
18+
19+
rows = [
20+
("Total Episodes", "{TOTAL_EPISODES}"),
21+
]
22+
23+
print_rule(SEPARATOR_COLOR)
24+
for key, value in rows:
25+
print_table_row(key, value, HEADER_COLOR, 15, term_width - 20)
26+
27+
rows = [
28+
("Upcoming Episodes", "{UPCOMING_EPISODES}"),
29+
]
30+
31+
print_rule(SEPARATOR_COLOR)
32+
for key, value in rows:
33+
print_table_row(key, value, HEADER_COLOR, 15, term_width - 20)
34+
35+
print_rule(SEPARATOR_COLOR)
36+
print(wrap_text(strip_markdown("""{SCHEDULE_TABLE}"""), term_width))

viu_media/assets/scripts/fzf/character-info.template.sh

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)