Files
marcus-web/scripts/gopher/generate_gophermaps.py
Marcus b7ac21093a Fix import path issue in gopher scripts
Add script directory to sys.path so imports work when scripts are
called from remote_publish.sh. Also remove unused variable and
no-op regex.
2026-01-14 16:24:42 -06:00

237 lines
6.7 KiB
Python

#!/usr/bin/env python3
"""
Generate gophermap files for blog categories and index.
Usage:
python scripts/gopher/generate_gophermaps.py
python scripts/gopher/generate_gophermaps.py --output gopher_build/blog/
"""
import argparse
import re
import sys
import yaml
from pathlib import Path
# Add script directory to path for imports
sys.path.insert(0, str(Path(__file__).parent))
from ascii_art import (
HEADER_BLOG_INDEX,
DIR_TO_HEADER,
DIR_TO_DESCRIPTION,
)
# Paths
SCRIPT_DIR = Path(__file__).parent
PROJECT_ROOT = SCRIPT_DIR.parent.parent
CONTENT_DIR = PROJECT_ROOT / "content" / "posts"
DEFAULT_OUTPUT = PROJECT_ROOT / "gopher_build" / "blog"
# Gophermap formatting
TAB = "\t"
FAKE_ENTRY = f"{TAB}fake{TAB}(NULL){TAB}0"
def info_line(text: str = "") -> str:
"""Generate an info (i) line for gophermap."""
return f"i{text}{FAKE_ENTRY}"
def file_link(label: str, path: str) -> str:
"""Generate a text file (0) link."""
return f"0{label}{TAB}{path}"
def dir_link(label: str, path: str) -> str:
"""Generate a directory (1) link."""
return f"1{label}{TAB}{path}"
def html_link(label: str, url: str) -> str:
"""Generate an HTML (h) link."""
return f"h{label}{TAB}URL:{url}"
def parse_frontmatter(filepath: Path) -> dict:
"""Parse YAML frontmatter from a markdown file."""
content = filepath.read_text()
if not content.startswith("---"):
return {}
end_match = re.search(r"\n---\n", content[3:])
if not end_match:
return {}
yaml_end = end_match.start() + 3
yaml_content = content[3:yaml_end]
try:
return yaml.safe_load(yaml_content) or {}
except yaml.YAMLError:
return {}
def get_posts_by_category(content_dir: Path) -> dict:
"""Get all phlog-enabled posts grouped by category."""
from ascii_art import SERIES_TO_DIR
categories = {}
for post_path in content_dir.glob("*.md"):
meta = parse_frontmatter(post_path)
# Skip if not phlog-enabled or is draft
if not meta.get("phlog", False) or meta.get("draft", False):
continue
series = meta.get("series", "Fun Center")
category = SERIES_TO_DIR.get(series, "fun-center")
if category not in categories:
categories[category] = []
# Extract date
date_obj = meta.get("date")
if hasattr(date_obj, "strftime"):
date_str = date_obj.strftime("%Y-%m-%d")
else:
date_str = str(date_obj)[:10] if date_obj else "1970-01-01"
categories[category].append({
"slug": post_path.stem,
"title": meta.get("title", post_path.stem),
"date": date_str,
"summary": meta.get("summary", ""),
})
# Sort each category by date (newest first)
for category in categories:
categories[category].sort(key=lambda x: x["date"], reverse=True)
return categories
def generate_category_gophermap(category: str, posts: list, output_dir: Path) -> Path:
"""Generate a gophermap for a category."""
header = DIR_TO_HEADER.get(category, "")
category_dir = output_dir / category
category_dir.mkdir(parents=True, exist_ok=True)
lines = []
# Add header art
for line in header.strip().split("\n"):
lines.append(info_line(line))
lines.append(info_line())
# Divider
lines.append(info_line("-" * 52))
lines.append(info_line())
# Posts
for post in posts:
date = post["date"]
title = post["title"]
slug = post["slug"]
summary = post.get("summary", "")
lines.append(file_link(f"[{date}] {title}", f"{slug}.txt"))
if summary:
lines.append(info_line(f" {summary[:48]}"))
lines.append(info_line())
# Footer divider and navigation
lines.append(info_line("-" * 52))
lines.append(dir_link("Back to Blog Index", "../"))
lines.append(dir_link("Back to Main Menu", "/users/mnw/"))
gophermap_path = category_dir / "gophermap"
gophermap_path.write_text("\n".join(lines))
return gophermap_path
def generate_blog_index(categories: dict, output_dir: Path) -> Path:
"""Generate the main blog index gophermap."""
lines = []
# Add header art
for line in HEADER_BLOG_INDEX.strip().split("\n"):
lines.append(info_line(line))
lines.append(info_line(" The Double Lunch Dispatch"))
lines.append(info_line())
# Divider
lines.append(info_line("-" * 48))
lines.append(info_line())
# Category listings
category_info = {
"franks-couch": ("Frank's Couch - Movie Reviews", "posts about films watched from the couch"),
"fun-center": ("Fun Center - Tech Posts", "posts about technology and open source"),
"beercalls": ("Beercalls - Thursday Night Adventures", "Yearly logs of Austin beer adventures"),
}
for category, (label, description) in category_info.items():
count = len(categories.get(category, []))
if count > 0:
lines.append(dir_link(label, f"{category}/"))
lines.append(info_line(f" {count} {description}"))
lines.append(info_line())
# Footer
lines.append(info_line("-" * 48))
lines.append(html_link("View on the web", "https://mnw.sdf.org/posts/"))
lines.append(dir_link("Back to Main Menu", "/users/mnw/"))
output_dir.mkdir(parents=True, exist_ok=True)
gophermap_path = output_dir / "gophermap"
gophermap_path.write_text("\n".join(lines))
return gophermap_path
def generate_all(output_dir: Path = None) -> dict:
"""Generate all gophermaps."""
output_dir = output_dir or DEFAULT_OUTPUT
# Get posts by category
categories = get_posts_by_category(CONTENT_DIR)
results = {
"categories": {},
"index": None,
}
# Generate category gophermaps
for category, posts in categories.items():
path = generate_category_gophermap(category, posts, output_dir)
results["categories"][category] = {
"path": path,
"count": len(posts),
}
print(f"Generated: {path.relative_to(output_dir)} ({len(posts)} posts)")
# Generate blog index
results["index"] = generate_blog_index(categories, output_dir)
print(f"Generated: {results['index'].relative_to(output_dir)}")
return results
def main():
parser = argparse.ArgumentParser(description="Generate gophermaps for blog")
parser.add_argument("--output", "-o", type=Path, help="Output directory")
args = parser.parse_args()
output = args.output or DEFAULT_OUTPUT
results = generate_all(output)
total_posts = sum(c["count"] for c in results["categories"].values())
print(f"\nGenerated gophermaps for {total_posts} posts in {len(results['categories'])} categories")
if __name__ == "__main__":
main()