Files
marcus-web/scripts/gopher/ascii_art.py
T
mnw e087a6eb83 Add gopher integration core infrastructure (Phase 1)
Create scripts for converting Hugo blog posts to gopher-friendly
plain text format. Includes ASCII art assets for headers/footers
and markdown-to-gopher conversion with proper line wrapping.
2026-01-14 16:24:09 -06:00

205 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""
ASCII art assets for gopher content generation.
Contains category headers, post templates, footers, and utility functions
for generating ASCII tables and dividers.
"""
# Line width for gopher content
LINE_WIDTH = 70
# Dividers
DOUBLE_LINE = "=" * LINE_WIDTH
SINGLE_LINE = "-" * LINE_WIDTH
BOX_LINE = "\u2500" * LINE_WIDTH # Unicode box drawing horizontal
# Post header (MNW owl)
POST_HEADER = """\
___ ___ ___
/\\ \\ /\\ \\ /\\ \\
|::\\ \\ \\:\\ \\ _\\:\\ \\
|:|:\\ \\ \\:\\ \\ /\\ \\:\\ \\
__|:|\\:\\ \\ _____\\:\\ \\ _\\:\\ \\:\\ \\
/::::|_\\:\\__\\ /::::::::\\__\\ /\\ \\:\\ \\:\\__\\
\\:\\~~\\ \\/__/ \\:\\~~\\~~\\/__/ \\:\\ \\:\\/:/ /
\\:\\ \\ \\:\\ \\ \\:\\ \\::/ /
\\:\\ \\ \\:\\ \\ \\:\\/:/ /
\\:\\__\\ \\:\\__\\ \\::/ /
\\/__/ \\/__/ \\/__/
mnw(at)sdf.org | SDF VOIP Ext. 1908
"""
# Category headers
HEADER_FUN_CENTER = """\
___ ___ _
| __| _ _ _ / __|___ _ _| |_ ___ _ _
| _| || | ' \\ | (__/ -_) ' \\ _/ -_) '_|
|_| \\_,_|_||_| \\___\\___|_||_\\__\\___|_|
Tech posts from the Double Lunch Dispatch
"""
HEADER_FRANKS_COUCH = """\
___ _ _ ___ _
| __| _ __ _ _ _| |_( )___ / __|___ _ _ __| |_
| _| '_/ _` | ' \\ / /|_-< | (__/ _ \\ || (_-< ' \\
|_||_| \\__,_|_||_\\_\\ /__/ \\___\\___/\\_,_/__/_||_|
Movie reviews from the couch
"""
HEADER_BEERCALLS = """\
___ _ _
| _ ) ___ ___ _ _ __ __ _| | |___
| _ \\/ -_) -_) '_/ _/ _` | | (_-<
|___/\\___\\___|_| \\__\\__,_|_|_/__/
Thursday night adventures in Austin
"""
HEADER_BLOG_INDEX = """\
___ _ ___ _ _
| _ )| |___ __ | __|_ _ | |_ _ _ (_)___ ___
| _ \\| / _ \\/ _|| _|| ' \\| _| '_|| / -_|_-<
|___/|_\\___/\\__||___|_||_|\\__|_| |_\\___/__/
Posts from mnw.sdf.org
"""
# Footer (SDF box)
POST_FOOTER = """\
__^__ __^__
( ___ )------------------------------------------------------( ___ )
| / | | \\ |
| / | This gopher space proudly hosted by SDF | \\ |
| \\ | mnw on pixelfed.social and tilde.zone | / |
| \\ | | / |
(_____)------------------------------------------------------(_____)
"""
# Map series names to gopher directory names
SERIES_TO_DIR = {
"Fun Center": "fun-center",
"Frank's Couch": "franks-couch",
"Beercalls": "beercalls",
}
# Map directory names to headers
DIR_TO_HEADER = {
"fun-center": HEADER_FUN_CENTER,
"franks-couch": HEADER_FRANKS_COUCH,
"beercalls": HEADER_BEERCALLS,
}
# Map directory names to descriptions
DIR_TO_DESCRIPTION = {
"fun-center": "Tech posts from the Double Lunch Dispatch",
"franks-couch": "Movie reviews from the couch",
"beercalls": "Thursday night adventures in Austin",
}
def get_section_header(title: str) -> str:
"""Generate a section header with double lines."""
return f"\n{DOUBLE_LINE}\n {title.upper()}\n{DOUBLE_LINE}\n"
def get_subheading(title: str) -> str:
"""Generate a subheading with dashes."""
return f"\n--- {title} ---\n"
def get_post_meta_header(date: str, series: str) -> str:
"""Generate the metadata header for a post."""
return f"""\
{SINGLE_LINE}
{date} | {series}
{SINGLE_LINE}
"""
def get_title_block(title: str, summary: str = "") -> str:
"""Generate a centered title block."""
# Center the title
centered_title = title.upper().center(LINE_WIDTH)
lines = [centered_title]
if summary:
centered_summary = summary.center(LINE_WIDTH)
lines.append(centered_summary)
return "\n".join(lines)
def generate_movie_table(
title: str,
year: int,
director: str = "",
runtime: int = 0,
genres: list = None,
web_url: str = "",
) -> str:
"""Generate an ASCII table for movie metadata."""
genres = genres or []
width = 65
border_h = "\u2500" # horizontal line
corner_tl = "\u250c" # top left
corner_tr = "\u2510" # top right
corner_bl = "\u2514" # bottom left
corner_br = "\u2518" # bottom right
border_v = "\u2502" # vertical
tee_l = "\u251c" # left tee
tee_r = "\u2524" # right tee
def row(content: str) -> str:
return f"{border_v} {content.ljust(width - 4)} {border_v}"
lines = [
f"{corner_tl}{border_h * width}{corner_tr}",
row(f"{title.upper()} ({year})"),
f"{tee_l}{border_h * width}{tee_r}",
]
if director:
lines.append(row(f"Director: {director}"))
if runtime:
lines.append(row(f"Runtime: {runtime} minutes"))
if genres:
lines.append(row(f"Genres: {', '.join(genres)}"))
lines.append(row(""))
if web_url:
lines.append(row(f"View on web: {web_url}"))
lines.append(f"{corner_bl}{border_h * width}{corner_br}")
return "\n".join(lines)
def wrap_text(text: str, width: int = LINE_WIDTH) -> str:
"""Wrap text to specified width, preserving paragraphs."""
import textwrap
paragraphs = text.split("\n\n")
wrapped = []
for para in paragraphs:
# Preserve single newlines within paragraphs as spaces
para = " ".join(para.split())
if para:
wrapped.append(textwrap.fill(para, width=width))
else:
wrapped.append("")
return "\n\n".join(wrapped)
def format_links_section(links: list) -> str:
"""Format a list of links for the footer section."""
if not links:
return ""
lines = [get_section_header("LINKS")]
for i, url in enumerate(links, 1):
lines.append(f"[{i}] {url}")
return "\n".join(lines)