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.
205 lines
5.8 KiB
Python
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)
|